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