1// Copyright 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/browser_window_controller.h" 6 7#include <cmath> 8#include <numeric> 9 10#include "base/command_line.h" 11#include "base/mac/bundle_locations.h" 12#include "base/mac/mac_util.h" 13#import "base/mac/sdk_forward_declarations.h" 14#include "base/strings/sys_string_conversions.h" 15#include "base/strings/utf_string_conversions.h" 16#include "chrome/app/chrome_command_ids.h" // IDC_* 17#include "chrome/browser/bookmarks/bookmark_model_factory.h" 18#include "chrome/browser/browser_process.h" 19#include "chrome/browser/devtools/devtools_window.h" 20#include "chrome/browser/fullscreen.h" 21#include "chrome/browser/profiles/avatar_menu.h" 22#include "chrome/browser/profiles/profile.h" 23#include "chrome/browser/profiles/profile_info_cache.h" 24#include "chrome/browser/profiles/profile_manager.h" 25#include "chrome/browser/signin/signin_ui_util.h" 26#include "chrome/browser/themes/theme_service.h" 27#include "chrome/browser/themes/theme_service_factory.h" 28#include "chrome/browser/ui/bookmarks/bookmark_editor.h" 29#include "chrome/browser/ui/browser.h" 30#include "chrome/browser/ui/browser_command_controller.h" 31#include "chrome/browser/ui/browser_commands.h" 32#include "chrome/browser/ui/browser_instant_controller.h" 33#include "chrome/browser/ui/browser_list.h" 34#include "chrome/browser/ui/browser_window_state.h" 35#import "chrome/browser/ui/cocoa/background_gradient_view.h" 36#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h" 37#import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_controller.h" 38#import "chrome/browser/ui/cocoa/browser/avatar_button_controller.h" 39#import "chrome/browser/ui/cocoa/browser_window_cocoa.h" 40#import "chrome/browser/ui/cocoa/browser_window_controller_private.h" 41#import "chrome/browser/ui/cocoa/browser_window_utils.h" 42#import "chrome/browser/ui/cocoa/dev_tools_controller.h" 43#import "chrome/browser/ui/cocoa/download/download_shelf_controller.h" 44#include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h" 45#import "chrome/browser/ui/cocoa/fast_resize_view.h" 46#import "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h" 47#import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h" 48#import "chrome/browser/ui/cocoa/framed_browser_window.h" 49#import "chrome/browser/ui/cocoa/fullscreen_window.h" 50#import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" 51#import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" 52#import "chrome/browser/ui/cocoa/nsview_additions.h" 53#import "chrome/browser/ui/cocoa/presentation_mode_controller.h" 54#import "chrome/browser/ui/cocoa/status_bubble_mac.h" 55#import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h" 56#import "chrome/browser/ui/cocoa/tab_contents/sad_tab_controller.h" 57#import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h" 58#import "chrome/browser/ui/cocoa/tabpose_window.h" 59#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" 60#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h" 61#import "chrome/browser/ui/cocoa/tabs/tab_view.h" 62#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" 63#include "chrome/browser/ui/fullscreen/fullscreen_controller.h" 64#include "chrome/browser/ui/omnibox/location_bar.h" 65#include "chrome/browser/ui/tabs/dock_info.h" 66#include "chrome/browser/ui/tabs/tab_strip_model.h" 67#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" 68#include "chrome/browser/ui/toolbar/encoding_menu_controller.h" 69#include "chrome/browser/ui/window_sizer/window_sizer.h" 70#include "chrome/common/chrome_switches.h" 71#include "chrome/common/url_constants.h" 72#include "components/web_modal/web_contents_modal_dialog_manager.h" 73#include "content/public/browser/render_view_host.h" 74#include "content/public/browser/render_widget_host_view.h" 75#include "content/public/browser/web_contents.h" 76#include "content/public/browser/web_contents_view.h" 77#include "content/public/common/content_switches.h" 78#include "grit/chromium_strings.h" 79#include "grit/generated_resources.h" 80#include "grit/locale_settings.h" 81#import "ui/base/cocoa/cocoa_event_utils.h" 82#include "ui/base/l10n/l10n_util.h" 83#include "ui/base/l10n/l10n_util_mac.h" 84#include "ui/gfx/mac/scoped_ns_disable_screen_updates.h" 85 86using l10n_util::GetStringUTF16; 87using l10n_util::GetNSStringWithFixup; 88using l10n_util::GetNSStringFWithFixup; 89 90// ORGANIZATION: This is a big file. It is (in principle) organized as follows 91// (in order): 92// 1. Interfaces. Very short, one-time-use classes may include an implementation 93// immediately after their interface. 94// 2. The general implementation section, ordered as follows: 95// i. Public methods and overrides. 96// ii. Overrides/implementations of undocumented methods. 97// iii. Delegate methods for various protocols, formal and informal, to which 98// |BrowserWindowController| conforms. 99// 3. (temporary) Implementation sections for various categories. 100// 101// Private methods are defined and implemented separately in 102// browser_window_controller_private.{h,mm}. 103// 104// Not all of the above guidelines are followed and more (re-)organization is 105// needed. BUT PLEASE TRY TO KEEP THIS FILE ORGANIZED. I'd rather re-organize as 106// little as possible, since doing so messes up the file's history. 107// 108// TODO(viettrungluu): [crbug.com/35543] on-going re-organization, splitting 109// things into multiple files -- the plan is as follows: 110// - in general, everything stays in browser_window_controller.h, but is split 111// off into categories (see below) 112// - core stuff stays in browser_window_controller.mm 113// - ... overrides also stay (without going into a category, in particular) 114// - private stuff which everyone needs goes into 115// browser_window_controller_private.{h,mm}; if no one else needs them, they 116// can go in individual files (see below) 117// - area/task-specific stuff go in browser_window_controller_<area>.mm 118// - ... in categories called "(<Area>)" or "(<PrivateArea>)" 119// Plan of action: 120// - first re-organize into categories 121// - then split into files 122 123// Notes on self-inflicted (not user-inflicted) window resizing and moving: 124// 125// When the bookmark bar goes from hidden to shown (on a non-NTP) page, or when 126// the download shelf goes from hidden to shown, we grow the window downwards in 127// order to maintain a constant content area size. When either goes from shown 128// to hidden, we consequently shrink the window from the bottom, also to keep 129// the content area size constant. To keep things simple, if the window is not 130// entirely on-screen, we don't grow/shrink the window. 131// 132// The complications come in when there isn't enough room (on screen) below the 133// window to accomodate the growth. In this case, we grow the window first 134// downwards, and then upwards. So, when it comes to shrinking, we do the 135// opposite: shrink from the top by the amount by which we grew at the top, and 136// then from the bottom -- unless the user moved/resized/zoomed the window, in 137// which case we "reset state" and just shrink from the bottom. 138// 139// A further complication arises due to the way in which "zoom" ("maximize") 140// works on Mac OS X. Basically, for our purposes, a window is "zoomed" whenever 141// it occupies the full available vertical space. (Note that the green zoom 142// button does not track zoom/unzoomed state per se, but basically relies on 143// this heuristic.) We don't, in general, want to shrink the window if the 144// window is zoomed (scenario: window is zoomed, download shelf opens -- which 145// doesn't cause window growth, download shelf closes -- shouldn't cause the 146// window to become unzoomed!). However, if we grew the window 147// (upwards/downwards) to become zoomed in the first place, we *should* shrink 148// the window by the amounts by which we grew (scenario: window occupies *most* 149// of vertical space, download shelf opens causing growth so that window 150// occupies all of vertical space -- i.e., window is effectively zoomed, 151// download shelf closes -- should return the window to its previous state). 152// 153// A major complication is caused by the way grows/shrinks are handled and 154// animated. Basically, the BWC doesn't see the global picture, but it sees 155// grows and shrinks in small increments (as dictated by the animation). Thus 156// window growth/shrinkage (at the top/bottom) have to be tracked incrementally. 157// Allowing shrinking from the zoomed state also requires tracking: We check on 158// any shrink whether we're both zoomed and have previously grown -- if so, we 159// set a flag, and constrain any resize by the allowed amounts. On further 160// shrinks, we check the flag (since the size/position of the window will no 161// longer indicate that the window is shrinking from an apparent zoomed state) 162// and if it's set we continue to constrain the resize. 163 164using content::OpenURLParams; 165using content::Referrer; 166using content::RenderWidgetHostView; 167using content::WebContents; 168using web_modal::WebContentsModalDialogManager; 169 170@interface NSWindow (NSPrivateApis) 171// Note: These functions are private, use -[NSObject respondsToSelector:] 172// before calling them. 173 174- (void)setBottomCornerRounded:(BOOL)rounded; 175 176- (NSRect)_growBoxRect; 177 178@end 179 180// Replicate specific 10.7 SDK declarations for building with prior SDKs. 181#if !defined(MAC_OS_X_VERSION_10_7) || \ 182 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 183 184enum { 185 NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7, 186 NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8 187}; 188 189enum { 190 NSFullScreenWindowMask = 1 << 14 191}; 192 193@interface NSWindow (LionSDKDeclarations) 194- (void)setRestorable:(BOOL)flag; 195@end 196 197#endif // MAC_OS_X_VERSION_10_7 198 199@implementation BrowserWindowController 200 201+ (BrowserWindowController*)browserWindowControllerForWindow:(NSWindow*)window { 202 while (window) { 203 id controller = [window windowController]; 204 if ([controller isKindOfClass:[BrowserWindowController class]]) 205 return (BrowserWindowController*)controller; 206 window = [window parentWindow]; 207 } 208 return nil; 209} 210 211+ (BrowserWindowController*)browserWindowControllerForView:(NSView*)view { 212 NSWindow* window = [view window]; 213 return [BrowserWindowController browserWindowControllerForWindow:window]; 214} 215 216+ (void)updateSigninItem:(id)signinItem 217 shouldShow:(BOOL)showSigninMenuItem 218 currentProfile:(Profile*)profile { 219 DCHECK([signinItem isKindOfClass:[NSMenuItem class]]); 220 NSMenuItem* signinMenuItem = static_cast<NSMenuItem*>(signinItem); 221 222 // Look for a separator immediately after the menu item so it can be hidden 223 // or shown appropriately along with the signin menu item. 224 NSMenuItem* followingSeparator = nil; 225 NSMenu* menu = [signinItem menu]; 226 if (menu) { 227 NSInteger signinItemIndex = [menu indexOfItem:signinMenuItem]; 228 DCHECK_NE(signinItemIndex, -1); 229 if ((signinItemIndex + 1) < [menu numberOfItems]) { 230 NSMenuItem* menuItem = [menu itemAtIndex:(signinItemIndex + 1)]; 231 if ([menuItem isSeparatorItem]) { 232 followingSeparator = menuItem; 233 } 234 } 235 } 236 237 base::string16 label = signin_ui_util::GetSigninMenuLabel(profile); 238 [signinMenuItem setTitle:l10n_util::FixUpWindowsStyleLabel(label)]; 239 [signinMenuItem setHidden:!showSigninMenuItem]; 240 [followingSeparator setHidden:!showSigninMenuItem]; 241} 242 243// Load the browser window nib and do any Cocoa-specific initialization. 244// Takes ownership of |browser|. Note that the nib also sets this controller 245// up as the window's delegate. 246- (id)initWithBrowser:(Browser*)browser { 247 return [self initWithBrowser:browser takeOwnership:YES]; 248} 249 250// Private(TestingAPI) init routine with testing options. 251- (id)initWithBrowser:(Browser*)browser takeOwnership:(BOOL)ownIt { 252 bool hasTabStrip = browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP); 253 if ((self = [super initTabWindowControllerWithTabStrip:hasTabStrip])) { 254 DCHECK(browser); 255 initializing_ = YES; 256 browser_.reset(browser); 257 ownsBrowser_ = ownIt; 258 NSWindow* window = [self window]; 259 windowShim_.reset(new BrowserWindowCocoa(browser, self)); 260 261 // Eagerly enable core animation if requested. 262 if ([self coreAnimationStatus] == 263 browser_window_controller::kCoreAnimationEnabledAlways) { 264 [[[self window] contentView] setWantsLayer:YES]; 265 [[self tabStripView] setWantsLayer:YES]; 266 } 267 268 // Set different minimum sizes on tabbed windows vs non-tabbed, e.g. popups. 269 // This has to happen before -enforceMinWindowSize: is called further down. 270 NSSize minSize = [self isTabbedWindow] ? 271 NSMakeSize(400, 272) : NSMakeSize(100, 122); 272 [[self window] setMinSize:minSize]; 273 274 // Create the bar visibility lock set; 10 is arbitrary, but should hopefully 275 // be big enough to hold all locks that'll ever be needed. 276 barVisibilityLocks_.reset([[NSMutableSet setWithCapacity:10] retain]); 277 278 // Set the window to not have rounded corners, which prevents the resize 279 // control from being inset slightly and looking ugly. Only bother to do 280 // this on Snow Leopard; on Lion and later all windows have rounded bottom 281 // corners, and this won't work anyway. 282 if (base::mac::IsOSSnowLeopard() && 283 [window respondsToSelector:@selector(setBottomCornerRounded:)]) 284 [window setBottomCornerRounded:NO]; 285 286 // Lion will attempt to automagically save and restore the UI. This 287 // functionality appears to be leaky (or at least interacts badly with our 288 // architecture) and thus BrowserWindowController never gets released. This 289 // prevents the browser from being able to quit <http://crbug.com/79113>. 290 if ([window respondsToSelector:@selector(setRestorable:)]) 291 [window setRestorable:NO]; 292 293 // Get the windows to swish in on Lion. 294 if ([window respondsToSelector:@selector(setAnimationBehavior:)]) 295 [window setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow]; 296 297 // Get the most appropriate size for the window, then enforce the 298 // minimum width and height. The window shim will handle flipping 299 // the coordinates for us so we can use it to save some code. 300 // Note that this may leave a significant portion of the window 301 // offscreen, but there will always be enough window onscreen to 302 // drag the whole window back into view. 303 ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT; 304 gfx::Rect desiredContentRect; 305 chrome::GetSavedWindowBoundsAndShowState(browser_.get(), 306 &desiredContentRect, 307 &show_state); 308 gfx::Rect windowRect = desiredContentRect; 309 windowRect = [self enforceMinWindowSize:windowRect]; 310 311 // When we are given x/y coordinates of 0 on a created popup window, assume 312 // none were given by the window.open() command. 313 if (browser_->is_type_popup() && 314 windowRect.x() == 0 && windowRect.y() == 0) { 315 gfx::Size size = windowRect.size(); 316 windowRect.set_origin( 317 WindowSizer::GetDefaultPopupOrigin(size, 318 browser_->host_desktop_type())); 319 } 320 321 // Size and position the window. Note that it is not yet onscreen. Popup 322 // windows may get resized later on in this function, once the actual size 323 // of the toolbar/tabstrip is known. 324 windowShim_->SetBounds(windowRect); 325 326 // Puts the incognito badge on the window frame, if necessary. 327 [self installAvatar]; 328 329 // Create a sub-controller for the docked devTools and add its view to the 330 // hierarchy. 331 devToolsController_.reset([[DevToolsController alloc] init]); 332 [[devToolsController_ view] setFrame:[[self tabContentArea] bounds]]; 333 [[self tabContentArea] addSubview:[devToolsController_ view]]; 334 335 // Create the overlayable contents controller. This provides the switch 336 // view that TabStripController needs. 337 overlayableContentsController_.reset( 338 [[OverlayableContentsController alloc] initWithBrowser:browser]); 339 [[overlayableContentsController_ view] 340 setFrame:[[devToolsController_ view] bounds]]; 341 [[devToolsController_ view] 342 addSubview:[overlayableContentsController_ view]]; 343 344 // Create a controller for the tab strip, giving it the model object for 345 // this window's Browser and the tab strip view. The controller will handle 346 // registering for the appropriate tab notifications from the back-end and 347 // managing the creation of new tabs. 348 [self createTabStripController]; 349 350 // Create a controller for the toolbar, giving it the toolbar model object 351 // and the toolbar view from the nib. The controller will handle 352 // registering for the appropriate command state changes from the back-end. 353 // Adds the toolbar to the content area. 354 toolbarController_.reset([[ToolbarController alloc] 355 initWithCommands:browser->command_controller()->command_updater() 356 profile:browser->profile() 357 browser:browser 358 resizeDelegate:self]); 359 [toolbarController_ setHasToolbar:[self hasToolbar] 360 hasLocationBar:[self hasLocationBar]]; 361 362 // Create a sub-controller for the bookmark bar. 363 bookmarkBarController_.reset( 364 [[BookmarkBarController alloc] 365 initWithBrowser:browser_.get() 366 initialWidth:NSWidth([[[self window] contentView] frame]) 367 delegate:self 368 resizeDelegate:self]); 369 [bookmarkBarController_ setBookmarkBarEnabled:[self supportsBookmarkBar]]; 370 371 // Create the infobar container view, so we can pass it to the 372 // ToolbarController. 373 infoBarContainerController_.reset( 374 [[InfoBarContainerController alloc] initWithResizeDelegate:self]); 375 [self updateInfoBarTipVisibility]; 376 377 // We don't want to try and show the bar before it gets placed in its parent 378 // view, so this step shoudn't be inside the bookmark bar controller's 379 // |-awakeFromNib|. 380 windowShim_->BookmarkBarStateChanged( 381 BookmarkBar::DONT_ANIMATE_STATE_CHANGE); 382 383 // Allow bar visibility to be changed. 384 [self enableBarVisibilityUpdates]; 385 386 // Force a relayout of all the various bars. 387 [self layoutSubviews]; 388 389 // Set the window to participate in Lion Fullscreen mode. Setting this flag 390 // has no effect on Snow Leopard or earlier. Panels can share a fullscreen 391 // space with a tabbed window, but they can not be primary fullscreen 392 // windows. Do this after |-layoutSubviews| so that the fullscreen button 393 // can be adjusted in FramedBrowserWindow. 394 NSUInteger collectionBehavior = [window collectionBehavior]; 395 collectionBehavior |= 396 browser_->type() == Browser::TYPE_TABBED || 397 browser_->type() == Browser::TYPE_POPUP ? 398 NSWindowCollectionBehaviorFullScreenPrimary : 399 NSWindowCollectionBehaviorFullScreenAuxiliary; 400 [window setCollectionBehavior:collectionBehavior]; 401 402 // For a popup window, |desiredContentRect| contains the desired height of 403 // the content, not of the whole window. Now that all the views are laid 404 // out, measure the current content area size and grow if needed. The 405 // window has not been placed onscreen yet, so this extra resize will not 406 // cause visible jank. 407 if (browser_->is_type_popup()) { 408 CGFloat deltaH = desiredContentRect.height() - 409 NSHeight([[self tabContentArea] frame]); 410 // Do not shrink the window, as that may break minimum size invariants. 411 if (deltaH > 0) { 412 // Convert from tabContentArea coordinates to window coordinates. 413 NSSize convertedSize = 414 [[self tabContentArea] convertSize:NSMakeSize(0, deltaH) 415 toView:nil]; 416 NSRect frame = [[self window] frame]; 417 frame.size.height += convertedSize.height; 418 frame.origin.y -= convertedSize.height; 419 [[self window] setFrame:frame display:NO]; 420 } 421 } 422 423 // Create the bridge for the status bubble. 424 statusBubble_ = new StatusBubbleMac([self window], self); 425 426 // Register for application hide/unhide notifications. 427 [[NSNotificationCenter defaultCenter] 428 addObserver:self 429 selector:@selector(applicationDidHide:) 430 name:NSApplicationDidHideNotification 431 object:nil]; 432 [[NSNotificationCenter defaultCenter] 433 addObserver:self 434 selector:@selector(applicationDidUnhide:) 435 name:NSApplicationDidUnhideNotification 436 object:nil]; 437 438 // This must be done after the view is added to the window since it relies 439 // on the window bounds to determine whether to show buttons or not. 440 if ([self hasToolbar]) // Do not create the buttons in popups. 441 [toolbarController_ createBrowserActionButtons]; 442 443 extension_keybinding_registry_.reset( 444 new ExtensionKeybindingRegistryCocoa(browser_->profile(), 445 [self window], 446 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS, 447 windowShim_.get())); 448 449 // We are done initializing now. 450 initializing_ = NO; 451 } 452 return self; 453} 454 455- (void)dealloc { 456 browser_->tab_strip_model()->CloseAllTabs(); 457 [downloadShelfController_ exiting]; 458 459 // Explicitly release |presentationModeController_| here, as it may call back 460 // to this BWC in |-dealloc|. We are required to call |-exitPresentationMode| 461 // before releasing the controller. 462 [presentationModeController_ exitPresentationMode]; 463 presentationModeController_.reset(); 464 465 // Under certain testing configurations we may not actually own the browser. 466 if (ownsBrowser_ == NO) 467 ignore_result(browser_.release()); 468 469 [[NSNotificationCenter defaultCenter] removeObserver:self]; 470 471 [super dealloc]; 472} 473 474- (gfx::Rect)enforceMinWindowSize:(gfx::Rect)bounds { 475 gfx::Rect checkedBounds = bounds; 476 477 NSSize minSize = [[self window] minSize]; 478 if (bounds.width() < minSize.width) 479 checkedBounds.set_width(minSize.width); 480 if (bounds.height() < minSize.height) 481 checkedBounds.set_height(minSize.height); 482 483 return checkedBounds; 484} 485 486- (BrowserWindow*)browserWindow { 487 return windowShim_.get(); 488} 489 490- (ToolbarController*)toolbarController { 491 return toolbarController_.get(); 492} 493 494- (TabStripController*)tabStripController { 495 return tabStripController_.get(); 496} 497 498- (FindBarCocoaController*)findBarCocoaController { 499 return findBarCocoaController_.get(); 500} 501 502- (InfoBarContainerController*)infoBarContainerController { 503 return infoBarContainerController_.get(); 504} 505 506- (StatusBubbleMac*)statusBubble { 507 return statusBubble_; 508} 509 510- (LocationBarViewMac*)locationBarBridge { 511 return [toolbarController_ locationBarBridge]; 512} 513 514- (NSView*)floatingBarBackingView { 515 return floatingBarBackingView_; 516} 517 518- (OverlayableContentsController*)overlayableContentsController { 519 return overlayableContentsController_; 520} 521 522- (Profile*)profile { 523 return browser_->profile(); 524} 525 526- (AvatarButtonController*)avatarButtonController { 527 return avatarButtonController_.get(); 528} 529 530- (void)destroyBrowser { 531 [NSApp removeWindowsItem:[self window]]; 532 533 // We need the window to go away now. 534 // We can't actually use |-autorelease| here because there's an embedded 535 // run loop in the |-performClose:| which contains its own autorelease pool. 536 // Instead call it after a zero-length delay, which gets us back to the main 537 // event loop. 538 [self performSelector:@selector(autorelease) 539 withObject:nil 540 afterDelay:0]; 541} 542 543// Called when the window meets the criteria to be closed (ie, 544// |-windowShouldClose:| returns YES). We must be careful to preserve the 545// semantics of BrowserWindow::Close() and not call the Browser's dtor directly 546// from this method. 547- (void)windowWillClose:(NSNotification*)notification { 548 DCHECK_EQ([notification object], [self window]); 549 DCHECK(browser_->tab_strip_model()->empty()); 550 [savedRegularWindow_ close]; 551 // We delete statusBubble here because we need to kill off the dependency 552 // that its window has on our window before our window goes away. 553 delete statusBubble_; 554 statusBubble_ = NULL; 555 // We can't actually use |-autorelease| here because there's an embedded 556 // run loop in the |-performClose:| which contains its own autorelease pool. 557 // Instead call it after a zero-length delay, which gets us back to the main 558 // event loop. 559 [self performSelector:@selector(autorelease) 560 withObject:nil 561 afterDelay:0]; 562} 563 564- (void)updateDevToolsForContents:(WebContents*)contents { 565 [devToolsController_ updateDevToolsForWebContents:contents 566 withProfile:browser_->profile()]; 567 [self updateAllowOverlappingViews:[self inPresentationMode]]; 568} 569 570// Called when the user wants to close a window or from the shutdown process. 571// The Browser object is in control of whether or not we're allowed to close. It 572// may defer closing due to several states, such as onUnload handlers needing to 573// be fired. If closing is deferred, the Browser will handle the processing 574// required to get us to the closing state and (by watching for all the tabs 575// going away) will again call to close the window when it's finally ready. 576- (BOOL)windowShouldClose:(id)sender { 577 // Disable updates while closing all tabs to avoid flickering. 578 gfx::ScopedNSDisableScreenUpdates disabler; 579 // Give beforeunload handlers the chance to cancel the close before we hide 580 // the window below. 581 if (!browser_->ShouldCloseWindow()) 582 return NO; 583 584 // saveWindowPositionIfNeeded: only works if we are the last active 585 // window, but orderOut: ends up activating another window, so we 586 // have to save the window position before we call orderOut:. 587 [self saveWindowPositionIfNeeded]; 588 589 bool fast_tab_closing_enabled = 590 CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableFastUnload); 591 592 if (!browser_->tab_strip_model()->empty()) { 593 // Tab strip isn't empty. Hide the frame (so it appears to have closed 594 // immediately) and close all the tabs, allowing the renderers to shut 595 // down. When the tab strip is empty we'll be called back again. 596 [[self window] orderOut:self]; 597 browser_->OnWindowClosing(); 598 if (fast_tab_closing_enabled) 599 browser_->tab_strip_model()->CloseAllTabs(); 600 return NO; 601 } else if (fast_tab_closing_enabled && 602 !browser_->HasCompletedUnloadProcessing()) { 603 // The browser needs to finish running unload handlers. 604 // Hide the window (so it appears to have closed immediately), and 605 // the browser will call us back again when it is ready to close. 606 [[self window] orderOut:self]; 607 return NO; 608 } 609 610 // the tab strip is empty, it's ok to close the window 611 return YES; 612} 613 614// Called right after our window became the main window. 615- (void)windowDidBecomeMain:(NSNotification*)notification { 616 BrowserList::SetLastActive(browser_.get()); 617 [self saveWindowPositionIfNeeded]; 618 619 // TODO(dmaclach): Instead of redrawing the whole window, views that care 620 // about the active window state should be registering for notifications. 621 [[self window] setViewsNeedDisplay:YES]; 622 623 // TODO(viettrungluu): For some reason, the above doesn't suffice. 624 if ([self isFullscreen]) 625 [floatingBarBackingView_ setNeedsDisplay:YES]; // Okay even if nil. 626} 627 628- (void)windowDidResignMain:(NSNotification*)notification { 629 // TODO(dmaclach): Instead of redrawing the whole window, views that care 630 // about the active window state should be registering for notifications. 631 [[self window] setViewsNeedDisplay:YES]; 632 633 // TODO(viettrungluu): For some reason, the above doesn't suffice. 634 if ([self isFullscreen]) 635 [floatingBarBackingView_ setNeedsDisplay:YES]; // Okay even if nil. 636} 637 638// Called when we are activated (when we gain focus). 639- (void)windowDidBecomeKey:(NSNotification*)notification { 640 // We need to activate the controls (in the "WebView"). To do this, get the 641 // selected WebContents's RenderWidgetHostView and tell it to activate. 642 if (WebContents* contents = 643 browser_->tab_strip_model()->GetActiveWebContents()) { 644 645 DevToolsWindow* devtoolsWindow = 646 DevToolsWindow::GetDockedInstanceForInspectedTab(contents); 647 if (devtoolsWindow) { 648 RenderWidgetHostView* devtoolsView = 649 devtoolsWindow->web_contents()->GetRenderWidgetHostView(); 650 if (devtoolsView && devtoolsView->HasFocus()) { 651 devtoolsView->SetActive(true); 652 return; 653 } 654 } 655 656 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 657 rwhv->SetActive(true); 658 } 659} 660 661// Called when we are deactivated (when we lose focus). 662- (void)windowDidResignKey:(NSNotification*)notification { 663 // If our app is still active and we're still the key window, ignore this 664 // message, since it just means that a menu extra (on the "system status bar") 665 // was activated; we'll get another |-windowDidResignKey| if we ever really 666 // lose key window status. 667 if ([NSApp isActive] && ([NSApp keyWindow] == [self window])) 668 return; 669 670 // We need to deactivate the controls (in the "WebView"). To do this, get the 671 // selected WebContents's RenderWidgetHostView and tell it to deactivate. 672 if (WebContents* contents = 673 browser_->tab_strip_model()->GetActiveWebContents()) { 674 675 DevToolsWindow* devtoolsWindow = 676 DevToolsWindow::GetDockedInstanceForInspectedTab(contents); 677 if (devtoolsWindow) { 678 RenderWidgetHostView* devtoolsView = 679 devtoolsWindow->web_contents()->GetRenderWidgetHostView(); 680 if (devtoolsView && devtoolsView->HasFocus()) { 681 devtoolsView->SetActive(false); 682 return; 683 } 684 } 685 686 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 687 rwhv->SetActive(false); 688 } 689} 690 691// Called when we have been minimized. 692- (void)windowDidMiniaturize:(NSNotification *)notification { 693 [self saveWindowPositionIfNeeded]; 694 695 // Let the selected RenderWidgetHostView know, so that it can tell plugins. 696 if (WebContents* contents = 697 browser_->tab_strip_model()->GetActiveWebContents()) { 698 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 699 rwhv->SetWindowVisibility(false); 700 } 701} 702 703// Called when we have been unminimized. 704- (void)windowDidDeminiaturize:(NSNotification *)notification { 705 // Let the selected RenderWidgetHostView know, so that it can tell plugins. 706 if (WebContents* contents = 707 browser_->tab_strip_model()->GetActiveWebContents()) { 708 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 709 rwhv->SetWindowVisibility(true); 710 } 711} 712 713// Called when the application has been hidden. 714- (void)applicationDidHide:(NSNotification *)notification { 715 // Let the selected RenderWidgetHostView know, so that it can tell plugins 716 // (unless we are minimized, in which case nothing has really changed). 717 if (![[self window] isMiniaturized]) { 718 if (WebContents* contents = 719 browser_->tab_strip_model()->GetActiveWebContents()) { 720 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 721 rwhv->SetWindowVisibility(false); 722 } 723 } 724} 725 726// Called when the application has been unhidden. 727- (void)applicationDidUnhide:(NSNotification *)notification { 728 // Let the selected RenderWidgetHostView know, so that it can tell plugins 729 // (unless we are minimized, in which case nothing has really changed). 730 if (![[self window] isMiniaturized]) { 731 if (WebContents* contents = 732 browser_->tab_strip_model()->GetActiveWebContents()) { 733 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 734 rwhv->SetWindowVisibility(true); 735 } 736 } 737} 738 739// Called when the user clicks the zoom button (or selects it from the Window 740// menu) to determine the "standard size" of the window, based on the content 741// and other factors. If the current size/location differs nontrivally from the 742// standard size, Cocoa resizes the window to the standard size, and saves the 743// current size as the "user size". If the current size/location is the same (up 744// to a fudge factor) as the standard size, Cocoa resizes the window to the 745// saved user size. (It is possible for the two to coincide.) In this way, the 746// zoom button acts as a toggle. We determine the standard size based on the 747// content, but enforce a minimum width (calculated using the dimensions of the 748// screen) to ensure websites with small intrinsic width (such as google.com) 749// don't end up with a wee window. Moreover, we always declare the standard 750// width to be at least as big as the current width, i.e., we never want zooming 751// to the standard width to shrink the window. This is consistent with other 752// browsers' behaviour, and is desirable in multi-tab situations. Note, however, 753// that the "toggle" behaviour means that the window can still be "unzoomed" to 754// the user size. 755- (NSRect)windowWillUseStandardFrame:(NSWindow*)window 756 defaultFrame:(NSRect)frame { 757 // Forget that we grew the window up (if we in fact did). 758 [self resetWindowGrowthState]; 759 760 // |frame| already fills the current screen. Never touch y and height since we 761 // always want to fill vertically. 762 763 // If the shift key is down, maximize. Hopefully this should make the 764 // "switchers" happy. 765 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) { 766 return frame; 767 } 768 769 // To prevent strange results on portrait displays, the basic minimum zoomed 770 // width is the larger of: 60% of available width, 60% of available height 771 // (bounded by available width). 772 const CGFloat kProportion = 0.6; 773 CGFloat zoomedWidth = 774 std::max(kProportion * NSWidth(frame), 775 std::min(kProportion * NSHeight(frame), NSWidth(frame))); 776 777 WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents(); 778 if (contents) { 779 // If the intrinsic width is bigger, then make it the zoomed width. 780 const int kScrollbarWidth = 16; // TODO(viettrungluu): ugh. 781 CGFloat intrinsicWidth = static_cast<CGFloat>( 782 contents->GetPreferredSize().width() + kScrollbarWidth); 783 zoomedWidth = std::max(zoomedWidth, 784 std::min(intrinsicWidth, NSWidth(frame))); 785 } 786 787 // Never shrink from the current size on zoom (see above). 788 NSRect currentFrame = [[self window] frame]; 789 zoomedWidth = std::max(zoomedWidth, NSWidth(currentFrame)); 790 791 // |frame| determines our maximum extents. We need to set the origin of the 792 // frame -- and only move it left if necessary. 793 if (currentFrame.origin.x + zoomedWidth > NSMaxX(frame)) 794 frame.origin.x = NSMaxX(frame) - zoomedWidth; 795 else 796 frame.origin.x = currentFrame.origin.x; 797 798 // Set the width. Don't touch y or height. 799 frame.size.width = zoomedWidth; 800 801 return frame; 802} 803 804- (void)activate { 805 [BrowserWindowUtils activateWindowForController:self]; 806} 807 808// Determine whether we should let a window zoom/unzoom to the given |newFrame|. 809// We avoid letting unzoom move windows between screens, because it's really 810// strange and unintuitive. 811- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame { 812 // Figure out which screen |newFrame| is on. 813 NSScreen* newScreen = nil; 814 CGFloat newScreenOverlapArea = 0.0; 815 for (NSScreen* screen in [NSScreen screens]) { 816 NSRect overlap = NSIntersectionRect(newFrame, [screen frame]); 817 CGFloat overlapArea = NSWidth(overlap) * NSHeight(overlap); 818 if (overlapArea > newScreenOverlapArea) { 819 newScreen = screen; 820 newScreenOverlapArea = overlapArea; 821 } 822 } 823 // If we're somehow not on any screen, allow the zoom. 824 if (!newScreen) 825 return YES; 826 827 // If the new screen is the current screen, we can return a definitive YES. 828 // Note: This check is not strictly necessary, but just short-circuits in the 829 // "no-brainer" case. To test the complicated logic below, comment this out! 830 NSScreen* curScreen = [window screen]; 831 if (newScreen == curScreen) 832 return YES; 833 834 // Worry a little: What happens when a window is on two (or more) screens? 835 // E.g., what happens in a 50-50 scenario? Cocoa may reasonably elect to zoom 836 // to the other screen rather than staying on the officially current one. So 837 // we compare overlaps with the current window frame, and see if Cocoa's 838 // choice was reasonable (allowing a small rounding error). This should 839 // hopefully avoid us ever erroneously denying a zoom when a window is on 840 // multiple screens. 841 NSRect curFrame = [window frame]; 842 NSRect newScrIntersectCurFr = NSIntersectionRect([newScreen frame], curFrame); 843 NSRect curScrIntersectCurFr = NSIntersectionRect([curScreen frame], curFrame); 844 if (NSWidth(newScrIntersectCurFr) * NSHeight(newScrIntersectCurFr) >= 845 (NSWidth(curScrIntersectCurFr) * NSHeight(curScrIntersectCurFr) - 1.0)) { 846 return YES; 847 } 848 849 // If it wasn't reasonable, return NO. 850 return NO; 851} 852 853// Adjusts the window height by the given amount. 854- (BOOL)adjustWindowHeightBy:(CGFloat)deltaH { 855 // By not adjusting the window height when initializing, we can ensure that 856 // the window opens with the same size that was saved on close. 857 if (initializing_ || [self isFullscreen] || deltaH == 0) 858 return NO; 859 860 NSWindow* window = [self window]; 861 NSRect windowFrame = [window frame]; 862 NSRect workarea = [[window screen] visibleFrame]; 863 864 // If the window is not already fully in the workarea, do not adjust its frame 865 // at all. 866 if (!NSContainsRect(workarea, windowFrame)) 867 return NO; 868 869 // Record the position of the top/bottom of the window, so we can easily check 870 // whether we grew the window upwards/downwards. 871 CGFloat oldWindowMaxY = NSMaxY(windowFrame); 872 CGFloat oldWindowMinY = NSMinY(windowFrame); 873 874 // We are "zoomed" if we occupy the full vertical space. 875 bool isZoomed = (windowFrame.origin.y == workarea.origin.y && 876 NSHeight(windowFrame) == NSHeight(workarea)); 877 878 // If we're shrinking the window.... 879 if (deltaH < 0) { 880 bool didChange = false; 881 882 // Don't reset if not currently zoomed since shrinking can take several 883 // steps! 884 if (isZoomed) 885 isShrinkingFromZoomed_ = YES; 886 887 // If we previously grew at the top, shrink as much as allowed at the top 888 // first. 889 if (windowTopGrowth_ > 0) { 890 CGFloat shrinkAtTopBy = MIN(-deltaH, windowTopGrowth_); 891 windowFrame.size.height -= shrinkAtTopBy; // Shrink the window. 892 deltaH += shrinkAtTopBy; // Update the amount left to shrink. 893 windowTopGrowth_ -= shrinkAtTopBy; // Update the growth state. 894 didChange = true; 895 } 896 897 // Similarly for the bottom (not an "else if" since we may have to 898 // simultaneously shrink at both the top and at the bottom). Note that 899 // |deltaH| may no longer be nonzero due to the above. 900 if (deltaH < 0 && windowBottomGrowth_ > 0) { 901 CGFloat shrinkAtBottomBy = MIN(-deltaH, windowBottomGrowth_); 902 windowFrame.origin.y += shrinkAtBottomBy; // Move the window up. 903 windowFrame.size.height -= shrinkAtBottomBy; // Shrink the window. 904 deltaH += shrinkAtBottomBy; // Update the amount left.... 905 windowBottomGrowth_ -= shrinkAtBottomBy; // Update the growth state. 906 didChange = true; 907 } 908 909 // If we're shrinking from zoomed but we didn't change the top or bottom 910 // (since we've reached the limits imposed by |window...Growth_|), then stop 911 // here. Don't reset |isShrinkingFromZoomed_| since we might get called 912 // again for the same shrink. 913 if (isShrinkingFromZoomed_ && !didChange) 914 return NO; 915 } else { 916 isShrinkingFromZoomed_ = NO; 917 918 // Don't bother with anything else. 919 if (isZoomed) 920 return NO; 921 } 922 923 // Shrinking from zoomed is handled above (and is constrained by 924 // |window...Growth_|). 925 if (!isShrinkingFromZoomed_) { 926 // Resize the window down until it hits the bottom of the workarea, then if 927 // needed continue resizing upwards. Do not resize the window to be taller 928 // than the current workarea. 929 // Resize the window as requested, keeping the top left corner fixed. 930 windowFrame.origin.y -= deltaH; 931 windowFrame.size.height += deltaH; 932 933 // If the bottom left corner is now outside the visible frame, move the 934 // window up to make it fit, but make sure not to move the top left corner 935 // out of the visible frame. 936 if (windowFrame.origin.y < workarea.origin.y) { 937 windowFrame.origin.y = workarea.origin.y; 938 windowFrame.size.height = 939 std::min(NSHeight(windowFrame), NSHeight(workarea)); 940 } 941 942 // Record (if applicable) how much we grew the window in either direction. 943 // (N.B.: These only record growth, not shrinkage.) 944 if (NSMaxY(windowFrame) > oldWindowMaxY) 945 windowTopGrowth_ += NSMaxY(windowFrame) - oldWindowMaxY; 946 if (NSMinY(windowFrame) < oldWindowMinY) 947 windowBottomGrowth_ += oldWindowMinY - NSMinY(windowFrame); 948 } 949 950 // Disable subview resizing while resizing the window, or else we will get 951 // unwanted renderer resizes. The calling code must call layoutSubviews to 952 // make things right again. 953 NSView* contentView = [window contentView]; 954 [contentView setAutoresizesSubviews:NO]; 955 [window setFrame:windowFrame display:NO]; 956 [contentView setAutoresizesSubviews:YES]; 957 return YES; 958} 959 960// Main method to resize browser window subviews. This method should be called 961// when resizing any child of the content view, rather than resizing the views 962// directly. If the view is already the correct height, does not force a 963// relayout. 964- (void)resizeView:(NSView*)view newHeight:(CGFloat)height { 965 // We should only ever be called for one of the following four views. 966 // |downloadShelfController_| may be nil. If we are asked to size the bookmark 967 // bar directly, its superview must be this controller's content view. 968 DCHECK(view); 969 DCHECK(view == [toolbarController_ view] || 970 view == [infoBarContainerController_ view] || 971 view == [downloadShelfController_ view] || 972 view == [bookmarkBarController_ view]); 973 974 // Change the height of the view and call |-layoutSubViews|. We set the height 975 // here without regard to where the view is on the screen or whether it needs 976 // to "grow up" or "grow down." The below call to |-layoutSubviews| will 977 // position each view correctly. 978 NSRect frame = [view frame]; 979 if (NSHeight(frame) == height) 980 return; 981 982 // Disable screen updates to prevent flickering. 983 if (view == [bookmarkBarController_ view] || 984 view == [downloadShelfController_ view]) { 985 [[self window] disableScreenUpdatesUntilFlush]; 986 } 987 988 // Grow or shrink the window by the amount of the height change. We adjust 989 // the window height only in two cases: 990 // 1) We are adjusting the height of the bookmark bar and it is currently 991 // animating either open or closed. 992 // 2) We are adjusting the height of the download shelf. 993 // 994 // We do not adjust the window height for bookmark bar changes on the NTP. 995 BOOL shouldAdjustBookmarkHeight = 996 [bookmarkBarController_ isAnimatingBetweenState:BookmarkBar::HIDDEN 997 andState:BookmarkBar::SHOW]; 998 999 BOOL resizeRectDirty = NO; 1000 if ((shouldAdjustBookmarkHeight && view == [bookmarkBarController_ view]) || 1001 view == [downloadShelfController_ view]) { 1002 CGFloat deltaH = height - NSHeight(frame); 1003 if ([self adjustWindowHeightBy:deltaH] && 1004 view == [downloadShelfController_ view]) { 1005 // If the window height didn't change, the download shelf will change the 1006 // size of the contents. If the contents size doesn't change, send it 1007 // an explicit grow box invalidation (else, the resize message does that.) 1008 resizeRectDirty = YES; 1009 } 1010 } 1011 1012 frame.size.height = height; 1013 // TODO(rohitrao): Determine if calling setFrame: twice is bad. 1014 [view setFrame:frame]; 1015 [self layoutSubviews]; 1016 1017 if (resizeRectDirty) { 1018 // Send new resize rect to foreground tab. 1019 if (content::WebContents* contents = 1020 browser_->tab_strip_model()->GetActiveWebContents()) { 1021 if (content::RenderViewHost* rvh = contents->GetRenderViewHost()) { 1022 rvh->ResizeRectChanged(windowShim_->GetRootWindowResizerRect()); 1023 } 1024 } 1025 } 1026} 1027 1028- (void)setAnimationInProgress:(BOOL)inProgress { 1029 [[self tabContentArea] setFastResizeMode:inProgress]; 1030} 1031 1032// Update a toggle state for an NSMenuItem if modified. 1033// Take care to ensure |item| looks like a NSMenuItem. 1034// Called by validateUserInterfaceItem:. 1035- (void)updateToggleStateWithTag:(NSInteger)tag forItem:(id)item { 1036 if (![item respondsToSelector:@selector(state)] || 1037 ![item respondsToSelector:@selector(setState:)]) 1038 return; 1039 1040 // On Windows this logic happens in bookmark_bar_view.cc. On the 1041 // Mac we're a lot more MVC happy so we've moved it into a 1042 // controller. To be clear, this simply updates the menu item; it 1043 // does not display the bookmark bar itself. 1044 if (tag == IDC_SHOW_BOOKMARK_BAR) { 1045 bool toggled = windowShim_->IsBookmarkBarVisible(); 1046 NSInteger oldState = [item state]; 1047 NSInteger newState = toggled ? NSOnState : NSOffState; 1048 if (oldState != newState) 1049 [item setState:newState]; 1050 } 1051 1052 // Update the checked/Unchecked state of items in the encoding menu. 1053 // On Windows, this logic is part of |EncodingMenuModel| in 1054 // browser/ui/views/toolbar_view.h. 1055 EncodingMenuController encoding_controller; 1056 if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) { 1057 DCHECK(browser_.get()); 1058 Profile* profile = browser_->profile(); 1059 DCHECK(profile); 1060 WebContents* current_tab = 1061 browser_->tab_strip_model()->GetActiveWebContents(); 1062 if (!current_tab) 1063 return; 1064 1065 const std::string encoding = current_tab->GetEncoding(); 1066 1067 bool toggled = encoding_controller.IsItemChecked(profile, encoding, tag); 1068 NSInteger oldState = [item state]; 1069 NSInteger newState = toggled ? NSOnState : NSOffState; 1070 if (oldState != newState) 1071 [item setState:newState]; 1072 } 1073} 1074 1075// Called to validate menu and toolbar items when this window is key. All the 1076// items we care about have been set with the |-commandDispatch:| or 1077// |-commandDispatchUsingKeyModifiers:| actions and a target of FirstResponder 1078// in IB. If it's not one of those, let it continue up the responder chain to be 1079// handled elsewhere. We pull out the tag as the cross-platform constant to 1080// differentiate and dispatch the various commands. 1081// NOTE: we might have to handle state for app-wide menu items, 1082// although we could cheat and directly ask the app controller if our 1083// command_updater doesn't support the command. This may or may not be an issue, 1084// too early to tell. 1085- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { 1086 SEL action = [item action]; 1087 BOOL enable = NO; 1088 if (action == @selector(commandDispatch:) || 1089 action == @selector(commandDispatchUsingKeyModifiers:)) { 1090 NSInteger tag = [item tag]; 1091 if (chrome::SupportsCommand(browser_.get(), tag)) { 1092 // Generate return value (enabled state) 1093 enable = chrome::IsCommandEnabled(browser_.get(), tag); 1094 switch (tag) { 1095 case IDC_CLOSE_TAB: 1096 // Disable "close tab" if the receiving window is not tabbed. 1097 // We simply check whether the item has a keyboard shortcut set here; 1098 // app_controller_mac.mm actually determines whether the item should 1099 // be enabled. 1100 if ([static_cast<NSObject*>(item) isKindOfClass:[NSMenuItem class]]) 1101 enable &= !![[static_cast<NSMenuItem*>(item) keyEquivalent] length]; 1102 break; 1103 case IDC_FULLSCREEN: { 1104 if ([static_cast<NSObject*>(item) isKindOfClass:[NSMenuItem class]]) { 1105 NSString* menuTitle = l10n_util::GetNSString( 1106 [self isFullscreen] && ![self inPresentationMode] ? 1107 IDS_EXIT_FULLSCREEN_MAC : 1108 IDS_ENTER_FULLSCREEN_MAC); 1109 [static_cast<NSMenuItem*>(item) setTitle:menuTitle]; 1110 1111 if (!chrome::mac::SupportsSystemFullscreen()) 1112 [static_cast<NSMenuItem*>(item) setHidden:YES]; 1113 } 1114 break; 1115 } 1116 case IDC_PRESENTATION_MODE: { 1117 if ([static_cast<NSObject*>(item) isKindOfClass:[NSMenuItem class]]) { 1118 NSString* menuTitle = l10n_util::GetNSString( 1119 [self inPresentationMode] ? IDS_EXIT_PRESENTATION_MAC : 1120 IDS_ENTER_PRESENTATION_MAC); 1121 [static_cast<NSMenuItem*>(item) setTitle:menuTitle]; 1122 } 1123 break; 1124 } 1125 case IDC_SHOW_SIGNIN: { 1126 Profile* original_profile = 1127 browser_->profile()->GetOriginalProfile(); 1128 [BrowserWindowController updateSigninItem:item 1129 shouldShow:enable 1130 currentProfile:original_profile]; 1131 break; 1132 } 1133 default: 1134 // Special handling for the contents of the Text Encoding submenu. On 1135 // Mac OS, instead of enabling/disabling the top-level menu item, we 1136 // enable/disable the submenu's contents (per Apple's HIG). 1137 EncodingMenuController encoding_controller; 1138 if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) { 1139 enable &= chrome::IsCommandEnabled(browser_.get(), 1140 IDC_ENCODING_MENU) ? YES : NO; 1141 } 1142 } 1143 1144 // If the item is toggleable, find its toggle state and 1145 // try to update it. This is a little awkward, but the alternative is 1146 // to check after a commandDispatch, which seems worse. 1147 [self updateToggleStateWithTag:tag forItem:item]; 1148 } 1149 } 1150 return enable; 1151} 1152 1153// Called when the user picks a menu or toolbar item when this window is key. 1154// Calls through to the browser object to execute the command. This assumes that 1155// the command is supported and doesn't check, otherwise it would have been 1156// disabled in the UI in validateUserInterfaceItem:. 1157- (void)commandDispatch:(id)sender { 1158 DCHECK(sender); 1159 // Identify the actual BWC to which the command should be dispatched. It might 1160 // belong to a background window, yet this controller gets it because it is 1161 // the foreground window's controller and thus in the responder chain. Some 1162 // senders don't have this problem (for example, menus only operate on the 1163 // foreground window), so this is only an issue for senders that are part of 1164 // windows. 1165 BrowserWindowController* targetController = self; 1166 if ([sender respondsToSelector:@selector(window)]) 1167 targetController = [[sender window] windowController]; 1168 DCHECK([targetController isKindOfClass:[BrowserWindowController class]]); 1169 DCHECK(targetController->browser_.get()); 1170 chrome::ExecuteCommand(targetController->browser_.get(), [sender tag]); 1171} 1172 1173// Same as |-commandDispatch:|, but executes commands using a disposition 1174// determined by the key flags. If the window is in the background and the 1175// command key is down, ignore the command key, but process any other modifiers. 1176- (void)commandDispatchUsingKeyModifiers:(id)sender { 1177 DCHECK(sender); 1178 1179 if (![sender isEnabled]) { 1180 // This code is reachable e.g. if the user mashes the back button, queuing 1181 // up a bunch of events before the button's enabled state is updated: 1182 // http://crbug.com/63254 1183 return; 1184 } 1185 1186 // See comment above for why we do this. 1187 BrowserWindowController* targetController = self; 1188 if ([sender respondsToSelector:@selector(window)]) 1189 targetController = [[sender window] windowController]; 1190 DCHECK([targetController isKindOfClass:[BrowserWindowController class]]); 1191 NSInteger command = [sender tag]; 1192 NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags]; 1193 if ((command == IDC_RELOAD) && 1194 (modifierFlags & (NSShiftKeyMask | NSControlKeyMask))) { 1195 command = IDC_RELOAD_IGNORING_CACHE; 1196 // Mask off Shift and Control so they don't affect the disposition below. 1197 modifierFlags &= ~(NSShiftKeyMask | NSControlKeyMask); 1198 } 1199 if (![[sender window] isMainWindow]) { 1200 // Remove the command key from the flags, it means "keep the window in 1201 // the background" in this case. 1202 modifierFlags &= ~NSCommandKeyMask; 1203 } 1204 WindowOpenDisposition disposition = 1205 ui::WindowOpenDispositionFromNSEventWithFlags( 1206 [NSApp currentEvent], modifierFlags); 1207 switch (command) { 1208 case IDC_BACK: 1209 case IDC_FORWARD: 1210 case IDC_RELOAD: 1211 case IDC_RELOAD_IGNORING_CACHE: 1212 if (disposition == CURRENT_TAB) { 1213 // Forcibly reset the location bar, since otherwise it won't discard any 1214 // ongoing user edits, since it doesn't realize this is a user-initiated 1215 // action. 1216 [targetController locationBarBridge]->Revert(); 1217 } 1218 } 1219 DCHECK(targetController->browser_.get()); 1220 chrome::ExecuteCommandWithDisposition(targetController->browser_.get(), 1221 command, disposition); 1222} 1223 1224// Called when another part of the internal codebase needs to execute a 1225// command. 1226- (void)executeCommand:(int)command { 1227 chrome::ExecuteCommand(browser_.get(), command); 1228} 1229 1230- (BOOL)handledByExtensionCommand:(NSEvent*)event { 1231 return extension_keybinding_registry_->ProcessKeyEvent( 1232 content::NativeWebKeyboardEvent(event)); 1233} 1234 1235// StatusBubble delegate method: tell the status bubble the frame it should 1236// position itself in. 1237- (NSRect)statusBubbleBaseFrame { 1238 NSView* view = [overlayableContentsController_ view]; 1239 return [view convertRect:[view bounds] toView:nil]; 1240} 1241 1242- (void)updateToolbarWithContents:(WebContents*)tab { 1243 [toolbarController_ updateToolbarWithContents:tab]; 1244} 1245 1246- (void)setStarredState:(BOOL)isStarred { 1247 [toolbarController_ setStarredState:isStarred]; 1248} 1249 1250- (void)zoomChangedForActiveTab:(BOOL)canShowBubble { 1251 [toolbarController_ zoomChangedForActiveTab:canShowBubble]; 1252} 1253 1254// Return the rect, in WebKit coordinates (flipped), of the window's grow box 1255// in the coordinate system of the content area of the currently selected tab. 1256// |windowGrowBox| needs to be in the window's coordinate system. 1257- (NSRect)selectedTabGrowBoxRect { 1258 NSWindow* window = [self window]; 1259 if (![window respondsToSelector:@selector(_growBoxRect)]) 1260 return NSZeroRect; 1261 1262 // Before we return a rect, we need to convert it from window coordinates 1263 // to tab content area coordinates and flip the coordinate system. 1264 NSRect growBoxRect = 1265 [[self tabContentArea] convertRect:[window _growBoxRect] fromView:nil]; 1266 growBoxRect.origin.y = 1267 NSHeight([[self tabContentArea] frame]) - NSMaxY(growBoxRect); 1268 return growBoxRect; 1269} 1270 1271// Accept tabs from a BrowserWindowController with the same Profile. 1272- (BOOL)canReceiveFrom:(TabWindowController*)source { 1273 if (![source isKindOfClass:[BrowserWindowController class]]) { 1274 return NO; 1275 } 1276 1277 BrowserWindowController* realSource = 1278 static_cast<BrowserWindowController*>(source); 1279 if (browser_->profile() != realSource->browser_->profile()) { 1280 return NO; 1281 } 1282 1283 // Can't drag a tab from a normal browser to a pop-up 1284 if (browser_->type() != realSource->browser_->type()) { 1285 return NO; 1286 } 1287 1288 return YES; 1289} 1290 1291// Move a given tab view to the location of the current placeholder. If there is 1292// no placeholder, it will go at the end. |controller| is the window controller 1293// of a tab being dropped from a different window. It will be nil if the drag is 1294// within the window, otherwise the tab is removed from that window before being 1295// placed into this one. The implementation will call |-removePlaceholder| since 1296// the drag is now complete. This also calls |-layoutTabs| internally so 1297// clients do not need to call it again. 1298- (void)moveTabView:(NSView*)view 1299 fromController:(TabWindowController*)dragController { 1300 if (dragController) { 1301 // Moving between windows. Figure out the WebContents to drop into our tab 1302 // model from the source window's model. 1303 BOOL isBrowser = 1304 [dragController isKindOfClass:[BrowserWindowController class]]; 1305 DCHECK(isBrowser); 1306 if (!isBrowser) return; 1307 BrowserWindowController* dragBWC = (BrowserWindowController*)dragController; 1308 int index = [dragBWC->tabStripController_ modelIndexForTabView:view]; 1309 WebContents* contents = 1310 dragBWC->browser_->tab_strip_model()->GetWebContentsAt(index); 1311 // The tab contents may have gone away if given a window.close() while it 1312 // is being dragged. If so, bail, we've got nothing to drop. 1313 if (!contents) 1314 return; 1315 1316 // Convert |view|'s frame (which starts in the source tab strip's coordinate 1317 // system) to the coordinate system of the destination tab strip. This needs 1318 // to be done before being detached so the window transforms can be 1319 // performed. 1320 NSRect destinationFrame = [view frame]; 1321 NSPoint tabOrigin = destinationFrame.origin; 1322 tabOrigin = [[dragController tabStripView] convertPoint:tabOrigin 1323 toView:nil]; 1324 tabOrigin = [[view window] convertBaseToScreen:tabOrigin]; 1325 tabOrigin = [[self window] convertScreenToBase:tabOrigin]; 1326 tabOrigin = [[self tabStripView] convertPoint:tabOrigin fromView:nil]; 1327 destinationFrame.origin = tabOrigin; 1328 1329 // Before the tab is detached from its originating tab strip, store the 1330 // pinned state so that it can be maintained between the windows. 1331 bool isPinned = dragBWC->browser_->tab_strip_model()->IsTabPinned(index); 1332 1333 // Now that we have enough information about the tab, we can remove it from 1334 // the dragging window. We need to do this *before* we add it to the new 1335 // window as this will remove the WebContents' delegate. 1336 [dragController detachTabView:view]; 1337 1338 // Deposit it into our model at the appropriate location (it already knows 1339 // where it should go from tracking the drag). Doing this sets the tab's 1340 // delegate to be the Browser. 1341 [tabStripController_ dropWebContents:contents 1342 withFrame:destinationFrame 1343 asPinnedTab:isPinned]; 1344 } else { 1345 // Moving within a window. 1346 int index = [tabStripController_ modelIndexForTabView:view]; 1347 [tabStripController_ moveTabFromIndex:index]; 1348 } 1349 1350 // Remove the placeholder since the drag is now complete. 1351 [self removePlaceholder]; 1352} 1353 1354// Tells the tab strip to forget about this tab in preparation for it being 1355// put into a different tab strip, such as during a drop on another window. 1356- (void)detachTabView:(NSView*)view { 1357 int index = [tabStripController_ modelIndexForTabView:view]; 1358 browser_->tab_strip_model()->DetachWebContentsAt(index); 1359} 1360 1361- (NSView*)activeTabView { 1362 return [tabStripController_ activeTabView]; 1363} 1364 1365- (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { 1366 [toolbarController_ setIsLoading:isLoading force:force]; 1367} 1368 1369// Make the location bar the first responder, if possible. 1370- (void)focusLocationBar:(BOOL)selectAll { 1371 [toolbarController_ focusLocationBar:selectAll]; 1372} 1373 1374- (void)focusTabContents { 1375 [[self window] makeFirstResponder:[tabStripController_ activeTabView]]; 1376} 1377 1378- (void)layoutTabs { 1379 [tabStripController_ layoutTabs]; 1380} 1381 1382- (TabWindowController*)detachTabToNewWindow:(TabView*)tabView { 1383 // Disable screen updates so that this appears as a single visual change. 1384 gfx::ScopedNSDisableScreenUpdates disabler; 1385 1386 // Fetch the tab contents for the tab being dragged. 1387 int index = [tabStripController_ modelIndexForTabView:tabView]; 1388 WebContents* contents = browser_->tab_strip_model()->GetWebContentsAt(index); 1389 1390 // Set the window size. Need to do this before we detach the tab so it's 1391 // still in the window. We have to flip the coordinates as that's what 1392 // is expected by the Browser code. 1393 NSWindow* sourceWindow = [tabView window]; 1394 NSRect windowRect = [sourceWindow frame]; 1395 NSScreen* screen = [sourceWindow screen]; 1396 windowRect.origin.y = NSHeight([screen frame]) - NSMaxY(windowRect); 1397 gfx::Rect browserRect(windowRect.origin.x, windowRect.origin.y, 1398 NSWidth(windowRect), NSHeight(windowRect)); 1399 1400 NSRect sourceTabRect = [tabView frame]; 1401 NSView* tabStrip = [self tabStripView]; 1402 1403 // Pushes tabView's frame back inside the tabstrip. 1404 NSSize tabOverflow = 1405 [self overflowFrom:[tabStrip convertRect:sourceTabRect toView:nil] 1406 to:[tabStrip frame]]; 1407 NSRect tabRect = NSOffsetRect(sourceTabRect, 1408 -tabOverflow.width, -tabOverflow.height); 1409 1410 // Before detaching the tab, store the pinned state. 1411 bool isPinned = browser_->tab_strip_model()->IsTabPinned(index); 1412 1413 // Detach it from the source window, which just updates the model without 1414 // deleting the tab contents. This needs to come before creating the new 1415 // Browser because it clears the WebContents' delegate, which gets hooked 1416 // up during creation of the new window. 1417 browser_->tab_strip_model()->DetachWebContentsAt(index); 1418 1419 // Create the new window with a single tab in its model, the one being 1420 // dragged. 1421 DockInfo dockInfo; 1422 TabStripModelDelegate::NewStripContents item; 1423 item.web_contents = contents; 1424 item.add_types = TabStripModel::ADD_ACTIVE | 1425 (isPinned ? TabStripModel::ADD_PINNED 1426 : TabStripModel::ADD_NONE); 1427 std::vector<TabStripModelDelegate::NewStripContents> contentses; 1428 contentses.push_back(item); 1429 Browser* newBrowser = browser_->tab_strip_model()->delegate()-> 1430 CreateNewStripWithContents(contentses, browserRect, dockInfo, false); 1431 1432 // Get the new controller by asking the new window for its delegate. 1433 BrowserWindowController* controller = 1434 reinterpret_cast<BrowserWindowController*>( 1435 [newBrowser->window()->GetNativeWindow() delegate]); 1436 DCHECK(controller && [controller isKindOfClass:[TabWindowController class]]); 1437 1438 // Force the added tab to the right size (remove stretching.) 1439 tabRect.size.height = [TabStripController defaultTabHeight]; 1440 1441 // And make sure we use the correct frame in the new view. 1442 [[controller tabStripController] setFrameOfActiveTab:tabRect]; 1443 return controller; 1444} 1445 1446- (void)insertPlaceholderForTab:(TabView*)tab 1447 frame:(NSRect)frame { 1448 [super insertPlaceholderForTab:tab frame:frame]; 1449 [tabStripController_ insertPlaceholderForTab:tab frame:frame]; 1450} 1451 1452- (void)removePlaceholder { 1453 [super removePlaceholder]; 1454 [tabStripController_ insertPlaceholderForTab:nil frame:NSZeroRect]; 1455} 1456 1457- (BOOL)isDragSessionActive { 1458 // The tab can be dragged within the existing tab strip or detached 1459 // into its own window (then the overlay window will be present). 1460 return [[self tabStripController] isDragSessionActive] || 1461 [self overlayWindow] != nil; 1462} 1463 1464- (BOOL)tabDraggingAllowed { 1465 return [tabStripController_ tabDraggingAllowed]; 1466} 1467 1468- (BOOL)tabTearingAllowed { 1469 return ![self isFullscreen]; 1470} 1471 1472- (BOOL)windowMovementAllowed { 1473 return ![self isFullscreen]; 1474} 1475 1476- (BOOL)isTabFullyVisible:(TabView*)tab { 1477 return [tabStripController_ isTabFullyVisible:tab]; 1478} 1479 1480- (void)showNewTabButton:(BOOL)show { 1481 [tabStripController_ showNewTabButton:show]; 1482} 1483 1484- (BOOL)shouldShowAvatar { 1485 if (![self hasTabStrip]) 1486 return NO; 1487 if (browser_->profile()->IsOffTheRecord()) 1488 return YES; 1489 1490 ProfileInfoCache& cache = 1491 g_browser_process->profile_manager()->GetProfileInfoCache(); 1492 if (cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath()) == 1493 std::string::npos) { 1494 return NO; 1495 } 1496 1497 return AvatarMenu::ShouldShowAvatarMenu(); 1498} 1499 1500- (BOOL)isBookmarkBarVisible { 1501 return [bookmarkBarController_ isVisible]; 1502} 1503 1504- (BOOL)isBookmarkBarAnimating { 1505 return [bookmarkBarController_ isAnimationRunning]; 1506} 1507 1508- (BookmarkBarController*)bookmarkBarController { 1509 return bookmarkBarController_; 1510} 1511 1512- (DevToolsController*)devToolsController { 1513 return devToolsController_; 1514} 1515 1516- (BOOL)isDownloadShelfVisible { 1517 return downloadShelfController_ != nil && 1518 [downloadShelfController_ isVisible]; 1519} 1520 1521- (DownloadShelfController*)downloadShelf { 1522 if (!downloadShelfController_.get()) { 1523 downloadShelfController_.reset([[DownloadShelfController alloc] 1524 initWithBrowser:browser_.get() resizeDelegate:self]); 1525 [[[self window] contentView] addSubview:[downloadShelfController_ view]]; 1526 } 1527 return downloadShelfController_; 1528} 1529 1530- (void)addFindBar:(FindBarCocoaController*)findBarCocoaController { 1531 // Shouldn't call addFindBar twice. 1532 DCHECK(!findBarCocoaController_.get()); 1533 1534 // Create a controller for the findbar. 1535 findBarCocoaController_.reset([findBarCocoaController retain]); 1536 [self layoutSubviews]; 1537 [self updateSubviewZOrder:[self inPresentationMode]]; 1538} 1539 1540- (NSWindow*)createFullscreenWindow { 1541 return [[[FullscreenWindow alloc] initForScreen:[[self window] screen]] 1542 autorelease]; 1543} 1544 1545- (NSInteger)numberOfTabs { 1546 // count() includes pinned tabs. 1547 return browser_->tab_strip_model()->count(); 1548} 1549 1550- (BOOL)hasLiveTabs { 1551 return !browser_->tab_strip_model()->empty(); 1552} 1553 1554- (NSString*)activeTabTitle { 1555 WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents(); 1556 return base::SysUTF16ToNSString(contents->GetTitle()); 1557} 1558 1559- (NSRect)regularWindowFrame { 1560 return [self isFullscreen] ? savedRegularWindowFrame_ : 1561 [[self window] frame]; 1562} 1563 1564// (Override of |TabWindowController| method.) 1565- (BOOL)hasTabStrip { 1566 return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP]; 1567} 1568 1569- (BOOL)isTabDraggable:(NSView*)tabView { 1570 // TODO(avi, thakis): ConstrainedWindowSheetController has no api to move 1571 // tabsheets between windows. Until then, we have to prevent having to move a 1572 // tabsheet between windows, e.g. no tearing off of tabs. 1573 int index = [tabStripController_ modelIndexForTabView:tabView]; 1574 WebContents* contents = browser_->tab_strip_model()->GetWebContentsAt(index); 1575 if (!contents) 1576 return NO; 1577 return !WebContentsModalDialogManager::FromWebContents(contents)-> 1578 IsDialogActive(); 1579} 1580 1581// TabStripControllerDelegate protocol. 1582- (void)onActivateTabWithContents:(WebContents*)contents { 1583 // Update various elements that are interested in knowing the current 1584 // WebContents. 1585 1586 // Update all the UI bits. 1587 windowShim_->UpdateTitleBar(); 1588 1589 [devToolsController_ updateDevToolsForWebContents:contents 1590 withProfile:browser_->profile()]; 1591 1592 // Update the bookmark bar. 1593 // Must do it after devtools updates, otherwise bookmark bar might 1594 // call resizeView -> layoutSubviews and cause unnecessary relayout. 1595 // TODO(viettrungluu): perhaps update to not terminate running animations (if 1596 // applicable)? 1597 windowShim_->BookmarkBarStateChanged( 1598 BookmarkBar::DONT_ANIMATE_STATE_CHANGE); 1599 1600 [infoBarContainerController_ changeWebContents:contents]; 1601 1602 [self updateAllowOverlappingViews:[self inPresentationMode]]; 1603} 1604 1605- (void)onTabChanged:(TabStripModelObserver::TabChangeType)change 1606 withContents:(WebContents*)contents { 1607 // Update titles if this is the currently selected tab and if it isn't just 1608 // the loading state which changed. 1609 if (change != TabStripModelObserver::LOADING_ONLY) 1610 windowShim_->UpdateTitleBar(); 1611 1612 // Update the bookmark bar if this is the currently selected tab and if it 1613 // isn't just the title which changed. This for transitions between the NTP 1614 // (showing its floating bookmark bar) and normal web pages (showing no 1615 // bookmark bar). 1616 // TODO(viettrungluu): perhaps update to not terminate running animations? 1617 if (change != TabStripModelObserver::TITLE_NOT_LOADING) { 1618 windowShim_->BookmarkBarStateChanged( 1619 BookmarkBar::DONT_ANIMATE_STATE_CHANGE); 1620 } 1621} 1622 1623- (void)onTabDetachedWithContents:(WebContents*)contents { 1624 [infoBarContainerController_ tabDetachedWithContents:contents]; 1625} 1626 1627- (void)userChangedTheme { 1628 NSView* contentView = [[self window] contentView]; 1629 [[contentView superview] cr_recursivelySetNeedsDisplay:YES]; 1630} 1631 1632- (ui::ThemeProvider*)themeProvider { 1633 return ThemeServiceFactory::GetForProfile(browser_->profile()); 1634} 1635 1636- (ThemedWindowStyle)themedWindowStyle { 1637 ThemedWindowStyle style = 0; 1638 if (browser_->profile()->IsOffTheRecord()) 1639 style |= THEMED_INCOGNITO; 1640 1641 if (browser_->is_devtools()) 1642 style |= THEMED_DEVTOOLS; 1643 if (browser_->is_type_popup()) 1644 style |= THEMED_POPUP; 1645 1646 return style; 1647} 1648 1649- (NSPoint)themeImagePositionForAlignment:(ThemeImageAlignment)alignment { 1650 NSView* windowChromeView = [[[self window] contentView] superview]; 1651 NSView* tabStripView = nil; 1652 if ([self hasTabStrip]) 1653 tabStripView = [self tabStripView]; 1654 return [BrowserWindowUtils themeImagePositionFor:windowChromeView 1655 withTabStrip:tabStripView 1656 alignment:alignment]; 1657} 1658 1659- (NSPoint)bookmarkBubblePoint { 1660 return [toolbarController_ bookmarkBubblePoint]; 1661} 1662 1663// Show the bookmark bubble (e.g. user just clicked on the STAR). 1664- (void)showBookmarkBubbleForURL:(const GURL&)url 1665 alreadyBookmarked:(BOOL)alreadyMarked { 1666 if (!bookmarkBubbleController_) { 1667 BookmarkModel* model = 1668 BookmarkModelFactory::GetForProfile(browser_->profile()); 1669 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url); 1670 bookmarkBubbleController_ = 1671 [[BookmarkBubbleController alloc] initWithParentWindow:[self window] 1672 model:model 1673 node:node 1674 alreadyBookmarked:alreadyMarked]; 1675 [bookmarkBubbleController_ showWindow:self]; 1676 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 1677 [center addObserver:self 1678 selector:@selector(bookmarkBubbleWindowWillClose:) 1679 name:NSWindowWillCloseNotification 1680 object:[bookmarkBubbleController_ window]]; 1681 } 1682} 1683 1684// Nil out the weak bookmark bubble controller reference. 1685- (void)bookmarkBubbleWindowWillClose:(NSNotification*)notification { 1686 DCHECK_EQ([notification object], [bookmarkBubbleController_ window]); 1687 1688 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 1689 [center removeObserver:self 1690 name:NSWindowWillCloseNotification 1691 object:[bookmarkBubbleController_ window]]; 1692 bookmarkBubbleController_ = nil; 1693} 1694 1695// Handle the editBookmarkNode: action sent from bookmark bubble controllers. 1696- (void)editBookmarkNode:(id)sender { 1697 BOOL responds = [sender respondsToSelector:@selector(node)]; 1698 DCHECK(responds); 1699 if (responds) { 1700 const BookmarkNode* node = [sender node]; 1701 if (node) 1702 BookmarkEditor::Show([self window], browser_->profile(), 1703 BookmarkEditor::EditDetails::EditNode(node), 1704 BookmarkEditor::SHOW_TREE); 1705 } 1706} 1707 1708// If the browser is in incognito mode or has multi-profiles, install the image 1709// view to decorate the window at the upper right. Use the same base y 1710// coordinate as the tab strip. 1711- (void)installAvatar { 1712 // Install the image into the badge view. Hide it for now; positioning and 1713 // sizing will be done by the layout code. The AvatarButton will choose which 1714 // image to display based on the browser. 1715 avatarButtonController_.reset( 1716 [[AvatarButtonController alloc] initWithBrowser:browser_.get()]); 1717 1718 NSView* view = [avatarButtonController_ view]; 1719 [view setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin]; 1720 [view setHidden:![self shouldShowAvatar]]; 1721 1722 // Install the view. 1723 [[[[self window] contentView] superview] addSubview:view]; 1724} 1725 1726// Called when we get a three-finger swipe. 1727- (void)swipeWithEvent:(NSEvent*)event { 1728 CGFloat deltaX = [event deltaX]; 1729 CGFloat deltaY = [event deltaY]; 1730 1731 // Map forwards and backwards to history; left is positive, right is negative. 1732 unsigned int command = 0; 1733 if (deltaX > 0.5) { 1734 command = IDC_BACK; 1735 } else if (deltaX < -0.5) { 1736 command = IDC_FORWARD; 1737 } else if (deltaY > 0.5) { 1738 // TODO(pinkerton): figure out page-up, http://crbug.com/16305 1739 } else if (deltaY < -0.5) { 1740 // TODO(pinkerton): figure out page-down, http://crbug.com/16305 1741 chrome::ExecuteCommand(browser_.get(), IDC_TABPOSE); 1742 } 1743 1744 // Ensure the command is valid first (ExecuteCommand() won't do that) and 1745 // then make it so. 1746 if (chrome::IsCommandEnabled(browser_.get(), command)) { 1747 chrome::ExecuteCommandWithDisposition( 1748 browser_.get(), 1749 command, 1750 ui::WindowOpenDispositionFromNSEvent(event)); 1751 } 1752} 1753 1754// Called repeatedly during a pinch gesture, with incremental change values. 1755- (void)magnifyWithEvent:(NSEvent*)event { 1756 // The deltaZ difference necessary to trigger a zoom action. Derived from 1757 // experimentation to find a value that feels reasonable. 1758 const float kZoomStepValue = 0.6; 1759 1760 // Find the (absolute) thresholds on either side of the current zoom factor, 1761 // then convert those to actual numbers to trigger a zoom in or out. 1762 // This logic deliberately makes the range around the starting zoom value for 1763 // the gesture twice as large as the other ranges (i.e., the notches are at 1764 // ..., -3*step, -2*step, -step, step, 2*step, 3*step, ... but not at 0) 1765 // so that it's easier to get back to your starting point than it is to 1766 // overshoot. 1767 float nextStep = (abs(currentZoomStepDelta_) + 1) * kZoomStepValue; 1768 float backStep = abs(currentZoomStepDelta_) * kZoomStepValue; 1769 float zoomInThreshold = (currentZoomStepDelta_ >= 0) ? nextStep : -backStep; 1770 float zoomOutThreshold = (currentZoomStepDelta_ <= 0) ? -nextStep : backStep; 1771 1772 unsigned int command = 0; 1773 totalMagnifyGestureAmount_ += [event magnification]; 1774 if (totalMagnifyGestureAmount_ > zoomInThreshold) { 1775 command = IDC_ZOOM_PLUS; 1776 } else if (totalMagnifyGestureAmount_ < zoomOutThreshold) { 1777 command = IDC_ZOOM_MINUS; 1778 } 1779 1780 if (command && chrome::IsCommandEnabled(browser_.get(), command)) { 1781 currentZoomStepDelta_ += (command == IDC_ZOOM_PLUS) ? 1 : -1; 1782 chrome::ExecuteCommandWithDisposition( 1783 browser_.get(), 1784 command, 1785 ui::WindowOpenDispositionFromNSEvent(event)); 1786 } 1787} 1788 1789// Delegate method called when window is resized. 1790- (void)windowDidResize:(NSNotification*)notification { 1791 [self saveWindowPositionIfNeeded]; 1792 1793 // Resize (and possibly move) the status bubble. Note that we may get called 1794 // when the status bubble does not exist. 1795 if (statusBubble_) { 1796 statusBubble_->UpdateSizeAndPosition(); 1797 } 1798 1799 // Let the selected RenderWidgetHostView know, so that it can tell plugins. 1800 if (WebContents* contents = 1801 browser_->tab_strip_model()->GetActiveWebContents()) { 1802 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 1803 rwhv->WindowFrameChanged(); 1804 } 1805 1806 // The FindBar needs to know its own position to properly detect overlaps 1807 // with find results. The position changes whenever the window is resized, 1808 // and |layoutSubviews| computes the FindBar's position. 1809 // TODO: calling |layoutSubviews| here is a waste, find a better way to 1810 // do this. 1811 if ([findBarCocoaController_ isFindBarVisible]) 1812 [self layoutSubviews]; 1813} 1814 1815// Handle the openLearnMoreAboutCrashLink: action from SadTabController when 1816// "Learn more" link in "Aw snap" page (i.e. crash page or sad tab) is 1817// clicked. Decoupling the action from its target makes unit testing possible. 1818- (void)openLearnMoreAboutCrashLink:(id)sender { 1819 if ([sender isKindOfClass:[SadTabController class]]) { 1820 SadTabController* sad_tab = static_cast<SadTabController*>(sender); 1821 WebContents* web_contents = [sad_tab webContents]; 1822 if (web_contents) { 1823 OpenURLParams params( 1824 GURL(chrome::kCrashReasonURL), Referrer(), CURRENT_TAB, 1825 content::PAGE_TRANSITION_LINK, false); 1826 web_contents->OpenURL(params); 1827 } 1828 } 1829} 1830 1831// Delegate method called when window did move. (See below for why we don't use 1832// |-windowWillMove:|, which is called less frequently than |-windowDidMove| 1833// instead.) 1834- (void)windowDidMove:(NSNotification*)notification { 1835 [self saveWindowPositionIfNeeded]; 1836 1837 NSWindow* window = [self window]; 1838 NSRect windowFrame = [window frame]; 1839 NSRect workarea = [[window screen] visibleFrame]; 1840 1841 // We reset the window growth state whenever the window is moved out of the 1842 // work area or away (up or down) from the bottom or top of the work area. 1843 // Unfortunately, Cocoa sends |-windowWillMove:| too frequently (including 1844 // when clicking on the title bar to activate), and of course 1845 // |-windowWillMove| is called too early for us to apply our heuristic. (The 1846 // heuristic we use for detecting window movement is that if |windowTopGrowth_ 1847 // > 0|, then we should be at the bottom of the work area -- if we're not, 1848 // we've moved. Similarly for the other side.) 1849 if (!NSContainsRect(workarea, windowFrame) || 1850 (windowTopGrowth_ > 0 && NSMinY(windowFrame) != NSMinY(workarea)) || 1851 (windowBottomGrowth_ > 0 && NSMaxY(windowFrame) != NSMaxY(workarea))) 1852 [self resetWindowGrowthState]; 1853 1854 // Let the selected RenderWidgetHostView know, so that it can tell plugins. 1855 if (WebContents* contents = 1856 browser_->tab_strip_model()->GetActiveWebContents()) { 1857 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 1858 rwhv->WindowFrameChanged(); 1859 } 1860} 1861 1862// Delegate method called when window will be resized; not called for 1863// |-setFrame:display:|. 1864- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize { 1865 [self resetWindowGrowthState]; 1866 return frameSize; 1867} 1868 1869// Delegate method: see |NSWindowDelegate| protocol. 1870- (id)windowWillReturnFieldEditor:(NSWindow*)sender toObject:(id)obj { 1871 // Ask the toolbar controller if it wants to return a custom field editor 1872 // for the specific object. 1873 return [toolbarController_ customFieldEditorForObject:obj]; 1874} 1875 1876// (Needed for |BookmarkBarControllerDelegate| protocol.) 1877- (void)bookmarkBar:(BookmarkBarController*)controller 1878 didChangeFromState:(BookmarkBar::State)oldState 1879 toState:(BookmarkBar::State)newState { 1880 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]]; 1881 [self adjustToolbarAndBookmarkBarForCompression: 1882 [controller getDesiredToolbarHeightCompression]]; 1883} 1884 1885// (Needed for |BookmarkBarControllerDelegate| protocol.) 1886- (void)bookmarkBar:(BookmarkBarController*)controller 1887willAnimateFromState:(BookmarkBar::State)oldState 1888 toState:(BookmarkBar::State)newState { 1889 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]]; 1890 [self adjustToolbarAndBookmarkBarForCompression: 1891 [controller getDesiredToolbarHeightCompression]]; 1892} 1893 1894// (Private/TestingAPI) 1895- (void)resetWindowGrowthState { 1896 windowTopGrowth_ = 0; 1897 windowBottomGrowth_ = 0; 1898 isShrinkingFromZoomed_ = NO; 1899} 1900 1901- (NSSize)overflowFrom:(NSRect)source 1902 to:(NSRect)target { 1903 // If |source|'s boundary is outside of |target|'s, set its distance 1904 // to |x|. Note that |source| can overflow to both side, but we 1905 // have nothing to do for such case. 1906 CGFloat x = 0; 1907 if (NSMaxX(target) < NSMaxX(source)) // |source| overflows to right 1908 x = NSMaxX(source) - NSMaxX(target); 1909 else if (NSMinX(source) < NSMinX(target)) // |source| overflows to left 1910 x = NSMinX(source) - NSMinX(target); 1911 1912 // Same as |x| above. 1913 CGFloat y = 0; 1914 if (NSMaxY(target) < NSMaxY(source)) 1915 y = NSMaxY(source) - NSMaxY(target); 1916 else if (NSMinY(source) < NSMinY(target)) 1917 y = NSMinY(source) - NSMinY(target); 1918 1919 return NSMakeSize(x, y); 1920} 1921 1922// (Private/TestingAPI) 1923- (FullscreenExitBubbleController*)fullscreenExitBubbleController { 1924 return fullscreenExitBubbleController_.get(); 1925} 1926 1927- (NSRect)omniboxPopupAnchorRect { 1928 // Start with toolbar rect. 1929 NSView* toolbarView = [toolbarController_ view]; 1930 NSRect anchorRect = [toolbarView frame]; 1931 1932 // Adjust to account for height and possible bookmark bar. Compress by 1 1933 // to account for the separator. 1934 anchorRect.origin.y = 1935 NSMaxY(anchorRect) - [toolbarController_ desiredHeightForCompression:1]; 1936 1937 // Shift to window base coordinates. 1938 return [[toolbarView superview] convertRect:anchorRect toView:nil]; 1939} 1940 1941- (void)layoutInfoBars { 1942 [self layoutSubviews]; 1943} 1944 1945- (void)sheetDidEnd:(NSWindow*)sheet 1946 returnCode:(NSInteger)code 1947 context:(void*)context { 1948 [sheet orderOut:self]; 1949} 1950 1951- (void)onFindBarVisibilityChanged { 1952 [self updateAllowOverlappingViews:[self inPresentationMode]]; 1953} 1954 1955- (void)onOverlappedViewShown { 1956 ++overlappedViewCount_; 1957 [self updateAllowOverlappingViews:[self inPresentationMode]]; 1958} 1959 1960- (void)onOverlappedViewHidden { 1961 --overlappedViewCount_; 1962 [self updateAllowOverlappingViews:[self inPresentationMode]]; 1963} 1964 1965@end // @implementation BrowserWindowController 1966 1967 1968@implementation BrowserWindowController(Fullscreen) 1969 1970- (void)handleLionToggleFullscreen { 1971 DCHECK(base::mac::IsOSLionOrLater()); 1972 chrome::ExecuteCommand(browser_.get(), IDC_FULLSCREEN); 1973} 1974 1975// On Lion, this method is called by either the Lion fullscreen button or the 1976// "Enter Full Screen" menu item. On Snow Leopard, this function is never 1977// called by the UI directly, but it provides the implementation for 1978// |-setPresentationMode:|. 1979- (void)setFullscreen:(BOOL)fullscreen { 1980 if (fullscreen == [self isFullscreen]) 1981 return; 1982 1983 if (!chrome::IsCommandEnabled(browser_.get(), IDC_FULLSCREEN)) 1984 return; 1985 1986 if (chrome::mac::SupportsSystemFullscreen() && !fullscreenWindow_) { 1987 enteredPresentationModeFromFullscreen_ = YES; 1988 if ([[self window] isKindOfClass:[FramedBrowserWindow class]]) 1989 [static_cast<FramedBrowserWindow*>([self window]) toggleSystemFullScreen]; 1990 } else { 1991 if (fullscreen) 1992 [self enterFullscreenForSnowLeopard]; 1993 else 1994 [self exitFullscreenForSnowLeopard]; 1995 } 1996} 1997 1998- (void)enterFullscreen { 1999 [self setFullscreen:YES]; 2000} 2001 2002- (void)exitFullscreen { 2003 [self setFullscreen:NO]; 2004} 2005 2006- (void)updateFullscreenExitBubbleURL:(const GURL&)url 2007 bubbleType:(FullscreenExitBubbleType)bubbleType { 2008 fullscreenUrl_ = url; 2009 fullscreenBubbleType_ = bubbleType; 2010 [self layoutSubviews]; 2011 [self showFullscreenExitBubbleIfNecessary]; 2012} 2013 2014- (BOOL)isFullscreen { 2015 return (fullscreenWindow_.get() != nil) || 2016 ([[self window] styleMask] & NSFullScreenWindowMask) || 2017 enteringFullscreen_; 2018} 2019 2020// On Lion, this function is called by either the presentation mode toggle 2021// button or the "Enter Presentation Mode" menu item. In the latter case, this 2022// function also triggers the Lion machinery to enter fullscreen mode as well as 2023// set presentation mode. On Snow Leopard, this function is called by the 2024// "Enter Presentation Mode" menu item, and triggering presentation mode always 2025// moves the user into fullscreen mode. 2026- (void)setPresentationMode:(BOOL)presentationMode 2027 url:(const GURL&)url 2028 bubbleType:(FullscreenExitBubbleType)bubbleType { 2029 fullscreenUrl_ = url; 2030 fullscreenBubbleType_ = bubbleType; 2031 2032 // Presentation mode on systems without fullscreen support maps directly to 2033 // fullscreen mode. 2034 if (!chrome::mac::SupportsSystemFullscreen()) { 2035 [self setFullscreen:presentationMode]; 2036 return; 2037 } 2038 2039 if (presentationMode) { 2040 BOOL fullscreen = [self isFullscreen]; 2041 enteredPresentationModeFromFullscreen_ = fullscreen; 2042 enteringPresentationMode_ = YES; 2043 2044 if (fullscreen) { 2045 // If already in fullscreen mode, just toggle the presentation mode 2046 // setting. Go through an elaborate dance to force the overlay to show, 2047 // then animate out once the mouse moves away. This helps draw attention 2048 // to the fact that the UI is in an overlay. Focus the tab contents 2049 // because the omnibox is the most likely source of bar visibility locks, 2050 // and taking focus away from the omnibox releases its lock. 2051 [self lockBarVisibilityForOwner:self withAnimation:NO delay:NO]; 2052 [self focusTabContents]; 2053 [self setPresentationModeInternal:YES forceDropdown:YES]; 2054 [self releaseBarVisibilityForOwner:self withAnimation:YES delay:YES]; 2055 // Since -windowDidEnterFullScreen: won't be called in the 2056 // fullscreen --> presentation mode case, manually show the exit bubble 2057 // and notify the change happened with WindowFullscreenStateChanged(). 2058 [self showFullscreenExitBubbleIfNecessary]; 2059 browser_->WindowFullscreenStateChanged(); 2060 } else { 2061 // If not in fullscreen mode, trigger the Lion fullscreen mode machinery. 2062 // Presentation mode will automatically be enabled in 2063 // |-windowWillEnterFullScreen:|. 2064 NSWindow* window = [self window]; 2065 if ([window isKindOfClass:[FramedBrowserWindow class]]) 2066 [static_cast<FramedBrowserWindow*>(window) toggleSystemFullScreen]; 2067 } 2068 } else { 2069 // Exiting presentation mode does not exit system fullscreen; it merely 2070 // switches from presentation mode to normal fullscreen. 2071 [self setPresentationModeInternal:NO forceDropdown:NO]; 2072 2073 // Since -windowDidExitFullScreen: won't be called in the 2074 // presentation mode --> normal fullscreen case, manually show the exit 2075 // bubble and notify the change happened with 2076 // WindowFullscreenStateChanged(). 2077 [self showFullscreenExitBubbleIfNecessary]; 2078 browser_->WindowFullscreenStateChanged(); 2079 } 2080} 2081 2082- (void)enterPresentationModeForURL:(const GURL&)url 2083 bubbleType:(FullscreenExitBubbleType)bubbleType { 2084 [self setPresentationMode:YES url:url bubbleType:bubbleType]; 2085} 2086 2087- (void)exitPresentationMode { 2088 // url: and bubbleType: are ignored when leaving presentation mode. 2089 [self setPresentationMode:NO url:GURL() bubbleType:FEB_TYPE_NONE]; 2090} 2091 2092- (void)enterFullscreenForURL:(const GURL&)url 2093 bubbleType:(FullscreenExitBubbleType)bubbleType { 2094 // This method may only be called in simplified fullscreen mode. 2095 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 2096 DCHECK(command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)); 2097 2098 [self enterFullscreenForSnowLeopard]; 2099 [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType]; 2100} 2101 2102- (BOOL)inPresentationMode { 2103 return presentationModeController_.get() && 2104 [presentationModeController_ inPresentationMode]; 2105} 2106 2107- (void)resizeFullscreenWindow { 2108 DCHECK([self isFullscreen]); 2109 if (![self isFullscreen]) 2110 return; 2111 2112 NSWindow* window = [self window]; 2113 [window setFrame:[[window screen] frame] display:YES]; 2114 [self layoutSubviews]; 2115} 2116 2117- (CGFloat)floatingBarShownFraction { 2118 return floatingBarShownFraction_; 2119} 2120 2121- (void)setFloatingBarShownFraction:(CGFloat)fraction { 2122 floatingBarShownFraction_ = fraction; 2123 [self layoutSubviews]; 2124} 2125 2126- (BOOL)isBarVisibilityLockedForOwner:(id)owner { 2127 DCHECK(owner); 2128 DCHECK(barVisibilityLocks_); 2129 return [barVisibilityLocks_ containsObject:owner]; 2130} 2131 2132- (void)lockBarVisibilityForOwner:(id)owner 2133 withAnimation:(BOOL)animate 2134 delay:(BOOL)delay { 2135 if (![self isBarVisibilityLockedForOwner:owner]) { 2136 [barVisibilityLocks_ addObject:owner]; 2137 2138 // If enabled, show the overlay if necessary (and if in presentation mode). 2139 if (barVisibilityUpdatesEnabled_) { 2140 [presentationModeController_ ensureOverlayShownWithAnimation:animate 2141 delay:delay]; 2142 } 2143 } 2144} 2145 2146- (void)releaseBarVisibilityForOwner:(id)owner 2147 withAnimation:(BOOL)animate 2148 delay:(BOOL)delay { 2149 if ([self isBarVisibilityLockedForOwner:owner]) { 2150 [barVisibilityLocks_ removeObject:owner]; 2151 2152 // If enabled, hide the overlay if necessary (and if in presentation mode). 2153 if (barVisibilityUpdatesEnabled_ && 2154 ![barVisibilityLocks_ count]) { 2155 [presentationModeController_ ensureOverlayHiddenWithAnimation:animate 2156 delay:delay]; 2157 } 2158 } 2159} 2160 2161- (BOOL)floatingBarHasFocus { 2162 NSResponder* focused = [[self window] firstResponder]; 2163 return [focused isKindOfClass:[AutocompleteTextFieldEditor class]]; 2164} 2165 2166- (void)tabposeWillClose:(NSNotification*)notif { 2167 // Re-show the container after Tabpose closes. 2168 [[infoBarContainerController_ view] setHidden:NO]; 2169} 2170 2171- (void)openTabpose { 2172 NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags]; 2173 BOOL slomo = (modifierFlags & NSShiftKeyMask) != 0; 2174 2175 // Cover info bars, inspector window, and detached bookmark bar on NTP. 2176 // Do not cover download shelf. 2177 NSRect activeArea = [[self tabContentArea] frame]; 2178 // Take out the anti-spoof height so that Tabpose doesn't draw on top of the 2179 // browser chrome. 2180 activeArea.size.height += 2181 NSHeight([[infoBarContainerController_ view] frame]) - 2182 [infoBarContainerController_ overlappingTipHeight]; 2183 if ([self isBookmarkBarVisible] && [self placeBookmarkBarBelowInfoBar]) { 2184 NSView* bookmarkBarView = [bookmarkBarController_ view]; 2185 activeArea.size.height += NSHeight([bookmarkBarView frame]); 2186 } 2187 2188 // Hide the infobar container so that the anti-spoof bulge doesn't show when 2189 // Tabpose is open. 2190 [[infoBarContainerController_ view] setHidden:YES]; 2191 2192 TabposeWindow* window = 2193 [TabposeWindow openTabposeFor:[self window] 2194 rect:activeArea 2195 slomo:slomo 2196 tabStripModel:browser_->tab_strip_model()]; 2197 2198 // When the Tabpose window closes, the infobar container needs to be made 2199 // visible again. 2200 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 2201 [center addObserver:self 2202 selector:@selector(tabposeWillClose:) 2203 name:NSWindowWillCloseNotification 2204 object:window]; 2205} 2206 2207@end // @implementation BrowserWindowController(Fullscreen) 2208 2209 2210@implementation BrowserWindowController(WindowType) 2211 2212- (BOOL)supportsWindowFeature:(int)feature { 2213 return browser_->SupportsWindowFeature( 2214 static_cast<Browser::WindowFeature>(feature)); 2215} 2216 2217- (BOOL)hasTitleBar { 2218 return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR]; 2219} 2220 2221- (BOOL)hasToolbar { 2222 return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR]; 2223} 2224 2225- (BOOL)hasLocationBar { 2226 return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR]; 2227} 2228 2229- (BOOL)supportsBookmarkBar { 2230 return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR]; 2231} 2232 2233- (BOOL)isTabbedWindow { 2234 return browser_->is_type_tabbed(); 2235} 2236 2237@end // @implementation BrowserWindowController(WindowType) 2238