1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/chrome_command_dispatcher_delegate.h"
#include "base/apple/owned_objc.h"
#include "base/check.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/global_keyboard_shortcuts_mac.h"
#include "components/input/native_web_keyboard_event.h"
#include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
#include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h"
#include "ui/base/accelerators/accelerator_manager.h"
#import "ui/base/cocoa/nsmenu_additions.h"
#include "ui/content_accelerators/accelerator_util.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/widget/widget.h"
@implementation ChromeCommandDispatcherDelegate
- (BOOL)eventHandledByViewsFocusManager:(NSEvent*)event
priority:
(ui::AcceleratorManager::HandlerPriority)
priority {
NSWindow* window = [event window];
if (!window) {
return NO;
}
// Logic for handling Views windows.
//
// There are 2 ways for extensions to register accelerators in Views:
// 1) As regular extension commands, see ExtensionKeybindingRegistryViews.
// This always has high priority.
// 2) As page/browser popup actions, see
// ExtensionActionPlatformDelegateViews. This always has high priority.
//
// The only reasonable way to access the registered accelerators for (1) and
// (2) is to use the FocusManager. That is what we do here. But that will also
// trigger any other sources of registered accelerators. This is actually
// desired.
//
// Note: FocusManager is also given an opportunity to consume the accelerator
// in the RenderWidgetHostView event handling path. That logic doesn't trigger
// when the focused view is not a RenderWidgetHostView, which is why this
// logic is necessary. Duplicating the logic adds a bit of redundant work,
// but doesn't cause problems.
input::NativeWebKeyboardEvent keyboard_event(
(base::apple::OwnedNSEvent(event)));
ui::Accelerator accelerator =
ui::GetAcceleratorFromNativeWebKeyboardEvent(keyboard_event);
auto* bridge =
remote_cocoa::NativeWidgetNSWindowBridge::GetFromNSWindow(window);
bool was_handled = false;
if (bridge) {
bridge->host()->HandleAccelerator(
accelerator,
priority == ui::AcceleratorManager::HandlerPriority::kHighPriority,
&was_handled);
}
return was_handled;
}
- (ui::PerformKeyEquivalentResult)prePerformKeyEquivalent:(NSEvent*)event
window:(NSWindow*)window {
// TODO(erikchen): Detect symbolic hot keys, and force control to be passed
// back to AppKit so that it can handle it correctly.
// https://crbug.com/846893.
NSResponder* responder = [window firstResponder];
if ([responder respondsToSelector:@selector(isKeyLocked:)]) {
if ([(id)responder isKeyLocked:event]) {
return ui::PerformKeyEquivalentResult::kUnhandled;
}
}
if ([self eventHandledByViewsFocusManager:event
priority:ui::AcceleratorManager::
kHighPriority]) {
return ui::PerformKeyEquivalentResult::kHandled;
}
// If this keyEquivalent corresponds to a Chrome command, trigger it directly
// via chrome::ExecuteCommand. We avoid going through the NSMenu for two
// reasons:
// * consistency - some commands are not present in the NSMenu. Furthermore,
// the NSMenu's contents can be dynamically updated, so there's no guarantee
// that passing the event to NSMenu will even do what we think it will do.
// * Avoiding sleeps. By default, the implementation of NSMenu
// performKeyEquivalent: has a nested run loop that spins for 100ms. If we
// avoid that by spinning our task runner in their private mode, there's a
// built in nanosleep. See https://crbug.com/836947#c8.
//
// By not passing the event to AppKit, we do lose out on the brief
// highlighting of the NSMenu.
CommandForKeyEventResult result = CommandForKeyEvent(event);
// Ignore new tab/window events if |event| is a key repeat to prevent
// users from accidentally opening too many empty tabs or windows.
if (event.isARepeat && (result.chrome_command == IDC_NEW_TAB ||
result.chrome_command == IDC_NEW_WINDOW ||
result.chrome_command == IDC_NEW_INCOGNITO_WINDOW)) {
return ui::PerformKeyEquivalentResult::kDrop;
}
if (!result.found()) {
return ui::PerformKeyEquivalentResult::kUnhandled;
}
auto* bridge =
remote_cocoa::NativeWidgetNSWindowBridge::GetFromNSWindow(window);
if (bridge == nullptr) {
return ui::PerformKeyEquivalentResult::kUnhandled;
}
bool will_execute = false;
const bool kIsBeforeFirstResponder = true;
// See if this command will execute on the window bridge side.
bridge->host()->WillExecuteCommand(result.chrome_command,
WindowOpenDisposition::CURRENT_TAB,
kIsBeforeFirstResponder, &will_execute);
// On macOS, command key shortcuts flash the title of their owning menu
// in the menu bar. In Chrome, that doesn't happen for File->New Window,
// File->New Tab, Tab->Select Next Tab and other commands executed on the
// window bridge side. Now that we know the command will be executed by
// the window bridge we'll manually flash the menu title. This also causes
// VoiceOver to speak the command, which wasn't happening before this change.
if (will_execute) {
[NSMenu flashMenuForChromeCommand:result.chrome_command];
}
bool was_executed = false;
bridge->host()->ExecuteCommand(result.chrome_command,
WindowOpenDisposition::CURRENT_TAB,
kIsBeforeFirstResponder, &was_executed);
return was_executed ? ui::PerformKeyEquivalentResult::kHandled
: ui::PerformKeyEquivalentResult::kUnhandled;
}
- (ui::PerformKeyEquivalentResult)postPerformKeyEquivalent:(NSEvent*)event
window:(NSWindow*)window
isRedispatch:(BOOL)isRedispatch {
if ([self eventHandledByViewsFocusManager:event
priority:ui::AcceleratorManager::
kNormalPriority]) {
return ui::PerformKeyEquivalentResult::kHandled;
}
CommandForKeyEventResult result = CommandForKeyEvent(event);
if (!result.found() && isRedispatch) {
result.chrome_command = DelayedWebContentsCommandForKeyEvent(event);
result.from_main_menu = false;
}
if (result.found()) {
auto* bridge =
remote_cocoa::NativeWidgetNSWindowBridge::GetFromNSWindow(window);
if (bridge) {
// postPerformKeyEquivalent: is only called on events that are not
// reserved. We want to bypass the main menu if and only if the event is
// reserved. As such, we let all events with main menu keyEquivalents be
// handled by the main menu.
if (result.from_main_menu) {
return ui::PerformKeyEquivalentResult::kPassToMainMenu;
}
bool was_executed = false;
bridge->host()->ExecuteCommand(
result.chrome_command, WindowOpenDisposition::CURRENT_TAB,
/*is_before_first_responder=*/false, &was_executed);
DCHECK(was_executed);
return ui::PerformKeyEquivalentResult::kHandled;
}
}
return ui::PerformKeyEquivalentResult::kUnhandled;
}
@end // ChromeCommandDispatchDelegate
|