blob: 6b65a7fe3e87c619989d84a288f24fc4b64d2e4a [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371// Copyright 2014 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.
Paul Lewis9950e182019-12-16 16:06:074
Tim van der Lippe59052492020-09-08 16:36:155import * as Common from '../common/common.js';
Paul Lewis0fd43712020-01-08 17:07:366import * as Host from '../host/host.js';
Tim van der Lippeee97fa32020-04-23 15:20:567import * as Platform from '../platform/platform.js';
Tim van der Lippe7f2002c2020-09-28 14:54:018import * as Root from '../root/root.js';
Tim van der Lippeaa76aa22020-02-14 14:38:249
Paul Lewis9950e182019-12-16 16:06:0710import {Action} from './Action.js'; // eslint-disable-line no-unused-vars
11import {ActionRegistry} from './ActionRegistry.js'; // eslint-disable-line no-unused-vars
12import {Context} from './Context.js';
13import {Dialog} from './Dialog.js';
Jack Lynch94a9b0c2020-03-11 21:45:1414import {Descriptor, KeyboardShortcut, Modifiers, Type} from './KeyboardShortcut.js'; // eslint-disable-line no-unused-vars
Paul Lewis9950e182019-12-16 16:06:0715import {isEditing} from './UIUtils.js';
16
Tim van der Lippe9c9fb122020-09-08 15:06:1717/** @type {!ShortcutRegistry} */
18let shortcutRegistryInstance;
Sigurd Schneider46da7db2020-05-20 13:45:1119
Paul Lewis9950e182019-12-16 16:06:0720export class ShortcutRegistry {
Blink Reformat4c46d092018-04-07 15:32:3721 /**
Paul Lewis9950e182019-12-16 16:06:0722 * @param {!ActionRegistry} actionRegistry
Blink Reformat4c46d092018-04-07 15:32:3723 */
Jack Lynch94a9b0c2020-03-11 21:45:1424 constructor(actionRegistry) {
Blink Reformat4c46d092018-04-07 15:32:3725 this._actionRegistry = actionRegistry;
Jack Lynch94a9b0c2020-03-11 21:45:1426 /** @type {!Platform.Multimap.<string, !KeyboardShortcut>} */
27 this._actionToShortcut = new Platform.Multimap();
Jack Lynch966040b2020-04-23 20:11:4428 this._keyMap = new ShortcutTreeNode(0, 0);
29 /** @type {?ShortcutTreeNode} */
30 this._activePrefixKey = null;
31 /** @type {?number} */
32 this._activePrefixTimeout = null;
33 /** @type {?function():Promise<void>} */
34 this._consumePrefix = null;
Jack Lynch42297a72020-06-21 01:54:3035 /** @type {!Set.<string>} */
36 this._devToolsDefaultShortcutActions = new Set();
Jack Lynch5f863da2020-09-16 23:18:4237 /** @type {!Platform.Multimap.<string, !KeyboardShortcut>} */
38 this._disabledDefaultShortcutsForAction = new Platform.Multimap();
39 this._keybindSetSetting = Common.Settings.Settings.instance().moduleSetting('activeKeybindSet');
Jack Lynch5f863da2020-09-16 23:18:4240 this._keybindSetSetting.addChangeListener(event => {
Jack Lynch9de6dbb2020-06-04 18:22:1241 Host.userMetrics.keybindSetSettingChanged(event.data);
42 this._registerBindings();
43 });
Jack Lynch5f863da2020-09-16 23:18:4244 this._userShortcutsSetting = Common.Settings.Settings.instance().moduleSetting('userShortcuts');
45 this._userShortcutsSetting.addChangeListener(this._registerBindings, this);
Jack Lynchf1f00fa2020-05-01 22:16:1246
Jack Lynch94a9b0c2020-03-11 21:45:1447 this._registerBindings();
Blink Reformat4c46d092018-04-07 15:32:3748 }
49
50 /**
Tim van der Lippe9c9fb122020-09-08 15:06:1751 * @param {{forceNew: ?boolean, actionRegistry: ?ActionRegistry}} opts
52 */
53 static instance(opts = {forceNew: null, actionRegistry: null}) {
54 const {forceNew, actionRegistry} = opts;
55 if (!shortcutRegistryInstance || forceNew) {
56 if (!actionRegistry) {
57 throw new Error('Missing actionRegistry for shortcutRegistry');
58 }
59 shortcutRegistryInstance = new ShortcutRegistry(actionRegistry);
60 }
61
62 return shortcutRegistryInstance;
63 }
64
65 /**
Blink Reformat4c46d092018-04-07 15:32:3766 * @param {number} key
Jack Lynch966040b2020-04-23 20:11:4467 * @param {!Object.<string, function():Promise.<boolean>>=} handlers
Paul Lewis9950e182019-12-16 16:06:0768 * @return {!Array.<!Action>}
Blink Reformat4c46d092018-04-07 15:32:3769 */
Jack Lynch966040b2020-04-23 20:11:4470 _applicableActions(key, handlers = {}) {
Benedikt Meurer0afca612020-10-05 10:03:5871 /** @type {!Array<string>} */
Jack Lynch966040b2020-04-23 20:11:4472 let actions = [];
73 const keyMap = this._activePrefixKey || this._keyMap;
74 const keyNode = keyMap.getNode(key);
75 if (keyNode) {
76 actions = keyNode.actions();
77 }
Tim van der Lipped1a00aa2020-08-19 16:03:5678 const applicableActions = this._actionRegistry.applicableActions(actions, Context.instance());
Jack Lynch966040b2020-04-23 20:11:4479 if (keyNode) {
80 for (const actionId of Object.keys(handlers)) {
81 if (keyNode.actions().indexOf(actionId) >= 0) {
82 const action = this._actionRegistry.action(actionId);
83 if (action) {
84 applicableActions.push(action);
85 }
86 }
87 }
88 }
89 return applicableActions;
Blink Reformat4c46d092018-04-07 15:32:3790 }
91
92 /**
Jack Lynch575e9fb2020-03-26 22:20:5193 * @param {string} action
94 * @return {!Array.<!KeyboardShortcut>}
95 */
96 shortcutsForAction(action) {
97 return [...this._actionToShortcut.get(action)];
98 }
99
100 /**
Jack Lynch5f863da2020-09-16 23:18:42101 * @param {!Array.<!Descriptor>} descriptors
102 */
103 actionsForDescriptors(descriptors) {
Benedikt Meurer0afca612020-10-05 10:03:58104 /** @type {?ShortcutTreeNode} */
Jack Lynch5f863da2020-09-16 23:18:42105 let keyMapNode = this._keyMap;
106 for (const {key} of descriptors) {
107 if (!keyMapNode) {
108 return [];
109 }
110 keyMapNode = keyMapNode.getNode(key);
111 }
112 return keyMapNode ? keyMapNode.actions() : [];
113 }
114
115 /**
Joel Einbinder67f28fb2018-08-02 00:33:47116 * @return {!Array<number>}
117 */
118 globalShortcutKeys() {
119 const keys = [];
Jack Lynch966040b2020-04-23 20:11:44120 for (const node of this._keyMap.chords().values()) {
121 const actions = node.actions();
Tim van der Lipped1a00aa2020-08-19 16:03:56122 const applicableActions = this._actionRegistry.applicableActions(actions, Context.instance());
Jack Lynch966040b2020-04-23 20:11:44123 if (applicableActions.length || node.hasChords()) {
124 keys.push(node.key());
Tim van der Lippe1d6e57a2019-09-30 11:55:34125 }
Joel Einbinder67f28fb2018-08-02 00:33:47126 }
127 return keys;
128 }
129
130 /**
Blink Reformat4c46d092018-04-07 15:32:37131 * @param {!Array.<string>} actionIds
132 * @return {!Array.<number>}
133 */
134 keysForActions(actionIds) {
Jack Lynchb8fb3c72020-04-21 05:36:16135 const keys = actionIds.flatMap(
136 action => [...this._actionToShortcut.get(action)].flatMap(
137 shortcut => shortcut.descriptors.map(descriptor => descriptor.key)));
138 return [...(new Set(keys))];
Blink Reformat4c46d092018-04-07 15:32:37139 }
140
141 /**
142 * @param {string} actionId
143 * @return {string|undefined}
144 */
145 shortcutTitleForAction(actionId) {
Benedikt Meurer0afca612020-10-05 10:03:58146 for (const shortcut of this._actionToShortcut.get(actionId)) {
147 return shortcut.title();
Tim van der Lippe1d6e57a2019-09-30 11:55:34148 }
Benedikt Meurer0afca612020-10-05 10:03:58149 return undefined;
Blink Reformat4c46d092018-04-07 15:32:37150 }
151
152 /**
153 * @param {!KeyboardEvent} event
Jack Lynch966040b2020-04-23 20:11:44154 * @param {!Object.<string, function():Promise.<boolean>>=} handlers
Blink Reformat4c46d092018-04-07 15:32:37155 */
Jack Lynch966040b2020-04-23 20:11:44156 handleShortcut(event, handlers) {
157 this.handleKey(KeyboardShortcut.makeKeyFromEvent(event), event.key, event, handlers);
Blink Reformat4c46d092018-04-07 15:32:37158 }
159
160 /**
Jack Lynch42297a72020-06-21 01:54:30161 * @param {string} actionId
162 * return {boolean}
163 */
164 actionHasDefaultShortcut(actionId) {
165 return this._devToolsDefaultShortcutActions.has(actionId);
166 }
167
168 /**
Blink Reformat4c46d092018-04-07 15:32:37169 * @param {!Element} element
Jack Lynch966040b2020-04-23 20:11:44170 * @param {!Object.<string, function():Promise.<boolean>>} handlers
Jack Lynch726aef92020-10-21 18:14:39171 * @return {function(!Event): void}
Blink Reformat4c46d092018-04-07 15:32:37172 */
Jack Lynch966040b2020-04-23 20:11:44173 addShortcutListener(element, handlers) {
174 // We only want keys for these specific actions to get handled this
Mathias Bynens5165a7a2020-06-10 05:51:43175 // way; all others should be allowed to bubble up.
176 const allowlistKeyMap = new ShortcutTreeNode(0, 0);
Jack Lynch966040b2020-04-23 20:11:44177 const shortcuts = Object.keys(handlers).flatMap(action => [...this._actionToShortcut.get(action)]);
178 shortcuts.forEach(shortcut => {
Mathias Bynens5165a7a2020-06-10 05:51:43179 allowlistKeyMap.addKeyMapping(shortcut.descriptors.map(descriptor => descriptor.key), shortcut.action);
Jack Lynch966040b2020-04-23 20:11:44180 });
181
Jack Lynch726aef92020-10-21 18:14:39182 /**
183 * @param {!Event} event
184 */
185 const listener = event => {
Jack Lynch966040b2020-04-23 20:11:44186 const key = KeyboardShortcut.makeKeyFromEvent(/** @type {!KeyboardEvent} */ (event));
Benedikt Meurer0afca612020-10-05 10:03:58187 const keyMap = this._activePrefixKey ? allowlistKeyMap.getNode(this._activePrefixKey.key()) : allowlistKeyMap;
188 if (!keyMap) {
189 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34190 }
Jack Lynch966040b2020-04-23 20:11:44191 if (keyMap.getNode(key)) {
192 this.handleShortcut(/** @type {!KeyboardEvent} */ (event), handlers);
193 }
Jack Lynch726aef92020-10-21 18:14:39194 };
195 element.addEventListener('keydown', listener);
196 return listener;
Blink Reformat4c46d092018-04-07 15:32:37197 }
198
199 /**
200 * @param {number} key
201 * @param {string} domKey
202 * @param {!KeyboardEvent=} event
Jack Lynch966040b2020-04-23 20:11:44203 * @param {!Object.<string, function():Promise.<boolean>>=} handlers
Blink Reformat4c46d092018-04-07 15:32:37204 */
Jack Lynch966040b2020-04-23 20:11:44205 async handleKey(key, domKey, event, handlers) {
Blink Reformat4c46d092018-04-07 15:32:37206 const keyModifiers = key >> 8;
Jack Lyncha0dd1f02020-05-04 23:33:35207 const hasHandlersOrPrefixKey = !!handlers || !!this._activePrefixKey;
Jack Lynch4a6f7532020-05-07 00:48:09208 const keyMapNode = this._keyMap.getNode(key);
209 const maybeHasActions = this._applicableActions(key, handlers).length > 0 || (keyMapNode && keyMapNode.hasChords());
Jack Lyncha0dd1f02020-05-04 23:33:35210 if ((!hasHandlersOrPrefixKey && isPossiblyInputKey()) || !maybeHasActions ||
Jack Lynch966040b2020-04-23 20:11:44211 KeyboardShortcut.isModifier(KeyboardShortcut.keyCodeAndModifiersFromKey(key).keyCode)) {
Blink Reformat4c46d092018-04-07 15:32:37212 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34213 }
Jack Lyncha0dd1f02020-05-04 23:33:35214 if (event) {
215 event.consume(true);
216 }
217 if (!hasHandlersOrPrefixKey && Dialog.hasInstance()) {
218 return;
219 }
Jack Lynch966040b2020-04-23 20:11:44220
221 if (this._activePrefixTimeout) {
222 clearTimeout(this._activePrefixTimeout);
223 const handled = await maybeExecuteActionForKey.call(this);
224 this._activePrefixKey = null;
225 this._activePrefixTimeout = null;
226 if (handled) {
227 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34228 }
Jack Lynch966040b2020-04-23 20:11:44229 if (this._consumePrefix) {
230 await this._consumePrefix();
231 }
232 }
Jack Lynch966040b2020-04-23 20:11:44233 if (keyMapNode && keyMapNode.hasChords()) {
Jack Lynch966040b2020-04-23 20:11:44234 this._activePrefixKey = keyMapNode;
235 this._consumePrefix = async () => {
236 this._activePrefixKey = null;
237 this._activePrefixTimeout = null;
238 await maybeExecuteActionForKey.call(this);
239 };
Benedikt Meurer0afca612020-10-05 10:03:58240 this._activePrefixTimeout = window.setTimeout(this._consumePrefix, KeyTimeout);
Jack Lynch966040b2020-04-23 20:11:44241 } else {
242 await maybeExecuteActionForKey.call(this);
Blink Reformat4c46d092018-04-07 15:32:37243 }
244
245 /**
246 * @return {boolean}
247 */
248 function isPossiblyInputKey() {
Paul Lewis9950e182019-12-16 16:06:07249 if (!event || !isEditing() || /^F\d+|Control|Shift|Alt|Meta|Escape|Win|U\+001B$/.test(domKey)) {
Blink Reformat4c46d092018-04-07 15:32:37250 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34251 }
Blink Reformat4c46d092018-04-07 15:32:37252
Tim van der Lippe1d6e57a2019-09-30 11:55:34253 if (!keyModifiers) {
Blink Reformat4c46d092018-04-07 15:32:37254 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34255 }
Blink Reformat4c46d092018-04-07 15:32:37256
Paul Lewis9950e182019-12-16 16:06:07257 const modifiers = Modifiers;
Joel Einbindera66e5bf2018-05-31 01:26:37258 // Undo/Redo will also cause input, so textual undo should take precedence over DevTools undo when editing.
Paul Lewis0fd43712020-01-08 17:07:36259 if (Host.Platform.isMac()) {
Paul Lewis9950e182019-12-16 16:06:07260 if (KeyboardShortcut.makeKey('z', modifiers.Meta) === key) {
Joel Einbindera66e5bf2018-05-31 01:26:37261 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34262 }
Paul Lewis9950e182019-12-16 16:06:07263 if (KeyboardShortcut.makeKey('z', modifiers.Meta | modifiers.Shift) === key) {
Joel Einbindera66e5bf2018-05-31 01:26:37264 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34265 }
Joel Einbindera66e5bf2018-05-31 01:26:37266 } else {
Paul Lewis9950e182019-12-16 16:06:07267 if (KeyboardShortcut.makeKey('z', modifiers.Ctrl) === key) {
Joel Einbindera66e5bf2018-05-31 01:26:37268 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34269 }
Paul Lewis9950e182019-12-16 16:06:07270 if (KeyboardShortcut.makeKey('y', modifiers.Ctrl) === key) {
Joel Einbindera66e5bf2018-05-31 01:26:37271 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34272 }
Paul Lewis0fd43712020-01-08 17:07:36273 if (!Host.Platform.isWin() && KeyboardShortcut.makeKey('z', modifiers.Ctrl | modifiers.Shift) === key) {
Joel Einbindera66e5bf2018-05-31 01:26:37274 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34275 }
Joel Einbindera66e5bf2018-05-31 01:26:37276 }
277
Tim van der Lippe1d6e57a2019-09-30 11:55:34278 if ((keyModifiers & (modifiers.Ctrl | modifiers.Alt)) === (modifiers.Ctrl | modifiers.Alt)) {
Paul Lewis0fd43712020-01-08 17:07:36279 return Host.Platform.isWin();
Tim van der Lippe1d6e57a2019-09-30 11:55:34280 }
Blink Reformat4c46d092018-04-07 15:32:37281
282 return !hasModifier(modifiers.Ctrl) && !hasModifier(modifiers.Alt) && !hasModifier(modifiers.Meta);
283 }
284
285 /**
286 * @param {number} mod
287 * @return {boolean}
288 */
289 function hasModifier(mod) {
290 return !!(keyModifiers & mod);
291 }
Jack Lynch966040b2020-04-23 20:11:44292
293 /**
294 * @return {!Promise.<boolean>};
295 * @this {!ShortcutRegistry}
296 */
297 async function maybeExecuteActionForKey() {
298 const actions = this._applicableActions(key, handlers);
299 if (!actions.length) {
300 return false;
301 }
Jack Lynch966040b2020-04-23 20:11:44302 for (const action of actions) {
303 let handled;
304 if (handlers && handlers[action.id()]) {
305 handled = await handlers[action.id()]();
306 }
307 if (!handlers) {
308 handled = await action.execute();
309 }
310 if (handled) {
311 Host.userMetrics.keyboardShortcutFired(action.id());
312 return true;
313 }
314 }
315 return false;
316 }
Blink Reformat4c46d092018-04-07 15:32:37317 }
318
319 /**
Jack Lynch94a9b0c2020-03-11 21:45:14320 * @param {!KeyboardShortcut} shortcut
Blink Reformat4c46d092018-04-07 15:32:37321 */
Jack Lynch5f863da2020-09-16 23:18:42322 registerUserShortcut(shortcut) {
323 for (const otherShortcut of this._disabledDefaultShortcutsForAction.get(shortcut.action)) {
324 if (otherShortcut.descriptorsMatch(shortcut.descriptors) &&
325 otherShortcut.hasKeybindSet(this._keybindSetSetting.get())) {
326 // this user shortcut is the same as a disabled default shortcut,
327 // so we should just enable the default
328 this.removeShortcut(otherShortcut);
329 return;
330 }
331 }
332 for (const otherShortcut of this._actionToShortcut.get(shortcut.action)) {
333 if (otherShortcut.descriptorsMatch(shortcut.descriptors) &&
334 otherShortcut.hasKeybindSet(this._keybindSetSetting.get())) {
335 // don't allow duplicate shortcuts
336 return;
337 }
338 }
339 this._addShortcutToSetting(shortcut);
340 }
341
342 /**
343 * @param {!KeyboardShortcut} shortcut
344 */
345 removeShortcut(shortcut) {
346 if (shortcut.type === Type.DefaultShortcut || shortcut.type === Type.KeybindSetShortcut) {
347 this._addShortcutToSetting(shortcut.changeType(Type.DisabledDefault));
348 } else {
349 this._removeShortcutFromSetting(shortcut);
350 }
351 }
352
353 /**
Jack Lynchfa6e27a2020-10-26 19:02:05354 * @param {string} actionId
355 * @return {!Set.<!KeyboardShortcut>}
356 */
357 disabledDefaultsForAction(actionId) {
358 return this._disabledDefaultShortcutsForAction.get(actionId);
359 }
360
361 /**
Jack Lynch5f863da2020-09-16 23:18:42362 * @param {!KeyboardShortcut} shortcut
363 */
364 _addShortcutToSetting(shortcut) {
365 const userShortcuts = this._userShortcutsSetting.get();
366 userShortcuts.push(shortcut);
367 this._userShortcutsSetting.set(userShortcuts);
368 }
369
370 /**
371 * @param {!KeyboardShortcut} shortcut
372 */
373 _removeShortcutFromSetting(shortcut) {
374 const userShortcuts = this._userShortcutsSetting.get();
375 const index = userShortcuts.findIndex(shortcut.equals, shortcut);
376 if (index !== -1) {
377 userShortcuts.splice(index, 1);
378 this._userShortcutsSetting.set(userShortcuts);
379 }
380 }
381
382 /**
383 * @param {!KeyboardShortcut} shortcut
384 */
Jack Lynch94a9b0c2020-03-11 21:45:14385 _registerShortcut(shortcut) {
386 this._actionToShortcut.set(shortcut.action, shortcut);
Jack Lynch966040b2020-04-23 20:11:44387 this._keyMap.addKeyMapping(shortcut.descriptors.map(descriptor => descriptor.key), shortcut.action);
Blink Reformat4c46d092018-04-07 15:32:37388 }
389
Jack Lynch94a9b0c2020-03-11 21:45:14390 _registerBindings() {
Jack Lynchf1f00fa2020-05-01 22:16:12391 this._actionToShortcut.clear();
392 this._keyMap.clear();
Jack Lynch5f863da2020-09-16 23:18:42393 const keybindSet = this._keybindSetSetting.get();
Tim van der Lippe7f2002c2020-09-28 14:54:01394 const extensions = Root.Runtime.Runtime.instance().extensions('action');
Jack Lynch5f863da2020-09-16 23:18:42395 this._disabledDefaultShortcutsForAction.clear();
396 this._devToolsDefaultShortcutActions.clear();
Benedikt Meurer0afca612020-10-05 10:03:58397 /** @type {!Array<!{keyCode: number, modifiers: number}>} */
Jack Lynch10d70552020-06-10 20:30:36398 const forwardedKeys = [];
Benedikt Meurer973bb962020-10-05 13:53:34399 if (Root.Runtime.experiments.isEnabled('keyboardShortcutEditor')) {
Benedikt Meurer0afca612020-10-05 10:03:58400 /** @type {!Array<!{action: string, descriptors: !Array.<!Descriptor>, type: !Type}>} */
Jack Lynch5f863da2020-09-16 23:18:42401 const userShortcuts = this._userShortcutsSetting.get();
Benedikt Meurer0afca612020-10-05 10:03:58402 for (const userShortcut of userShortcuts) {
Jack Lynch5f863da2020-09-16 23:18:42403 const shortcut = KeyboardShortcut.createShortcutFromSettingObject(userShortcut);
404 if (shortcut.type === Type.DisabledDefault) {
405 this._disabledDefaultShortcutsForAction.set(shortcut.action, shortcut);
406 } else {
407 if (ForwardedActions.has(shortcut.action)) {
408 forwardedKeys.push(
Benedikt Meurer0afca612020-10-05 10:03:58409 ...shortcut.descriptors.map(descriptor => KeyboardShortcut.keyCodeAndModifiersFromKey(descriptor.key)));
Jack Lynch5f863da2020-09-16 23:18:42410 }
411 this._registerShortcut(shortcut);
412 }
Benedikt Meurer0afca612020-10-05 10:03:58413 }
Jack Lynch5f863da2020-09-16 23:18:42414 }
Blink Reformat4c46d092018-04-07 15:32:37415 extensions.forEach(registerExtension, this);
Jack Lynch10d70552020-06-10 20:30:36416 Host.InspectorFrontendHost.InspectorFrontendHostInstance.setWhitelistedShortcuts(JSON.stringify(forwardedKeys));
Blink Reformat4c46d092018-04-07 15:32:37417
418 /**
Tim van der Lippe99e59b82019-09-30 20:00:59419 * @param {!Root.Runtime.Extension} extension
Tim van der Lippe0830b3d2019-10-03 13:20:07420 * @this {ShortcutRegistry}
Blink Reformat4c46d092018-04-07 15:32:37421 */
422 function registerExtension(extension) {
423 const descriptor = extension.descriptor();
Jack Lynch94a9b0c2020-03-11 21:45:14424 const bindings = descriptor.bindings;
Blink Reformat4c46d092018-04-07 15:32:37425 for (let i = 0; bindings && i < bindings.length; ++i) {
Jack Lynchf1f00fa2020-05-01 22:16:12426 const keybindSets = bindings[i].keybindSets;
427 if (!platformMatches(bindings[i].platform) || !keybindSetsMatch(keybindSets)) {
Blink Reformat4c46d092018-04-07 15:32:37428 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34429 }
Jack Lynch966040b2020-04-23 20:11:44430 const keys = bindings[i].shortcut.split(/\s+/);
431 const shortcutDescriptors = keys.map(KeyboardShortcut.makeDescriptorFromBindingShortcut);
432 if (shortcutDescriptors.length > 0) {
Jack Lynchf1f00fa2020-05-01 22:16:12433 const actionId = /** @type {string} */ (descriptor.actionId);
Jack Lynch5f863da2020-09-16 23:18:42434
435 if (this._isDisabledDefault(shortcutDescriptors, actionId)) {
436 this._devToolsDefaultShortcutActions.add(actionId);
437 continue;
438 }
439
Jack Lynch10d70552020-06-10 20:30:36440 if (ForwardedActions.has(actionId)) {
441 forwardedKeys.push(
442 ...shortcutDescriptors.map(shortcut => KeyboardShortcut.keyCodeAndModifiersFromKey(shortcut.key)));
443 }
Jack Lynchf1f00fa2020-05-01 22:16:12444 if (!keybindSets) {
Jack Lynch42297a72020-06-21 01:54:30445 this._devToolsDefaultShortcutActions.add(actionId);
Jack Lynchf1f00fa2020-05-01 22:16:12446 this._registerShortcut(new KeyboardShortcut(shortcutDescriptors, actionId, Type.DefaultShortcut));
447 } else {
Jack Lynch42297a72020-06-21 01:54:30448 if (keybindSets.includes(DefaultShortcutSetting)) {
449 this._devToolsDefaultShortcutActions.add(actionId);
450 }
Jack Lynchf1f00fa2020-05-01 22:16:12451 this._registerShortcut(
Jack Lynch42297a72020-06-21 01:54:30452 new KeyboardShortcut(shortcutDescriptors, actionId, Type.KeybindSetShortcut, new Set(keybindSets)));
Jack Lynchf1f00fa2020-05-01 22:16:12453 }
Jack Lynchf0d14242020-04-07 22:42:04454 }
Blink Reformat4c46d092018-04-07 15:32:37455 }
456 }
457
458 /**
459 * @param {string=} platformsString
460 * @return {boolean}
461 */
462 function platformMatches(platformsString) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34463 if (!platformsString) {
Blink Reformat4c46d092018-04-07 15:32:37464 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34465 }
Blink Reformat4c46d092018-04-07 15:32:37466 const platforms = platformsString.split(',');
467 let isMatch = false;
Paul Lewis0fd43712020-01-08 17:07:36468 const currentPlatform = Host.Platform.platform();
Tim van der Lippe1d6e57a2019-09-30 11:55:34469 for (let i = 0; !isMatch && i < platforms.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37470 isMatch = platforms[i] === currentPlatform;
Tim van der Lippe1d6e57a2019-09-30 11:55:34471 }
Blink Reformat4c46d092018-04-07 15:32:37472 return isMatch;
473 }
Jack Lynchf1f00fa2020-05-01 22:16:12474
475 /**
476 * @param {!Array<string>=} keybindSets
477 */
478 function keybindSetsMatch(keybindSets) {
479 if (!keybindSets) {
480 return true;
481 }
482 return keybindSets.includes(keybindSet);
483 }
Blink Reformat4c46d092018-04-07 15:32:37484 }
Jack Lynch5f863da2020-09-16 23:18:42485
Benedikt Meurer0afca612020-10-05 10:03:58486 /**
487 * @param {!Array<!{key: number, name: string}>} shortcutDescriptors
488 * @param {string} action
489 */
Jack Lynch5f863da2020-09-16 23:18:42490 _isDisabledDefault(shortcutDescriptors, action) {
491 const disabledDefaults = this._disabledDefaultShortcutsForAction.get(action);
492 for (const disabledDefault of disabledDefaults) {
493 if (disabledDefault.descriptorsMatch(shortcutDescriptors)) {
494 return true;
495 }
496 }
497 return false;
498 }
Tim van der Lippe0830b3d2019-10-03 13:20:07499}
Blink Reformat4c46d092018-04-07 15:32:37500
Jack Lynch966040b2020-04-23 20:11:44501export class ShortcutTreeNode {
502 /**
503 * @param {number} key
504 * @param {number=} depth
505 */
506 constructor(key, depth = 0) {
507 this._key = key;
508 /** @type {!Array.<string>} */
509 this._actions = [];
510 this._chords = new Map();
511 this._depth = depth;
512 }
513
514 /**
515 * @param {string} action
516 */
517 addAction(action) {
518 this._actions.push(action);
519 }
520
521 /**
522 * @return {number}
523 */
524 key() {
525 return this._key;
526 }
527
528 /**
529 * @return {!Map.<number, !ShortcutTreeNode>}
530 */
531 chords() {
532 return this._chords;
533 }
534
535 /**
536 * @return {boolean}
537 */
538 hasChords() {
539 return this._chords.size > 0;
540 }
541
542 /**
543 * @param {!Array.<number>} keys
544 * @param {string} action
545 */
546 addKeyMapping(keys, action) {
547 if (keys.length < this._depth) {
548 return;
549 }
550
551 if (keys.length === this._depth) {
552 this.addAction(action);
553 } else {
554 const key = keys[this._depth];
555 if (!this._chords.has(key)) {
556 this._chords.set(key, new ShortcutTreeNode(key, this._depth + 1));
557 }
558 this._chords.get(key).addKeyMapping(keys, action);
559 }
560 }
561
562 /**
563 * @param {number} key
564 * @return {?ShortcutTreeNode}
565 */
566 getNode(key) {
567 return this._chords.get(key) || null;
568 }
569
570 /**
571 * @return {!Array.<string>}
572 */
573 actions() {
574 return this._actions;
575 }
Jack Lynchf1f00fa2020-05-01 22:16:12576
577 clear() {
578 this._actions = [];
579 this._chords = new Map();
580 }
Jack Lynch966040b2020-04-23 20:11:44581}
582
Sigurd Schneider46da7db2020-05-20 13:45:11583
Tim van der Lippe0830b3d2019-10-03 13:20:07584export class ForwardedShortcut {}
Blink Reformat4c46d092018-04-07 15:32:37585
Tim van der Lippe0830b3d2019-10-03 13:20:07586ForwardedShortcut.instance = new ForwardedShortcut();
Jack Lynch966040b2020-04-23 20:11:44587
Jack Lynch10d70552020-06-10 20:30:36588export const ForwardedActions = new Set([
589 'main.toggle-dock', 'debugger.toggle-breakpoints-active', 'debugger.toggle-pause', 'commandMenu.show', 'console.show'
590]);
Jack Lynch966040b2020-04-23 20:11:44591export const KeyTimeout = 1000;
Jack Lynchf1f00fa2020-05-01 22:16:12592export const DefaultShortcutSetting = 'devToolsDefault';