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