• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2011 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#include "chrome/browser/ui/cocoa/browser_window_cocoa.h"
6
7#include "base/command_line.h"
8#include "base/logging.h"
9#include "base/message_loop.h"
10#include "base/sys_string_conversions.h"
11#include "chrome/app/chrome_command_ids.h"
12#include "chrome/browser/bookmarks/bookmark_utils.h"
13#include "chrome/browser/download/download_shelf.h"
14#include "chrome/browser/global_keyboard_shortcuts_mac.h"
15#include "chrome/browser/page_info_window.h"
16#include "chrome/browser/prefs/pref_service.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/sidebar/sidebar_container.h"
19#include "chrome/browser/sidebar/sidebar_manager.h"
20#include "chrome/browser/ui/browser.h"
21#include "chrome/browser/ui/browser_list.h"
22#import "chrome/browser/ui/cocoa/browser_window_controller.h"
23#import "chrome/browser/ui/cocoa/bug_report_window_controller.h"
24#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
25#import "chrome/browser/ui/cocoa/content_settings/collected_cookies_mac.h"
26#import "chrome/browser/ui/cocoa/download/download_shelf_controller.h"
27#import "chrome/browser/ui/cocoa/html_dialog_window_controller.h"
28#import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
29#import "chrome/browser/ui/cocoa/nsmenuitem_additions.h"
30#include "chrome/browser/ui/cocoa/repost_form_warning_mac.h"
31#include "chrome/browser/ui/cocoa/restart_browser.h"
32#include "chrome/browser/ui/cocoa/status_bubble_mac.h"
33#include "chrome/browser/ui/cocoa/task_manager_mac.h"
34#import "chrome/browser/ui/cocoa/theme_install_bubble_view.h"
35#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
36#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
37#include "chrome/common/pref_names.h"
38#include "content/browser/tab_contents/tab_contents.h"
39#include "content/common/native_web_keyboard_event.h"
40#include "content/common/notification_service.h"
41#include "grit/chromium_strings.h"
42#include "grit/generated_resources.h"
43#include "ui/base/l10n/l10n_util_mac.h"
44#include "ui/gfx/rect.h"
45
46BrowserWindowCocoa::BrowserWindowCocoa(Browser* browser,
47                                       BrowserWindowController* controller,
48                                       NSWindow* window)
49  : browser_(browser),
50    controller_(controller),
51    confirm_close_factory_(browser) {
52  // This pref applies to all windows, so all must watch for it.
53  registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
54                 NotificationService::AllSources());
55  registrar_.Add(this, NotificationType::SIDEBAR_CHANGED,
56                 NotificationService::AllSources());
57}
58
59BrowserWindowCocoa::~BrowserWindowCocoa() {
60}
61
62void BrowserWindowCocoa::Show() {
63  // The Browser associated with this browser window must become the active
64  // browser at the time |Show()| is called. This is the natural behaviour under
65  // Windows, but |-makeKeyAndOrderFront:| won't send |-windowDidBecomeMain:|
66  // until we return to the runloop. Therefore any calls to
67  // |BrowserList::GetLastActive()| (for example, in bookmark_util), will return
68  // the previous browser instead if we don't explicitly set it here.
69  BrowserList::SetLastActive(browser_);
70
71  [window() makeKeyAndOrderFront:controller_];
72}
73
74void BrowserWindowCocoa::ShowInactive() {
75    [window() orderFront:controller_];
76}
77
78void BrowserWindowCocoa::SetBounds(const gfx::Rect& bounds) {
79  SetFullscreen(false);
80  NSRect cocoa_bounds = NSMakeRect(bounds.x(), 0, bounds.width(),
81                                   bounds.height());
82  // Flip coordinates based on the primary screen.
83  NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
84  cocoa_bounds.origin.y =
85      [screen frame].size.height - bounds.height() - bounds.y();
86
87  [window() setFrame:cocoa_bounds display:YES];
88}
89
90// Callers assume that this doesn't immediately delete the Browser object.
91// The controller implementing the window delegate methods called from
92// |-performClose:| must take precautions to ensure that.
93void BrowserWindowCocoa::Close() {
94  // If there is an overlay window, we contain a tab being dragged between
95  // windows. Don't hide the window as it makes the UI extra confused. We can
96  // still close the window, as that will happen when the drag completes.
97  if ([controller_ overlayWindow]) {
98    [controller_ deferPerformClose];
99  } else {
100    // Make sure we hide the window immediately. Even though performClose:
101    // calls orderOut: eventually, it leaves the window on-screen long enough
102    // that we start to see tabs shutting down. http://crbug.com/23959
103    // TODO(viettrungluu): This is kind of bad, since |-performClose:| calls
104    // |-windowShouldClose:| (on its delegate, which is probably the
105    // controller) which may return |NO| causing the window to not be closed,
106    // thereby leaving a hidden window. In fact, our window-closing procedure
107    // involves a (indirect) recursion on |-performClose:|, which is also bad.
108    [window() orderOut:controller_];
109    [window() performClose:controller_];
110  }
111}
112
113void BrowserWindowCocoa::Activate() {
114  [controller_ activate];
115}
116
117void BrowserWindowCocoa::Deactivate() {
118  // TODO(jcivelli): http://crbug.com/51364 Implement me.
119  NOTIMPLEMENTED();
120}
121
122void BrowserWindowCocoa::FlashFrame() {
123  [NSApp requestUserAttention:NSInformationalRequest];
124}
125
126bool BrowserWindowCocoa::IsActive() const {
127  return [window() isKeyWindow];
128}
129
130gfx::NativeWindow BrowserWindowCocoa::GetNativeHandle() {
131  return window();
132}
133
134BrowserWindowTesting* BrowserWindowCocoa::GetBrowserWindowTesting() {
135  return NULL;
136}
137
138StatusBubble* BrowserWindowCocoa::GetStatusBubble() {
139  return [controller_ statusBubble];
140}
141
142void BrowserWindowCocoa::ToolbarSizeChanged(bool is_animating) {
143  // According to beng, this is an ugly method that comes from the days when the
144  // download shelf was a ChromeView attached to the TabContents, and as its
145  // size changed via animation it notified through TCD/etc to the browser view
146  // to relayout for each tick of the animation. We don't need anything of the
147  // sort on Mac.
148}
149
150void BrowserWindowCocoa::UpdateTitleBar() {
151  NSString* newTitle =
152      base::SysUTF16ToNSString(browser_->GetWindowTitleForCurrentTab());
153
154  // Work around Cocoa bug: if a window changes title during the tracking of the
155  // Window menu it doesn't display well and the constant re-sorting of the list
156  // makes it difficult for the user to pick the desired window. Delay window
157  // title updates until the default run-loop mode.
158
159  if (pending_window_title_.get())
160    [[NSRunLoop currentRunLoop]
161        cancelPerformSelector:@selector(setTitle:)
162                       target:window()
163                     argument:pending_window_title_.get()];
164
165  pending_window_title_.reset([newTitle copy]);
166  [[NSRunLoop currentRunLoop]
167      performSelector:@selector(setTitle:)
168               target:window()
169             argument:newTitle
170                order:0
171                modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
172}
173
174void BrowserWindowCocoa::ShelfVisibilityChanged() {
175  // Mac doesn't yet support showing the bookmark bar at a different size on
176  // the new tab page. When it does, this method should attempt to relayout the
177  // bookmark bar/extension shelf as their preferred height may have changed.
178  // http://crbug.com/43346
179}
180
181void BrowserWindowCocoa::UpdateDevTools() {
182  [controller_ updateDevToolsForContents:
183      browser_->GetSelectedTabContents()];
184}
185
186void BrowserWindowCocoa::UpdateLoadingAnimations(bool should_animate) {
187  // Do nothing on Mac.
188}
189
190void BrowserWindowCocoa::SetStarredState(bool is_starred) {
191  [controller_ setStarredState:is_starred ? YES : NO];
192}
193
194gfx::Rect BrowserWindowCocoa::GetRestoredBounds() const {
195  // Flip coordinates based on the primary screen.
196  NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
197  NSRect frame = [controller_ regularWindowFrame];
198  gfx::Rect bounds(frame.origin.x, 0, frame.size.width, frame.size.height);
199  bounds.set_y([screen frame].size.height - frame.origin.y - frame.size.height);
200  return bounds;
201}
202
203gfx::Rect BrowserWindowCocoa::GetBounds() const {
204  return GetRestoredBounds();
205}
206
207bool BrowserWindowCocoa::IsMaximized() const {
208  return [window() isZoomed];
209}
210
211void BrowserWindowCocoa::SetFullscreen(bool fullscreen) {
212  [controller_ setFullscreen:fullscreen];
213}
214
215bool BrowserWindowCocoa::IsFullscreen() const {
216  return !![controller_ isFullscreen];
217}
218
219bool BrowserWindowCocoa::IsFullscreenBubbleVisible() const {
220  return false;
221}
222
223void BrowserWindowCocoa::ConfirmAddSearchProvider(
224    const TemplateURL* template_url,
225    Profile* profile) {
226  NOTIMPLEMENTED();
227}
228
229LocationBar* BrowserWindowCocoa::GetLocationBar() const {
230  return [controller_ locationBarBridge];
231}
232
233void BrowserWindowCocoa::SetFocusToLocationBar(bool select_all) {
234  [controller_ focusLocationBar:select_all ? YES : NO];
235}
236
237void BrowserWindowCocoa::UpdateReloadStopState(bool is_loading, bool force) {
238  [controller_ setIsLoading:is_loading force:force];
239}
240
241void BrowserWindowCocoa::UpdateToolbar(TabContentsWrapper* contents,
242                                       bool should_restore_state) {
243  [controller_ updateToolbarWithContents:contents->tab_contents()
244                      shouldRestoreState:should_restore_state ? YES : NO];
245}
246
247void BrowserWindowCocoa::FocusToolbar() {
248  // Not needed on the Mac.
249}
250
251void BrowserWindowCocoa::FocusAppMenu() {
252  // Chrome uses the standard Mac OS X menu bar, so this isn't needed.
253}
254
255void BrowserWindowCocoa::RotatePaneFocus(bool forwards) {
256  // Not needed on the Mac.
257}
258
259void BrowserWindowCocoa::FocusBookmarksToolbar() {
260  // Not needed on the Mac.
261}
262
263void BrowserWindowCocoa::FocusChromeOSStatus() {
264  // Not needed on the Mac.
265}
266
267bool BrowserWindowCocoa::IsBookmarkBarVisible() const {
268  return (browser_->profile()->GetPrefs()->GetBoolean(
269              prefs::kShowBookmarkBar) &&
270          browser_->profile()->GetPrefs()->GetBoolean(
271              prefs::kEnableBookmarkBar));
272}
273
274bool BrowserWindowCocoa::IsBookmarkBarAnimating() const {
275  return [controller_ isBookmarkBarAnimating];
276}
277
278bool BrowserWindowCocoa::IsTabStripEditable() const {
279  return ![controller_ isDragSessionActive];
280}
281
282bool BrowserWindowCocoa::IsToolbarVisible() const {
283  return browser_->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) ||
284         browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR);
285}
286
287// This is called from Browser, which in turn is called directly from
288// a menu option.  All we do here is set a preference.  The act of
289// setting the preference sends notifications to all windows who then
290// know what to do.
291void BrowserWindowCocoa::ToggleBookmarkBar() {
292  bookmark_utils::ToggleWhenVisible(browser_->profile());
293}
294
295void BrowserWindowCocoa::AddFindBar(
296    FindBarCocoaController* find_bar_cocoa_controller) {
297  return [controller_ addFindBar:find_bar_cocoa_controller];
298}
299
300void BrowserWindowCocoa::ShowAboutChromeDialog() {
301  // Go through AppController's implementation to bring up the branded panel.
302  [[NSApp delegate] orderFrontStandardAboutPanel:nil];
303}
304
305void BrowserWindowCocoa::ShowUpdateChromeDialog() {
306  restart_browser::RequestRestart(window());
307}
308
309void BrowserWindowCocoa::ShowTaskManager() {
310  TaskManagerMac::Show(false);
311}
312
313void BrowserWindowCocoa::ShowBackgroundPages() {
314  TaskManagerMac::Show(true);
315}
316
317void BrowserWindowCocoa::ShowBookmarkBubble(const GURL& url,
318                                            bool already_bookmarked) {
319  [controller_ showBookmarkBubbleForURL:url
320                      alreadyBookmarked:(already_bookmarked ? YES : NO)];
321}
322
323bool BrowserWindowCocoa::IsDownloadShelfVisible() const {
324  return [controller_ isDownloadShelfVisible] != NO;
325}
326
327DownloadShelf* BrowserWindowCocoa::GetDownloadShelf() {
328  DownloadShelfController* shelfController = [controller_ downloadShelf];
329  return [shelfController bridge];
330}
331
332void BrowserWindowCocoa::ShowRepostFormWarningDialog(
333    TabContents* tab_contents) {
334  RepostFormWarningMac::Create(GetNativeHandle(), tab_contents);
335}
336
337void BrowserWindowCocoa::ShowCollectedCookiesDialog(TabContents* tab_contents) {
338  // Deletes itself on close.
339  new CollectedCookiesMac(GetNativeHandle(), tab_contents);
340}
341
342void BrowserWindowCocoa::ShowThemeInstallBubble() {
343  ThemeInstallBubbleView::Show(window());
344}
345
346// We allow closing the window here since the real quit decision on Mac is made
347// in [AppController quit:].
348void BrowserWindowCocoa::ConfirmBrowserCloseWithPendingDownloads() {
349  // Call InProgressDownloadResponse asynchronously to avoid a crash when the
350  // browser window is closed here (http://crbug.com/44454).
351  MessageLoop::current()->PostTask(
352      FROM_HERE,
353      confirm_close_factory_.NewRunnableMethod(
354          &Browser::InProgressDownloadResponse,
355          true));
356}
357
358void BrowserWindowCocoa::ShowHTMLDialog(HtmlDialogUIDelegate* delegate,
359                                        gfx::NativeWindow parent_window) {
360  [HtmlDialogWindowController showHtmlDialog:delegate
361                                     profile:browser_->profile()];
362}
363
364void BrowserWindowCocoa::UserChangedTheme() {
365  [controller_ userChangedTheme];
366}
367
368int BrowserWindowCocoa::GetExtraRenderViewHeight() const {
369  // Currently this is only used on linux.
370  return 0;
371}
372
373void BrowserWindowCocoa::TabContentsFocused(TabContents* tab_contents) {
374  NOTIMPLEMENTED();
375}
376
377void BrowserWindowCocoa::ShowPageInfo(Profile* profile,
378                                      const GURL& url,
379                                      const NavigationEntry::SSLStatus& ssl,
380                                      bool show_history) {
381  browser::ShowPageInfoBubble(window(), profile, url, ssl, show_history);
382}
383
384void BrowserWindowCocoa::ShowAppMenu() {
385  // No-op. Mac doesn't support showing the menus via alt keys.
386}
387
388bool BrowserWindowCocoa::PreHandleKeyboardEvent(
389    const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) {
390  if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char)
391    return false;
392
393  DCHECK(event.os_event != NULL);
394  int id = GetCommandId(event);
395  if (id == -1)
396    return false;
397
398  if (browser_->IsReservedCommandOrKey(id, event))
399    return HandleKeyboardEventInternal(event.os_event);
400
401  DCHECK(is_keyboard_shortcut != NULL);
402  *is_keyboard_shortcut = true;
403
404  return false;
405}
406
407void BrowserWindowCocoa::HandleKeyboardEvent(
408    const NativeWebKeyboardEvent& event) {
409  if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char)
410    return;
411
412  DCHECK(event.os_event != NULL);
413  HandleKeyboardEventInternal(event.os_event);
414}
415
416@interface MenuWalker : NSObject
417+ (NSMenuItem*)itemForKeyEquivalent:(NSEvent*)key
418                               menu:(NSMenu*)menu;
419@end
420
421@implementation MenuWalker
422+ (NSMenuItem*)itemForKeyEquivalent:(NSEvent*)key
423                               menu:(NSMenu*)menu {
424  NSMenuItem* result = nil;
425
426  for (NSMenuItem *item in [menu itemArray]) {
427    NSMenu* submenu = [item submenu];
428    if (submenu) {
429      if (submenu != [NSApp servicesMenu])
430        result = [self itemForKeyEquivalent:key
431                                       menu:submenu];
432    } else if ([item cr_firesForKeyEventIfEnabled:key]) {
433      result = item;
434    }
435
436    if (result)
437      break;
438  }
439
440  return result;
441}
442@end
443
444int BrowserWindowCocoa::GetCommandId(const NativeWebKeyboardEvent& event) {
445  if ([event.os_event type] != NSKeyDown)
446    return -1;
447
448  // Look in menu.
449  NSMenuItem* item = [MenuWalker itemForKeyEquivalent:event.os_event
450                                                 menu:[NSApp mainMenu]];
451
452  if (item && [item action] == @selector(commandDispatch:) && [item tag] > 0)
453    return [item tag];
454
455  // "Close window" doesn't use the |commandDispatch:| mechanism. Menu items
456  // that do not correspond to IDC_ constants need no special treatment however,
457  // as they can't be blacklisted in |Browser::IsReservedCommandOrKey()| anyhow.
458  if (item && [item action] == @selector(performClose:))
459    return IDC_CLOSE_WINDOW;
460
461  // "Exit" doesn't use the |commandDispatch:| mechanism either.
462  if (item && [item action] == @selector(terminate:))
463    return IDC_EXIT;
464
465  // Look in secondary keyboard shortcuts.
466  NSUInteger modifiers = [event.os_event modifierFlags];
467  const bool cmdKey = (modifiers & NSCommandKeyMask) != 0;
468  const bool shiftKey = (modifiers & NSShiftKeyMask) != 0;
469  const bool cntrlKey = (modifiers & NSControlKeyMask) != 0;
470  const bool optKey = (modifiers & NSAlternateKeyMask) != 0;
471  const int keyCode = [event.os_event keyCode];
472  const unichar keyChar = KeyCharacterForEvent(event.os_event);
473
474  int cmdNum = CommandForWindowKeyboardShortcut(
475      cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar);
476  if (cmdNum != -1)
477    return cmdNum;
478
479  cmdNum = CommandForBrowserKeyboardShortcut(
480      cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar);
481  if (cmdNum != -1)
482    return cmdNum;
483
484  return -1;
485}
486
487bool BrowserWindowCocoa::HandleKeyboardEventInternal(NSEvent* event) {
488  ChromeEventProcessingWindow* event_window =
489      static_cast<ChromeEventProcessingWindow*>(window());
490  DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]);
491
492  // Do not fire shortcuts on key up.
493  if ([event type] == NSKeyDown) {
494    // Send the event to the menu before sending it to the browser/window
495    // shortcut handling, so that if a user configures cmd-left to mean
496    // "previous tab", it takes precedence over the built-in "history back"
497    // binding. Other than that, the |-redispatchKeyEvent:| call would take care
498    // of invoking the original menu item shortcut as well.
499
500    if ([[NSApp mainMenu] performKeyEquivalent:event])
501      return true;
502
503    if ([event_window handleExtraBrowserKeyboardShortcut:event])
504      return true;
505
506    if ([event_window handleExtraWindowKeyboardShortcut:event])
507      return true;
508
509    if ([event_window handleDelayedWindowKeyboardShortcut:event])
510      return true;
511  }
512
513  return [event_window redispatchKeyEvent:event];
514}
515
516void BrowserWindowCocoa::ShowCreateWebAppShortcutsDialog(
517    TabContentsWrapper* tab_contents) {
518  NOTIMPLEMENTED();
519}
520
521void BrowserWindowCocoa::ShowCreateChromeAppShortcutsDialog(
522    Profile* profile, const Extension* app) {
523  NOTIMPLEMENTED();
524}
525
526void BrowserWindowCocoa::Cut() {
527  [NSApp sendAction:@selector(cut:) to:nil from:nil];
528}
529
530void BrowserWindowCocoa::Copy() {
531  [NSApp sendAction:@selector(copy:) to:nil from:nil];
532}
533
534void BrowserWindowCocoa::Paste() {
535  [NSApp sendAction:@selector(paste:) to:nil from:nil];
536}
537
538void BrowserWindowCocoa::ToggleTabStripMode() {
539  [controller_ toggleTabStripDisplayMode];
540}
541
542void BrowserWindowCocoa::OpenTabpose() {
543  [controller_ openTabpose];
544}
545
546void BrowserWindowCocoa::PrepareForInstant() {
547  // TODO: implement fade as done on windows.
548}
549
550void BrowserWindowCocoa::ShowInstant(TabContentsWrapper* preview) {
551  [controller_ showInstant:preview->tab_contents()];
552}
553
554void BrowserWindowCocoa::HideInstant(bool instant_is_active) {
555  [controller_ hideInstant];
556
557  // TODO: add support for |instant_is_active|.
558}
559
560gfx::Rect BrowserWindowCocoa::GetInstantBounds() {
561  // Flip coordinates based on the primary screen.
562  NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
563  NSRect monitorFrame = [screen frame];
564  NSRect frame = [controller_ instantFrame];
565  gfx::Rect bounds(NSRectToCGRect(frame));
566  bounds.set_y(NSHeight(monitorFrame) - bounds.y() - bounds.height());
567  return bounds;
568}
569
570void BrowserWindowCocoa::Observe(NotificationType type,
571                                 const NotificationSource& source,
572                                 const NotificationDetails& details) {
573  switch (type.value) {
574    // Only the key window gets a direct toggle from the menu.
575    // Other windows hear about it from the notification.
576    case NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED:
577      [controller_ updateBookmarkBarVisibilityWithAnimation:YES];
578      break;
579    case NotificationType::SIDEBAR_CHANGED:
580      UpdateSidebarForContents(
581          Details<SidebarContainer>(details)->tab_contents());
582      break;
583    default:
584      NOTREACHED();  // we don't ask for anything else!
585      break;
586  }
587}
588
589void BrowserWindowCocoa::DestroyBrowser() {
590  [controller_ destroyBrowser];
591
592  // at this point the controller is dead (autoreleased), so
593  // make sure we don't try to reference it any more.
594}
595
596NSWindow* BrowserWindowCocoa::window() const {
597  return [controller_ window];
598}
599
600void BrowserWindowCocoa::UpdateSidebarForContents(TabContents* tab_contents) {
601  if (tab_contents == browser_->GetSelectedTabContents()) {
602    [controller_ updateSidebarForContents:tab_contents];
603  }
604}
605