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