Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / tabs / tab_strip_controller.mm
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
6
7 #import <QuartzCore/QuartzCore.h>
8
9 #include <cmath>
10 #include <limits>
11 #include <string>
12
13 #include "base/command_line.h"
14 #include "base/mac/mac_util.h"
15 #include "base/mac/scoped_nsautorelease_pool.h"
16 #include "base/metrics/histogram.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/strings/sys_string_conversions.h"
19 #include "chrome/app/chrome_command_ids.h"
20 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
21 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
22 #include "chrome/browser/extensions/tab_helper.h"
23 #include "chrome/browser/favicon/favicon_tab_helper.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/profiles/profile_manager.h"
26 #include "chrome/browser/themes/theme_service.h"
27 #include "chrome/browser/ui/browser.h"
28 #include "chrome/browser/ui/browser_navigator.h"
29 #include "chrome/browser/ui/browser_tabstrip.h"
30 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
31 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet_controller.h"
32 #include "chrome/browser/ui/cocoa/drag_util.h"
33 #import "chrome/browser/ui/cocoa/image_button_cell.h"
34 #import "chrome/browser/ui/cocoa/new_tab_button.h"
35 #import "chrome/browser/ui/cocoa/tab_contents/favicon_util_mac.h"
36 #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
37 #import "chrome/browser/ui/cocoa/tabs/media_indicator_view.h"
38 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h"
39 #import "chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.h"
40 #import "chrome/browser/ui/cocoa/tabs/tab_strip_model_observer_bridge.h"
41 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
42 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
43 #include "chrome/browser/ui/find_bar/find_bar.h"
44 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
45 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
46 #include "chrome/browser/ui/tabs/tab_menu_model.h"
47 #include "chrome/browser/ui/tabs/tab_strip_model.h"
48 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
49 #include "chrome/browser/ui/tabs/tab_utils.h"
50 #include "chrome/common/chrome_switches.h"
51 #include "chrome/common/pref_names.h"
52 #include "components/metrics/proto/omnibox_event.pb.h"
53 #include "components/omnibox/autocomplete_match.h"
54 #include "components/url_fixer/url_fixer.h"
55 #include "components/web_modal/web_contents_modal_dialog_manager.h"
56 #include "content/public/browser/navigation_controller.h"
57 #include "content/public/browser/user_metrics.h"
58 #include "content/public/browser/web_contents.h"
59 #include "grit/generated_resources.h"
60 #include "grit/theme_resources.h"
61 #include "grit/ui_resources.h"
62 #include "skia/ext/skia_utils_mac.h"
63 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
64 #include "ui/base/cocoa/animation_utils.h"
65 #import "ui/base/cocoa/tracking_area.h"
66 #include "ui/base/l10n/l10n_util.h"
67 #include "ui/base/models/list_selection_model.h"
68 #include "ui/base/resource/resource_bundle.h"
69 #include "ui/base/theme_provider.h"
70 #include "ui/gfx/image/image.h"
71 #include "ui/gfx/mac/scoped_ns_disable_screen_updates.h"
72
73 using base::UserMetricsAction;
74 using content::OpenURLParams;
75 using content::Referrer;
76 using content::WebContents;
77
78 namespace {
79
80 // A value to indicate tab layout should use the full available width of the
81 // view.
82 const CGFloat kUseFullAvailableWidth = -1.0;
83
84 // The amount by which tabs overlap.
85 // Needs to be <= the x position of the favicon within a tab. Else, every time
86 // the throbber is painted, the throbber's invalidation will also invalidate
87 // parts of the tab to the left, and two tabs's backgrounds need to be painted
88 // on each throbber frame instead of one.
89 const CGFloat kTabOverlap = 19.0;
90
91 // The amount by which mini tabs are separated from normal tabs.
92 const CGFloat kLastMiniTabSpacing = 2.0;
93
94 // The amount by which the new tab button is offset (from the tabs).
95 const CGFloat kNewTabButtonOffset = 8.0;
96
97 // Time (in seconds) in which tabs animate to their final position.
98 const NSTimeInterval kAnimationDuration = 0.125;
99
100 // Helper class for doing NSAnimationContext calls that takes a bool to disable
101 // all the work.  Useful for code that wants to conditionally animate.
102 class ScopedNSAnimationContextGroup {
103  public:
104   explicit ScopedNSAnimationContextGroup(bool animate)
105       : animate_(animate) {
106     if (animate_) {
107       [NSAnimationContext beginGrouping];
108     }
109   }
110
111   ~ScopedNSAnimationContextGroup() {
112     if (animate_) {
113       [NSAnimationContext endGrouping];
114     }
115   }
116
117   void SetCurrentContextDuration(NSTimeInterval duration) {
118     if (animate_) {
119       [[NSAnimationContext currentContext] gtm_setDuration:duration
120                                                  eventMask:NSLeftMouseUpMask];
121     }
122   }
123
124   void SetCurrentContextShortestDuration() {
125     if (animate_) {
126       // The minimum representable time interval.  This used to stop an
127       // in-progress animation as quickly as possible.
128       const NSTimeInterval kMinimumTimeInterval =
129           std::numeric_limits<NSTimeInterval>::min();
130       // Directly set the duration to be short, avoiding the Steve slowmotion
131       // ettect the gtm_setDuration: provides.
132       [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval];
133     }
134   }
135
136 private:
137   bool animate_;
138   DISALLOW_COPY_AND_ASSIGN(ScopedNSAnimationContextGroup);
139 };
140
141 // Creates an NSImage with size |size| and bitmap image representations for both
142 // 1x and 2x scale factors. |drawingHandler| is called once for every scale
143 // factor.  This is similar to -[NSImage imageWithSize:flipped:drawingHandler:],
144 // but this function always evaluates drawingHandler eagerly, and it works on
145 // 10.6 and 10.7.
146 NSImage* CreateImageWithSize(NSSize size,
147                              void (^drawingHandler)(NSSize)) {
148   base::scoped_nsobject<NSImage> result([[NSImage alloc] initWithSize:size]);
149   [NSGraphicsContext saveGraphicsState];
150   for (ui::ScaleFactor scale_factor : ui::GetSupportedScaleFactors()) {
151     float scale = GetScaleForScaleFactor(scale_factor);
152     NSBitmapImageRep *bmpImageRep = [[[NSBitmapImageRep alloc]
153         initWithBitmapDataPlanes:NULL
154                       pixelsWide:size.width * scale
155                       pixelsHigh:size.height * scale
156                    bitsPerSample:8
157                  samplesPerPixel:4
158                         hasAlpha:YES
159                         isPlanar:NO
160                   colorSpaceName:NSDeviceRGBColorSpace
161                      bytesPerRow:0
162                     bitsPerPixel:0] autorelease];
163     [bmpImageRep setSize:size];
164     [NSGraphicsContext setCurrentContext:
165         [NSGraphicsContext graphicsContextWithBitmapImageRep:bmpImageRep]];
166     drawingHandler(size);
167     [result addRepresentation:bmpImageRep];
168   }
169   [NSGraphicsContext restoreGraphicsState];
170
171   return result.release();
172 }
173
174 // Takes a normal bitmap and a mask image and returns an image the size of the
175 // mask that has pixels from |image| but alpha information from |mask|.
176 NSImage* ApplyMask(NSImage* image, NSImage* mask) {
177   return [CreateImageWithSize([mask size], ^(NSSize size) {
178       // Skip a few pixels from the top of the tab background gradient, because
179       // the new tab button is not drawn at the very top of the browser window.
180       const int kYOffset = 10;
181       CGFloat width = size.width;
182       CGFloat height = size.height;
183
184       // In some themes, the tab background image is narrower than the
185       // new tab button, so tile the background image.
186       CGFloat x = 0;
187       // The floor() is to make sure images with odd widths don't draw to the
188       // same pixel twice on retina displays. (Using NSDrawThreePartImage()
189       // caused a startup perf regression, so that cannot be used.)
190       CGFloat tileWidth = floor(std::min(width, [image size].width));
191       while (x < width) {
192         [image drawAtPoint:NSMakePoint(x, 0)
193                   fromRect:NSMakeRect(0,
194                                       [image size].height - height - kYOffset,
195                                       tileWidth,
196                                       height)
197                  operation:NSCompositeCopy
198                   fraction:1.0];
199         x += tileWidth;
200       }
201
202       [mask drawAtPoint:NSZeroPoint
203                fromRect:NSMakeRect(0, 0, width, height)
204               operation:NSCompositeDestinationIn
205                fraction:1.0];
206   }) autorelease];
207 }
208
209 // Paints |overlay| on top of |ground|.
210 NSImage* Overlay(NSImage* ground, NSImage* overlay, CGFloat alpha) {
211   DCHECK_EQ([ground size].width, [overlay size].width);
212   DCHECK_EQ([ground size].height, [overlay size].height);
213
214   return [CreateImageWithSize([ground size], ^(NSSize size) {
215       CGFloat width = size.width;
216       CGFloat height = size.height;
217       [ground drawAtPoint:NSZeroPoint
218                  fromRect:NSMakeRect(0, 0, width, height)
219                 operation:NSCompositeCopy
220                  fraction:1.0];
221       [overlay drawAtPoint:NSZeroPoint
222                   fromRect:NSMakeRect(0, 0, width, height)
223                  operation:NSCompositeSourceOver
224                   fraction:alpha];
225   }) autorelease];
226 }
227
228 }  // namespace
229
230 @interface TabStripController (Private)
231 - (void)addSubviewToPermanentList:(NSView*)aView;
232 - (void)regenerateSubviewList;
233 - (NSInteger)indexForContentsView:(NSView*)view;
234 - (NSImage*)iconImageForContents:(content::WebContents*)contents;
235 - (void)updateIconsForContents:(content::WebContents*)contents
236                        atIndex:(NSInteger)modelIndex;
237 - (void)layoutTabsWithAnimation:(BOOL)animate
238              regenerateSubviews:(BOOL)doUpdate;
239 - (void)animationDidStop:(CAAnimation*)animation
240            forController:(TabController*)controller
241                 finished:(BOOL)finished;
242 - (NSInteger)indexFromModelIndex:(NSInteger)index;
243 - (void)clickNewTabButton:(id)sender;
244 - (NSInteger)numberOfOpenTabs;
245 - (NSInteger)numberOfOpenMiniTabs;
246 - (NSInteger)numberOfOpenNonMiniTabs;
247 - (void)mouseMoved:(NSEvent*)event;
248 - (void)setTabTrackingAreasEnabled:(BOOL)enabled;
249 - (void)droppingURLsAt:(NSPoint)point
250             givesIndex:(NSInteger*)index
251            disposition:(WindowOpenDisposition*)disposition;
252 - (void)setNewTabButtonHoverState:(BOOL)showHover;
253 - (void)themeDidChangeNotification:(NSNotification*)notification;
254 - (void)setNewTabImages;
255 @end
256
257 // A simple view class that prevents the Window Server from dragging the area
258 // behind tabs. Sometimes core animation confuses it. Unfortunately, it can also
259 // falsely pick up clicks during rapid tab closure, so we have to account for
260 // that.
261 @interface TabStripControllerDragBlockingView : NSView {
262   TabStripController* controller_;  // weak; owns us
263 }
264
265 - (id)initWithFrame:(NSRect)frameRect
266          controller:(TabStripController*)controller;
267
268 // Runs a nested runloop to do window move tracking. Overriding
269 // -mouseDownCanMoveWindow with a dynamic result instead doesn't work:
270 // http://www.cocoabuilder.com/archive/cocoa/219261-conditional-mousedowncanmovewindow-for-nsview.html
271 // http://www.cocoabuilder.com/archive/cocoa/92973-brushed-metal-window-dragging.html
272 - (void)trackClickForWindowMove:(NSEvent*)event;
273 @end
274
275 @implementation TabStripControllerDragBlockingView
276 - (BOOL)mouseDownCanMoveWindow {
277   return NO;
278 }
279
280 - (void)drawRect:(NSRect)rect {
281 }
282
283 - (id)initWithFrame:(NSRect)frameRect
284          controller:(TabStripController*)controller {
285   if ((self = [super initWithFrame:frameRect])) {
286     controller_ = controller;
287   }
288   return self;
289 }
290
291 // In "rapid tab closure" mode (i.e., the user is clicking close tab buttons in
292 // rapid succession), the animations confuse Cocoa's hit testing (which appears
293 // to use cached results, among other tricks), so this view can somehow end up
294 // getting a mouse down event. Thus we do an explicit hit test during rapid tab
295 // closure, and if we find that we got a mouse down we shouldn't have, we send
296 // it off to the appropriate view.
297 - (void)mouseDown:(NSEvent*)event {
298   NSView* superview = [self superview];
299   NSPoint hitLocation =
300       [[superview superview] convertPoint:[event locationInWindow]
301                                  fromView:nil];
302   NSView* hitView = [superview hitTest:hitLocation];
303
304   if ([controller_ inRapidClosureMode]) {
305     if (hitView != self) {
306       [hitView mouseDown:event];
307       return;
308     }
309   }
310
311   if (hitView == self) {
312     BrowserWindowController* windowController =
313         [BrowserWindowController browserWindowControllerForView:self];
314     if (![windowController isFullscreen]) {
315       [self trackClickForWindowMove:event];
316       return;
317     }
318   }
319   [super mouseDown:event];
320 }
321
322 - (void)trackClickForWindowMove:(NSEvent*)event {
323   NSWindow* window = [self window];
324   NSPoint frameOrigin = [window frame].origin;
325   NSPoint lastEventLoc = [window convertBaseToScreen:[event locationInWindow]];
326   while ((event = [NSApp nextEventMatchingMask:
327       NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask
328                                     untilDate:[NSDate distantFuture]
329                                        inMode:NSEventTrackingRunLoopMode
330                                       dequeue:YES]) &&
331       [event type] != NSLeftMouseUp) {
332     base::mac::ScopedNSAutoreleasePool pool;
333
334     NSPoint now = [window convertBaseToScreen:[event locationInWindow]];
335     frameOrigin.x += now.x - lastEventLoc.x;
336     frameOrigin.y += now.y - lastEventLoc.y;
337     [window setFrameOrigin:frameOrigin];
338     lastEventLoc = now;
339   }
340 }
341
342 @end
343
344 #pragma mark -
345
346 // A delegate, owned by the CAAnimation system, that is alerted when the
347 // animation to close a tab is completed. Calls back to the given tab strip
348 // to let it know that |controller_| is ready to be removed from the model.
349 // Since we only maintain weak references, the tab strip must call -invalidate:
350 // to prevent the use of dangling pointers.
351 @interface TabCloseAnimationDelegate : NSObject {
352  @private
353   TabStripController* strip_;  // weak; owns us indirectly
354   TabController* controller_;  // weak
355 }
356
357 // Will tell |strip| when the animation for |controller|'s view has completed.
358 // These should not be nil, and will not be retained.
359 - (id)initWithTabStrip:(TabStripController*)strip
360          tabController:(TabController*)controller;
361
362 // Invalidates this object so that no further calls will be made to
363 // |strip_|.  This should be called when |strip_| is released, to
364 // prevent attempts to call into the released object.
365 - (void)invalidate;
366
367 // CAAnimation delegate method
368 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished;
369
370 @end
371
372 @implementation TabCloseAnimationDelegate
373
374 - (id)initWithTabStrip:(TabStripController*)strip
375          tabController:(TabController*)controller {
376   if ((self = [super init])) {
377     DCHECK(strip && controller);
378     strip_ = strip;
379     controller_ = controller;
380   }
381   return self;
382 }
383
384 - (void)invalidate {
385   strip_ = nil;
386   controller_ = nil;
387 }
388
389 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
390   [strip_ animationDidStop:animation
391              forController:controller_
392                   finished:finished];
393 }
394
395 @end
396
397 #pragma mark -
398
399 // In general, there is a one-to-one correspondence between TabControllers,
400 // TabViews, TabContentsControllers, and the WebContents in the
401 // TabStripModel. In the steady-state, the indices line up so an index coming
402 // from the model is directly mapped to the same index in the parallel arrays
403 // holding our views and controllers. This is also true when new tabs are
404 // created (even though there is a small period of animation) because the tab is
405 // present in the model while the TabView is animating into place. As a result,
406 // nothing special need be done to handle "new tab" animation.
407 //
408 // This all goes out the window with the "close tab" animation. The animation
409 // kicks off in |-tabDetachedWithContents:atIndex:| with the notification that
410 // the tab has been removed from the model. The simplest solution at this
411 // point would be to remove the views and controllers as well, however once
412 // the TabView is removed from the view list, the tab z-order code takes care of
413 // removing it from the tab strip and we'll get no animation. That means if
414 // there is to be any visible animation, the TabView needs to stay around until
415 // its animation is complete. In order to maintain consistency among the
416 // internal parallel arrays, this means all structures are kept around until
417 // the animation completes. At this point, though, the model and our internal
418 // structures are out of sync: the indices no longer line up. As a result,
419 // there is a concept of a "model index" which represents an index valid in
420 // the TabStripModel. During steady-state, the "model index" is just the same
421 // index as our parallel arrays (as above), but during tab close animations,
422 // it is different, offset by the number of tabs preceding the index which
423 // are undergoing tab closing animation. As a result, the caller needs to be
424 // careful to use the available conversion routines when accessing the internal
425 // parallel arrays (e.g., -indexFromModelIndex:). Care also needs to be taken
426 // during tab layout to ignore closing tabs in the total width calculations and
427 // in individual tab positioning (to avoid moving them right back to where they
428 // were).
429 //
430 // In order to prevent actions being taken on tabs which are closing, the tab
431 // itself gets marked as such so it no longer will send back its select action
432 // or allow itself to be dragged. In addition, drags on the tab strip as a
433 // whole are disabled while there are tabs closing.
434
435 @implementation TabStripController
436
437 @synthesize leftIndentForControls = leftIndentForControls_;
438 @synthesize rightIndentForControls = rightIndentForControls_;
439
440 - (id)initWithView:(TabStripView*)view
441         switchView:(NSView*)switchView
442            browser:(Browser*)browser
443           delegate:(id<TabStripControllerDelegate>)delegate {
444   DCHECK(view && switchView && browser && delegate);
445   if ((self = [super init])) {
446     tabStripView_.reset([view retain]);
447     [tabStripView_ setController:self];
448     switchView_ = switchView;
449     browser_ = browser;
450     tabStripModel_ = browser_->tab_strip_model();
451     hoverTabSelector_.reset(new HoverTabSelector(tabStripModel_));
452     delegate_ = delegate;
453     bridge_.reset(new TabStripModelObserverBridge(tabStripModel_, self));
454     dragController_.reset(
455         [[TabStripDragController alloc] initWithTabStripController:self]);
456     tabContentsArray_.reset([[NSMutableArray alloc] init]);
457     tabArray_.reset([[NSMutableArray alloc] init]);
458     NSWindow* browserWindow = [view window];
459
460     // Important note: any non-tab subviews not added to |permanentSubviews_|
461     // (see |-addSubviewToPermanentList:|) will be wiped out.
462     permanentSubviews_.reset([[NSMutableArray alloc] init]);
463
464     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
465     defaultFavicon_.reset(
466         rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).CopyNSImage());
467
468     [self setLeftIndentForControls:[[self class] defaultLeftIndentForControls]];
469     [self setRightIndentForControls:0];
470
471     // Add this invisible view first so that it is ordered below other views.
472     dragBlockingView_.reset(
473         [[TabStripControllerDragBlockingView alloc] initWithFrame:NSZeroRect
474                                                        controller:self]);
475     [self addSubviewToPermanentList:dragBlockingView_];
476
477     newTabButton_ = [view getNewTabButton];
478     [newTabButton_ setWantsLayer:YES];
479     [self addSubviewToPermanentList:newTabButton_];
480     [newTabButton_ setTarget:self];
481     [newTabButton_ setAction:@selector(clickNewTabButton:)];
482
483     [self setNewTabImages];
484     newTabButtonShowingHoverImage_ = NO;
485     newTabTrackingArea_.reset(
486         [[CrTrackingArea alloc] initWithRect:[newTabButton_ bounds]
487                                      options:(NSTrackingMouseEnteredAndExited |
488                                               NSTrackingActiveAlways)
489                                        owner:self
490                                     userInfo:nil]);
491     if (browserWindow)  // Nil for Browsers without a tab strip (e.g. popups).
492       [newTabTrackingArea_ clearOwnerWhenWindowWillClose:browserWindow];
493     [newTabButton_ addTrackingArea:newTabTrackingArea_.get()];
494     targetFrames_.reset([[NSMutableDictionary alloc] init]);
495
496     newTabTargetFrame_ = NSZeroRect;
497     availableResizeWidth_ = kUseFullAvailableWidth;
498
499     closingControllers_.reset([[NSMutableSet alloc] init]);
500
501     // Install the permanent subviews.
502     [self regenerateSubviewList];
503
504     // Watch for notifications that the tab strip view has changed size so
505     // we can tell it to layout for the new size.
506     [[NSNotificationCenter defaultCenter]
507         addObserver:self
508            selector:@selector(tabViewFrameChanged:)
509                name:NSViewFrameDidChangeNotification
510              object:tabStripView_];
511
512     [[NSNotificationCenter defaultCenter]
513         addObserver:self
514            selector:@selector(themeDidChangeNotification:)
515                name:kBrowserThemeDidChangeNotification
516              object:nil];
517
518     trackingArea_.reset([[CrTrackingArea alloc]
519         initWithRect:NSZeroRect  // Ignored by NSTrackingInVisibleRect
520              options:NSTrackingMouseEnteredAndExited |
521                      NSTrackingMouseMoved |
522                      NSTrackingActiveAlways |
523                      NSTrackingInVisibleRect
524                owner:self
525             userInfo:nil]);
526     if (browserWindow)  // Nil for Browsers without a tab strip (e.g. popups).
527       [trackingArea_ clearOwnerWhenWindowWillClose:browserWindow];
528     [tabStripView_ addTrackingArea:trackingArea_.get()];
529
530     // Check to see if the mouse is currently in our bounds so we can
531     // enable the tracking areas.  Otherwise we won't get hover states
532     // or tab gradients if we load the window up under the mouse.
533     NSPoint mouseLoc = [[view window] mouseLocationOutsideOfEventStream];
534     mouseLoc = [view convertPoint:mouseLoc fromView:nil];
535     if (NSPointInRect(mouseLoc, [view bounds])) {
536       [self setTabTrackingAreasEnabled:YES];
537       mouseInside_ = YES;
538     }
539
540     // Set accessibility descriptions. http://openradar.appspot.com/7496255
541     NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_NEWTAB);
542     [[newTabButton_ cell]
543         accessibilitySetOverrideValue:description
544                          forAttribute:NSAccessibilityDescriptionAttribute];
545
546     // Controller may have been (re-)created by switching layout modes, which
547     // means the tab model is already fully formed with tabs. Need to walk the
548     // list and create the UI for each.
549     const int existingTabCount = tabStripModel_->count();
550     const content::WebContents* selection =
551         tabStripModel_->GetActiveWebContents();
552     for (int i = 0; i < existingTabCount; ++i) {
553       content::WebContents* currentContents =
554           tabStripModel_->GetWebContentsAt(i);
555       [self insertTabWithContents:currentContents
556                           atIndex:i
557                      inForeground:NO];
558       if (selection == currentContents) {
559         // Must manually force a selection since the model won't send
560         // selection messages in this scenario.
561         [self
562             activateTabWithContents:currentContents
563                    previousContents:NULL
564                             atIndex:i
565                              reason:TabStripModelObserver::CHANGE_REASON_NONE];
566       }
567     }
568     // Don't lay out the tabs until after the controller has been fully
569     // constructed.
570     if (existingTabCount) {
571       [self performSelectorOnMainThread:@selector(layoutTabs)
572                              withObject:nil
573                           waitUntilDone:NO];
574     }
575   }
576   return self;
577 }
578
579 - (void)dealloc {
580   [tabStripView_ setController:nil];
581
582   if (trackingArea_.get())
583     [tabStripView_ removeTrackingArea:trackingArea_.get()];
584
585   [newTabButton_ removeTrackingArea:newTabTrackingArea_.get()];
586   // Invalidate all closing animations so they don't call back to us after
587   // we're gone.
588   for (TabController* controller in closingControllers_.get()) {
589     NSView* view = [controller view];
590     [[[view animationForKey:@"frameOrigin"] delegate] invalidate];
591   }
592   [[NSNotificationCenter defaultCenter] removeObserver:self];
593   [tabStripView_ removeAllToolTips];
594   [super dealloc];
595 }
596
597 + (CGFloat)defaultTabHeight {
598   return 26.0;
599 }
600
601 + (CGFloat)defaultLeftIndentForControls {
602   // Default indentation leaves enough room so tabs don't overlap with the
603   // window controls.
604   return 70.0;
605 }
606
607 // Finds the TabContentsController associated with the given index into the tab
608 // model and swaps out the sole child of the contentArea to display its
609 // contents.
610 - (void)swapInTabAtIndex:(NSInteger)modelIndex {
611   DCHECK(modelIndex >= 0 && modelIndex < tabStripModel_->count());
612   NSInteger index = [self indexFromModelIndex:modelIndex];
613   TabContentsController* controller = [tabContentsArray_ objectAtIndex:index];
614
615   // Make sure we do not draw any transient arrangements of views.
616   gfx::ScopedNSDisableScreenUpdates ns_disabler;
617   // Make sure that any layers that move are not animated to their new
618   // positions.
619   ScopedCAActionDisabler ca_disabler;
620
621   // Resize the new view to fit the window. Calling |view| may lazily
622   // instantiate the TabContentsController from the nib. Until we call
623   // |-ensureContentsVisible|, the controller doesn't install the RWHVMac into
624   // the view hierarchy. This is in order to avoid sending the renderer a
625   // spurious default size loaded from the nib during the call to |-view|.
626   NSView* newView = [controller view];
627
628   // Turns content autoresizing off, so removing and inserting views won't
629   // trigger unnecessary content relayout.
630   [controller ensureContentsSizeDoesNotChange];
631
632   // Remove the old view from the view hierarchy. We know there's only one
633   // child of |switchView_| because we're the one who put it there. There
634   // may not be any children in the case of a tab that's been closed, in
635   // which case there's no swapping going on.
636   NSArray* subviews = [switchView_ subviews];
637   if ([subviews count]) {
638     NSView* oldView = [subviews objectAtIndex:0];
639     // Set newView frame to the oldVew frame to prevent NSSplitView hosting
640     // sidebar and tab content from resizing sidebar's content view.
641     // ensureContentsVisible (see below) sets content size and autoresizing
642     // properties.
643     [newView setFrame:[oldView frame]];
644     [switchView_ replaceSubview:oldView with:newView];
645   } else {
646     [newView setFrame:[switchView_ bounds]];
647     [switchView_ addSubview:newView];
648   }
649
650   // New content is in place, delegate should adjust itself accordingly.
651   [delegate_ onActivateTabWithContents:[controller webContents]];
652
653   // It also restores content autoresizing properties.
654   [controller ensureContentsVisible];
655
656   NSWindow* parentWindow = [switchView_ window];
657   ConstrainedWindowSheetController* sheetController =
658       [ConstrainedWindowSheetController
659           controllerForParentWindow:parentWindow];
660   [sheetController parentViewDidBecomeActive:newView];
661 }
662
663 // Create a new tab view and set its cell correctly so it draws the way we want
664 // it to. It will be sized and positioned by |-layoutTabs| so there's no need to
665 // set the frame here. This also creates the view as hidden, it will be
666 // shown during layout.
667 - (TabController*)newTab {
668   TabController* controller = [[[TabController alloc] init] autorelease];
669   [controller setTarget:self];
670   [controller setAction:@selector(selectTab:)];
671   [[controller view] setHidden:YES];
672
673   return controller;
674 }
675
676 // (Private) Handles a click on the new tab button.
677 - (void)clickNewTabButton:(id)sender {
678   content::RecordAction(UserMetricsAction("NewTab_Button"));
679   UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
680                             TabStripModel::NEW_TAB_ENUM_COUNT);
681   tabStripModel_->delegate()->AddTabAt(GURL(), -1, true);
682 }
683
684 // (Private) Returns the number of open tabs in the tab strip. This is the
685 // number of TabControllers we know about (as there's a 1-to-1 mapping from
686 // these controllers to a tab) less the number of closing tabs.
687 - (NSInteger)numberOfOpenTabs {
688   return static_cast<NSInteger>(tabStripModel_->count());
689 }
690
691 // (Private) Returns the number of open, mini-tabs.
692 - (NSInteger)numberOfOpenMiniTabs {
693   // Ask the model for the number of mini tabs. Note that tabs which are in
694   // the process of closing (i.e., whose controllers are in
695   // |closingControllers_|) have already been removed from the model.
696   return tabStripModel_->IndexOfFirstNonMiniTab();
697 }
698
699 // (Private) Returns the number of open, non-mini tabs.
700 - (NSInteger)numberOfOpenNonMiniTabs {
701   NSInteger number = [self numberOfOpenTabs] - [self numberOfOpenMiniTabs];
702   DCHECK_GE(number, 0);
703   return number;
704 }
705
706 // Given an index into the tab model, returns the index into the tab controller
707 // or tab contents controller array accounting for tabs that are currently
708 // closing. For example, if there are two tabs in the process of closing before
709 // |index|, this returns |index| + 2. If there are no closing tabs, this will
710 // return |index|.
711 - (NSInteger)indexFromModelIndex:(NSInteger)index {
712   DCHECK_GE(index, 0);
713   if (index < 0)
714     return index;
715
716   NSInteger i = 0;
717   for (TabController* controller in tabArray_.get()) {
718     if ([closingControllers_ containsObject:controller]) {
719       DCHECK([[controller tabView] isClosing]);
720       ++index;
721     }
722     if (i == index)  // No need to check anything after, it has no effect.
723       break;
724     ++i;
725   }
726   return index;
727 }
728
729 // Given an index into |tabArray_|, return the corresponding index into
730 // |tabStripModel_| or NSNotFound if the specified tab does not exist in
731 // the model (if it's closing, for example).
732 - (NSInteger)modelIndexFromIndex:(NSInteger)index {
733   NSInteger modelIndex = 0;
734   NSInteger arrayIndex = 0;
735   for (TabController* controller in tabArray_.get()) {
736     if (![closingControllers_ containsObject:controller]) {
737       if (arrayIndex == index)
738         return modelIndex;
739       ++modelIndex;
740     } else if (arrayIndex == index) {
741       // Tab is closing - no model index.
742       return NSNotFound;
743     }
744     ++arrayIndex;
745   }
746   return NSNotFound;
747 }
748
749 // Returns the index of the subview |view|. Returns -1 if not present. Takes
750 // closing tabs into account such that this index will correctly match the tab
751 // model. If |view| is in the process of closing, returns -1, as closing tabs
752 // are no longer in the model.
753 - (NSInteger)modelIndexForTabView:(NSView*)view {
754   NSInteger index = 0;
755   for (TabController* current in tabArray_.get()) {
756     // If |current| is closing, skip it.
757     if ([closingControllers_ containsObject:current])
758       continue;
759     else if ([current view] == view)
760       return index;
761     ++index;
762   }
763   return -1;
764 }
765
766 // Returns the index of the contents subview |view|. Returns -1 if not present.
767 // Takes closing tabs into account such that this index will correctly match the
768 // tab model. If |view| is in the process of closing, returns -1, as closing
769 // tabs are no longer in the model.
770 - (NSInteger)modelIndexForContentsView:(NSView*)view {
771   NSInteger index = 0;
772   NSInteger i = 0;
773   for (TabContentsController* current in tabContentsArray_.get()) {
774     // If the TabController corresponding to |current| is closing, skip it.
775     TabController* controller = [tabArray_ objectAtIndex:i];
776     if ([closingControllers_ containsObject:controller]) {
777       ++i;
778       continue;
779     } else if ([current view] == view) {
780       return index;
781     }
782     ++index;
783     ++i;
784   }
785   return -1;
786 }
787
788 - (NSArray*)selectedViews {
789   NSMutableArray* views = [NSMutableArray arrayWithCapacity:[tabArray_ count]];
790   for (TabController* tab in tabArray_.get()) {
791     if ([tab selected])
792       [views addObject:[tab tabView]];
793   }
794   return views;
795 }
796
797 // Returns the view at the given index, using the array of TabControllers to
798 // get the associated view. Returns nil if out of range.
799 - (NSView*)viewAtIndex:(NSUInteger)index {
800   if (index >= [tabArray_ count])
801     return NULL;
802   return [[tabArray_ objectAtIndex:index] view];
803 }
804
805 - (NSUInteger)viewsCount {
806   return [tabArray_ count];
807 }
808
809 // Called when the user clicks a tab. Tell the model the selection has changed,
810 // which feeds back into us via a notification.
811 - (void)selectTab:(id)sender {
812   DCHECK([sender isKindOfClass:[NSView class]]);
813   int index = [self modelIndexForTabView:sender];
814   NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
815   if (tabStripModel_->ContainsIndex(index)) {
816     if (modifiers & NSCommandKeyMask && modifiers & NSShiftKeyMask) {
817       tabStripModel_->AddSelectionFromAnchorTo(index);
818     } else if (modifiers & NSShiftKeyMask) {
819       tabStripModel_->ExtendSelectionTo(index);
820     } else if (modifiers & NSCommandKeyMask) {
821       tabStripModel_->ToggleSelectionAt(index);
822     } else {
823       tabStripModel_->ActivateTabAt(index, true);
824     }
825   }
826 }
827
828 // Called when the user closes a tab. Asks the model to close the tab. |sender|
829 // is the TabView that is potentially going away.
830 - (void)closeTab:(id)sender {
831   DCHECK([sender isKindOfClass:[TabView class]]);
832
833   // Cancel any pending tab transition.
834   hoverTabSelector_->CancelTabTransition();
835
836   if ([hoveredTab_ isEqual:sender]) {
837     hoveredTab_ = nil;
838   }
839
840   NSInteger index = [self modelIndexForTabView:sender];
841   if (!tabStripModel_->ContainsIndex(index))
842     return;
843
844   content::RecordAction(UserMetricsAction("CloseTab_Mouse"));
845   const NSInteger numberOfOpenTabs = [self numberOfOpenTabs];
846   if (numberOfOpenTabs > 1) {
847     bool isClosingLastTab = index == numberOfOpenTabs - 1;
848     if (!isClosingLastTab) {
849       // Limit the width available for laying out tabs so that tabs are not
850       // resized until a later time (when the mouse leaves the tab strip).
851       // However, if the tab being closed is a pinned tab, break out of
852       // rapid-closure mode since the mouse is almost guaranteed not to be over
853       // the closebox of the adjacent tab (due to the difference in widths).
854       // TODO(pinkerton): re-visit when handling tab overflow.
855       // http://crbug.com/188
856       if (tabStripModel_->IsTabPinned(index)) {
857         availableResizeWidth_ = kUseFullAvailableWidth;
858       } else {
859         NSView* penultimateTab = [self viewAtIndex:numberOfOpenTabs - 2];
860         availableResizeWidth_ = NSMaxX([penultimateTab frame]);
861       }
862     } else {
863       // If the rightmost tab is closed, change the available width so that
864       // another tab's close button lands below the cursor (assuming the tabs
865       // are currently below their maximum width and can grow).
866       NSView* lastTab = [self viewAtIndex:numberOfOpenTabs - 1];
867       availableResizeWidth_ = NSMaxX([lastTab frame]);
868     }
869     tabStripModel_->CloseWebContentsAt(
870         index,
871         TabStripModel::CLOSE_USER_GESTURE |
872         TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
873   } else {
874     // Use the standard window close if this is the last tab
875     // this prevents the tab from being removed from the model until after
876     // the window dissapears
877     [[tabStripView_ window] performClose:nil];
878   }
879 }
880
881 // Dispatch context menu commands for the given tab controller.
882 - (void)commandDispatch:(TabStripModel::ContextMenuCommand)command
883           forController:(TabController*)controller {
884   int index = [self modelIndexForTabView:[controller view]];
885   if (tabStripModel_->ContainsIndex(index))
886     tabStripModel_->ExecuteContextMenuCommand(index, command);
887 }
888
889 // Returns YES if the specificed command should be enabled for the given
890 // controller.
891 - (BOOL)isCommandEnabled:(TabStripModel::ContextMenuCommand)command
892            forController:(TabController*)controller {
893   int index = [self modelIndexForTabView:[controller view]];
894   if (!tabStripModel_->ContainsIndex(index))
895     return NO;
896   return tabStripModel_->IsContextMenuCommandEnabled(index, command) ? YES : NO;
897 }
898
899 // Returns a context menu model for a given controller. Caller owns the result.
900 - (ui::SimpleMenuModel*)contextMenuModelForController:(TabController*)controller
901     menuDelegate:(ui::SimpleMenuModel::Delegate*)delegate {
902   int index = [self modelIndexForTabView:[controller view]];
903   return new TabMenuModel(delegate, tabStripModel_, index);
904 }
905
906 // Returns a weak reference to the controller that manages dragging of tabs.
907 - (id<TabDraggingEventTarget>)dragController {
908   return dragController_.get();
909 }
910
911 - (void)insertPlaceholderForTab:(TabView*)tab frame:(NSRect)frame {
912   placeholderTab_ = tab;
913   placeholderFrame_ = frame;
914   [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:NO];
915 }
916
917 - (BOOL)isDragSessionActive {
918   return placeholderTab_ != nil;
919 }
920
921 - (BOOL)isTabFullyVisible:(TabView*)tab {
922   NSRect frame = [tab frame];
923   return NSMinX(frame) >= [self leftIndentForControls] &&
924       NSMaxX(frame) <= (NSMaxX([tabStripView_ frame]) -
925                         [self rightIndentForControls]);
926 }
927
928 - (void)showNewTabButton:(BOOL)show {
929   forceNewTabButtonHidden_ = show ? NO : YES;
930   if (forceNewTabButtonHidden_)
931     [newTabButton_ setHidden:YES];
932 }
933
934 // Lay out all tabs in the order of their TabContentsControllers, which matches
935 // the ordering in the TabStripModel. This call isn't that expensive, though
936 // it is O(n) in the number of tabs. Tabs will animate to their new position
937 // if the window is visible and |animate| is YES.
938 // TODO(pinkerton): Note this doesn't do too well when the number of min-sized
939 // tabs would cause an overflow. http://crbug.com/188
940 - (void)layoutTabsWithAnimation:(BOOL)animate
941              regenerateSubviews:(BOOL)doUpdate {
942   DCHECK([NSThread isMainThread]);
943   if (![tabArray_ count])
944     return;
945
946   const CGFloat kMaxTabWidth = [TabController maxTabWidth];
947   const CGFloat kMinTabWidth = [TabController minTabWidth];
948   const CGFloat kMinSelectedTabWidth = [TabController minSelectedTabWidth];
949   const CGFloat kMiniTabWidth = [TabController miniTabWidth];
950   const CGFloat kAppTabWidth = [TabController appTabWidth];
951
952   NSRect enclosingRect = NSZeroRect;
953   ScopedNSAnimationContextGroup mainAnimationGroup(animate);
954   mainAnimationGroup.SetCurrentContextDuration(kAnimationDuration);
955
956   // Update the current subviews and their z-order if requested.
957   if (doUpdate)
958     [self regenerateSubviewList];
959
960   // Compute the base width of tabs given how much room we're allowed. Note that
961   // mini-tabs have a fixed width. We may not be able to use the entire width
962   // if the user is quickly closing tabs. This may be negative, but that's okay
963   // (taken care of by |MAX()| when calculating tab sizes).
964   CGFloat availableSpace = 0;
965   if ([self inRapidClosureMode]) {
966     availableSpace = availableResizeWidth_;
967   } else {
968     availableSpace = NSWidth([tabStripView_ frame]);
969
970     // Account for the width of the new tab button.
971     availableSpace -= NSWidth([newTabButton_ frame]) + kNewTabButtonOffset;
972
973     // Account for the right-side controls if not in rapid closure mode.
974     // (In rapid closure mode, the available width is set based on the
975     // position of the rightmost tab, not based on the width of the tab strip,
976     // so the right controls have already been accounted for.)
977     availableSpace -= [self rightIndentForControls];
978   }
979
980   // Need to leave room for the left-side controls even in rapid closure mode.
981   availableSpace -= [self leftIndentForControls];
982
983   // If there are any mini tabs, account for the extra spacing between the last
984   // mini tab and the first regular tab.
985   if ([self numberOfOpenMiniTabs])
986     availableSpace -= kLastMiniTabSpacing;
987
988   // This may be negative, but that's okay (taken care of by |MAX()| when
989   // calculating tab sizes). "mini" tabs in horizontal mode just get a special
990   // section, they don't change size.
991   CGFloat availableSpaceForNonMini = availableSpace;
992   availableSpaceForNonMini -=
993       [self numberOfOpenMiniTabs] * (kMiniTabWidth - kTabOverlap);
994
995   // Initialize |nonMiniTabWidth| in case there aren't any non-mini-tabs; this
996   // value shouldn't actually be used.
997   CGFloat nonMiniTabWidth = kMaxTabWidth;
998   CGFloat nonMiniTabWidthFraction = 0;
999   const NSInteger numberOfOpenNonMiniTabs = [self numberOfOpenNonMiniTabs];
1000   if (numberOfOpenNonMiniTabs) {
1001     // Find the width of a non-mini-tab. This only applies to horizontal
1002     // mode. Add in the amount we "get back" from the tabs overlapping.
1003     availableSpaceForNonMini += (numberOfOpenNonMiniTabs - 1) * kTabOverlap;
1004
1005     // Divide up the space between the non-mini-tabs.
1006     nonMiniTabWidth = availableSpaceForNonMini / numberOfOpenNonMiniTabs;
1007
1008     // Clamp the width between the max and min.
1009     nonMiniTabWidth = MAX(MIN(nonMiniTabWidth, kMaxTabWidth), kMinTabWidth);
1010
1011     // Separate integral and fractional parts.
1012     CGFloat integralPart = std::floor(nonMiniTabWidth);
1013     nonMiniTabWidthFraction = nonMiniTabWidth - integralPart;
1014     nonMiniTabWidth = integralPart;
1015   }
1016
1017   BOOL visible = [[tabStripView_ window] isVisible];
1018
1019   CGFloat offset = [self leftIndentForControls];
1020   bool hasPlaceholderGap = false;
1021   // Whether or not the last tab processed by the loop was a mini tab.
1022   BOOL isLastTabMini = NO;
1023   CGFloat tabWidthAccumulatedFraction = 0;
1024   NSInteger laidOutNonMiniTabs = 0;
1025
1026   // Remove all the tooltip rects on the tab strip so that we can re-apply
1027   // them to correspond with the new tab positions.
1028   [tabStripView_ removeAllToolTips];
1029
1030   for (TabController* tab in tabArray_.get()) {
1031     // Ignore a tab that is going through a close animation.
1032     if ([closingControllers_ containsObject:tab])
1033       continue;
1034
1035     BOOL isPlaceholder = [[tab view] isEqual:placeholderTab_];
1036     NSRect tabFrame = [[tab view] frame];
1037     tabFrame.size.height = [[self class] defaultTabHeight];
1038     tabFrame.origin.y = 0;
1039     tabFrame.origin.x = offset;
1040
1041     // If the tab is hidden, we consider it a new tab. We make it visible
1042     // and animate it in.
1043     BOOL newTab = [[tab view] isHidden];
1044     if (newTab)
1045       [[tab view] setHidden:NO];
1046
1047     if (isPlaceholder) {
1048       // Move the current tab to the correct location instantly.
1049       // We need a duration or else it doesn't cancel an inflight animation.
1050       ScopedNSAnimationContextGroup localAnimationGroup(animate);
1051       localAnimationGroup.SetCurrentContextShortestDuration();
1052       tabFrame.origin.x = placeholderFrame_.origin.x;
1053       id target = animate ? [[tab view] animator] : [tab view];
1054       [target setFrame:tabFrame];
1055
1056       // Store the frame by identifier to avoid redundant calls to animator.
1057       NSValue* identifier = [NSValue valueWithPointer:[tab view]];
1058       [targetFrames_ setObject:[NSValue valueWithRect:tabFrame]
1059                         forKey:identifier];
1060       continue;
1061     }
1062
1063     if (placeholderTab_ && !hasPlaceholderGap) {
1064       const CGFloat placeholderMin = NSMinX(placeholderFrame_);
1065       // If the left edge is to the left of the placeholder's left, but the
1066       // mid is to the right of it slide over to make space for it.
1067       if (NSMidX(tabFrame) > placeholderMin) {
1068         hasPlaceholderGap = true;
1069         offset += NSWidth(placeholderFrame_);
1070         offset -= kTabOverlap;
1071         tabFrame.origin.x = offset;
1072       }
1073     }
1074
1075     // Set the width. Selected tabs are slightly wider when things get really
1076     // small and thus we enforce a different minimum width.
1077     BOOL isMini = [tab mini];
1078     if (isMini) {
1079       tabFrame.size.width = [tab app] ? kAppTabWidth : kMiniTabWidth;
1080     } else {
1081       // Tabs have non-integer widths. Assign the integer part to the tab, and
1082       // keep an accumulation of the fractional parts. When the fractional
1083       // accumulation gets to be more than one pixel, assign that to the current
1084       // tab being laid out. This is vaguely inspired by Bresenham's line
1085       // algorithm.
1086       tabFrame.size.width = nonMiniTabWidth;
1087       tabWidthAccumulatedFraction += nonMiniTabWidthFraction;
1088
1089       if (tabWidthAccumulatedFraction >= 1.0) {
1090         ++tabFrame.size.width;
1091         --tabWidthAccumulatedFraction;
1092       }
1093
1094       // In case of rounding error, give any left over pixels to the last tab.
1095       if (laidOutNonMiniTabs == numberOfOpenNonMiniTabs - 1 &&
1096           tabWidthAccumulatedFraction > 0.5) {
1097         ++tabFrame.size.width;
1098       }
1099
1100       ++laidOutNonMiniTabs;
1101     }
1102
1103     if ([tab selected])
1104       tabFrame.size.width = MAX(tabFrame.size.width, kMinSelectedTabWidth);
1105
1106     // If this is the first non-mini tab, then add a bit of spacing between this
1107     // and the last mini tab.
1108     if (!isMini && isLastTabMini) {
1109       offset += kLastMiniTabSpacing;
1110       tabFrame.origin.x = offset;
1111     }
1112     isLastTabMini = isMini;
1113
1114     // Animate a new tab in by putting it below the horizon unless told to put
1115     // it in a specific location (i.e., from a drop).
1116     if (newTab && visible && animate) {
1117       if (NSEqualRects(droppedTabFrame_, NSZeroRect)) {
1118         [[tab view] setFrame:NSOffsetRect(tabFrame, 0, -NSHeight(tabFrame))];
1119       } else {
1120         [[tab view] setFrame:droppedTabFrame_];
1121         droppedTabFrame_ = NSZeroRect;
1122       }
1123     }
1124
1125     // Check the frame by identifier to avoid redundant calls to animator.
1126     id frameTarget = visible && animate ? [[tab view] animator] : [tab view];
1127     NSValue* identifier = [NSValue valueWithPointer:[tab view]];
1128     NSValue* oldTargetValue = [targetFrames_ objectForKey:identifier];
1129     if (!oldTargetValue ||
1130         !NSEqualRects([oldTargetValue rectValue], tabFrame)) {
1131       [frameTarget setFrame:tabFrame];
1132       [targetFrames_ setObject:[NSValue valueWithRect:tabFrame]
1133                         forKey:identifier];
1134     }
1135
1136     enclosingRect = NSUnionRect(tabFrame, enclosingRect);
1137
1138     offset += NSWidth(tabFrame);
1139     offset -= kTabOverlap;
1140
1141     // Create a rect which starts at the point where the tab overlap will end so
1142     // that as the mouse cursor crosses over the boundary it will get updated.
1143     // The inset is based on a multiplier of the height.
1144     float insetWidth = NSHeight(tabFrame) * [TabView insetMultiplier];
1145     // NSInsetRect will also expose the "insetWidth" at the right of the tab.
1146     NSRect tabToolTipRect = NSInsetRect(tabFrame, insetWidth, 0);
1147     [tabStripView_ addToolTipRect:tabToolTipRect owner:self userData:nil];
1148
1149     // Also create two more rects in the remaining space so that the tooltip
1150     // is more likely to get updated crossing tabs.
1151     // These rects "cover" the right edge of the previous tab that was exposed
1152     // since the tabs overlap.
1153     tabToolTipRect = tabFrame;
1154     tabToolTipRect.size.width = insetWidth / 2.0;
1155     [tabStripView_ addToolTipRect:tabToolTipRect owner:self userData:nil];
1156
1157     tabToolTipRect = NSOffsetRect(tabToolTipRect, insetWidth / 2.0, 0);
1158     [tabStripView_ addToolTipRect:tabToolTipRect owner:self userData:nil];
1159   }
1160
1161   // Hide the new tab button if we're explicitly told to. It may already
1162   // be hidden, doing it again doesn't hurt. Otherwise position it
1163   // appropriately, showing it if necessary.
1164   if (forceNewTabButtonHidden_) {
1165     [newTabButton_ setHidden:YES];
1166   } else {
1167     NSRect newTabNewFrame = [newTabButton_ frame];
1168     // We've already ensured there's enough space for the new tab button
1169     // so we don't have to check it against the available space. We do need
1170     // to make sure we put it after any placeholder.
1171     CGFloat maxTabX = MAX(offset, NSMaxX(placeholderFrame_) - kTabOverlap);
1172     newTabNewFrame.origin = NSMakePoint(maxTabX + kNewTabButtonOffset, 0);
1173     if ([tabContentsArray_ count])
1174       [newTabButton_ setHidden:NO];
1175
1176     if (!NSEqualRects(newTabTargetFrame_, newTabNewFrame)) {
1177       // Set the new tab button image correctly based on where the cursor is.
1178       NSWindow* window = [tabStripView_ window];
1179       NSPoint currentMouse = [window mouseLocationOutsideOfEventStream];
1180       currentMouse = [tabStripView_ convertPoint:currentMouse fromView:nil];
1181
1182       BOOL shouldShowHover = [newTabButton_ pointIsOverButton:currentMouse];
1183       [self setNewTabButtonHoverState:shouldShowHover];
1184
1185       // Move the new tab button into place. We want to animate the new tab
1186       // button if it's moving to the left (closing a tab), but not when it's
1187       // moving to the right (inserting a new tab). If moving right, we need
1188       // to use a very small duration to make sure we cancel any in-flight
1189       // animation to the left.
1190       if (visible && animate) {
1191         ScopedNSAnimationContextGroup localAnimationGroup(true);
1192         BOOL movingLeft = NSMinX(newTabNewFrame) < NSMinX(newTabTargetFrame_);
1193         if (!movingLeft) {
1194           localAnimationGroup.SetCurrentContextShortestDuration();
1195         }
1196         [[newTabButton_ animator] setFrame:newTabNewFrame];
1197         newTabTargetFrame_ = newTabNewFrame;
1198       } else {
1199         [newTabButton_ setFrame:newTabNewFrame];
1200         newTabTargetFrame_ = newTabNewFrame;
1201       }
1202     }
1203   }
1204
1205   [dragBlockingView_ setFrame:enclosingRect];
1206
1207   // Add a catch-all tooltip rect which will handle any remaining tab strip
1208   // region not covered by tab-specific rects.
1209   [tabStripView_ addToolTipRect:enclosingRect owner:self userData:nil];
1210
1211   // Mark that we've successfully completed layout of at least one tab.
1212   initialLayoutComplete_ = YES;
1213 }
1214
1215 // Return the current hovered tab's tooltip when requested by the tooltip
1216 // manager.
1217 - (NSString*) view:(NSView*)view
1218   stringForToolTip:(NSToolTipTag)tag
1219              point:(NSPoint)point
1220           userData:(void*)data {
1221   return [hoveredTab_ toolTipText];
1222 }
1223
1224 // When we're told to layout from the public API we usually want to animate,
1225 // except when it's the first time.
1226 - (void)layoutTabs {
1227   [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:YES];
1228 }
1229
1230 - (void)layoutTabsWithoutAnimation {
1231   [self layoutTabsWithAnimation:NO regenerateSubviews:YES];
1232 }
1233
1234 // Handles setting the title of the tab based on the given |contents|. Uses
1235 // a canned string if |contents| is NULL.
1236 - (void)setTabTitle:(TabController*)tab withContents:(WebContents*)contents {
1237   base::string16 title;
1238   if (contents)
1239     title = contents->GetTitle();
1240   if (title.empty())
1241     title = l10n_util::GetStringUTF16(IDS_BROWSER_WINDOW_MAC_TAB_UNTITLED);
1242   [tab setTitle:base::SysUTF16ToNSString(title)];
1243
1244   const base::string16& toolTip = chrome::AssembleTabTooltipText(
1245       title, chrome::GetTabMediaStateForContents(contents));
1246   [tab setToolTip:base::SysUTF16ToNSString(toolTip)];
1247 }
1248
1249 // Called when a notification is received from the model to insert a new tab
1250 // at |modelIndex|.
1251 - (void)insertTabWithContents:(content::WebContents*)contents
1252                       atIndex:(NSInteger)modelIndex
1253                  inForeground:(bool)inForeground {
1254   DCHECK(contents);
1255   DCHECK(modelIndex == TabStripModel::kNoTab ||
1256          tabStripModel_->ContainsIndex(modelIndex));
1257
1258   // Cancel any pending tab transition.
1259   hoverTabSelector_->CancelTabTransition();
1260
1261   // Take closing tabs into account.
1262   NSInteger index = [self indexFromModelIndex:modelIndex];
1263
1264   // Make a new tab. Load the contents of this tab from the nib and associate
1265   // the new controller with |contents| so it can be looked up later.
1266   base::scoped_nsobject<TabContentsController> contentsController(
1267       [[TabContentsController alloc] initWithContents:contents]);
1268   [tabContentsArray_ insertObject:contentsController atIndex:index];
1269
1270   // Make a new tab and add it to the strip. Keep track of its controller.
1271   TabController* newController = [self newTab];
1272   [newController setMini:tabStripModel_->IsMiniTab(modelIndex)];
1273   [newController setPinned:tabStripModel_->IsTabPinned(modelIndex)];
1274   [newController setApp:tabStripModel_->IsAppTab(modelIndex)];
1275   [newController setUrl:contents->GetURL()];
1276   [tabArray_ insertObject:newController atIndex:index];
1277   NSView* newView = [newController view];
1278
1279   // Set the originating frame to just below the strip so that it animates
1280   // upwards as it's being initially layed out. Oddly, this works while doing
1281   // something similar in |-layoutTabs| confuses the window server.
1282   [newView setFrame:NSOffsetRect([newView frame],
1283                                  0, -[[self class] defaultTabHeight])];
1284
1285   [self setTabTitle:newController withContents:contents];
1286
1287   // If a tab is being inserted, we can again use the entire tab strip width
1288   // for layout.
1289   availableResizeWidth_ = kUseFullAvailableWidth;
1290
1291   // We don't need to call |-layoutTabs| if the tab will be in the foreground
1292   // because it will get called when the new tab is selected by the tab model.
1293   // Whenever |-layoutTabs| is called, it'll also add the new subview.
1294   if (!inForeground) {
1295     [self layoutTabs];
1296   }
1297
1298   // During normal loading, we won't yet have a favicon and we'll get
1299   // subsequent state change notifications to show the throbber, but when we're
1300   // dragging a tab out into a new window, we have to put the tab's favicon
1301   // into the right state up front as we won't be told to do it from anywhere
1302   // else.
1303   [self updateIconsForContents:contents atIndex:modelIndex];
1304 }
1305
1306 // Called before |contents| is deactivated.
1307 - (void)tabDeactivatedWithContents:(content::WebContents*)contents {
1308   contents->StoreFocus();
1309 }
1310
1311 // Called when a notification is received from the model to select a particular
1312 // tab. Swaps in the toolbar and content area associated with |newContents|.
1313 - (void)activateTabWithContents:(content::WebContents*)newContents
1314                previousContents:(content::WebContents*)oldContents
1315                         atIndex:(NSInteger)modelIndex
1316                          reason:(int)reason {
1317   // Take closing tabs into account.
1318   if (oldContents) {
1319     int oldModelIndex =
1320         browser_->tab_strip_model()->GetIndexOfWebContents(oldContents);
1321     if (oldModelIndex != -1) {  // When closing a tab, the old tab may be gone.
1322       NSInteger oldIndex = [self indexFromModelIndex:oldModelIndex];
1323       TabContentsController* oldController =
1324           [tabContentsArray_ objectAtIndex:oldIndex];
1325       [oldController willBecomeUnselectedTab];
1326       oldContents->WasHidden();
1327     }
1328   }
1329
1330   NSUInteger activeIndex = [self indexFromModelIndex:modelIndex];
1331
1332   [tabArray_ enumerateObjectsUsingBlock:^(TabController* current,
1333                                           NSUInteger index,
1334                                           BOOL* stop) {
1335       [current setActive:index == activeIndex];
1336   }];
1337
1338   // Tell the new tab contents it is about to become the selected tab. Here it
1339   // can do things like make sure the toolbar is up to date.
1340   TabContentsController* newController =
1341       [tabContentsArray_ objectAtIndex:activeIndex];
1342   [newController willBecomeSelectedTab];
1343
1344   // Relayout for new tabs and to let the selected tab grow to be larger in
1345   // size than surrounding tabs if the user has many. This also raises the
1346   // selected tab to the top.
1347   [self layoutTabs];
1348
1349   // Swap in the contents for the new tab.
1350   [self swapInTabAtIndex:modelIndex];
1351
1352   if (newContents) {
1353     newContents->WasShown();
1354     newContents->RestoreFocus();
1355   }
1356 }
1357
1358 - (void)tabSelectionChanged {
1359   // First get the vector of indices, which is allays sorted in ascending order.
1360   ui::ListSelectionModel::SelectedIndices selection(
1361       tabStripModel_->selection_model().selected_indices());
1362   // Iterate through all of the tabs, selecting each as necessary.
1363   ui::ListSelectionModel::SelectedIndices::iterator iter = selection.begin();
1364   int i = 0;
1365   for (TabController* current in tabArray_.get()) {
1366     BOOL selected = iter != selection.end() &&
1367         [self indexFromModelIndex:*iter] == i;
1368     [current setSelected:selected];
1369     if (selected)
1370       ++iter;
1371     ++i;
1372   }
1373 }
1374
1375 - (void)tabReplacedWithContents:(content::WebContents*)newContents
1376                previousContents:(content::WebContents*)oldContents
1377                         atIndex:(NSInteger)modelIndex {
1378   NSInteger index = [self indexFromModelIndex:modelIndex];
1379   TabContentsController* oldController =
1380       [tabContentsArray_ objectAtIndex:index];
1381   DCHECK_EQ(oldContents, [oldController webContents]);
1382
1383   // Simply create a new TabContentsController for |newContents| and place it
1384   // into the array, replacing |oldContents|.  An ActiveTabChanged notification
1385   // will follow, at which point we will install the new view.
1386   base::scoped_nsobject<TabContentsController> newController(
1387       [[TabContentsController alloc] initWithContents:newContents]);
1388
1389   // Bye bye, |oldController|.
1390   [tabContentsArray_ replaceObjectAtIndex:index withObject:newController];
1391
1392   // Fake a tab changed notification to force tab titles and favicons to update.
1393   [self tabChangedWithContents:newContents
1394                        atIndex:modelIndex
1395                     changeType:TabStripModelObserver::ALL];
1396 }
1397
1398 // Remove all knowledge about this tab and its associated controller, and remove
1399 // the view from the strip.
1400 - (void)removeTab:(TabController*)controller {
1401   // Cancel any pending tab transition.
1402   hoverTabSelector_->CancelTabTransition();
1403
1404   NSUInteger index = [tabArray_ indexOfObject:controller];
1405
1406   // Release the tab contents controller so those views get destroyed. This
1407   // will remove all the tab content Cocoa views from the hierarchy. A
1408   // subsequent "select tab" notification will follow from the model. To
1409   // tell us what to swap in in its absence.
1410   [tabContentsArray_ removeObjectAtIndex:index];
1411
1412   // Remove the view from the tab strip.
1413   NSView* tab = [controller view];
1414   [tab removeFromSuperview];
1415
1416   // Remove ourself as an observer.
1417   [[NSNotificationCenter defaultCenter]
1418       removeObserver:self
1419                 name:NSViewDidUpdateTrackingAreasNotification
1420               object:tab];
1421
1422   // Clear the tab controller's target.
1423   // TODO(viettrungluu): [crbug.com/23829] Find a better way to handle the tab
1424   // controller's target.
1425   [controller setTarget:nil];
1426
1427   if ([hoveredTab_ isEqual:tab])
1428     hoveredTab_ = nil;
1429
1430   NSValue* identifier = [NSValue valueWithPointer:tab];
1431   [targetFrames_ removeObjectForKey:identifier];
1432
1433   // Once we're totally done with the tab, delete its controller
1434   [tabArray_ removeObjectAtIndex:index];
1435 }
1436
1437 // Called by the CAAnimation delegate when the tab completes the closing
1438 // animation.
1439 - (void)animationDidStop:(CAAnimation*)animation
1440            forController:(TabController*)controller
1441                 finished:(BOOL)finished{
1442   [[animation delegate] invalidate];
1443   [closingControllers_ removeObject:controller];
1444   [self removeTab:controller];
1445 }
1446
1447 // Save off which TabController is closing and tell its view's animator
1448 // where to move the tab to. Registers a delegate to call back when the
1449 // animation is complete in order to remove the tab from the model.
1450 - (void)startClosingTabWithAnimation:(TabController*)closingTab {
1451   DCHECK([NSThread isMainThread]);
1452
1453   // Cancel any pending tab transition.
1454   hoverTabSelector_->CancelTabTransition();
1455
1456   // Save off the controller into the set of animating tabs. This alerts
1457   // the layout method to not do anything with it and allows us to correctly
1458   // calculate offsets when working with indices into the model.
1459   [closingControllers_ addObject:closingTab];
1460
1461   // Mark the tab as closing. This prevents it from generating any drags or
1462   // selections while it's animating closed.
1463   [[closingTab tabView] setClosing:YES];
1464
1465   // Register delegate (owned by the animation system).
1466   NSView* tabView = [closingTab view];
1467   CAAnimation* animation = [[tabView animationForKey:@"frameOrigin"] copy];
1468   [animation autorelease];
1469   base::scoped_nsobject<TabCloseAnimationDelegate> delegate(
1470       [[TabCloseAnimationDelegate alloc] initWithTabStrip:self
1471                                             tabController:closingTab]);
1472   [animation setDelegate:delegate.get()];  // Retains delegate.
1473   NSMutableDictionary* animationDictionary =
1474       [NSMutableDictionary dictionaryWithDictionary:[tabView animations]];
1475   [animationDictionary setObject:animation forKey:@"frameOrigin"];
1476   [tabView setAnimations:animationDictionary];
1477
1478   // Periscope down! Animate the tab.
1479   NSRect newFrame = [tabView frame];
1480   newFrame = NSOffsetRect(newFrame, 0, -newFrame.size.height);
1481   ScopedNSAnimationContextGroup animationGroup(true);
1482   animationGroup.SetCurrentContextDuration(kAnimationDuration);
1483   [[tabView animator] setFrame:newFrame];
1484 }
1485
1486 // Called when a notification is received from the model that the given tab
1487 // has gone away. Start an animation then force a layout to put everything
1488 // in motion.
1489 - (void)tabDetachedWithContents:(content::WebContents*)contents
1490                         atIndex:(NSInteger)modelIndex {
1491   // Take closing tabs into account.
1492   NSInteger index = [self indexFromModelIndex:modelIndex];
1493
1494   // Cancel any pending tab transition.
1495   hoverTabSelector_->CancelTabTransition();
1496
1497   TabController* tab = [tabArray_ objectAtIndex:index];
1498   if (tabStripModel_->count() > 0) {
1499     [self startClosingTabWithAnimation:tab];
1500     [self layoutTabs];
1501   } else {
1502     // Don't remove the tab, as that makes the window look jarring without any
1503     // tabs. Instead, simply mark it as closing to prevent the tab from
1504     // generating any drags or selections.
1505     [[tab tabView] setClosing:YES];
1506   }
1507
1508   [delegate_ onTabDetachedWithContents:contents];
1509 }
1510
1511 // A helper routine for creating an NSImageView to hold the favicon or app icon
1512 // for |contents|.
1513 - (NSImage*)iconImageForContents:(content::WebContents*)contents {
1514   extensions::TabHelper* extensions_tab_helper =
1515       extensions::TabHelper::FromWebContents(contents);
1516   BOOL isApp = extensions_tab_helper->is_app();
1517   NSImage* image = nil;
1518   // Favicons come from the renderer, and the renderer draws everything in the
1519   // system color space.
1520   CGColorSpaceRef colorSpace = base::mac::GetSystemColorSpace();
1521   if (isApp) {
1522     SkBitmap* icon = extensions_tab_helper->GetExtensionAppIcon();
1523     if (icon)
1524       image = gfx::SkBitmapToNSImageWithColorSpace(*icon, colorSpace);
1525   } else {
1526     image = mac::FaviconForWebContents(contents);
1527   }
1528
1529   // Either we don't have a valid favicon or there was some issue converting it
1530   // from an SkBitmap. Either way, just show the default.
1531   if (!image)
1532     image = defaultFavicon_.get();
1533
1534   return image;
1535 }
1536
1537 // Updates the current loading state, replacing the icon view with a favicon,
1538 // a throbber, the default icon, or nothing at all.
1539 - (void)updateIconsForContents:(content::WebContents*)contents
1540                        atIndex:(NSInteger)modelIndex {
1541   if (!contents)
1542     return;
1543
1544   static NSImage* throbberWaitingImage =
1545       ResourceBundle::GetSharedInstance().GetNativeImageNamed(
1546           IDR_THROBBER_WAITING).CopyNSImage();
1547   static NSImage* throbberLoadingImage =
1548       ResourceBundle::GetSharedInstance().GetNativeImageNamed(
1549           IDR_THROBBER).CopyNSImage();
1550   static NSImage* sadFaviconImage =
1551       ResourceBundle::GetSharedInstance().GetNativeImageNamed(
1552           IDR_SAD_FAVICON).CopyNSImage();
1553
1554   // Take closing tabs into account.
1555   NSInteger index = [self indexFromModelIndex:modelIndex];
1556   TabController* tabController = [tabArray_ objectAtIndex:index];
1557
1558   FaviconTabHelper* favicon_tab_helper =
1559       FaviconTabHelper::FromWebContents(contents);
1560   bool oldHasIcon = [tabController iconView] != nil;
1561   bool newHasIcon = favicon_tab_helper->ShouldDisplayFavicon() ||
1562       tabStripModel_->IsMiniTab(modelIndex);  // Always show icon if mini.
1563
1564   TabLoadingState oldState = [tabController loadingState];
1565   TabLoadingState newState = kTabDone;
1566   NSImage* throbberImage = nil;
1567   if (contents->IsCrashed()) {
1568     newState = kTabCrashed;
1569     newHasIcon = true;
1570   } else if (contents->IsWaitingForResponse()) {
1571     newState = kTabWaiting;
1572     throbberImage = throbberWaitingImage;
1573   } else if (contents->IsLoadingToDifferentDocument()) {
1574     newState = kTabLoading;
1575     throbberImage = throbberLoadingImage;
1576   }
1577
1578   if (oldState != newState)
1579     [tabController setLoadingState:newState];
1580
1581   // While loading, this function is called repeatedly with the same state.
1582   // To avoid expensive unnecessary view manipulation, only make changes when
1583   // the state is actually changing.  When loading is complete (kTabDone),
1584   // every call to this function is significant.
1585   if (newState == kTabDone || oldState != newState ||
1586       oldHasIcon != newHasIcon) {
1587     if (newHasIcon) {
1588       if (newState == kTabDone) {
1589         [tabController setIconImage:[self iconImageForContents:contents]];
1590         const TabMediaState mediaState =
1591             chrome::GetTabMediaStateForContents(contents);
1592         // Create MediaIndicatorView upon first use.
1593         if (mediaState != TAB_MEDIA_STATE_NONE &&
1594             ![tabController mediaIndicatorView]) {
1595           MediaIndicatorView* const mediaIndicatorView =
1596               [[[MediaIndicatorView alloc] init] autorelease];
1597           [tabController setMediaIndicatorView:mediaIndicatorView];
1598         }
1599         [[tabController mediaIndicatorView] updateIndicator:mediaState];
1600       } else if (newState == kTabCrashed) {
1601         [tabController setIconImage:sadFaviconImage withToastAnimation:YES];
1602         [[tabController mediaIndicatorView]
1603           updateIndicator:TAB_MEDIA_STATE_NONE];
1604       } else {
1605         [tabController setIconImage:throbberImage];
1606       }
1607     } else {
1608       [tabController setIconImage:nil];
1609     }
1610   }
1611 }
1612
1613 // Called when a notification is received from the model that the given tab
1614 // has been updated. |loading| will be YES when we only want to update the
1615 // throbber state, not anything else about the (partially) loading tab.
1616 - (void)tabChangedWithContents:(content::WebContents*)contents
1617                        atIndex:(NSInteger)modelIndex
1618                     changeType:(TabStripModelObserver::TabChangeType)change {
1619   // Take closing tabs into account.
1620   NSInteger index = [self indexFromModelIndex:modelIndex];
1621
1622   if (modelIndex == tabStripModel_->active_index())
1623     [delegate_ onTabChanged:change withContents:contents];
1624
1625   if (change == TabStripModelObserver::TITLE_NOT_LOADING) {
1626     // TODO(sky): make this work.
1627     // We'll receive another notification of the change asynchronously.
1628     return;
1629   }
1630
1631   TabController* tabController = [tabArray_ objectAtIndex:index];
1632
1633   if (change != TabStripModelObserver::LOADING_ONLY)
1634     [self setTabTitle:tabController withContents:contents];
1635
1636   [self updateIconsForContents:contents atIndex:modelIndex];
1637
1638   TabContentsController* updatedController =
1639       [tabContentsArray_ objectAtIndex:index];
1640   [updatedController tabDidChange:contents];
1641 }
1642
1643 // Called when a tab is moved (usually by drag&drop). Keep our parallel arrays
1644 // in sync with the tab strip model. It can also be pinned/unpinned
1645 // simultaneously, so we need to take care of that.
1646 - (void)tabMovedWithContents:(content::WebContents*)contents
1647                    fromIndex:(NSInteger)modelFrom
1648                      toIndex:(NSInteger)modelTo {
1649   // Take closing tabs into account.
1650   NSInteger from = [self indexFromModelIndex:modelFrom];
1651   NSInteger to = [self indexFromModelIndex:modelTo];
1652
1653   // Cancel any pending tab transition.
1654   hoverTabSelector_->CancelTabTransition();
1655
1656   base::scoped_nsobject<TabContentsController> movedTabContentsController(
1657       [[tabContentsArray_ objectAtIndex:from] retain]);
1658   [tabContentsArray_ removeObjectAtIndex:from];
1659   [tabContentsArray_ insertObject:movedTabContentsController.get()
1660                           atIndex:to];
1661   base::scoped_nsobject<TabController> movedTabController(
1662       [[tabArray_ objectAtIndex:from] retain]);
1663   DCHECK([movedTabController isKindOfClass:[TabController class]]);
1664   [tabArray_ removeObjectAtIndex:from];
1665   [tabArray_ insertObject:movedTabController.get() atIndex:to];
1666
1667   // The tab moved, which means that the mini-tab state may have changed.
1668   if (tabStripModel_->IsMiniTab(modelTo) != [movedTabController mini])
1669     [self tabMiniStateChangedWithContents:contents atIndex:modelTo];
1670
1671   [self layoutTabs];
1672 }
1673
1674 // Called when a tab is pinned or unpinned without moving.
1675 - (void)tabMiniStateChangedWithContents:(content::WebContents*)contents
1676                                 atIndex:(NSInteger)modelIndex {
1677   // Take closing tabs into account.
1678   NSInteger index = [self indexFromModelIndex:modelIndex];
1679
1680   TabController* tabController = [tabArray_ objectAtIndex:index];
1681   DCHECK([tabController isKindOfClass:[TabController class]]);
1682
1683   // Don't do anything if the change was already picked up by the move event.
1684   if (tabStripModel_->IsMiniTab(modelIndex) == [tabController mini])
1685     return;
1686
1687   [tabController setMini:tabStripModel_->IsMiniTab(modelIndex)];
1688   [tabController setPinned:tabStripModel_->IsTabPinned(modelIndex)];
1689   [tabController setApp:tabStripModel_->IsAppTab(modelIndex)];
1690   [tabController setUrl:contents->GetURL()];
1691   [self updateIconsForContents:contents atIndex:modelIndex];
1692   // If the tab is being restored and it's pinned, the mini state is set after
1693   // the tab has already been rendered, so re-layout the tabstrip. In all other
1694   // cases, the state is set before the tab is rendered so this isn't needed.
1695   [self layoutTabs];
1696 }
1697
1698 - (void)setFrame:(NSRect)frame ofTabView:(NSView*)view {
1699   NSValue* identifier = [NSValue valueWithPointer:view];
1700   [targetFrames_ setObject:[NSValue valueWithRect:frame]
1701                     forKey:identifier];
1702   [view setFrame:frame];
1703 }
1704
1705 - (TabStripModel*)tabStripModel {
1706   return tabStripModel_;
1707 }
1708
1709 - (NSArray*)tabViews {
1710   NSMutableArray* views = [NSMutableArray arrayWithCapacity:[tabArray_ count]];
1711   for (TabController* tab in tabArray_.get()) {
1712     [views addObject:[tab tabView]];
1713   }
1714   return views;
1715 }
1716
1717 - (NSView*)activeTabView {
1718   int activeIndex = tabStripModel_->active_index();
1719   // Take closing tabs into account. They can't ever be selected.
1720   activeIndex = [self indexFromModelIndex:activeIndex];
1721   return [self viewAtIndex:activeIndex];
1722 }
1723
1724 - (int)indexOfPlaceholder {
1725   // Use |tabArray_| here instead of the tab strip count in order to get the
1726   // correct index when there are closing tabs to the left of the placeholder.
1727   const int count = [tabArray_ count];
1728
1729   // No placeholder, return the end of the strip.
1730   if (placeholderTab_ == nil)
1731     return count;
1732
1733   double placeholderX = placeholderFrame_.origin.x;
1734   int index = 0;
1735   int location = 0;
1736   while (index < count) {
1737     // Ignore closing tabs for simplicity. The only drawback of this is that
1738     // if the placeholder is placed right before one or several contiguous
1739     // currently closing tabs, the associated TabController will start at the
1740     // end of the closing tabs.
1741     if ([closingControllers_ containsObject:[tabArray_ objectAtIndex:index]]) {
1742       index++;
1743       continue;
1744     }
1745     NSView* curr = [self viewAtIndex:index];
1746     // The placeholder tab works by changing the frame of the tab being dragged
1747     // to be the bounds of the placeholder, so we need to skip it while we're
1748     // iterating, otherwise we'll end up off by one.  Note This only effects
1749     // dragging to the right, not to the left.
1750     if (curr == placeholderTab_) {
1751       index++;
1752       continue;
1753     }
1754     if (placeholderX <= NSMinX([curr frame]))
1755       break;
1756     index++;
1757     location++;
1758   }
1759   return location;
1760 }
1761
1762 // Move the given tab at index |from| in this window to the location of the
1763 // current placeholder.
1764 - (void)moveTabFromIndex:(NSInteger)from {
1765   int toIndex = [self indexOfPlaceholder];
1766   // Cancel any pending tab transition.
1767   hoverTabSelector_->CancelTabTransition();
1768   tabStripModel_->MoveWebContentsAt(from, toIndex, true);
1769 }
1770
1771 // Drop a given WebContents at the location of the current placeholder.
1772 // If there is no placeholder, it will go at the end. Used when dragging from
1773 // another window when we don't have access to the WebContents as part of our
1774 // strip. |frame| is in the coordinate system of the tab strip view and
1775 // represents where the user dropped the new tab so it can be animated into its
1776 // correct location when the tab is added to the model. If the tab was pinned in
1777 // its previous window, setting |pinned| to YES will propagate that state to the
1778 // new window. Mini-tabs are either app or pinned tabs; the app state is stored
1779 // by the |contents|, but the |pinned| state is the caller's responsibility.
1780 - (void)dropWebContents:(WebContents*)contents
1781                 atIndex:(int)modelIndex
1782               withFrame:(NSRect)frame
1783             asPinnedTab:(BOOL)pinned
1784                activate:(BOOL)activate {
1785   // Mark that the new tab being created should start at |frame|. It will be
1786   // reset as soon as the tab has been positioned.
1787   droppedTabFrame_ = frame;
1788
1789   // Insert it into this tab strip. We want it in the foreground and to not
1790   // inherit the current tab's group.
1791   tabStripModel_->InsertWebContentsAt(
1792       modelIndex,
1793       contents,
1794       (activate ? TabStripModel::ADD_ACTIVE : TabStripModel::ADD_NONE) |
1795           (pinned ? TabStripModel::ADD_PINNED : TabStripModel::ADD_NONE));
1796 }
1797
1798 // Called when the tab strip view changes size. As we only registered for
1799 // changes on our view, we know it's only for our view. Layout w/out
1800 // animations since they are blocked by the resize nested runloop. We need
1801 // the views to adjust immediately. Neither the tabs nor their z-order are
1802 // changed, so we don't need to update the subviews.
1803 - (void)tabViewFrameChanged:(NSNotification*)info {
1804   [self layoutTabsWithAnimation:NO regenerateSubviews:NO];
1805 }
1806
1807 // Called when the tracking areas for any given tab are updated. This allows
1808 // the individual tabs to update their hover states correctly.
1809 // Only generates the event if the cursor is in the tab strip.
1810 - (void)tabUpdateTracking:(NSNotification*)notification {
1811   DCHECK([[notification object] isKindOfClass:[TabView class]]);
1812   DCHECK(mouseInside_);
1813   NSWindow* window = [tabStripView_ window];
1814   NSPoint location = [window mouseLocationOutsideOfEventStream];
1815   if (NSPointInRect(location, [tabStripView_ frame])) {
1816     NSEvent* mouseEvent = [NSEvent mouseEventWithType:NSMouseMoved
1817                                              location:location
1818                                         modifierFlags:0
1819                                             timestamp:0
1820                                          windowNumber:[window windowNumber]
1821                                               context:nil
1822                                           eventNumber:0
1823                                            clickCount:0
1824                                              pressure:0];
1825     [self mouseMoved:mouseEvent];
1826   }
1827 }
1828
1829 - (BOOL)inRapidClosureMode {
1830   return availableResizeWidth_ != kUseFullAvailableWidth;
1831 }
1832
1833 // Disable tab dragging when there are any pending animations.
1834 - (BOOL)tabDraggingAllowed {
1835   return [closingControllers_ count] == 0;
1836 }
1837
1838 - (void)mouseMoved:(NSEvent*)event {
1839   // Use hit test to figure out what view we are hovering over.
1840   NSView* targetView = [tabStripView_ hitTest:[event locationInWindow]];
1841
1842   // Set the new tab button hover state iff the mouse is over the button.
1843   BOOL shouldShowHoverImage = [targetView isKindOfClass:[NewTabButton class]];
1844   [self setNewTabButtonHoverState:shouldShowHoverImage];
1845
1846   TabView* tabView = (TabView*)targetView;
1847   if (![tabView isKindOfClass:[TabView class]]) {
1848     if ([[tabView superview] isKindOfClass:[TabView class]]) {
1849       tabView = (TabView*)[targetView superview];
1850     } else {
1851       tabView = nil;
1852     }
1853   }
1854
1855   if (hoveredTab_ != tabView) {
1856     [hoveredTab_ mouseExited:nil];  // We don't pass event because moved events
1857     [tabView mouseEntered:nil];  // don't have valid tracking areas
1858     hoveredTab_ = tabView;
1859   } else {
1860     [hoveredTab_ mouseMoved:event];
1861   }
1862 }
1863
1864 - (void)mouseEntered:(NSEvent*)event {
1865   NSTrackingArea* area = [event trackingArea];
1866   if ([area isEqual:trackingArea_]) {
1867     mouseInside_ = YES;
1868     [self setTabTrackingAreasEnabled:YES];
1869     [self mouseMoved:event];
1870   }
1871 }
1872
1873 // Called when the tracking area is in effect which means we're tracking to
1874 // see if the user leaves the tab strip with their mouse. When they do,
1875 // reset layout to use all available width.
1876 - (void)mouseExited:(NSEvent*)event {
1877   NSTrackingArea* area = [event trackingArea];
1878   if ([area isEqual:trackingArea_]) {
1879     mouseInside_ = NO;
1880     [self setTabTrackingAreasEnabled:NO];
1881     availableResizeWidth_ = kUseFullAvailableWidth;
1882     [hoveredTab_ mouseExited:event];
1883     hoveredTab_ = nil;
1884     [self layoutTabs];
1885   } else if ([area isEqual:newTabTrackingArea_]) {
1886     // If the mouse is moved quickly enough, it is possible for the mouse to
1887     // leave the tabstrip without sending any mouseMoved: messages at all.
1888     // Since this would result in the new tab button incorrectly staying in the
1889     // hover state, disable the hover image on every mouse exit.
1890     [self setNewTabButtonHoverState:NO];
1891   }
1892 }
1893
1894 // Enable/Disable the tracking areas for the tabs. They are only enabled
1895 // when the mouse is in the tabstrip.
1896 - (void)setTabTrackingAreasEnabled:(BOOL)enabled {
1897   NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1898   for (TabController* controller in tabArray_.get()) {
1899     TabView* tabView = [controller tabView];
1900     if (enabled) {
1901       // Set self up to observe tabs so hover states will be correct.
1902       [defaultCenter addObserver:self
1903                         selector:@selector(tabUpdateTracking:)
1904                             name:NSViewDidUpdateTrackingAreasNotification
1905                           object:tabView];
1906     } else {
1907       [defaultCenter removeObserver:self
1908                                name:NSViewDidUpdateTrackingAreasNotification
1909                              object:tabView];
1910     }
1911     [tabView setTrackingEnabled:enabled];
1912   }
1913 }
1914
1915 // Sets the new tab button's image based on the current hover state.  Does
1916 // nothing if the hover state is already correct.
1917 - (void)setNewTabButtonHoverState:(BOOL)shouldShowHover {
1918   if (shouldShowHover && !newTabButtonShowingHoverImage_) {
1919     newTabButtonShowingHoverImage_ = YES;
1920     [[newTabButton_ cell] setIsMouseInside:YES];
1921   } else if (!shouldShowHover && newTabButtonShowingHoverImage_) {
1922     newTabButtonShowingHoverImage_ = NO;
1923     [[newTabButton_ cell] setIsMouseInside:NO];
1924   }
1925 }
1926
1927 // Adds the given subview to (the end of) the list of permanent subviews
1928 // (specified from bottom up). These subviews will always be below the
1929 // transitory subviews (tabs). |-regenerateSubviewList| must be called to
1930 // effectuate the addition.
1931 - (void)addSubviewToPermanentList:(NSView*)aView {
1932   if (aView)
1933     [permanentSubviews_ addObject:aView];
1934 }
1935
1936 // Update the subviews, keeping the permanent ones (or, more correctly, putting
1937 // in the ones listed in permanentSubviews_), and putting in the current tabs in
1938 // the correct z-order. Any current subviews which is neither in the permanent
1939 // list nor a (current) tab will be removed. So if you add such a subview, you
1940 // should call |-addSubviewToPermanentList:| (or better yet, call that and then
1941 // |-regenerateSubviewList| to actually add it).
1942 - (void)regenerateSubviewList {
1943   // Remove self as an observer from all the old tabs before a new set of
1944   // potentially different tabs is put in place.
1945   [self setTabTrackingAreasEnabled:NO];
1946
1947   // Subviews to put in (in bottom-to-top order), beginning with the permanent
1948   // ones.
1949   NSMutableArray* subviews = [NSMutableArray arrayWithArray:permanentSubviews_];
1950
1951   NSView* activeTabView = nil;
1952   // Go through tabs in reverse order, since |subviews| is bottom-to-top.
1953   for (TabController* tab in [tabArray_ reverseObjectEnumerator]) {
1954     NSView* tabView = [tab view];
1955     if ([tab active]) {
1956       DCHECK(!activeTabView);
1957       activeTabView = tabView;
1958     } else {
1959       [subviews addObject:tabView];
1960     }
1961   }
1962   if (activeTabView) {
1963     [subviews addObject:activeTabView];
1964   }
1965   WithNoAnimation noAnimation;
1966   [tabStripView_ setSubviews:subviews];
1967   [self setTabTrackingAreasEnabled:mouseInside_];
1968 }
1969
1970 // Get the index and disposition for a potential URL(s) drop given a point (in
1971 // the |TabStripView|'s coordinates). It considers only the x-coordinate of the
1972 // given point. If it's in the "middle" of a tab, it drops on that tab. If it's
1973 // to the left, it inserts to the left, and similarly for the right.
1974 - (void)droppingURLsAt:(NSPoint)point
1975             givesIndex:(NSInteger*)index
1976            disposition:(WindowOpenDisposition*)disposition {
1977   // Proportion of the tab which is considered the "middle" (and causes things
1978   // to drop on that tab).
1979   const double kMiddleProportion = 0.5;
1980   const double kLRProportion = (1.0 - kMiddleProportion) / 2.0;
1981
1982   DCHECK(index && disposition);
1983   NSInteger i = 0;
1984   for (TabController* tab in tabArray_.get()) {
1985     NSView* view = [tab view];
1986     DCHECK([view isKindOfClass:[TabView class]]);
1987
1988     // Recall that |-[NSView frame]| is in its superview's coordinates, so a
1989     // |TabView|'s frame is in the coordinates of the |TabStripView| (which
1990     // matches the coordinate system of |point|).
1991     NSRect frame = [view frame];
1992
1993     // Modify the frame to make it "unoverlapped".
1994     frame.origin.x += kTabOverlap / 2.0;
1995     frame.size.width -= kTabOverlap;
1996     if (frame.size.width < 1.0)
1997       frame.size.width = 1.0;  // try to avoid complete failure
1998
1999     // Drop in a new tab to the left of tab |i|?
2000     if (point.x < (frame.origin.x + kLRProportion * frame.size.width)) {
2001       *index = i;
2002       *disposition = NEW_FOREGROUND_TAB;
2003       return;
2004     }
2005
2006     // Drop on tab |i|?
2007     if (point.x <= (frame.origin.x +
2008                        (1.0 - kLRProportion) * frame.size.width)) {
2009       *index = i;
2010       *disposition = CURRENT_TAB;
2011       return;
2012     }
2013
2014     // (Dropping in a new tab to the right of tab |i| will be taken care of in
2015     // the next iteration.)
2016     i++;
2017   }
2018
2019   // If we've made it here, we want to append a new tab to the end.
2020   *index = -1;
2021   *disposition = NEW_FOREGROUND_TAB;
2022 }
2023
2024 - (void)openURL:(GURL*)url inView:(NSView*)view at:(NSPoint)point {
2025   // Get the index and disposition.
2026   NSInteger index;
2027   WindowOpenDisposition disposition;
2028   [self droppingURLsAt:point
2029             givesIndex:&index
2030            disposition:&disposition];
2031
2032   // Either insert a new tab or open in a current tab.
2033   switch (disposition) {
2034     case NEW_FOREGROUND_TAB: {
2035       content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
2036       chrome::NavigateParams params(browser_, *url,
2037                                     content::PAGE_TRANSITION_TYPED);
2038       params.disposition = disposition;
2039       params.tabstrip_index = index;
2040       params.tabstrip_add_types =
2041           TabStripModel::ADD_ACTIVE | TabStripModel::ADD_FORCE_INDEX;
2042       chrome::Navigate(&params);
2043       break;
2044     }
2045     case CURRENT_TAB: {
2046       content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
2047       OpenURLParams params(
2048           *url, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_TYPED, false);
2049       tabStripModel_->GetWebContentsAt(index)->OpenURL(params);
2050       tabStripModel_->ActivateTabAt(index, true);
2051       break;
2052     }
2053     default:
2054       NOTIMPLEMENTED();
2055   }
2056 }
2057
2058 // (URLDropTargetController protocol)
2059 - (void)dropURLs:(NSArray*)urls inView:(NSView*)view at:(NSPoint)point {
2060   DCHECK_EQ(view, tabStripView_.get());
2061
2062   if ([urls count] < 1) {
2063     NOTREACHED();
2064     return;
2065   }
2066
2067   //TODO(viettrungluu): dropping multiple URLs.
2068   if ([urls count] > 1)
2069     NOTIMPLEMENTED();
2070
2071   // Get the first URL and fix it up.
2072   GURL url(GURL(url_fixer::FixupURL(
2073       base::SysNSStringToUTF8([urls objectAtIndex:0]), std::string())));
2074
2075   [self openURL:&url inView:view at:point];
2076 }
2077
2078 // (URLDropTargetController protocol)
2079 - (void)dropText:(NSString*)text inView:(NSView*)view at:(NSPoint)point {
2080   DCHECK_EQ(view, tabStripView_.get());
2081
2082   // If the input is plain text, classify the input and make the URL.
2083   AutocompleteMatch match;
2084   AutocompleteClassifierFactory::GetForProfile(browser_->profile())->Classify(
2085       base::SysNSStringToUTF16(text), false, false,
2086       metrics::OmniboxEventProto::BLANK, &match, NULL);
2087   GURL url(match.destination_url);
2088
2089   [self openURL:&url inView:view at:point];
2090 }
2091
2092 // (URLDropTargetController protocol)
2093 - (void)indicateDropURLsInView:(NSView*)view at:(NSPoint)point {
2094   DCHECK_EQ(view, tabStripView_.get());
2095
2096   // The minimum y-coordinate at which one should consider place the arrow.
2097   const CGFloat arrowBaseY = 25;
2098
2099   NSInteger index;
2100   WindowOpenDisposition disposition;
2101   [self droppingURLsAt:point
2102             givesIndex:&index
2103            disposition:&disposition];
2104
2105   NSPoint arrowPos = NSMakePoint(0, arrowBaseY);
2106   if (index == -1) {
2107     // Append a tab at the end.
2108     DCHECK(disposition == NEW_FOREGROUND_TAB);
2109     NSInteger lastIndex = [tabArray_ count] - 1;
2110     NSRect overRect = [[[tabArray_ objectAtIndex:lastIndex] view] frame];
2111     arrowPos.x = overRect.origin.x + overRect.size.width - kTabOverlap / 2.0;
2112   } else {
2113     NSRect overRect = [[[tabArray_ objectAtIndex:index] view] frame];
2114     switch (disposition) {
2115       case NEW_FOREGROUND_TAB:
2116         // Insert tab (to the left of the given tab).
2117         arrowPos.x = overRect.origin.x + kTabOverlap / 2.0;
2118         break;
2119       case CURRENT_TAB:
2120         // Overwrite the given tab.
2121         arrowPos.x = overRect.origin.x + overRect.size.width / 2.0;
2122         break;
2123       default:
2124         NOTREACHED();
2125     }
2126   }
2127
2128   [tabStripView_ setDropArrowPosition:arrowPos];
2129   [tabStripView_ setDropArrowShown:YES];
2130   [tabStripView_ setNeedsDisplay:YES];
2131
2132   // Perform a delayed tab transition if hovering directly over a tab.
2133   if (index != -1 && disposition == CURRENT_TAB) {
2134     NSInteger modelIndex = [self modelIndexFromIndex:index];
2135     // Only start the transition if it has a valid model index (i.e. it's not
2136     // in the middle of closing).
2137     if (modelIndex != NSNotFound) {
2138       hoverTabSelector_->StartTabTransition(modelIndex);
2139       return;
2140     }
2141   }
2142   // If a tab transition was not started, cancel the pending one.
2143   hoverTabSelector_->CancelTabTransition();
2144 }
2145
2146 // (URLDropTargetController protocol)
2147 - (void)hideDropURLsIndicatorInView:(NSView*)view {
2148   DCHECK_EQ(view, tabStripView_.get());
2149
2150   // Cancel any pending tab transition.
2151   hoverTabSelector_->CancelTabTransition();
2152
2153   if ([tabStripView_ dropArrowShown]) {
2154     [tabStripView_ setDropArrowShown:NO];
2155     [tabStripView_ setNeedsDisplay:YES];
2156   }
2157 }
2158
2159 // (URLDropTargetController protocol)
2160 - (BOOL)isUnsupportedDropData:(id<NSDraggingInfo>)info {
2161   return drag_util::IsUnsupportedDropData(browser_->profile(), info);
2162 }
2163
2164 - (TabContentsController*)activeTabContentsController {
2165   int modelIndex = tabStripModel_->active_index();
2166   if (modelIndex < 0)
2167     return nil;
2168   NSInteger index = [self indexFromModelIndex:modelIndex];
2169   if (index < 0 ||
2170       index >= (NSInteger)[tabContentsArray_ count])
2171     return nil;
2172   return [tabContentsArray_ objectAtIndex:index];
2173 }
2174
2175 - (void)themeDidChangeNotification:(NSNotification*)notification {
2176   [self setNewTabImages];
2177 }
2178
2179 - (void)setNewTabImages {
2180   ThemeService *theme =
2181       static_cast<ThemeService*>([[tabStripView_ window] themeProvider]);
2182   if (!theme)
2183     return;
2184
2185   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
2186   NSImage* mask = rb.GetNativeImageNamed(IDR_NEWTAB_BUTTON_MASK).ToNSImage();
2187   NSImage* normal = rb.GetNativeImageNamed(IDR_NEWTAB_BUTTON).ToNSImage();
2188   NSImage* hover = rb.GetNativeImageNamed(IDR_NEWTAB_BUTTON_H).ToNSImage();
2189   NSImage* pressed = rb.GetNativeImageNamed(IDR_NEWTAB_BUTTON_P).ToNSImage();
2190
2191   NSImage* foreground = ApplyMask(
2192       theme->GetNSImageNamed(IDR_THEME_TAB_BACKGROUND), mask);
2193
2194   [[newTabButton_ cell] setImage:Overlay(foreground, normal, 1.0)
2195                   forButtonState:image_button_cell::kDefaultState];
2196   [[newTabButton_ cell] setImage:Overlay(foreground, hover, 1.0)
2197                   forButtonState:image_button_cell::kHoverState];
2198   [[newTabButton_ cell] setImage:Overlay(foreground, pressed, 1.0)
2199                     forButtonState:image_button_cell::kPressedState];
2200
2201   // IDR_THEME_TAB_BACKGROUND_INACTIVE is only used with the default theme.
2202   if (theme->UsingDefaultTheme()) {
2203     const CGFloat alpha = tabs::kImageNoFocusAlpha;
2204     NSImage* background = ApplyMask(
2205         theme->GetNSImageNamed(IDR_THEME_TAB_BACKGROUND_INACTIVE), mask);
2206     [[newTabButton_ cell] setImage:Overlay(background, normal, alpha)
2207                     forButtonState:image_button_cell::kDefaultStateBackground];
2208     [[newTabButton_ cell] setImage:Overlay(background, hover, alpha)
2209                     forButtonState:image_button_cell::kHoverStateBackground];
2210   } else {
2211     [[newTabButton_ cell] setImage:nil
2212                     forButtonState:image_button_cell::kDefaultStateBackground];
2213     [[newTabButton_ cell] setImage:nil
2214                     forButtonState:image_button_cell::kHoverStateBackground];
2215   }
2216 }
2217
2218 @end
2219
2220 NSView* GetSheetParentViewForWebContents(WebContents* web_contents) {
2221   // View hierarchy of the contents view:
2222   // NSView  -- switchView, same for all tabs
2223   // +- NSView  -- TabContentsController's view
2224   //    +- WebContentsViewCocoa
2225   //
2226   // Changing it? Do not forget to modify
2227   // -[TabStripController swapInTabAtIndex:] too.
2228   return [web_contents->GetNativeView() superview];
2229 }
2230
2231 NSRect GetSheetParentBoundsForParentView(NSView* view) {
2232   // If the devtools view is open, it shrinks the size of the WebContents, so go
2233   // up the hierarchy to the devtools container view to avoid that. Note that
2234   // the devtools view is always in the hierarchy even if it is not open or it
2235   // is detached.
2236   NSView* devtools_view = [[[view superview] superview] superview];
2237   return [devtools_view convertRect:[devtools_view bounds] toView:nil];
2238 }