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