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