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