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