File: command_dispatcher.mm

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; 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,806; 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 (262 lines) | stat: -rw-r--r-- 10,779 bytes parent folder | download | duplicates (7)
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
// 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 "ui/base/cocoa/command_dispatcher.h"

#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/trace_event/trace_event.h"
#import "ui/base/cocoa/user_interface_item_command_handler.h"

// Expose -[NSWindow hasKeyAppearance], which determines whether the traffic
// lights on the window are "lit". CommandDispatcher uses this property on a
// parent window to decide whether keys and commands should bubble up.
@interface NSWindow (PrivateAPI)
- (BOOL)hasKeyAppearance;
@end

namespace {

// Duplicate the given key event, but changing the associated window.
NSEvent* KeyEventForWindow(NSWindow* window, NSEvent* event) {
  NSEventType event_type = event.type;

  // Convert the event's location from the original window's coordinates into
  // our own.
  NSPoint location = event.locationInWindow;
  location = [event.window convertPointToScreen:location];
  location = [window convertPointFromScreen:location];

  // Various things *only* apply to key down/up.
  bool is_a_repeat = false;
  NSString* characters = nil;
  NSString* characters_ignoring_modifiers = nil;
  if (event_type == NSEventTypeKeyDown || event_type == NSEventTypeKeyUp) {
    is_a_repeat = event.ARepeat;
    characters = event.characters;
    characters_ignoring_modifiers = event.charactersIgnoringModifiers;
  }

  return [NSEvent keyEventWithType:event_type
                          location:location
                     modifierFlags:event.modifierFlags
                         timestamp:event.timestamp
                      windowNumber:window.windowNumber
                           context:nil
                        characters:characters
       charactersIgnoringModifiers:characters_ignoring_modifiers
                         isARepeat:is_a_repeat
                           keyCode:event.keyCode];
}

}  // namespace

@implementation CommandDispatcher {
  BOOL _eventHandled;
  BOOL _isRedispatchingKeyEvent;

  NSWindow<CommandDispatchingWindow>* __weak _owner;  // Weak, owns us.
}

@synthesize delegate = _delegate;

- (instancetype)initWithOwner:(NSWindow<CommandDispatchingWindow>*)owner {
  if ((self = [super init])) {
    _owner = owner;
  }
  return self;
}

// When an event is being redispatched, its window is rewritten to be the owner_
// of the CommandDispatcher. However, AppKit may still choose to send the event
// to the key window. To check of an event is being redispatched, we check the
// event's window.
- (BOOL)isEventBeingRedispatched:(NSEvent*)event {
  if ([event.window conformsToProtocol:@protocol(CommandDispatchingWindow)]) {
    NSObject<CommandDispatchingWindow>* window =
        static_cast<NSObject<CommandDispatchingWindow>*>(event.window);
    return [window commandDispatcher]->_isRedispatchingKeyEvent;
  }
  return NO;
}

// |_delegate| may be nil in this method. Rather than adding nil checks to every
// call, we rely on the fact that method calls to nil return nil, and that nil
// == ui::PerformKeyEquivalentResult::kUnhandled;
- (BOOL)performKeyEquivalent:(NSEvent*)event {
  // TODO(bokan): Tracing added temporarily to diagnose crbug.com/1039833.
  TRACE_EVENT2("ui", "CommandDispatcher::performKeyEquivalent", "window num",
               _owner.windowNumber, "is keyWin", NSApp.keyWindow == _owner);
  DCHECK_EQ(NSEventTypeKeyDown, event.type);

  // If the event is being redispatched, then this is the second time
  // performKeyEquivalent: is being called on the event. The first time, a
  // WebContents was firstResponder and claimed to have handled the event [but
  // instead sent the event asynchronously to the renderer process]. The
  // renderer process chose not to handle the event, and the consumer
  // redispatched the event by calling -[CommandDispatchingWindow
  // redispatchKeyEvent:].
  //
  // We skip all steps before postPerformKeyEquivalent, since those were already
  // triggered on the first pass of the event.
  if ([self isEventBeingRedispatched:event]) {
    // TODO(bokan): Tracing added temporarily to diagnose crbug.com/1039833.
    TRACE_EVENT_INSTANT0("ui", "IsRedispatch", TRACE_EVENT_SCOPE_THREAD);
    ui::PerformKeyEquivalentResult result =
        [_delegate postPerformKeyEquivalent:event
                                     window:_owner
                               isRedispatch:YES];
    TRACE_EVENT_INSTANT1("ui", "postPerformKeyEquivalent",
                         TRACE_EVENT_SCOPE_THREAD, "result", result);
    if (result == ui::PerformKeyEquivalentResult::kHandled)
      return YES;
    if (result == ui::PerformKeyEquivalentResult::kPassToMainMenu)
      return NO;
    return [_owner.commandDispatchParent performKeyEquivalent:event];
  }

  // First, give the delegate an opportunity to consume this event.
  ui::PerformKeyEquivalentResult result =
      [_delegate prePerformKeyEquivalent:event window:_owner];
  if (result == ui::PerformKeyEquivalentResult::kHandled ||
      result == ui::PerformKeyEquivalentResult::kDrop)
    return YES;
  if (result == ui::PerformKeyEquivalentResult::kPassToMainMenu)
    return NO;

  // Next, pass the event down the NSView hierarchy. Surprisingly, this doesn't
  // use the responder chain. See implementation of -[NSWindow
  // performKeyEquivalent:]. If the view hierarchy contains a
  // RenderWidgetHostViewCocoa, it may choose to return true, and to
  // asynchronously pass the event to the renderer. See
  // -[RenderWidgetHostViewCocoa performKeyEquivalent:].
  if ([_owner defaultPerformKeyEquivalent:event])
    return YES;

  // If the firstResponder [e.g. omnibox] chose not to handle the keyEquivalent,
  // then give the delegate another chance to consume it.
  result =
      [_delegate postPerformKeyEquivalent:event window:_owner isRedispatch:NO];
  if (result == ui::PerformKeyEquivalentResult::kHandled)
    return YES;
  if (result == ui::PerformKeyEquivalentResult::kPassToMainMenu)
    return NO;

  // Allow commands to "bubble up" to CommandDispatchers in parent windows, if
  // they were not handled here.
  return [_owner.commandDispatchParent performKeyEquivalent:event];
}

- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
                       forHandler:(id<UserInterfaceItemCommandHandler>)handler {
  // Since this class implements these selectors, |super| will always say they
  // are enabled. Only use [super] to validate other selectors. If there is no
  // command handler, defer to AppController.
  if (item.action == @selector(commandDispatch:) ||
      item.action == @selector(commandDispatchUsingKeyModifiers:)) {
    if (handler) {
      // -dispatch:.. can't later decide to bubble events because
      // -commandDispatch:.. is assumed to always succeed. So, if there is a
      // |handler|, only validate against that for -commandDispatch:.
      return [handler validateUserInterfaceItem:item window:_owner];
    }

    id appController = [NSApp delegate];
    DCHECK([appController
        respondsToSelector:@selector(validateUserInterfaceItem:)]);
    if ([appController validateUserInterfaceItem:item])
      return YES;
  }

  // -defaultValidateUserInterfaceItem: in most cases will return YES. If
  // there is a command dispatch parent give it a chance to validate the item.
  if (!_owner.commandDispatchParent) {
    // Note this may validate an action bubbled up from a child window. However,
    // if the child window also -respondsToSelector: (but validated it `NO`),
    // the action will be dispatched to the child only, which may NSBeep().
    // TODO(tapted): Fix this. E.g. bubble up validation via the
    // commandDispatchParent's CommandDispatcher rather than the
    // NSUserInterfaceValidations protocol, so that this step can be skipped.
    return [_owner defaultValidateUserInterfaceItem:item];
  }

  return [_owner.commandDispatchParent validateUserInterfaceItem:item];
}

- (BOOL)redispatchKeyEvent:(NSEvent*)event {
  // TODO(bokan): Tracing added temporarily to diagnose crbug.com/1039833.
  TRACE_EVENT2("ui", "CommandDispatcher::redispatchKeyEvent", "window num",
               _owner.windowNumber, "event window num",
               event.window.windowNumber);
  DCHECK(!_isRedispatchingKeyEvent);
  base::AutoReset<BOOL> resetter(&_isRedispatchingKeyEvent, YES);

  DCHECK(event);
  NSEventType eventType = event.type;
  CHECK(eventType == NSEventTypeKeyDown || eventType == NSEventTypeKeyUp ||
        eventType == NSEventTypeFlagsChanged);

  // Sometimes, an event will be redispatched from a child window to a parent
  // window to allow the parent window a chance to handle it. In that case, fix
  // up the native event to reference the correct window. Failure to do this can
  // cause infinite redispatch loops; see https://crbug.com/1085578 for more
  // details.
  if (event.window != _owner) {
    event = KeyEventForWindow(_owner, event);
  }

  // Redispatch the event.
  _eventHandled = YES;
  [NSApp sendEvent:event];

  // If the event was not handled by [NSApp sendEvent:], the preSendEvent:
  // method below will be called, and because the event is being redispatched,
  // |eventHandled_| will be set to NO.
  return _eventHandled;
}

- (BOOL)preSendEvent:(NSEvent*)event {
  // TODO(bokan): Tracing added temporarily to diagnose crbug.com/1039833.
  TRACE_EVENT2("ui", "CommandDispatcher::preSendEvent", "window num",
               _owner.windowNumber, "event window num",
               event.window.windowNumber);

  // AppKit does not call performKeyEquivalent: if the event only has the
  // NSEventModifierFlagOption modifier. However, Chrome wants to treat these
  // events just like keyEquivalents, since they can be consumed by extensions.
  if (event.type == NSEventTypeKeyDown &&
      (event.modifierFlags & NSEventModifierFlagOption)) {
    BOOL handled = [self performKeyEquivalent:event];
    if (handled)
      return YES;
  }

  if ([self isEventBeingRedispatched:event]) {
    // If we get here, then the event was not handled by NSApplication.
    _eventHandled = NO;
    // Return YES to stop native -sendEvent handling.
    return YES;
  }

  return NO;
}

- (void)dispatch:(id)sender
      forHandler:(id<UserInterfaceItemCommandHandler>)handler {
  if (handler)
    [handler commandDispatch:sender window:_owner];
  else
    [_owner.commandDispatchParent commandDispatch:sender];
}

- (void)dispatchUsingKeyModifiers:(id)sender
                       forHandler:(id<UserInterfaceItemCommandHandler>)handler {
  if (handler)
    [handler commandDispatchUsingKeyModifiers:sender window:_owner];
  else
    [_owner.commandDispatchParent commandDispatchUsingKeyModifiers:sender];
}

@end