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