File: chrome_command_dispatcher_delegate.mm

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (183 lines) | stat: -rw-r--r-- 7,819 bytes parent folder | download | duplicates (4)
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