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.
5 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
10 #include "base/command_line.h"
11 #include "base/mac/bundle_locations.h"
12 #import "base/mac/foundation_util.h"
13 #include "base/mac/mac_util.h"
14 #import "base/mac/sdk_forward_declarations.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/app/chrome_command_ids.h" // IDC_*
18 #import "chrome/browser/app_controller_mac.h"
19 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
20 #include "chrome/browser/bookmarks/chrome_bookmark_client.h"
21 #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/devtools/devtools_window.h"
24 #include "chrome/browser/extensions/extension_commands_global_registry.h"
25 #include "chrome/browser/fullscreen.h"
26 #include "chrome/browser/profiles/avatar_menu.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/profiles/profile_info_cache.h"
29 #include "chrome/browser/profiles/profile_manager.h"
30 #include "chrome/browser/profiles/profiles_state.h"
31 #include "chrome/browser/themes/theme_service.h"
32 #include "chrome/browser/themes/theme_service_factory.h"
33 #include "chrome/browser/translate/chrome_translate_client.h"
34 #include "chrome/browser/ui/bookmarks/bookmark_editor.h"
35 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
36 #include "chrome/browser/ui/browser.h"
37 #include "chrome/browser/ui/browser_command_controller.h"
38 #include "chrome/browser/ui/browser_commands.h"
39 #include "chrome/browser/ui/browser_instant_controller.h"
40 #include "chrome/browser/ui/browser_list.h"
41 #include "chrome/browser/ui/browser_window_state.h"
42 #import "chrome/browser/ui/cocoa/background_gradient_view.h"
43 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
44 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_controller.h"
45 #import "chrome/browser/ui/cocoa/browser_window_cocoa.h"
46 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
47 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
48 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
49 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
50 #import "chrome/browser/ui/cocoa/download/download_shelf_controller.h"
51 #include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h"
52 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
53 #import "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h"
54 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
55 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
56 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
57 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
58 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
59 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h"
60 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
61 #import "chrome/browser/ui/cocoa/profiles/avatar_base_controller.h"
62 #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h"
63 #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h"
64 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
65 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
66 #import "chrome/browser/ui/cocoa/tab_contents/sad_tab_controller.h"
67 #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
68 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
69 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
70 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
71 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
72 #import "chrome/browser/ui/cocoa/translate/translate_bubble_controller.h"
73 #include "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
74 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
75 #include "chrome/browser/ui/location_bar/location_bar.h"
76 #include "chrome/browser/ui/tabs/tab_strip_model.h"
77 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
78 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
79 #include "chrome/browser/ui/translate/translate_bubble_model_impl.h"
80 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
81 #include "chrome/browser/ui/window_sizer/window_sizer.h"
82 #include "chrome/common/chrome_switches.h"
83 #include "chrome/common/extensions/command.h"
84 #include "chrome/common/url_constants.h"
85 #include "chrome/grit/generated_resources.h"
86 #include "chrome/grit/locale_settings.h"
87 #include "components/bookmarks/browser/bookmark_model.h"
88 #include "components/signin/core/common/profile_management_switches.h"
89 #include "components/translate/core/browser/translate_manager.h"
90 #include "components/translate/core/browser/translate_ui_delegate.h"
91 #include "components/web_modal/popup_manager.h"
92 #include "components/web_modal/web_contents_modal_dialog_manager.h"
93 #include "content/public/browser/render_view_host.h"
94 #include "content/public/browser/render_widget_host_view.h"
95 #include "content/public/browser/web_contents.h"
96 #import "ui/base/cocoa/cocoa_base_utils.h"
97 #import "ui/base/cocoa/nsview_additions.h"
98 #include "ui/base/l10n/l10n_util.h"
99 #include "ui/base/l10n/l10n_util_mac.h"
100 #include "ui/gfx/mac/scoped_ns_disable_screen_updates.h"
102 using bookmarks::BookmarkModel;
103 using bookmarks::BookmarkNode;
104 using l10n_util::GetStringUTF16;
105 using l10n_util::GetNSStringWithFixup;
106 using l10n_util::GetNSStringFWithFixup;
108 // ORGANIZATION: This is a big file. It is (in principle) organized as follows
110 // 1. Interfaces. Very short, one-time-use classes may include an implementation
111 // immediately after their interface.
112 // 2. The general implementation section, ordered as follows:
113 // i. Public methods and overrides.
114 // ii. Overrides/implementations of undocumented methods.
115 // iii. Delegate methods for various protocols, formal and informal, to which
116 // |BrowserWindowController| conforms.
117 // 3. (temporary) Implementation sections for various categories.
119 // Private methods are defined and implemented separately in
120 // browser_window_controller_private.{h,mm}.
122 // Not all of the above guidelines are followed and more (re-)organization is
123 // needed. BUT PLEASE TRY TO KEEP THIS FILE ORGANIZED. I'd rather re-organize as
124 // little as possible, since doing so messes up the file's history.
126 // TODO(viettrungluu): [crbug.com/35543] on-going re-organization, splitting
127 // things into multiple files -- the plan is as follows:
128 // - in general, everything stays in browser_window_controller.h, but is split
129 // off into categories (see below)
130 // - core stuff stays in browser_window_controller.mm
131 // - ... overrides also stay (without going into a category, in particular)
132 // - private stuff which everyone needs goes into
133 // browser_window_controller_private.{h,mm}; if no one else needs them, they
134 // can go in individual files (see below)
135 // - area/task-specific stuff go in browser_window_controller_<area>.mm
136 // - ... in categories called "(<Area>)" or "(<PrivateArea>)"
138 // - first re-organize into categories
139 // - then split into files
141 // Notes on self-inflicted (not user-inflicted) window resizing and moving:
143 // When the bookmark bar goes from hidden to shown (on a non-NTP) page, or when
144 // the download shelf goes from hidden to shown, we grow the window downwards in
145 // order to maintain a constant content area size. When either goes from shown
146 // to hidden, we consequently shrink the window from the bottom, also to keep
147 // the content area size constant. To keep things simple, if the window is not
148 // entirely on-screen, we don't grow/shrink the window.
150 // The complications come in when there isn't enough room (on screen) below the
151 // window to accomodate the growth. In this case, we grow the window first
152 // downwards, and then upwards. So, when it comes to shrinking, we do the
153 // opposite: shrink from the top by the amount by which we grew at the top, and
154 // then from the bottom -- unless the user moved/resized/zoomed the window, in
155 // which case we "reset state" and just shrink from the bottom.
157 // A further complication arises due to the way in which "zoom" ("maximize")
158 // works on Mac OS X. Basically, for our purposes, a window is "zoomed" whenever
159 // it occupies the full available vertical space. (Note that the green zoom
160 // button does not track zoom/unzoomed state per se, but basically relies on
161 // this heuristic.) We don't, in general, want to shrink the window if the
162 // window is zoomed (scenario: window is zoomed, download shelf opens -- which
163 // doesn't cause window growth, download shelf closes -- shouldn't cause the
164 // window to become unzoomed!). However, if we grew the window
165 // (upwards/downwards) to become zoomed in the first place, we *should* shrink
166 // the window by the amounts by which we grew (scenario: window occupies *most*
167 // of vertical space, download shelf opens causing growth so that window
168 // occupies all of vertical space -- i.e., window is effectively zoomed,
169 // download shelf closes -- should return the window to its previous state).
171 // A major complication is caused by the way grows/shrinks are handled and
172 // animated. Basically, the BWC doesn't see the global picture, but it sees
173 // grows and shrinks in small increments (as dictated by the animation). Thus
174 // window growth/shrinkage (at the top/bottom) have to be tracked incrementally.
175 // Allowing shrinking from the zoomed state also requires tracking: We check on
176 // any shrink whether we're both zoomed and have previously grown -- if so, we
177 // set a flag, and constrain any resize by the allowed amounts. On further
178 // shrinks, we check the flag (since the size/position of the window will no
179 // longer indicate that the window is shrinking from an apparent zoomed state)
180 // and if it's set we continue to constrain the resize.
182 using content::OpenURLParams;
183 using content::Referrer;
184 using content::RenderWidgetHostView;
185 using content::WebContents;
187 @interface NSWindow (NSPrivateApis)
188 // Note: These functions are private, use -[NSObject respondsToSelector:]
189 // before calling them.
191 - (void)setBottomCornerRounded:(BOOL)rounded;
193 - (NSRect)_growBoxRect;
197 @implementation BrowserWindowController
199 + (BrowserWindowController*)browserWindowControllerForWindow:(NSWindow*)window {
201 id controller = [window windowController];
202 if ([controller isKindOfClass:[BrowserWindowController class]])
203 return (BrowserWindowController*)controller;
204 window = [window parentWindow];
209 + (BrowserWindowController*)browserWindowControllerForView:(NSView*)view {
210 NSWindow* window = [view window];
211 return [BrowserWindowController browserWindowControllerForWindow:window];
214 // Load the browser window nib and do any Cocoa-specific initialization.
215 // Takes ownership of |browser|. Note that the nib also sets this controller
216 // up as the window's delegate.
217 - (id)initWithBrowser:(Browser*)browser {
218 return [self initWithBrowser:browser takeOwnership:YES];
221 // Private(TestingAPI) init routine with testing options.
222 - (id)initWithBrowser:(Browser*)browser takeOwnership:(BOOL)ownIt {
223 bool hasTabStrip = browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP);
224 if ((self = [super initTabWindowControllerWithTabStrip:hasTabStrip])) {
227 browser_.reset(browser);
228 ownsBrowser_ = ownIt;
229 NSWindow* window = [self window];
230 // Make the content view for the window have a layer. This will make all
231 // sub-views have layers. This is necessary to ensure correct layer
232 // ordering of all child views and their layers.
233 [[window contentView] setWantsLayer:YES];
234 windowShim_.reset(new BrowserWindowCocoa(browser, self));
236 // Set different minimum sizes on tabbed windows vs non-tabbed, e.g. popups.
237 // This has to happen before -enforceMinWindowSize: is called further down.
238 NSSize minSize = [self isTabbedWindow] ?
239 NSMakeSize(400, 272) : NSMakeSize(100, 122);
240 [[self window] setMinSize:minSize];
242 // Create the bar visibility lock set; 10 is arbitrary, but should hopefully
243 // be big enough to hold all locks that'll ever be needed.
244 barVisibilityLocks_.reset([[NSMutableSet setWithCapacity:10] retain]);
246 // Set the window to not have rounded corners, which prevents the resize
247 // control from being inset slightly and looking ugly. Only bother to do
248 // this on Snow Leopard; on Lion and later all windows have rounded bottom
249 // corners, and this won't work anyway.
250 if (base::mac::IsOSSnowLeopard() &&
251 [window respondsToSelector:@selector(setBottomCornerRounded:)])
252 [window setBottomCornerRounded:NO];
254 // Lion will attempt to automagically save and restore the UI. This
255 // functionality appears to be leaky (or at least interacts badly with our
256 // architecture) and thus BrowserWindowController never gets released. This
257 // prevents the browser from being able to quit <http://crbug.com/79113>.
258 if ([window respondsToSelector:@selector(setRestorable:)])
259 [window setRestorable:NO];
261 // Get the windows to swish in on Lion.
262 if ([window respondsToSelector:@selector(setAnimationBehavior:)])
263 [window setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow];
265 // Get the most appropriate size for the window, then enforce the
266 // minimum width and height. The window shim will handle flipping
267 // the coordinates for us so we can use it to save some code.
268 // Note that this may leave a significant portion of the window
269 // offscreen, but there will always be enough window onscreen to
270 // drag the whole window back into view.
271 ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT;
272 gfx::Rect desiredContentRect;
273 chrome::GetSavedWindowBoundsAndShowState(browser_.get(),
276 gfx::Rect windowRect = desiredContentRect;
277 windowRect = [self enforceMinWindowSize:windowRect];
279 // When we are given x/y coordinates of 0 on a created popup window, assume
280 // none were given by the window.open() command.
281 if (browser_->is_type_popup() &&
282 windowRect.x() == 0 && windowRect.y() == 0) {
283 gfx::Size size = windowRect.size();
284 windowRect.set_origin(
285 WindowSizer::GetDefaultPopupOrigin(size,
286 browser_->host_desktop_type()));
289 // Size and position the window. Note that it is not yet onscreen. Popup
290 // windows may get resized later on in this function, once the actual size
291 // of the toolbar/tabstrip is known.
292 windowShim_->SetBounds(windowRect);
294 // Puts the incognito badge on the window frame, if necessary.
295 [self installAvatar];
297 // Create a sub-controller for the docked devTools and add its view to the
299 devToolsController_.reset([[DevToolsController alloc] init]);
300 [[devToolsController_ view] setFrame:[[self tabContentArea] bounds]];
301 [[self tabContentArea] addSubview:[devToolsController_ view]];
303 // Create the overlayable contents controller. This provides the switch
304 // view that TabStripController needs.
305 overlayableContentsController_.reset(
306 [[OverlayableContentsController alloc] initWithBrowser:browser]);
307 [[overlayableContentsController_ view]
308 setFrame:[[devToolsController_ view] bounds]];
309 [[devToolsController_ view]
310 addSubview:[overlayableContentsController_ view]];
312 // Create a controller for the tab strip, giving it the model object for
313 // this window's Browser and the tab strip view. The controller will handle
314 // registering for the appropriate tab notifications from the back-end and
315 // managing the creation of new tabs.
316 [self createTabStripController];
318 // Create a controller for the toolbar, giving it the toolbar model object
319 // and the toolbar view from the nib. The controller will handle
320 // registering for the appropriate command state changes from the back-end.
321 // Adds the toolbar to the content area.
322 toolbarController_.reset([[ToolbarController alloc]
323 initWithCommands:browser->command_controller()->command_updater()
324 profile:browser->profile()
326 resizeDelegate:self]);
327 [toolbarController_ setHasToolbar:[self hasToolbar]
328 hasLocationBar:[self hasLocationBar]];
330 // Create a sub-controller for the bookmark bar.
331 bookmarkBarController_.reset(
332 [[BookmarkBarController alloc]
333 initWithBrowser:browser_.get()
334 initialWidth:NSWidth([[[self window] contentView] frame])
336 resizeDelegate:self]);
337 [bookmarkBarController_ setBookmarkBarEnabled:[self supportsBookmarkBar]];
339 // Create the infobar container view, so we can pass it to the
340 // ToolbarController.
341 infoBarContainerController_.reset(
342 [[InfoBarContainerController alloc] initWithResizeDelegate:self]);
343 [self updateInfoBarTipVisibility];
345 // We don't want to try and show the bar before it gets placed in its parent
346 // view, so this step shoudn't be inside the bookmark bar controller's
348 windowShim_->BookmarkBarStateChanged(
349 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
351 // Allow bar visibility to be changed.
352 [self enableBarVisibilityUpdates];
354 // Set the window to participate in Lion Fullscreen mode. Setting this flag
355 // has no effect on Snow Leopard or earlier. Panels can share a fullscreen
356 // space with a tabbed window, but they can not be primary fullscreen
358 // This ensures the fullscreen button is appropriately positioned. It must
359 // be done before calling layoutSubviews because the new avatar button's
360 // position depends on the fullscreen button's position, as well as
361 // TabStripController's rightIndentForControls.
362 // The fullscreen button's position may depend on the old avatar button's
363 // width, but that does not require calling layoutSubviews first.
364 NSUInteger collectionBehavior = [window collectionBehavior];
365 collectionBehavior |=
366 browser_->type() == Browser::TYPE_TABBED ||
367 browser_->type() == Browser::TYPE_POPUP ?
368 NSWindowCollectionBehaviorFullScreenPrimary :
369 NSWindowCollectionBehaviorFullScreenAuxiliary;
370 [window setCollectionBehavior:collectionBehavior];
372 [self layoutSubviews];
374 // For a popup window, |desiredContentRect| contains the desired height of
375 // the content, not of the whole window. Now that all the views are laid
376 // out, measure the current content area size and grow if needed. The
377 // window has not been placed onscreen yet, so this extra resize will not
378 // cause visible jank.
379 if (browser_->is_type_popup()) {
380 CGFloat deltaH = desiredContentRect.height() -
381 NSHeight([[self tabContentArea] frame]);
382 // Do not shrink the window, as that may break minimum size invariants.
384 // Convert from tabContentArea coordinates to window coordinates.
385 NSSize convertedSize =
386 [[self tabContentArea] convertSize:NSMakeSize(0, deltaH)
388 NSRect frame = [[self window] frame];
389 frame.size.height += convertedSize.height;
390 frame.origin.y -= convertedSize.height;
391 [[self window] setFrame:frame display:NO];
395 // Create the bridge for the status bubble.
396 statusBubble_ = new StatusBubbleMac([self window], self);
398 // Register for application hide/unhide notifications.
399 [[NSNotificationCenter defaultCenter]
401 selector:@selector(applicationDidHide:)
402 name:NSApplicationDidHideNotification
404 [[NSNotificationCenter defaultCenter]
406 selector:@selector(applicationDidUnhide:)
407 name:NSApplicationDidUnhideNotification
410 // This must be done after the view is added to the window since it relies
411 // on the window bounds to determine whether to show buttons or not.
412 if ([self hasToolbar]) // Do not create the buttons in popups.
413 [toolbarController_ createBrowserActionButtons];
415 extension_keybinding_registry_.reset(
416 new ExtensionKeybindingRegistryCocoa(browser_->profile(),
418 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
421 // We are done initializing now.
428 browser_->tab_strip_model()->CloseAllTabs();
429 [downloadShelfController_ exiting];
431 // Explicitly release |presentationModeController_| here, as it may call back
432 // to this BWC in |-dealloc|. We are required to call |-exitPresentationMode|
433 // before releasing the controller.
434 [presentationModeController_ exitPresentationMode];
435 presentationModeController_.reset();
437 // Under certain testing configurations we may not actually own the browser.
438 if (ownsBrowser_ == NO)
439 ignore_result(browser_.release());
441 [[NSNotificationCenter defaultCenter] removeObserver:self];
446 - (gfx::Rect)enforceMinWindowSize:(gfx::Rect)bounds {
447 gfx::Rect checkedBounds = bounds;
449 NSSize minSize = [[self window] minSize];
450 if (bounds.width() < minSize.width)
451 checkedBounds.set_width(minSize.width);
452 if (bounds.height() < minSize.height)
453 checkedBounds.set_height(minSize.height);
455 return checkedBounds;
458 - (BrowserWindow*)browserWindow {
459 return windowShim_.get();
462 - (ToolbarController*)toolbarController {
463 return toolbarController_.get();
466 - (TabStripController*)tabStripController {
467 return tabStripController_.get();
470 - (FindBarCocoaController*)findBarCocoaController {
471 return findBarCocoaController_.get();
474 - (InfoBarContainerController*)infoBarContainerController {
475 return infoBarContainerController_.get();
478 - (StatusBubbleMac*)statusBubble {
479 return statusBubble_;
482 - (LocationBarViewMac*)locationBarBridge {
483 return [toolbarController_ locationBarBridge];
486 - (NSView*)floatingBarBackingView {
487 return floatingBarBackingView_;
490 - (OverlayableContentsController*)overlayableContentsController {
491 return overlayableContentsController_;
494 - (Profile*)profile {
495 return browser_->profile();
498 - (AvatarBaseController*)avatarButtonController {
499 return avatarButtonController_.get();
502 - (void)destroyBrowser {
503 [NSApp removeWindowsItem:[self window]];
505 // We need the window to go away now.
506 // We can't actually use |-autorelease| here because there's an embedded
507 // run loop in the |-performClose:| which contains its own autorelease pool.
508 // Instead call it after a zero-length delay, which gets us back to the main
510 [self performSelector:@selector(autorelease)
515 // Called when the window meets the criteria to be closed (ie,
516 // |-windowShouldClose:| returns YES). We must be careful to preserve the
517 // semantics of BrowserWindow::Close() and not call the Browser's dtor directly
519 - (void)windowWillClose:(NSNotification*)notification {
520 DCHECK_EQ([notification object], [self window]);
521 DCHECK(browser_->tab_strip_model()->empty());
522 [savedRegularWindow_ close];
523 // We delete statusBubble here because we need to kill off the dependency
524 // that its window has on our window before our window goes away.
525 delete statusBubble_;
526 statusBubble_ = NULL;
527 // We can't actually use |-autorelease| here because there's an embedded
528 // run loop in the |-performClose:| which contains its own autorelease pool.
529 // Instead call it after a zero-length delay, which gets us back to the main
531 [self performSelector:@selector(autorelease)
536 - (void)updateDevToolsForContents:(WebContents*)contents {
537 BOOL layout_changed =
538 [devToolsController_ updateDevToolsForWebContents:contents
539 withProfile:browser_->profile()];
540 if (layout_changed && [findBarCocoaController_ isFindBarVisible])
541 [self layoutSubviews];
544 // Called when the user wants to close a window or from the shutdown process.
545 // The Browser object is in control of whether or not we're allowed to close. It
546 // may defer closing due to several states, such as onUnload handlers needing to
547 // be fired. If closing is deferred, the Browser will handle the processing
548 // required to get us to the closing state and (by watching for all the tabs
549 // going away) will again call to close the window when it's finally ready.
550 - (BOOL)windowShouldClose:(id)sender {
551 // Disable updates while closing all tabs to avoid flickering.
552 gfx::ScopedNSDisableScreenUpdates disabler;
553 // Give beforeunload handlers the chance to cancel the close before we hide
555 if (!browser_->ShouldCloseWindow())
558 // saveWindowPositionIfNeeded: only works if we are the last active
559 // window, but orderOut: ends up activating another window, so we
560 // have to save the window position before we call orderOut:.
561 [self saveWindowPositionIfNeeded];
563 bool fast_tab_closing_enabled =
564 base::CommandLine::ForCurrentProcess()->HasSwitch(
565 switches::kEnableFastUnload);
567 if (!browser_->tab_strip_model()->empty()) {
568 // Tab strip isn't empty. Hide the frame (so it appears to have closed
569 // immediately) and close all the tabs, allowing the renderers to shut
570 // down. When the tab strip is empty we'll be called back again.
571 [[self window] orderOut:self];
572 browser_->OnWindowClosing();
573 if (fast_tab_closing_enabled)
574 browser_->tab_strip_model()->CloseAllTabs();
576 } else if (fast_tab_closing_enabled &&
577 !browser_->HasCompletedUnloadProcessing()) {
578 // The browser needs to finish running unload handlers.
579 // Hide the window (so it appears to have closed immediately), and
580 // the browser will call us back again when it is ready to close.
581 [[self window] orderOut:self];
585 // the tab strip is empty, it's ok to close the window
589 // Called right after our window became the main window.
590 - (void)windowDidBecomeMain:(NSNotification*)notification {
591 if (chrome::GetLastActiveBrowser() != browser_) {
592 BrowserList::SetLastActive(browser_.get());
593 [self saveWindowPositionIfNeeded];
596 [[[self window] contentView] cr_recursivelyInvokeBlock:^(id view) {
597 if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)])
598 [view windowDidChangeActive];
601 extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile())
602 ->set_registry_for_active_window(extension_keybinding_registry_.get());
605 - (void)windowDidResignMain:(NSNotification*)notification {
606 [[[self window] contentView] cr_recursivelyInvokeBlock:^(id view) {
607 if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)])
608 [view windowDidChangeActive];
611 extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile())
612 ->set_registry_for_active_window(nullptr);
615 // Called when we are activated (when we gain focus).
616 - (void)windowDidBecomeKey:(NSNotification*)notification {
617 // We need to activate the controls (in the "WebView"). To do this, get the
618 // selected WebContents's RenderWidgetHostView and tell it to activate.
619 if (WebContents* contents =
620 browser_->tab_strip_model()->GetActiveWebContents()) {
622 WebContents* devtools = DevToolsWindow::GetInTabWebContents(
625 RenderWidgetHostView* devtoolsView = devtools->GetRenderWidgetHostView();
626 if (devtoolsView && devtoolsView->HasFocus()) {
627 devtoolsView->SetActive(true);
632 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
633 rwhv->SetActive(true);
637 // Called when we are deactivated (when we lose focus).
638 - (void)windowDidResignKey:(NSNotification*)notification {
639 // If our app is still active and we're still the key window, ignore this
640 // message, since it just means that a menu extra (on the "system status bar")
641 // was activated; we'll get another |-windowDidResignKey| if we ever really
642 // lose key window status.
643 if ([NSApp isActive] && ([NSApp keyWindow] == [self window]))
646 // We need to deactivate the controls (in the "WebView"). To do this, get the
647 // selected WebContents's RenderWidgetHostView and tell it to deactivate.
648 if (WebContents* contents =
649 browser_->tab_strip_model()->GetActiveWebContents()) {
651 WebContents* devtools = DevToolsWindow::GetInTabWebContents(
654 RenderWidgetHostView* devtoolsView = devtools->GetRenderWidgetHostView();
655 if (devtoolsView && devtoolsView->HasFocus()) {
656 devtoolsView->SetActive(false);
661 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
662 rwhv->SetActive(false);
666 // Called when we have been minimized.
667 - (void)windowDidMiniaturize:(NSNotification *)notification {
668 [self saveWindowPositionIfNeeded];
670 // Let the selected RenderWidgetHostView know, so that it can tell plugins.
671 if (WebContents* contents =
672 browser_->tab_strip_model()->GetActiveWebContents()) {
673 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
674 rwhv->SetWindowVisibility(false);
678 // Called when we have been unminimized.
679 - (void)windowDidDeminiaturize:(NSNotification *)notification {
680 // Let the selected RenderWidgetHostView know, so that it can tell plugins.
681 if (WebContents* contents =
682 browser_->tab_strip_model()->GetActiveWebContents()) {
683 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
684 rwhv->SetWindowVisibility(true);
688 // Called when the application has been hidden.
689 - (void)applicationDidHide:(NSNotification *)notification {
690 // Let the selected RenderWidgetHostView know, so that it can tell plugins
691 // (unless we are minimized, in which case nothing has really changed).
692 if (![[self window] isMiniaturized]) {
693 if (WebContents* contents =
694 browser_->tab_strip_model()->GetActiveWebContents()) {
695 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
696 rwhv->SetWindowVisibility(false);
701 // Called when the application has been unhidden.
702 - (void)applicationDidUnhide:(NSNotification *)notification {
703 // Let the selected RenderWidgetHostView know, so that it can tell plugins
704 // (unless we are minimized, in which case nothing has really changed).
705 if (![[self window] isMiniaturized]) {
706 if (WebContents* contents =
707 browser_->tab_strip_model()->GetActiveWebContents()) {
708 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
709 rwhv->SetWindowVisibility(true);
714 // Called when the user clicks the zoom button (or selects it from the Window
715 // menu) to determine the "standard size" of the window, based on the content
716 // and other factors. If the current size/location differs nontrivally from the
717 // standard size, Cocoa resizes the window to the standard size, and saves the
718 // current size as the "user size". If the current size/location is the same (up
719 // to a fudge factor) as the standard size, Cocoa resizes the window to the
720 // saved user size. (It is possible for the two to coincide.) In this way, the
721 // zoom button acts as a toggle. We determine the standard size based on the
722 // content, but enforce a minimum width (calculated using the dimensions of the
723 // screen) to ensure websites with small intrinsic width (such as google.com)
724 // don't end up with a wee window. Moreover, we always declare the standard
725 // width to be at least as big as the current width, i.e., we never want zooming
726 // to the standard width to shrink the window. This is consistent with other
727 // browsers' behaviour, and is desirable in multi-tab situations. Note, however,
728 // that the "toggle" behaviour means that the window can still be "unzoomed" to
730 // Note: this method is also called from -isZoomed. If the returned zoomed rect
731 // equals the current window's frame, -isZoomed returns YES.
732 - (NSRect)windowWillUseStandardFrame:(NSWindow*)window
733 defaultFrame:(NSRect)frame {
734 // Forget that we grew the window up (if we in fact did).
735 [self resetWindowGrowthState];
737 // |frame| already fills the current screen. Never touch y and height since we
738 // always want to fill vertically.
740 // If the shift key is down, maximize. Hopefully this should make the
741 // "switchers" happy.
742 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
746 // To prevent strange results on portrait displays, the basic minimum zoomed
747 // width is the larger of: 60% of available width, 60% of available height
748 // (bounded by available width).
749 const CGFloat kProportion = 0.6;
750 CGFloat zoomedWidth =
751 std::max(kProportion * NSWidth(frame),
752 std::min(kProportion * NSHeight(frame), NSWidth(frame)));
754 WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
756 // If the intrinsic width is bigger, then make it the zoomed width.
757 const int kScrollbarWidth = 16; // TODO(viettrungluu): ugh.
758 CGFloat intrinsicWidth = static_cast<CGFloat>(
759 contents->GetPreferredSize().width() + kScrollbarWidth);
760 zoomedWidth = std::max(zoomedWidth,
761 std::min(intrinsicWidth, NSWidth(frame)));
764 // Never shrink from the current size on zoom (see above).
765 NSRect currentFrame = [[self window] frame];
766 zoomedWidth = std::max(zoomedWidth, NSWidth(currentFrame));
768 // |frame| determines our maximum extents. We need to set the origin of the
769 // frame -- and only move it left if necessary.
770 if (currentFrame.origin.x + zoomedWidth > NSMaxX(frame))
771 frame.origin.x = NSMaxX(frame) - zoomedWidth;
773 frame.origin.x = currentFrame.origin.x;
775 // Set the width. Don't touch y or height.
776 frame.size.width = zoomedWidth;
782 [BrowserWindowUtils activateWindowForController:self];
785 // Determine whether we should let a window zoom/unzoom to the given |newFrame|.
786 // We avoid letting unzoom move windows between screens, because it's really
787 // strange and unintuitive.
788 - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame {
789 // Figure out which screen |newFrame| is on.
790 NSScreen* newScreen = nil;
791 CGFloat newScreenOverlapArea = 0.0;
792 for (NSScreen* screen in [NSScreen screens]) {
793 NSRect overlap = NSIntersectionRect(newFrame, [screen frame]);
794 CGFloat overlapArea = NSWidth(overlap) * NSHeight(overlap);
795 if (overlapArea > newScreenOverlapArea) {
797 newScreenOverlapArea = overlapArea;
800 // If we're somehow not on any screen, allow the zoom.
804 // If the new screen is the current screen, we can return a definitive YES.
805 // Note: This check is not strictly necessary, but just short-circuits in the
806 // "no-brainer" case. To test the complicated logic below, comment this out!
807 NSScreen* curScreen = [window screen];
808 if (newScreen == curScreen)
811 // Worry a little: What happens when a window is on two (or more) screens?
812 // E.g., what happens in a 50-50 scenario? Cocoa may reasonably elect to zoom
813 // to the other screen rather than staying on the officially current one. So
814 // we compare overlaps with the current window frame, and see if Cocoa's
815 // choice was reasonable (allowing a small rounding error). This should
816 // hopefully avoid us ever erroneously denying a zoom when a window is on
818 NSRect curFrame = [window frame];
819 NSRect newScrIntersectCurFr = NSIntersectionRect([newScreen frame], curFrame);
820 NSRect curScrIntersectCurFr = NSIntersectionRect([curScreen frame], curFrame);
821 if (NSWidth(newScrIntersectCurFr) * NSHeight(newScrIntersectCurFr) >=
822 (NSWidth(curScrIntersectCurFr) * NSHeight(curScrIntersectCurFr) - 1.0)) {
826 // If it wasn't reasonable, return NO.
830 // Adjusts the window height by the given amount.
831 - (BOOL)adjustWindowHeightBy:(CGFloat)deltaH {
832 // By not adjusting the window height when initializing, we can ensure that
833 // the window opens with the same size that was saved on close.
834 if (initializing_ || [self isInAnyFullscreenMode] || deltaH == 0)
837 NSWindow* window = [self window];
838 NSRect windowFrame = [window frame];
839 NSRect workarea = [[window screen] visibleFrame];
841 // Prevent the window from growing smaller than its minimum height:
842 // http://crbug.com/230400 .
844 CGFloat minWindowHeight = [window minSize].height;
845 if (windowFrame.size.height + deltaH < minWindowHeight) {
846 // |deltaH| + |windowFrame.size.height| = |minWindowHeight|.
847 deltaH = minWindowHeight - windowFrame.size.height;
854 // If the window is not already fully in the workarea, do not adjust its frame
856 if (!NSContainsRect(workarea, windowFrame))
859 // Record the position of the top/bottom of the window, so we can easily check
860 // whether we grew the window upwards/downwards.
861 CGFloat oldWindowMaxY = NSMaxY(windowFrame);
862 CGFloat oldWindowMinY = NSMinY(windowFrame);
864 // We are "zoomed" if we occupy the full vertical space.
865 bool isZoomed = (windowFrame.origin.y == workarea.origin.y &&
866 NSHeight(windowFrame) == NSHeight(workarea));
868 // If we're shrinking the window....
870 bool didChange = false;
872 // Don't reset if not currently zoomed since shrinking can take several
875 isShrinkingFromZoomed_ = YES;
877 // If we previously grew at the top, shrink as much as allowed at the top
879 if (windowTopGrowth_ > 0) {
880 CGFloat shrinkAtTopBy = MIN(-deltaH, windowTopGrowth_);
881 windowFrame.size.height -= shrinkAtTopBy; // Shrink the window.
882 deltaH += shrinkAtTopBy; // Update the amount left to shrink.
883 windowTopGrowth_ -= shrinkAtTopBy; // Update the growth state.
887 // Similarly for the bottom (not an "else if" since we may have to
888 // simultaneously shrink at both the top and at the bottom). Note that
889 // |deltaH| may no longer be nonzero due to the above.
890 if (deltaH < 0 && windowBottomGrowth_ > 0) {
891 CGFloat shrinkAtBottomBy = MIN(-deltaH, windowBottomGrowth_);
892 windowFrame.origin.y += shrinkAtBottomBy; // Move the window up.
893 windowFrame.size.height -= shrinkAtBottomBy; // Shrink the window.
894 deltaH += shrinkAtBottomBy; // Update the amount left....
895 windowBottomGrowth_ -= shrinkAtBottomBy; // Update the growth state.
899 // If we're shrinking from zoomed but we didn't change the top or bottom
900 // (since we've reached the limits imposed by |window...Growth_|), then stop
901 // here. Don't reset |isShrinkingFromZoomed_| since we might get called
902 // again for the same shrink.
903 if (isShrinkingFromZoomed_ && !didChange)
906 isShrinkingFromZoomed_ = NO;
908 // Don't bother with anything else.
913 // Shrinking from zoomed is handled above (and is constrained by
914 // |window...Growth_|).
915 if (!isShrinkingFromZoomed_) {
916 // Resize the window down until it hits the bottom of the workarea, then if
917 // needed continue resizing upwards. Do not resize the window to be taller
918 // than the current workarea.
919 // Resize the window as requested, keeping the top left corner fixed.
920 windowFrame.origin.y -= deltaH;
921 windowFrame.size.height += deltaH;
923 // If the bottom left corner is now outside the visible frame, move the
924 // window up to make it fit, but make sure not to move the top left corner
925 // out of the visible frame.
926 if (windowFrame.origin.y < workarea.origin.y) {
927 windowFrame.origin.y = workarea.origin.y;
928 windowFrame.size.height =
929 std::min(NSHeight(windowFrame), NSHeight(workarea));
932 // Record (if applicable) how much we grew the window in either direction.
933 // (N.B.: These only record growth, not shrinkage.)
934 if (NSMaxY(windowFrame) > oldWindowMaxY)
935 windowTopGrowth_ += NSMaxY(windowFrame) - oldWindowMaxY;
936 if (NSMinY(windowFrame) < oldWindowMinY)
937 windowBottomGrowth_ += oldWindowMinY - NSMinY(windowFrame);
940 // Disable subview resizing while resizing the window, or else we will get
941 // unwanted renderer resizes. The calling code must call layoutSubviews to
942 // make things right again.
943 NSView* chromeContentView = [self chromeContentView];
944 BOOL autoresizesSubviews = [chromeContentView autoresizesSubviews];
945 [chromeContentView setAutoresizesSubviews:NO];
946 [window setFrame:windowFrame display:NO];
947 [chromeContentView setAutoresizesSubviews:autoresizesSubviews];
951 // Main method to resize browser window subviews. This method should be called
952 // when resizing any child of the content view, rather than resizing the views
953 // directly. If the view is already the correct height, does not force a
955 - (void)resizeView:(NSView*)view newHeight:(CGFloat)height {
956 // We should only ever be called for one of the following four views.
957 // |downloadShelfController_| may be nil. If we are asked to size the bookmark
958 // bar directly, its superview must be this controller's content view.
960 DCHECK(view == [toolbarController_ view] ||
961 view == [infoBarContainerController_ view] ||
962 view == [downloadShelfController_ view] ||
963 view == [bookmarkBarController_ view]);
965 // The infobar has insufficient information to determine its new height. It
966 // knows the total height of all of the info bars (which is what it passes
967 // into this method), but knows nothing about the maximum arrow height, which
968 // is determined by this class.
969 if (view == [infoBarContainerController_ view]) {
970 base::scoped_nsobject<BrowserWindowLayout> layout(
971 [[BrowserWindowLayout alloc] init]);
972 [self updateLayoutParameters:layout];
973 // Use the new height for the info bar.
974 [layout setInfoBarHeight:height];
976 chrome::LayoutOutput output = [layout computeLayout];
978 height = NSHeight(output.infoBarFrame);
981 // Change the height of the view and call |-layoutSubViews|. We set the height
982 // here without regard to where the view is on the screen or whether it needs
983 // to "grow up" or "grow down." The below call to |-layoutSubviews| will
984 // position each view correctly.
985 NSRect frame = [view frame];
986 if (NSHeight(frame) == height)
989 // Disable screen updates to prevent flickering.
990 gfx::ScopedNSDisableScreenUpdates disabler;
992 // Grow or shrink the window by the amount of the height change. We adjust
993 // the window height only in two cases:
994 // 1) We are adjusting the height of the bookmark bar and it is currently
995 // animating either open or closed.
996 // 2) We are adjusting the height of the download shelf.
998 // We do not adjust the window height for bookmark bar changes on the NTP.
999 BOOL shouldAdjustBookmarkHeight =
1000 [bookmarkBarController_ isAnimatingBetweenState:BookmarkBar::HIDDEN
1001 andState:BookmarkBar::SHOW];
1003 BOOL resizeRectDirty = NO;
1004 if ((shouldAdjustBookmarkHeight && view == [bookmarkBarController_ view]) ||
1005 view == [downloadShelfController_ view]) {
1006 CGFloat deltaH = height - NSHeight(frame);
1007 if ([self adjustWindowHeightBy:deltaH] &&
1008 view == [downloadShelfController_ view]) {
1009 // If the window height didn't change, the download shelf will change the
1010 // size of the contents. If the contents size doesn't change, send it
1011 // an explicit grow box invalidation (else, the resize message does that.)
1012 resizeRectDirty = YES;
1016 frame.size.height = height;
1017 // TODO(rohitrao): Determine if calling setFrame: twice is bad.
1018 [view setFrame:frame];
1019 [self layoutSubviews];
1021 if (resizeRectDirty) {
1022 // Send new resize rect to foreground tab.
1023 if (content::WebContents* contents =
1024 browser_->tab_strip_model()->GetActiveWebContents()) {
1025 if (content::RenderViewHost* rvh = contents->GetRenderViewHost()) {
1026 rvh->ResizeRectChanged(windowShim_->GetRootWindowResizerRect());
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:)])
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 = [(NSMenuItem*)item state];
1047 NSInteger newState = toggled ? NSOnState : NSOffState;
1048 if (oldState != newState)
1049 [item setState:newState];
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();
1060 WebContents* current_tab =
1061 browser_->tab_strip_model()->GetActiveWebContents();
1065 const std::string encoding = current_tab->GetEncoding();
1067 bool toggled = encoding_controller.IsItemChecked(profile, encoding, tag);
1068 NSInteger oldState = [(NSMenuItem*)item state];
1069 NSInteger newState = toggled ? NSOnState : NSOffState;
1070 if (oldState != newState)
1071 [item setState:newState];
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];
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);
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
1100 if (NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item))
1101 enable &= !![[menuItem keyEquivalent] length];
1103 case IDC_FULLSCREEN: {
1104 if (NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item)) {
1105 NSString* menuTitle = l10n_util::GetNSString(
1106 [self isInAppKitFullscreen] && ![self inPresentationMode]
1107 ? IDS_EXIT_FULLSCREEN_MAC
1108 : IDS_ENTER_FULLSCREEN_MAC);
1109 [menuItem setTitle:menuTitle];
1111 if (!chrome::mac::SupportsSystemFullscreen())
1112 [menuItem setHidden:YES];
1116 case IDC_PRESENTATION_MODE: {
1117 if (NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item)) {
1118 NSString* menuTitle = l10n_util::GetNSString(
1119 [self inPresentationMode] ? IDS_EXIT_PRESENTATION_MAC :
1120 IDS_ENTER_PRESENTATION_MAC);
1121 [menuItem setTitle:menuTitle];
1125 case IDC_SHOW_SIGNIN: {
1126 Profile* original_profile =
1127 browser_->profile()->GetOriginalProfile();
1128 [AppController updateSigninItem:item
1130 currentProfile:original_profile];
1133 case IDC_BOOKMARK_PAGE: {
1134 // Extensions have the ability to hide the bookmark page menu item.
1135 // This only affects the bookmark page menu item under the main menu.
1136 // The bookmark page menu item under the wrench menu has its
1137 // visibility controlled by WrenchMenuModel.
1139 chrome::ShouldRemoveBookmarkThisPageUI(browser_->profile());
1140 NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item);
1141 [menuItem setHidden:shouldHide];
1144 case IDC_BOOKMARK_ALL_TABS: {
1145 // Extensions have the ability to hide the bookmark all tabs menu
1146 // item. This only affects the bookmark page menu item under the main
1147 // menu. The bookmark page menu item under the wrench menu has its
1148 // visibility controlled by WrenchMenuModel.
1150 chrome::ShouldRemoveBookmarkOpenPagesUI(browser_->profile());
1151 NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item);
1152 [menuItem setHidden:shouldHide];
1156 // Special handling for the contents of the Text Encoding submenu. On
1157 // Mac OS, instead of enabling/disabling the top-level menu item, we
1158 // enable/disable the submenu's contents (per Apple's HIG).
1159 EncodingMenuController encoding_controller;
1160 if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) {
1161 enable &= chrome::IsCommandEnabled(browser_.get(),
1162 IDC_ENCODING_MENU) ? YES : NO;
1166 // If the item is toggleable, find its toggle state and
1167 // try to update it. This is a little awkward, but the alternative is
1168 // to check after a commandDispatch, which seems worse.
1169 [self updateToggleStateWithTag:tag forItem:item];
1175 // Called when the user picks a menu or toolbar item when this window is key.
1176 // Calls through to the browser object to execute the command. This assumes that
1177 // the command is supported and doesn't check, otherwise it would have been
1178 // disabled in the UI in validateUserInterfaceItem:.
1179 - (void)commandDispatch:(id)sender {
1181 // Identify the actual BWC to which the command should be dispatched. It might
1182 // belong to a background window, yet this controller gets it because it is
1183 // the foreground window's controller and thus in the responder chain. Some
1184 // senders don't have this problem (for example, menus only operate on the
1185 // foreground window), so this is only an issue for senders that are part of
1187 BrowserWindowController* targetController = self;
1188 if ([sender respondsToSelector:@selector(window)])
1189 targetController = [[sender window] windowController];
1190 DCHECK([targetController isKindOfClass:[BrowserWindowController class]]);
1191 DCHECK(targetController->browser_.get());
1192 chrome::ExecuteCommand(targetController->browser_.get(), [sender tag]);
1195 // Same as |-commandDispatch:|, but executes commands using a disposition
1196 // determined by the key flags. If the window is in the background and the
1197 // command key is down, ignore the command key, but process any other modifiers.
1198 - (void)commandDispatchUsingKeyModifiers:(id)sender {
1201 if (![sender isEnabled]) {
1202 // This code is reachable e.g. if the user mashes the back button, queuing
1203 // up a bunch of events before the button's enabled state is updated:
1204 // http://crbug.com/63254
1208 // See comment above for why we do this.
1209 BrowserWindowController* targetController = self;
1210 if ([sender respondsToSelector:@selector(window)])
1211 targetController = [[sender window] windowController];
1212 DCHECK([targetController isKindOfClass:[BrowserWindowController class]]);
1213 DCHECK(targetController->browser_.get());
1215 NSInteger command = [sender tag];
1216 NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
1217 if ((command == IDC_RELOAD) &&
1218 (modifierFlags & (NSShiftKeyMask | NSControlKeyMask))) {
1219 command = IDC_RELOAD_IGNORING_CACHE;
1220 // Mask off Shift and Control so they don't affect the disposition below.
1221 modifierFlags &= ~(NSShiftKeyMask | NSControlKeyMask);
1223 if (![[sender window] isMainWindow]) {
1224 // Remove the command key from the flags, it means "keep the window in
1225 // the background" in this case.
1226 modifierFlags &= ~NSCommandKeyMask;
1228 chrome::ExecuteCommandWithDisposition(
1229 targetController->browser_.get(), command,
1230 ui::WindowOpenDispositionFromNSEventWithFlags(
1231 [NSApp currentEvent], modifierFlags));
1234 // Called when another part of the internal codebase needs to execute a
1236 - (void)executeCommand:(int)command {
1237 chrome::ExecuteCommand(browser_.get(), command);
1240 - (BOOL)handledByExtensionCommand:(NSEvent*)event
1241 priority:(ui::AcceleratorManager::HandlerPriority)priority {
1242 return extension_keybinding_registry_->ProcessKeyEvent(
1243 content::NativeWebKeyboardEvent(event), priority);
1246 // StatusBubble delegate method: tell the status bubble the frame it should
1247 // position itself in.
1248 - (NSRect)statusBubbleBaseFrame {
1249 NSView* view = [overlayableContentsController_ view];
1250 return [view convertRect:[view bounds] toView:nil];
1253 - (void)updateToolbarWithContents:(WebContents*)tab {
1254 [toolbarController_ updateToolbarWithContents:tab];
1257 - (void)resetTabState:(WebContents*)tab {
1258 [toolbarController_ resetTabState:tab];
1261 - (void)setStarredState:(BOOL)isStarred {
1262 [toolbarController_ setStarredState:isStarred];
1265 - (void)setCurrentPageIsTranslated:(BOOL)on {
1266 [toolbarController_ setTranslateIconLit:on];
1269 - (void)onActiveTabChanged:(content::WebContents*)oldContents
1270 to:(content::WebContents*)newContents {
1271 // No need to remove previous bubble. It will close itself.
1272 PermissionBubbleManager* manager(nullptr);
1274 manager = PermissionBubbleManager::FromWebContents(oldContents);
1276 manager->SetView(nullptr);
1280 if (!permissionBubbleCocoa_.get()) {
1281 DCHECK(browser_.get());
1282 permissionBubbleCocoa_.reset(new PermissionBubbleCocoa(browser_.get()));
1284 manager = PermissionBubbleManager::FromWebContents(newContents);
1286 manager->SetView(permissionBubbleCocoa_.get());
1290 - (void)zoomChangedForActiveTab:(BOOL)canShowBubble {
1291 [toolbarController_ zoomChangedForActiveTab:canShowBubble];
1294 // Return the rect, in WebKit coordinates (flipped), of the window's grow box
1295 // in the coordinate system of the content area of the currently selected tab.
1296 // |windowGrowBox| needs to be in the window's coordinate system.
1297 - (NSRect)selectedTabGrowBoxRect {
1298 NSWindow* window = [self window];
1299 if (![window respondsToSelector:@selector(_growBoxRect)])
1302 // Before we return a rect, we need to convert it from window coordinates
1303 // to tab content area coordinates and flip the coordinate system.
1304 NSRect growBoxRect =
1305 [[self tabContentArea] convertRect:[window _growBoxRect] fromView:nil];
1306 growBoxRect.origin.y =
1307 NSHeight([[self tabContentArea] frame]) - NSMaxY(growBoxRect);
1311 // Accept tabs from a BrowserWindowController with the same Profile.
1312 - (BOOL)canReceiveFrom:(TabWindowController*)source {
1313 BrowserWindowController* realSource =
1314 base::mac::ObjCCast<BrowserWindowController>(source);
1315 if (!realSource || browser_->profile() != realSource->browser_->profile()) {
1319 // Can't drag a tab from a normal browser to a pop-up
1320 if (browser_->type() != realSource->browser_->type()) {
1327 // Move a given tab view to the location of the current placeholder. If there is
1328 // no placeholder, it will go at the end. |controller| is the window controller
1329 // of a tab being dropped from a different window. It will be nil if the drag is
1330 // within the window, otherwise the tab is removed from that window before being
1331 // placed into this one. The implementation will call |-removePlaceholder| since
1332 // the drag is now complete. This also calls |-layoutTabs| internally so
1333 // clients do not need to call it again.
1334 - (void)moveTabViews:(NSArray*)views
1335 fromController:(TabWindowController*)dragController {
1336 if (dragController) {
1337 // Moving between windows.
1338 NSView* activeTabView = [dragController activeTabView];
1339 BrowserWindowController* dragBWC =
1340 base::mac::ObjCCastStrict<BrowserWindowController>(dragController);
1342 // We will drop the tabs starting at indexOfPlaceholder, and increment from
1343 // there. We remove the placehoder before dropping the tabs, so that the
1344 // new tab animation's destination frame is correct.
1345 int tabIndex = [tabStripController_ indexOfPlaceholder];
1346 [self removePlaceholder];
1348 for (NSView* view in views) {
1349 // Figure out the WebContents to drop into our tab model from the source
1351 int index = [dragBWC->tabStripController_ modelIndexForTabView:view];
1352 WebContents* contents =
1353 dragBWC->browser_->tab_strip_model()->GetWebContentsAt(index);
1354 // The tab contents may have gone away if given a window.close() while it
1355 // is being dragged. If so, bail, we've got nothing to drop.
1359 // Convert |view|'s frame (which starts in the source tab strip's
1360 // coordinate system) to the coordinate system of the destination tab
1361 // strip. This needs to be done before being detached so the window
1362 // transforms can be performed.
1363 NSRect destinationFrame = [view frame];
1364 NSPoint tabOrigin = destinationFrame.origin;
1365 tabOrigin = [[dragController tabStripView] convertPoint:tabOrigin
1367 tabOrigin = [[dragController window] convertBaseToScreen:tabOrigin];
1368 tabOrigin = [[self window] convertScreenToBase:tabOrigin];
1369 tabOrigin = [[self tabStripView] convertPoint:tabOrigin fromView:nil];
1370 destinationFrame.origin = tabOrigin;
1372 // Before the tab is detached from its originating tab strip, store the
1373 // pinned state so that it can be maintained between the windows.
1374 bool isPinned = dragBWC->browser_->tab_strip_model()->IsTabPinned(index);
1376 // Now that we have enough information about the tab, we can remove it
1377 // from the dragging window. We need to do this *before* we add it to the
1378 // new window as this will remove the WebContents' delegate.
1379 [dragController detachTabView:view];
1381 // Deposit it into our model at the appropriate location (it already knows
1382 // where it should go from tracking the drag). Doing this sets the tab's
1383 // delegate to be the Browser.
1384 [tabStripController_ dropWebContents:contents
1386 withFrame:destinationFrame
1387 asPinnedTab:isPinned
1388 activate:view == activeTabView];
1391 // Moving within a window.
1392 for (NSView* view in views) {
1393 int index = [tabStripController_ modelIndexForTabView:view];
1394 [tabStripController_ moveTabFromIndex:index];
1396 [self removePlaceholder];
1400 // Tells the tab strip to forget about this tab in preparation for it being
1401 // put into a different tab strip, such as during a drop on another window.
1402 - (void)detachTabView:(NSView*)view {
1403 int index = [tabStripController_ modelIndexForTabView:view];
1404 browser_->tab_strip_model()->DetachWebContentsAt(index);
1407 - (NSArray*)tabViews {
1408 return [tabStripController_ tabViews];
1411 - (NSView*)activeTabView {
1412 return [tabStripController_ activeTabView];
1415 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force {
1416 [toolbarController_ setIsLoading:isLoading force:force];
1419 // Make the location bar the first responder, if possible.
1420 - (void)focusLocationBar:(BOOL)selectAll {
1421 [toolbarController_ focusLocationBar:selectAll];
1424 - (void)focusTabContents {
1425 content::WebContents* const activeWebContents =
1426 browser_->tab_strip_model()->GetActiveWebContents();
1427 if (activeWebContents)
1428 activeWebContents->Focus();
1431 - (void)layoutTabs {
1432 [tabStripController_ layoutTabs];
1435 - (TabWindowController*)detachTabsToNewWindow:(NSArray*)tabViews
1436 draggedTab:(NSView*)draggedTab {
1437 DCHECK_GT([tabViews count], 0U);
1439 // Disable screen updates so that this appears as a single visual change.
1440 gfx::ScopedNSDisableScreenUpdates disabler;
1442 // Set the window size. Need to do this before we detach the tab so it's
1443 // still in the window. We have to flip the coordinates as that's what
1444 // is expected by the Browser code.
1445 NSWindow* sourceWindow = [draggedTab window];
1446 NSRect windowRect = [sourceWindow frame];
1447 NSScreen* screen = [sourceWindow screen];
1448 windowRect.origin.y = NSHeight([screen frame]) - NSMaxY(windowRect);
1449 gfx::Rect browserRect(windowRect.origin.x, windowRect.origin.y,
1450 NSWidth(windowRect), NSHeight(windowRect));
1452 std::vector<TabStripModelDelegate::NewStripContents> contentses;
1453 TabStripModel* model = browser_->tab_strip_model();
1455 for (TabView* tabView in tabViews) {
1456 // Fetch the tab contents for the tab being dragged.
1457 int index = [tabStripController_ modelIndexForTabView:tabView];
1458 bool isPinned = model->IsTabPinned(index);
1459 bool isActive = (index == model->active_index());
1461 TabStripModelDelegate::NewStripContents item;
1462 item.web_contents = model->GetWebContentsAt(index);
1464 (isActive ? TabStripModel::ADD_ACTIVE : TabStripModel::ADD_NONE) |
1465 (isPinned ? TabStripModel::ADD_PINNED : TabStripModel::ADD_NONE);
1466 contentses.push_back(item);
1469 for (TabView* tabView in tabViews) {
1470 int index = [tabStripController_ modelIndexForTabView:tabView];
1471 // Detach it from the source window, which just updates the model without
1472 // deleting the tab contents. This needs to come before creating the new
1473 // Browser because it clears the WebContents' delegate, which gets hooked
1474 // up during creation of the new window.
1475 model->DetachWebContentsAt(index);
1478 // Create a new window with the dragged tabs in its model.
1479 Browser* newBrowser = browser_->tab_strip_model()->delegate()->
1480 CreateNewStripWithContents(contentses, browserRect, false);
1482 // Get the new controller by asking the new window for its delegate.
1483 BrowserWindowController* controller =
1484 reinterpret_cast<BrowserWindowController*>(
1485 [newBrowser->window()->GetNativeWindow() delegate]);
1486 DCHECK(controller && [controller isKindOfClass:[TabWindowController class]]);
1488 // And make sure we use the correct frame in the new view.
1489 TabStripController* tabStripController = [controller tabStripController];
1490 NSView* tabStrip = [self tabStripView];
1491 NSEnumerator* tabEnumerator = [tabViews objectEnumerator];
1492 for (NSView* newView in [tabStripController tabViews]) {
1493 NSView* oldView = [tabEnumerator nextObject];
1495 // Pushes tabView's frame back inside the tabstrip.
1496 NSRect sourceTabRect = [oldView frame];
1497 NSSize tabOverflow =
1498 [self overflowFrom:[tabStrip convertRect:sourceTabRect toView:nil]
1499 to:[tabStrip frame]];
1501 NSOffsetRect(sourceTabRect, -tabOverflow.width, -tabOverflow.height);
1502 // Force the added tab to the right size (remove stretching.)
1503 tabRect.size.height = [TabStripController defaultTabHeight];
1505 [tabStripController setFrame:tabRect ofTabView:newView];
1512 - (void)insertPlaceholderForTab:(TabView*)tab
1513 frame:(NSRect)frame {
1514 [super insertPlaceholderForTab:tab frame:frame];
1515 [tabStripController_ insertPlaceholderForTab:tab frame:frame];
1518 - (void)removePlaceholder {
1519 [super removePlaceholder];
1520 [tabStripController_ insertPlaceholderForTab:nil frame:NSZeroRect];
1523 - (BOOL)isDragSessionActive {
1524 // The tab can be dragged within the existing tab strip or detached
1525 // into its own window (then the overlay window will be present).
1526 return [[self tabStripController] isDragSessionActive] ||
1527 [self overlayWindow] != nil;
1530 - (BOOL)tabDraggingAllowed {
1531 return [tabStripController_ tabDraggingAllowed];
1534 - (BOOL)tabTearingAllowed {
1535 return ![self isInAnyFullscreenMode];
1538 - (BOOL)windowMovementAllowed {
1539 return ![self isInAnyFullscreenMode];
1542 - (BOOL)isTabFullyVisible:(TabView*)tab {
1543 return [tabStripController_ isTabFullyVisible:tab];
1546 - (void)showNewTabButton:(BOOL)show {
1547 [tabStripController_ showNewTabButton:show];
1550 - (BOOL)shouldShowAvatar {
1551 if (![self hasTabStrip])
1553 if (browser_->profile()->IsOffTheRecord())
1556 ProfileInfoCache& cache =
1557 g_browser_process->profile_manager()->GetProfileInfoCache();
1558 if (cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath()) ==
1559 std::string::npos) {
1563 return AvatarMenu::ShouldShowAvatarMenu();
1566 - (BOOL)shouldUseNewAvatarButton {
1567 return switches::IsNewAvatarMenu() &&
1568 profiles::IsRegularOrGuestSession(browser_.get());
1571 - (BOOL)isBookmarkBarVisible {
1572 return [bookmarkBarController_ isVisible];
1575 - (BOOL)isBookmarkBarAnimating {
1576 return [bookmarkBarController_ isAnimationRunning];
1579 - (BookmarkBarController*)bookmarkBarController {
1580 return bookmarkBarController_;
1583 - (DevToolsController*)devToolsController {
1584 return devToolsController_;
1587 - (BOOL)isDownloadShelfVisible {
1588 return downloadShelfController_ != nil &&
1589 [downloadShelfController_ isVisible];
1592 - (void)createAndAddDownloadShelf {
1593 if (!downloadShelfController_.get()) {
1594 downloadShelfController_.reset([[DownloadShelfController alloc]
1595 initWithBrowser:browser_.get() resizeDelegate:self]);
1596 [self.chromeContentView addSubview:[downloadShelfController_ view]];
1600 - (DownloadShelfController*)downloadShelf {
1601 return downloadShelfController_;
1604 - (void)addFindBar:(FindBarCocoaController*)findBarCocoaController {
1605 // Shouldn't call addFindBar twice.
1606 DCHECK(!findBarCocoaController_.get());
1608 // Create a controller for the findbar.
1609 findBarCocoaController_.reset([findBarCocoaController retain]);
1610 [self layoutSubviews];
1611 [self updateSubviewZOrder];
1614 - (NSWindow*)createFullscreenWindow {
1615 return [[[FullscreenWindow alloc] initForScreen:[[self window] screen]]
1619 - (NSInteger)numberOfTabs {
1620 // count() includes pinned tabs.
1621 return browser_->tab_strip_model()->count();
1624 - (BOOL)hasLiveTabs {
1625 return !browser_->tab_strip_model()->empty();
1628 - (NSString*)activeTabTitle {
1629 WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
1630 return base::SysUTF16ToNSString(contents->GetTitle());
1633 - (NSRect)regularWindowFrame {
1634 return [self isInAnyFullscreenMode] ? savedRegularWindowFrame_
1635 : [[self window] frame];
1638 // (Override of |TabWindowController| method.)
1639 - (BOOL)hasTabStrip {
1640 return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP];
1643 - (BOOL)isTabDraggable:(NSView*)tabView {
1644 // TODO(avi, thakis): ConstrainedWindowSheetController has no api to move
1645 // tabsheets between windows. Until then, we have to prevent having to move a
1646 // tabsheet between windows, e.g. no tearing off of tabs.
1647 int index = [tabStripController_ modelIndexForTabView:tabView];
1648 WebContents* contents = browser_->tab_strip_model()->GetWebContentsAt(index);
1652 return !web_modal::PopupManager::FromWebContents(contents)->
1653 IsWebModalDialogActive(contents);
1656 // TabStripControllerDelegate protocol.
1657 - (void)onActivateTabWithContents:(WebContents*)contents {
1658 // Update various elements that are interested in knowing the current
1661 // Update all the UI bits.
1662 windowShim_->UpdateTitleBar();
1664 // Update the bookmark bar.
1665 // TODO(viettrungluu): perhaps update to not terminate running animations (if
1667 windowShim_->BookmarkBarStateChanged(
1668 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
1670 [infoBarContainerController_ changeWebContents:contents];
1672 // Must do this after bookmark and infobar updates to avoid
1673 // unnecesary resize in contents.
1674 [devToolsController_ updateDevToolsForWebContents:contents
1675 withProfile:browser_->profile()];
1678 - (void)onTabChanged:(TabStripModelObserver::TabChangeType)change
1679 withContents:(WebContents*)contents {
1680 // Update titles if this is the currently selected tab and if it isn't just
1681 // the loading state which changed.
1682 if (change != TabStripModelObserver::LOADING_ONLY)
1683 windowShim_->UpdateTitleBar();
1685 // Update the bookmark bar if this is the currently selected tab and if it
1686 // isn't just the title which changed. This for transitions between the NTP
1687 // (showing its floating bookmark bar) and normal web pages (showing no
1689 // TODO(viettrungluu): perhaps update to not terminate running animations?
1690 if (change != TabStripModelObserver::TITLE_NOT_LOADING) {
1691 windowShim_->BookmarkBarStateChanged(
1692 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
1696 - (void)onTabDetachedWithContents:(WebContents*)contents {
1697 [infoBarContainerController_ tabDetachedWithContents:contents];
1700 - (void)userChangedTheme {
1701 NSView* rootView = [[[self window] contentView] superview];
1702 [rootView cr_recursivelyInvokeBlock:^(id view) {
1703 if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)])
1704 [view windowDidChangeTheme];
1706 // TODO(andresantoso): Remove this once all themed views respond to
1707 // windowDidChangeTheme above.
1708 [view setNeedsDisplay:YES];
1712 - (ui::ThemeProvider*)themeProvider {
1713 return ThemeServiceFactory::GetForProfile(browser_->profile());
1716 - (ThemedWindowStyle)themedWindowStyle {
1717 ThemedWindowStyle style = 0;
1718 if (browser_->profile()->IsOffTheRecord())
1719 style |= THEMED_INCOGNITO;
1721 if (browser_->is_devtools())
1722 style |= THEMED_DEVTOOLS;
1723 if (browser_->is_type_popup())
1724 style |= THEMED_POPUP;
1729 - (NSPoint)themeImagePositionForAlignment:(ThemeImageAlignment)alignment {
1730 NSView* windowChromeView = [[[self window] contentView] superview];
1731 NSView* tabStripView = nil;
1732 if ([self hasTabStrip])
1733 tabStripView = [self tabStripView];
1734 return [BrowserWindowUtils themeImagePositionFor:windowChromeView
1735 withTabStrip:tabStripView
1736 alignment:alignment];
1739 - (NSPoint)bookmarkBubblePoint {
1740 return [toolbarController_ bookmarkBubblePoint];
1743 // Show the bookmark bubble (e.g. user just clicked on the STAR).
1744 - (void)showBookmarkBubbleForURL:(const GURL&)url
1745 alreadyBookmarked:(BOOL)alreadyMarked {
1746 if (!bookmarkBubbleController_) {
1747 BookmarkModel* model =
1748 BookmarkModelFactory::GetForProfile(browser_->profile());
1749 ChromeBookmarkClient* client =
1750 ChromeBookmarkClientFactory::GetForProfile(browser_->profile());
1751 const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url);
1752 bookmarkBubbleController_ =
1753 [[BookmarkBubbleController alloc] initWithParentWindow:[self window]
1757 alreadyBookmarked:alreadyMarked];
1758 [bookmarkBubbleController_ showWindow:self];
1759 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1760 [center addObserver:self
1761 selector:@selector(bookmarkBubbleWindowWillClose:)
1762 name:NSWindowWillCloseNotification
1763 object:[bookmarkBubbleController_ window]];
1767 // Nil out the weak bookmark bubble controller reference.
1768 - (void)bookmarkBubbleWindowWillClose:(NSNotification*)notification {
1769 DCHECK_EQ([notification object], [bookmarkBubbleController_ window]);
1771 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1772 [center removeObserver:self
1773 name:NSWindowWillCloseNotification
1774 object:[bookmarkBubbleController_ window]];
1775 bookmarkBubbleController_ = nil;
1778 // Handle the editBookmarkNode: action sent from bookmark bubble controllers.
1779 - (void)editBookmarkNode:(id)sender {
1780 BOOL responds = [sender respondsToSelector:@selector(node)];
1783 const BookmarkNode* node = [sender node];
1785 BookmarkEditor::Show([self window], browser_->profile(),
1786 BookmarkEditor::EditDetails::EditNode(node),
1787 BookmarkEditor::SHOW_TREE);
1791 - (void)showTranslateBubbleForWebContents:(content::WebContents*)contents
1792 step:(translate::TranslateStep)step
1793 errorType:(translate::TranslateErrors::Type)
1795 // TODO(hajimehoshi): The similar logic exists at TranslateBubbleView::
1796 // ShowBubble. This should be unified.
1797 if (translateBubbleController_) {
1798 // When the user reads the advanced setting panel, the bubble should not be
1799 // changed because they are focusing on the bubble.
1800 if (translateBubbleController_.webContents == contents &&
1801 translateBubbleController_.model->GetViewState() ==
1802 TranslateBubbleModel::VIEW_STATE_ADVANCED) {
1805 if (step != translate::TRANSLATE_STEP_TRANSLATE_ERROR) {
1806 TranslateBubbleModel::ViewState viewState =
1807 TranslateBubbleModelImpl::TranslateStepToViewState(step);
1808 [translateBubbleController_ switchView:viewState];
1810 [translateBubbleController_ switchToErrorView:errorType];
1815 std::string sourceLanguage;
1816 std::string targetLanguage;
1817 ChromeTranslateClient::GetTranslateLanguages(
1818 contents, &sourceLanguage, &targetLanguage);
1820 scoped_ptr<translate::TranslateUIDelegate> uiDelegate(
1821 new translate::TranslateUIDelegate(
1822 ChromeTranslateClient::GetManagerFromWebContents(contents)
1826 scoped_ptr<TranslateBubbleModel> model(
1827 new TranslateBubbleModelImpl(step, uiDelegate.Pass()));
1828 translateBubbleController_ = [[TranslateBubbleController alloc]
1829 initWithParentWindow:self
1831 webContents:contents];
1832 [translateBubbleController_ showWindow:nil];
1834 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1835 [center addObserver:self
1836 selector:@selector(translateBubbleWindowWillClose:)
1837 name:NSWindowWillCloseNotification
1838 object:[translateBubbleController_ window]];
1841 - (void)dismissPermissionBubble {
1842 if (permissionBubbleCocoa_)
1843 permissionBubbleCocoa_->Hide();
1846 // Nil out the weak translate bubble controller reference.
1847 - (void)translateBubbleWindowWillClose:(NSNotification*)notification {
1848 DCHECK_EQ([notification object], [translateBubbleController_ window]);
1850 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1851 [center removeObserver:self
1852 name:NSWindowWillCloseNotification
1853 object:[translateBubbleController_ window]];
1854 translateBubbleController_ = nil;
1857 // If the browser is in incognito mode or has multi-profiles, install the image
1858 // view to decorate the window at the upper right. Use the same base y
1859 // coordinate as the tab strip.
1860 - (void)installAvatar {
1861 // Install the image into the badge view. Hide it for now; positioning and
1862 // sizing will be done by the layout code. The AvatarIcon will choose which
1863 // image to display based on the browser. The AvatarButton will display
1864 // the browser profile's name unless the browser is incognito.
1866 if ([self shouldUseNewAvatarButton]) {
1867 avatarButtonController_.reset(
1868 [[AvatarButtonController alloc] initWithBrowser:browser_.get()]);
1870 avatarButtonController_.reset(
1871 [[AvatarIconController alloc] initWithBrowser:browser_.get()]);
1873 view = [avatarButtonController_ view];
1874 [view setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
1875 [view setHidden:![self shouldShowAvatar]];
1877 // Install the view.
1878 [[[self window] contentView] addSubview:view];
1881 // Called when we get a three-finger swipe.
1882 - (void)swipeWithEvent:(NSEvent*)event {
1883 CGFloat deltaX = [event deltaX];
1884 CGFloat deltaY = [event deltaY];
1886 // Map forwards and backwards to history; left is positive, right is negative.
1887 unsigned int command = 0;
1890 } else if (deltaX < -0.5) {
1891 command = IDC_FORWARD;
1892 } else if (deltaY > 0.5) {
1893 // TODO(pinkerton): figure out page-up, http://crbug.com/16305
1894 } else if (deltaY < -0.5) {
1895 // TODO(pinkerton): figure out page-down, http://crbug.com/16305
1898 // Ensure the command is valid first (ExecuteCommand() won't do that) and
1900 if (chrome::IsCommandEnabled(browser_.get(), command)) {
1901 chrome::ExecuteCommandWithDisposition(
1904 ui::WindowOpenDispositionFromNSEvent(event));
1908 // Delegate method called when window is resized.
1909 - (void)windowDidResize:(NSNotification*)notification {
1910 [self saveWindowPositionIfNeeded];
1912 // Resize (and possibly move) the status bubble. Note that we may get called
1913 // when the status bubble does not exist.
1914 if (statusBubble_) {
1915 statusBubble_->UpdateSizeAndPosition();
1918 // Let the selected RenderWidgetHostView know, so that it can tell plugins.
1919 if (WebContents* contents =
1920 browser_->tab_strip_model()->GetActiveWebContents()) {
1921 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
1922 rwhv->WindowFrameChanged();
1925 // The FindBar needs to know its own position to properly detect overlaps
1926 // with find results. The position changes whenever the window is resized,
1927 // and |layoutSubviews| computes the FindBar's position.
1928 // TODO: calling |layoutSubviews| here is a waste, find a better way to
1930 if ([findBarCocoaController_ isFindBarVisible])
1931 [self layoutSubviews];
1934 // Handle the openLearnMoreAboutCrashLink: action from SadTabController when
1935 // "Learn more" link in "Aw snap" page (i.e. crash page or sad tab) is
1936 // clicked. Decoupling the action from its target makes unit testing possible.
1937 - (void)openLearnMoreAboutCrashLink:(id)sender {
1938 if (SadTabController* sadTab =
1939 base::mac::ObjCCast<SadTabController>(sender)) {
1940 WebContents* webContents = [sadTab webContents];
1942 OpenURLParams params(
1943 GURL(chrome::kCrashReasonURL), Referrer(), CURRENT_TAB,
1944 ui::PAGE_TRANSITION_LINK, false);
1945 webContents->OpenURL(params);
1950 // Delegate method called when window did move. (See below for why we don't use
1951 // |-windowWillMove:|, which is called less frequently than |-windowDidMove|
1953 - (void)windowDidMove:(NSNotification*)notification {
1954 [self saveWindowPositionIfNeeded];
1956 NSWindow* window = [self window];
1957 NSRect windowFrame = [window frame];
1958 NSRect workarea = [[window screen] visibleFrame];
1960 // We reset the window growth state whenever the window is moved out of the
1961 // work area or away (up or down) from the bottom or top of the work area.
1962 // Unfortunately, Cocoa sends |-windowWillMove:| too frequently (including
1963 // when clicking on the title bar to activate), and of course
1964 // |-windowWillMove| is called too early for us to apply our heuristic. (The
1965 // heuristic we use for detecting window movement is that if |windowTopGrowth_
1966 // > 0|, then we should be at the bottom of the work area -- if we're not,
1967 // we've moved. Similarly for the other side.)
1968 if (!NSContainsRect(workarea, windowFrame) ||
1969 (windowTopGrowth_ > 0 && NSMinY(windowFrame) != NSMinY(workarea)) ||
1970 (windowBottomGrowth_ > 0 && NSMaxY(windowFrame) != NSMaxY(workarea)))
1971 [self resetWindowGrowthState];
1973 // Let the selected RenderWidgetHostView know, so that it can tell plugins.
1974 if (WebContents* contents =
1975 browser_->tab_strip_model()->GetActiveWebContents()) {
1976 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
1977 rwhv->WindowFrameChanged();
1981 // Delegate method called when window will be resized; not called for
1982 // |-setFrame:display:|.
1983 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize {
1984 [self resetWindowGrowthState];
1988 // Delegate method: see |NSWindowDelegate| protocol.
1989 - (id)windowWillReturnFieldEditor:(NSWindow*)sender toObject:(id)obj {
1990 // Ask the toolbar controller if it wants to return a custom field editor
1991 // for the specific object.
1992 return [toolbarController_ customFieldEditorForObject:obj];
1995 // (Needed for |BookmarkBarControllerDelegate| protocol.)
1996 - (void)bookmarkBar:(BookmarkBarController*)controller
1997 didChangeFromState:(BookmarkBar::State)oldState
1998 toState:(BookmarkBar::State)newState {
1999 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
2000 [self adjustToolbarAndBookmarkBarForCompression:
2001 [controller getDesiredToolbarHeightCompression]];
2004 // (Needed for |BookmarkBarControllerDelegate| protocol.)
2005 - (void)bookmarkBar:(BookmarkBarController*)controller
2006 willAnimateFromState:(BookmarkBar::State)oldState
2007 toState:(BookmarkBar::State)newState {
2008 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
2009 [self adjustToolbarAndBookmarkBarForCompression:
2010 [controller getDesiredToolbarHeightCompression]];
2013 // (Private/TestingAPI)
2014 - (void)resetWindowGrowthState {
2015 windowTopGrowth_ = 0;
2016 windowBottomGrowth_ = 0;
2017 isShrinkingFromZoomed_ = NO;
2020 - (NSSize)overflowFrom:(NSRect)source
2022 // If |source|'s boundary is outside of |target|'s, set its distance
2023 // to |x|. Note that |source| can overflow to both side, but we
2024 // have nothing to do for such case.
2026 if (NSMaxX(target) < NSMaxX(source)) // |source| overflows to right
2027 x = NSMaxX(source) - NSMaxX(target);
2028 else if (NSMinX(source) < NSMinX(target)) // |source| overflows to left
2029 x = NSMinX(source) - NSMinX(target);
2031 // Same as |x| above.
2033 if (NSMaxY(target) < NSMaxY(source))
2034 y = NSMaxY(source) - NSMaxY(target);
2035 else if (NSMinY(source) < NSMinY(target))
2036 y = NSMinY(source) - NSMinY(target);
2038 return NSMakeSize(x, y);
2041 // (Private/TestingAPI)
2042 - (ExclusiveAccessBubbleWindowController*)
2043 exclusiveAccessBubbleWindowController {
2044 return exclusiveAccessBubbleWindowController_.get();
2047 - (NSRect)omniboxPopupAnchorRect {
2048 // Start with toolbar rect.
2049 NSView* toolbarView = [toolbarController_ view];
2050 NSRect anchorRect = [toolbarView frame];
2052 // Adjust to account for height and possible bookmark bar. Compress by 1
2053 // to account for the separator.
2054 anchorRect.origin.y =
2055 NSMaxY(anchorRect) - [toolbarController_ desiredHeightForCompression:1];
2057 // Shift to window base coordinates.
2058 return [[toolbarView superview] convertRect:anchorRect toView:nil];
2061 - (void)sheetDidEnd:(NSWindow*)sheet
2062 returnCode:(NSInteger)code
2063 context:(void*)context {
2064 [sheet orderOut:self];
2067 - (void)executeExtensionCommand:(const std::string&)extension_id
2068 command:(const extensions::Command&)command {
2069 // Global commands are handled by the ExtensionCommandsGlobalRegistry
2071 DCHECK(!command.global());
2072 extension_keybinding_registry_->ExecuteCommand(extension_id,
2073 command.accelerator());
2076 @end // @implementation BrowserWindowController
2078 @implementation BrowserWindowController(Fullscreen)
2080 - (void)handleLionToggleFullscreen {
2081 DCHECK(base::mac::IsOSLionOrLater());
2082 chrome::ExecuteCommand(browser_.get(), IDC_FULLSCREEN);
2085 - (void)enterBrowserFullscreenWithToolbar:(BOOL)withToolbar {
2086 if (!chrome::mac::SupportsSystemFullscreen()) {
2087 if (![self isInImmersiveFullscreen])
2088 [self enterImmersiveFullscreen];
2092 if ([self isInAppKitFullscreen]) {
2093 [self updateFullscreenWithToolbar:withToolbar];
2095 // Need to invoke AppKit Fullscreen API. Presentation mode (if set) will
2096 // automatically be enabled in |-windowWillEnterFullScreen:|.
2097 enteringPresentationMode_ = !withToolbar;
2098 [self enterAppKitFullscreen];
2102 - (void)updateFullscreenWithToolbar:(BOOL)withToolbar {
2103 [self adjustUIForSlidingFullscreenStyle:
2104 withToolbar ? fullscreen_mac::OMNIBOX_TABS_PRESENT
2105 : fullscreen_mac::OMNIBOX_TABS_HIDDEN];
2108 - (void)updateFullscreenExitBubbleURL:(const GURL&)url
2109 bubbleType:(ExclusiveAccessBubbleType)bubbleType {
2110 fullscreenUrl_ = url;
2111 exclusiveAccessBubbleType_ = bubbleType;
2112 [self layoutSubviews];
2113 [self showFullscreenExitBubbleIfNecessary];
2116 - (BOOL)isInAnyFullscreenMode {
2117 return [self isInImmersiveFullscreen] || [self isInAppKitFullscreen];
2120 - (BOOL)isInImmersiveFullscreen {
2121 return fullscreenWindow_.get() != nil || enteringImmersiveFullscreen_;
2124 - (BOOL)isInAppKitFullscreen {
2125 return ([[self window] styleMask] & NSFullScreenWindowMask) ==
2126 NSFullScreenWindowMask ||
2127 enteringAppKitFullscreen_;
2130 - (void)enterExtensionFullscreenForURL:(const GURL&)url
2131 bubbleType:(ExclusiveAccessBubbleType)bubbleType {
2132 if (chrome::mac::SupportsSystemFullscreen()) {
2133 fullscreenUrl_ = url;
2134 exclusiveAccessBubbleType_ = bubbleType;
2135 [self enterBrowserFullscreenWithToolbar:NO];
2137 [self enterImmersiveFullscreen];
2138 DCHECK(!url.is_empty());
2139 [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType];
2143 - (void)enterWebContentFullscreenForURL:(const GURL&)url
2144 bubbleType:(ExclusiveAccessBubbleType)bubbleType {
2145 [self enterImmersiveFullscreen];
2146 if (!url.is_empty())
2147 [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType];
2150 - (void)exitAnyFullscreen {
2151 // TODO(erikchen): Fullscreen modes should stack. Should be able to exit
2152 // Immersive Fullscreen and still be in AppKit Fullscreen.
2153 if ([self isInAppKitFullscreen])
2154 [self exitAppKitFullscreen];
2155 if ([self isInImmersiveFullscreen])
2156 [self exitImmersiveFullscreen];
2159 - (BOOL)inPresentationMode {
2160 return presentationModeController_.get() &&
2161 [presentationModeController_ inPresentationMode] &&
2162 presentationModeController_.get().slidingStyle ==
2163 fullscreen_mac::OMNIBOX_TABS_HIDDEN;
2166 - (void)resizeFullscreenWindow {
2167 DCHECK([self isInAnyFullscreenMode]);
2168 if (![self isInAnyFullscreenMode])
2171 NSWindow* window = [self window];
2172 [window setFrame:[[window screen] frame] display:YES];
2173 [self layoutSubviews];
2176 - (BOOL)isBarVisibilityLockedForOwner:(id)owner {
2178 DCHECK(barVisibilityLocks_);
2179 return [barVisibilityLocks_ containsObject:owner];
2182 - (void)lockBarVisibilityForOwner:(id)owner
2183 withAnimation:(BOOL)animate
2185 if (![self isBarVisibilityLockedForOwner:owner]) {
2186 [barVisibilityLocks_ addObject:owner];
2188 // If enabled, show the overlay if necessary (and if in presentation mode).
2189 if (barVisibilityUpdatesEnabled_) {
2190 [presentationModeController_ ensureOverlayShownWithAnimation:animate
2196 - (void)releaseBarVisibilityForOwner:(id)owner
2197 withAnimation:(BOOL)animate
2199 if ([self isBarVisibilityLockedForOwner:owner]) {
2200 [barVisibilityLocks_ removeObject:owner];
2202 // If enabled, hide the overlay if necessary (and if in presentation mode).
2203 if (barVisibilityUpdatesEnabled_ &&
2204 ![barVisibilityLocks_ count]) {
2205 [presentationModeController_ ensureOverlayHiddenWithAnimation:animate
2211 - (BOOL)floatingBarHasFocus {
2212 NSResponder* focused = [[self window] firstResponder];
2213 return [focused isKindOfClass:[AutocompleteTextFieldEditor class]];
2216 @end // @implementation BrowserWindowController(Fullscreen)
2219 @implementation BrowserWindowController(WindowType)
2221 - (BOOL)supportsWindowFeature:(int)feature {
2222 return browser_->SupportsWindowFeature(
2223 static_cast<Browser::WindowFeature>(feature));
2226 - (BOOL)hasTitleBar {
2227 return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR];
2230 - (BOOL)hasToolbar {
2231 return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR];
2234 - (BOOL)hasLocationBar {
2235 return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR];
2238 - (BOOL)supportsBookmarkBar {
2239 return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR];
2242 - (BOOL)isTabbedWindow {
2243 return browser_->is_type_tabbed();
2246 @end // @implementation BrowserWindowController(WindowType)