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