UNPKG

chromium-bidi

Version:

An implementation of the WebDriver BiDi protocol for Chromium implemented as a JavaScript layer translating between BiDi and CDP, running inside a Chrome tab.

1,429 lines (1,415 loc) 691 kB
(function () { 'use strict'; function mitt(n){return {all:n=n||new Map,on:function(t,e){var i=n.get(t);i?i.push(e):n.set(t,[e]);},off:function(t,e){var i=n.get(t);i&&(e?i.splice(i.indexOf(e)>>>0,1):n.set(t,[]));},emit:function(t,e){var i=n.get(t);i&&i.slice().map(function(n){n(e);}),(i=n.get("*"))&&i.slice().map(function(n){n(t,e);});}}} /** * Copyright 2022 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class EventEmitter { #emitter = mitt(); on(type, handler) { this.#emitter.on(type, handler); return this; } once(event, handler) { const onceHandler = (eventData) => { handler(eventData); this.off(event, onceHandler); }; return this.on(event, onceHandler); } off(type, handler) { this.#emitter.off(type, handler); return this; } emit(event, eventData) { this.#emitter.emit(event, eventData); } removeAllListeners(event) { if (event) { this.#emitter.all.delete(event); } else { this.#emitter.all.clear(); } return this; } } /** * Copyright 2021 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var LogType; (function (LogType) { LogType["bidi"] = "bidi"; LogType["cdp"] = "cdp"; LogType["debug"] = "debug"; LogType["debugError"] = "debug:error"; LogType["debugInfo"] = "debug:info"; LogType["debugWarn"] = "debug:warn"; })(LogType || (LogType = {})); /** * Copyright 2022 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var _a$6; class ProcessingQueue { static LOGGER_PREFIX = `${LogType.debug}:queue`; #logger; #processor; #queue = []; #isProcessing = false; constructor(processor, logger) { this.#processor = processor; this.#logger = logger; } add(entry, name) { this.#queue.push([entry, name]); void this.#processIfNeeded(); } async #processIfNeeded() { if (this.#isProcessing) { return; } this.#isProcessing = true; while (this.#queue.length > 0) { const arrayEntry = this.#queue.shift(); if (!arrayEntry) { continue; } const [entryPromise, name] = arrayEntry; this.#logger?.(_a$6.LOGGER_PREFIX, 'Processing event:', name); await entryPromise .then((entry) => { if (entry.kind === 'error') { this.#logger?.(LogType.debugError, 'Event threw before sending:', entry.error.message, entry.error.stack); return; } return this.#processor(entry.value); }) .catch((error) => { this.#logger?.(LogType.debugError, 'Event was not processed:', error?.message); }); } this.#isProcessing = false; } } _a$6 = ProcessingQueue; /** * Copyright 2023 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var BiDiModule; (function (BiDiModule) { BiDiModule["Bluetooth"] = "bluetooth"; BiDiModule["Browser"] = "browser"; BiDiModule["BrowsingContext"] = "browsingContext"; BiDiModule["Cdp"] = "goog:cdp"; BiDiModule["DeprecatedCdp"] = "cdp"; BiDiModule["Input"] = "input"; BiDiModule["Log"] = "log"; BiDiModule["Network"] = "network"; BiDiModule["Script"] = "script"; BiDiModule["Session"] = "session"; })(BiDiModule || (BiDiModule = {})); var Script$2; (function (Script) { (function (EventNames) { EventNames["Message"] = "script.message"; EventNames["RealmCreated"] = "script.realmCreated"; EventNames["RealmDestroyed"] = "script.realmDestroyed"; })(Script.EventNames || (Script.EventNames = {})); })(Script$2 || (Script$2 = {})); var Log$1; (function (Log) { (function (EventNames) { EventNames["LogEntryAdded"] = "log.entryAdded"; })(Log.EventNames || (Log.EventNames = {})); })(Log$1 || (Log$1 = {})); var BrowsingContext$2; (function (BrowsingContext) { (function (EventNames) { EventNames["ContextCreated"] = "browsingContext.contextCreated"; EventNames["ContextDestroyed"] = "browsingContext.contextDestroyed"; EventNames["DomContentLoaded"] = "browsingContext.domContentLoaded"; EventNames["DownloadWillBegin"] = "browsingContext.downloadWillBegin"; EventNames["FragmentNavigated"] = "browsingContext.fragmentNavigated"; EventNames["HistoryUpdated"] = "browsingContext.historyUpdated"; EventNames["Load"] = "browsingContext.load"; EventNames["NavigationAborted"] = "browsingContext.navigationAborted"; EventNames["NavigationCommitted"] = "browsingContext.navigationCommitted"; EventNames["NavigationFailed"] = "browsingContext.navigationFailed"; EventNames["NavigationStarted"] = "browsingContext.navigationStarted"; EventNames["UserPromptClosed"] = "browsingContext.userPromptClosed"; EventNames["UserPromptOpened"] = "browsingContext.userPromptOpened"; })(BrowsingContext.EventNames || (BrowsingContext.EventNames = {})); })(BrowsingContext$2 || (BrowsingContext$2 = {})); var Network$2; (function (Network) { (function (EventNames) { EventNames["AuthRequired"] = "network.authRequired"; EventNames["BeforeRequestSent"] = "network.beforeRequestSent"; EventNames["FetchError"] = "network.fetchError"; EventNames["ResponseCompleted"] = "network.responseCompleted"; EventNames["ResponseStarted"] = "network.responseStarted"; })(Network.EventNames || (Network.EventNames = {})); })(Network$2 || (Network$2 = {})); var Bluetooth$2; (function (Bluetooth) { (function (EventNames) { EventNames["RequestDevicePromptUpdated"] = "bluetooth.requestDevicePromptUpdated"; })(Bluetooth.EventNames || (Bluetooth.EventNames = {})); })(Bluetooth$2 || (Bluetooth$2 = {})); const EVENT_NAMES = new Set([ ...Object.values(BiDiModule), ...Object.values(Bluetooth$2.EventNames), ...Object.values(BrowsingContext$2.EventNames), ...Object.values(Log$1.EventNames), ...Object.values(Network$2.EventNames), ...Object.values(Script$2.EventNames), ]); class Exception extends Error { error; message; stacktrace; constructor(error, message, stacktrace) { super(); this.error = error; this.message = message; this.stacktrace = stacktrace; } toErrorResponse(commandId) { return { type: 'error', id: commandId, error: this.error, message: this.message, stacktrace: this.stacktrace, }; } } class InvalidArgumentException extends Exception { constructor(message, stacktrace) { super("invalid argument" , message, stacktrace); } } class InvalidSelectorException extends Exception { constructor(message, stacktrace) { super("invalid selector" , message, stacktrace); } } class MoveTargetOutOfBoundsException extends Exception { constructor(message, stacktrace) { super("move target out of bounds" , message, stacktrace); } } class NoSuchAlertException extends Exception { constructor(message, stacktrace) { super("no such alert" , message, stacktrace); } } class NoSuchElementException extends Exception { constructor(message, stacktrace) { super("no such element" , message, stacktrace); } } class NoSuchFrameException extends Exception { constructor(message, stacktrace) { super("no such frame" , message, stacktrace); } } class NoSuchHandleException extends Exception { constructor(message, stacktrace) { super("no such handle" , message, stacktrace); } } class NoSuchHistoryEntryException extends Exception { constructor(message, stacktrace) { super("no such history entry" , message, stacktrace); } } class NoSuchInterceptException extends Exception { constructor(message, stacktrace) { super("no such intercept" , message, stacktrace); } } class NoSuchNodeException extends Exception { constructor(message, stacktrace) { super("no such node" , message, stacktrace); } } class NoSuchRequestException extends Exception { constructor(message, stacktrace) { super("no such request" , message, stacktrace); } } class NoSuchScriptException extends Exception { constructor(message, stacktrace) { super("no such script" , message, stacktrace); } } class NoSuchUserContextException extends Exception { constructor(message, stacktrace) { super("no such user context" , message, stacktrace); } } class UnknownCommandException extends Exception { constructor(message, stacktrace) { super("unknown command" , message, stacktrace); } } class UnknownErrorException extends Exception { constructor(message, stacktrace = new Error().stack) { super("unknown error" , message, stacktrace); } } class UnableToCaptureScreenException extends Exception { constructor(message, stacktrace) { super("unable to capture screen" , message, stacktrace); } } class UnsupportedOperationException extends Exception { constructor(message, stacktrace) { super("unsupported operation" , message, stacktrace); } } class UnableToSetCookieException extends Exception { constructor(message, stacktrace) { super("unable to set cookie" , message, stacktrace); } } class UnableToSetFileInputException extends Exception { constructor(message, stacktrace) { super("unable to set file input" , message, stacktrace); } } class InvalidWebExtensionException extends Exception { constructor(message, stacktrace) { super("invalid web extension" , message, stacktrace); } } class NoSuchWebExtensionException extends Exception { constructor(message, stacktrace) { super("no such web extension" , message, stacktrace); } } /** * Copyright 2023 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class BidiNoOpParser { parseHandleRequestDevicePromptParams(params) { return params; } parseSimulateAdapterParameters(params) { return params; } parseSimulateAdvertisementParameters(params) { return params; } parseSimulatePreconnectedPeripheralParameters(params) { return params; } parseRemoveUserContextParams(params) { return params; } parseActivateParams(params) { return params; } parseCaptureScreenshotParams(params) { return params; } parseCloseParams(params) { return params; } parseCreateParams(params) { return params; } parseGetTreeParams(params) { return params; } parseHandleUserPromptParams(params) { return params; } parseLocateNodesParams(params) { return params; } parseNavigateParams(params) { return params; } parsePrintParams(params) { return params; } parseReloadParams(params) { return params; } parseSetViewportParams(params) { return params; } parseTraverseHistoryParams(params) { return params; } parseGetSessionParams(params) { return params; } parseResolveRealmParams(params) { return params; } parseSendCommandParams(params) { return params; } parseAddPreloadScriptParams(params) { return params; } parseCallFunctionParams(params) { return params; } parseDisownParams(params) { return params; } parseEvaluateParams(params) { return params; } parseGetRealmsParams(params) { return params; } parseRemovePreloadScriptParams(params) { return params; } parsePerformActionsParams(params) { return params; } parseReleaseActionsParams(params) { return params; } parseSetFilesParams(params) { return params; } parseAddInterceptParams(params) { return params; } parseContinueRequestParams(params) { return params; } parseContinueResponseParams(params) { return params; } parseContinueWithAuthParams(params) { return params; } parseFailRequestParams(params) { return params; } parseProvideResponseParams(params) { return params; } parseRemoveInterceptParams(params) { return params; } parseSetCacheBehavior(params) { return params; } parseSetPermissionsParams(params) { return params; } parseSubscribeParams(params) { return params; } parseUnsubscribeParams(params) { return params; } parseDeleteCookiesParams(params) { return params; } parseGetCookiesParams(params) { return params; } parseSetCookieParams(params) { return params; } parseInstallParams(params) { return params; } parseUninstallParams(params) { return params; } } /** * Copyright 2023 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class BrowserProcessor { #browserCdpClient; #browsingContextStorage; #userContextStorage; constructor(browserCdpClient, browsingContextStorage, userContextStorage) { this.#browserCdpClient = browserCdpClient; this.#browsingContextStorage = browsingContextStorage; this.#userContextStorage = userContextStorage; } close() { setTimeout(() => this.#browserCdpClient.sendCommand('Browser.close'), 0); return {}; } async createUserContext(params) { const request = { proxyServer: params['goog:proxyServer'] ?? undefined, }; const proxyBypassList = params['goog:proxyBypassList'] ?? undefined; if (proxyBypassList) { request.proxyBypassList = proxyBypassList.join(','); } const context = await this.#browserCdpClient.sendCommand('Target.createBrowserContext', request); return { userContext: context.browserContextId, }; } async removeUserContext(params) { const userContext = params.userContext; if (userContext === 'default') { throw new InvalidArgumentException('`default` user context cannot be removed'); } try { await this.#browserCdpClient.sendCommand('Target.disposeBrowserContext', { browserContextId: userContext, }); } catch (err) { if (err.message.startsWith('Failed to find context with id')) { throw new NoSuchUserContextException(err.message); } throw err; } return {}; } async getUserContexts() { return { userContexts: await this.#userContextStorage.getUserContexts(), }; } async #getWindowInfo(targetId) { const windowInfo = await this.#browserCdpClient.sendCommand('Browser.getWindowForTarget', { targetId }); return { active: false, clientWindow: `${windowInfo.windowId}`, state: windowInfo.bounds.windowState ?? 'normal', height: windowInfo.bounds.height ?? 0, width: windowInfo.bounds.width ?? 0, x: windowInfo.bounds.left ?? 0, y: windowInfo.bounds.top ?? 0, }; } async getClientWindows() { const topLevelTargetIds = this.#browsingContextStorage .getTopLevelContexts() .map((b) => b.cdpTarget.id); const clientWindows = await Promise.all(topLevelTargetIds.map(async (targetId) => await this.#getWindowInfo(targetId))); const uniqueClientWindowIds = new Set(); const uniqueClientWindows = new Array(); for (const window of clientWindows) { if (!uniqueClientWindowIds.has(window.clientWindow)) { uniqueClientWindowIds.add(window.clientWindow); uniqueClientWindows.push(window); } } return { clientWindows: uniqueClientWindows }; } } /** * Copyright 2023 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class CdpProcessor { #browsingContextStorage; #realmStorage; #cdpConnection; #browserCdpClient; constructor(browsingContextStorage, realmStorage, cdpConnection, browserCdpClient) { this.#browsingContextStorage = browsingContextStorage; this.#realmStorage = realmStorage; this.#cdpConnection = cdpConnection; this.#browserCdpClient = browserCdpClient; } getSession(params) { const context = params.context; const sessionId = this.#browsingContextStorage.getContext(context).cdpTarget.cdpSessionId; if (sessionId === undefined) { return {}; } return { session: sessionId }; } resolveRealm(params) { const context = params.realm; const realm = this.#realmStorage.getRealm({ realmId: context }); if (realm === undefined) { throw new UnknownErrorException(`Could not find realm ${params.realm}`); } return { executionContextId: realm.executionContextId }; } async sendCommand(params) { const client = params.session ? this.#cdpConnection.getCdpClient(params.session) : this.#browserCdpClient; const result = await client.sendCommand(params.method, params.params); return { result, session: params.session, }; } } class BrowsingContextProcessor { #browserCdpClient; #browsingContextStorage; #eventManager; constructor(browserCdpClient, browsingContextStorage, eventManager) { this.#browserCdpClient = browserCdpClient; this.#browsingContextStorage = browsingContextStorage; this.#eventManager = eventManager; this.#eventManager.addSubscribeHook(BrowsingContext$2.EventNames.ContextCreated, this.#onContextCreatedSubscribeHook.bind(this)); } getTree(params) { const resultContexts = params.root === undefined ? this.#browsingContextStorage.getTopLevelContexts() : [this.#browsingContextStorage.getContext(params.root)]; return { contexts: resultContexts.map((c) => c.serializeToBidiValue(params.maxDepth ?? Number.MAX_VALUE)), }; } async create(params) { let referenceContext; let userContext = 'default'; if (params.referenceContext !== undefined) { referenceContext = this.#browsingContextStorage.getContext(params.referenceContext); if (!referenceContext.isTopLevelContext()) { throw new InvalidArgumentException(`referenceContext should be a top-level context`); } userContext = referenceContext.userContext; } if (params.userContext !== undefined) { userContext = params.userContext; } const existingContexts = this.#browsingContextStorage .getAllContexts() .filter((context) => context.userContext === userContext); let newWindow = false; switch (params.type) { case "tab" : newWindow = false; break; case "window" : newWindow = true; break; } if (!existingContexts.length) { newWindow = true; } let result; try { result = await this.#browserCdpClient.sendCommand('Target.createTarget', { url: 'about:blank', newWindow, browserContextId: userContext === 'default' ? undefined : userContext, background: params.background === true, }); } catch (err) { if ( err.message.startsWith('Failed to find browser context with id') || err.message === 'browserContextId') { throw new NoSuchUserContextException(`The context ${userContext} was not found`); } throw err; } const context = await this.#browsingContextStorage.waitForContext(result.targetId); await context.lifecycleLoaded(); return { context: context.id }; } navigate(params) { const context = this.#browsingContextStorage.getContext(params.context); return context.navigate(params.url, params.wait ?? "none" ); } reload(params) { const context = this.#browsingContextStorage.getContext(params.context); return context.reload(params.ignoreCache ?? false, params.wait ?? "none" ); } async activate(params) { const context = this.#browsingContextStorage.getContext(params.context); if (!context.isTopLevelContext()) { throw new InvalidArgumentException('Activation is only supported on the top-level context'); } await context.activate(); return {}; } async captureScreenshot(params) { const context = this.#browsingContextStorage.getContext(params.context); return await context.captureScreenshot(params); } async print(params) { const context = this.#browsingContextStorage.getContext(params.context); return await context.print(params); } async setViewport(params) { const context = this.#browsingContextStorage.getContext(params.context); if (!context.isTopLevelContext()) { throw new InvalidArgumentException('Emulating viewport is only supported on the top-level context'); } await context.setViewport(params.viewport, params.devicePixelRatio); return {}; } async traverseHistory(params) { const context = this.#browsingContextStorage.getContext(params.context); if (!context) { throw new InvalidArgumentException(`No browsing context with id ${params.context}`); } if (!context.isTopLevelContext()) { throw new InvalidArgumentException('Traversing history is only supported on the top-level context'); } await context.traverseHistory(params.delta); return {}; } async handleUserPrompt(params) { const context = this.#browsingContextStorage.getContext(params.context); try { await context.handleUserPrompt(params.accept, params.userText); } catch (error) { if (error.message?.includes('No dialog is showing')) { throw new NoSuchAlertException('No dialog is showing'); } throw error; } return {}; } async close(params) { const context = this.#browsingContextStorage.getContext(params.context); if (!context.isTopLevelContext()) { throw new InvalidArgumentException(`Non top-level browsing context ${context.id} cannot be closed.`); } const parentCdpClient = context.cdpTarget.parentCdpClient; try { const detachedFromTargetPromise = new Promise((resolve) => { const onContextDestroyed = (event) => { if (event.targetId === params.context) { parentCdpClient.off('Target.detachedFromTarget', onContextDestroyed); resolve(); } }; parentCdpClient.on('Target.detachedFromTarget', onContextDestroyed); }); try { if (params.promptUnload) { await context.close(); } else { await parentCdpClient.sendCommand('Target.closeTarget', { targetId: params.context, }); } } catch (error) { if (!parentCdpClient.isCloseError(error)) { throw error; } } await detachedFromTargetPromise; } catch (error) { if (!(error.code === -32e3 && error.message === 'Not attached to an active page')) { throw error; } } return {}; } async locateNodes(params) { const context = this.#browsingContextStorage.getContext(params.context); return await context.locateNodes(params); } #onContextCreatedSubscribeHook(contextId) { const context = this.#browsingContextStorage.getContext(contextId); const contextsToReport = [ context, ...this.#browsingContextStorage.getContext(contextId).allChildren, ]; contextsToReport.forEach((context) => { this.#eventManager.registerEvent({ type: 'event', method: BrowsingContext$2.EventNames.ContextCreated, params: context.serializeToBidiValue(), }, context.id); }); return Promise.resolve(); } } /** * Copyright 2023 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function assert(predicate, message) { if (!predicate) { throw new Error(message ?? 'Internal assertion failed.'); } } /* * Copyright 2024 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function isSingleComplexGrapheme(value) { return isSingleGrapheme(value) && value.length > 1; } function isSingleGrapheme(value) { const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' }); return [...segmenter.segment(value)].length === 1; } /** * Copyright 2023 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class NoneSource { type = "none" ; } class KeySource { type = "key" ; pressed = new Set(); #modifiers = 0; get modifiers() { return this.#modifiers; } get alt() { return (this.#modifiers & 1) === 1; } set alt(value) { this.#setModifier(value, 1); } get ctrl() { return (this.#modifiers & 2) === 2; } set ctrl(value) { this.#setModifier(value, 2); } get meta() { return (this.#modifiers & 4) === 4; } set meta(value) { this.#setModifier(value, 4); } get shift() { return (this.#modifiers & 8) === 8; } set shift(value) { this.#setModifier(value, 8); } #setModifier(value, bit) { if (value) { this.#modifiers |= bit; } else { this.#modifiers &= ~bit; } } } class PointerSource { type = "pointer" ; subtype; pointerId; pressed = new Set(); x = 0; y = 0; radiusX; radiusY; force; constructor(id, subtype) { this.pointerId = id; this.subtype = subtype; } get buttons() { let buttons = 0; for (const button of this.pressed) { switch (button) { case 0: buttons |= 1; break; case 1: buttons |= 4; break; case 2: buttons |= 2; break; case 3: buttons |= 8; break; case 4: buttons |= 16; break; } } return buttons; } static ClickContext = class ClickContext { static #DOUBLE_CLICK_TIME_MS = 500; static #MAX_DOUBLE_CLICK_RADIUS = 2; count = 0; #x; #y; #time; constructor(x, y, time) { this.#x = x; this.#y = y; this.#time = time; } compare(context) { return ( context.#time - this.#time > ClickContext.#DOUBLE_CLICK_TIME_MS || Math.abs(context.#x - this.#x) > ClickContext.#MAX_DOUBLE_CLICK_RADIUS || Math.abs(context.#y - this.#y) > ClickContext.#MAX_DOUBLE_CLICK_RADIUS); } }; #clickContexts = new Map(); setClickCount(button, context) { let storedContext = this.#clickContexts.get(button); if (!storedContext || storedContext.compare(context)) { storedContext = context; } ++storedContext.count; this.#clickContexts.set(button, storedContext); return storedContext.count; } getClickCount(button) { return this.#clickContexts.get(button)?.count ?? 0; } resetClickCount() { this.#clickContexts = new Map(); } } class WheelSource { type = "wheel" ; } /** * Copyright 2023 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function getNormalizedKey(value) { switch (value) { case '\uE000': return 'Unidentified'; case '\uE001': return 'Cancel'; case '\uE002': return 'Help'; case '\uE003': return 'Backspace'; case '\uE004': return 'Tab'; case '\uE005': return 'Clear'; case '\uE006': case '\uE007': return 'Enter'; case '\uE008': return 'Shift'; case '\uE009': return 'Control'; case '\uE00A': return 'Alt'; case '\uE00B': return 'Pause'; case '\uE00C': return 'Escape'; case '\uE00D': return ' '; case '\uE00E': return 'PageUp'; case '\uE00F': return 'PageDown'; case '\uE010': return 'End'; case '\uE011': return 'Home'; case '\uE012': return 'ArrowLeft'; case '\uE013': return 'ArrowUp'; case '\uE014': return 'ArrowRight'; case '\uE015': return 'ArrowDown'; case '\uE016': return 'Insert'; case '\uE017': return 'Delete'; case '\uE018': return ';'; case '\uE019': return '='; case '\uE01A': return '0'; case '\uE01B': return '1'; case '\uE01C': return '2'; case '\uE01D': return '3'; case '\uE01E': return '4'; case '\uE01F': return '5'; case '\uE020': return '6'; case '\uE021': return '7'; case '\uE022': return '8'; case '\uE023': return '9'; case '\uE024': return '*'; case '\uE025': return '+'; case '\uE026': return ','; case '\uE027': return '-'; case '\uE028': return '.'; case '\uE029': return '/'; case '\uE031': return 'F1'; case '\uE032': return 'F2'; case '\uE033': return 'F3'; case '\uE034': return 'F4'; case '\uE035': return 'F5'; case '\uE036': return 'F6'; case '\uE037': return 'F7'; case '\uE038': return 'F8'; case '\uE039': return 'F9'; case '\uE03A': return 'F10'; case '\uE03B': return 'F11'; case '\uE03C': return 'F12'; case '\uE03D': return 'Meta'; case '\uE040': return 'ZenkakuHankaku'; case '\uE050': return 'Shift'; case '\uE051': return 'Control'; case '\uE052': return 'Alt'; case '\uE053': return 'Meta'; case '\uE054': return 'PageUp'; case '\uE055': return 'PageDown'; case '\uE056': return 'End'; case '\uE057': return 'Home'; case '\uE058': return 'ArrowLeft'; case '\uE059': return 'ArrowUp'; case '\uE05A': return 'ArrowRight'; case '\uE05B': return 'ArrowDown'; case '\uE05C': return 'Insert'; case '\uE05D': return 'Delete'; default: return value; } } function getKeyCode(key) { switch (key) { case '`': case '~': return 'Backquote'; case '\\': case '|': return 'Backslash'; case '\uE003': return 'Backspace'; case '[': case '{': return 'BracketLeft'; case ']': case '}': return 'BracketRight'; case ',': case '<': return 'Comma'; case '0': case ')': return 'Digit0'; case '1': case '!': return 'Digit1'; case '2': case '@': return 'Digit2'; case '3': case '#': return 'Digit3'; case '4': case '$': return 'Digit4'; case '5': case '%': return 'Digit5'; case '6': case '^': return 'Digit6'; case '7': case '&': return 'Digit7'; case '8': case '*': return 'Digit8'; case '9': case '(': return 'Digit9'; case '=': case '+': return 'Equal'; case '>': return 'IntlBackslash'; case 'a': case 'A': return 'KeyA'; case 'b': case 'B': return 'KeyB'; case 'c': case 'C': return 'KeyC'; case 'd': case 'D': return 'KeyD'; case 'e': case 'E': return 'KeyE'; case 'f': case 'F': return 'KeyF'; case 'g': case 'G': return 'KeyG'; case 'h': case 'H': return 'KeyH'; case 'i': case 'I': return 'KeyI'; case 'j': case 'J': return 'KeyJ'; case 'k': case 'K': return 'KeyK'; case 'l': case 'L': return 'KeyL'; case 'm': case 'M': return 'KeyM'; case 'n': case 'N': return 'KeyN'; case 'o': case 'O': return 'KeyO'; case 'p': case 'P': return 'KeyP'; case 'q': case 'Q': return 'KeyQ'; case 'r': case 'R': return 'KeyR'; case 's': case 'S': return 'KeyS'; case 't': case 'T': return 'KeyT'; case 'u': case 'U': return 'KeyU'; case 'v': case 'V': return 'KeyV'; case 'w': case 'W': return 'KeyW'; case 'x': case 'X': return 'KeyX'; case 'y': case 'Y': return 'KeyY'; case 'z': case 'Z': return 'KeyZ'; case '-': case '_': return 'Minus'; case '.': return 'Period'; case "'": case '"': return 'Quote'; case ';': case ':': return 'Semicolon'; case '/': case '?': return 'Slash'; case '\uE00A': return 'AltLeft'; case '\uE052': return 'AltRight'; case '\uE009': return 'ControlLeft'; case '\uE051': return 'ControlRight'; case '\uE006': return 'Enter'; case '\uE00B': return 'Pause'; case '\uE03D': return 'MetaLeft'; case '\uE053': return 'MetaRight'; case '\uE008': return 'ShiftLeft'; case '\uE050': return 'ShiftRight'; case ' ': case '\uE00D': return 'Space'; case '\uE004': return 'Tab'; case '\uE017': return 'Delete'; case '\uE010': return 'End'; case '\uE002': return 'Help'; case '\uE011': return 'Home'; case '\uE016': return 'Insert'; case '\uE00F': return 'PageDown'; case '\uE00E': return 'PageUp'; case '\uE015': return 'ArrowDown'; case '\uE012': return 'ArrowLeft'; case '\uE014': return 'ArrowRight'; case '\uE013': return 'ArrowUp'; case '\uE00C': return 'Escape'; case '\uE031': return 'F1'; case '\uE032': return 'F2'; case '\uE033': return 'F3'; case '\uE034': return 'F4'; case '\uE035': return 'F5'; case '\uE036': return 'F6'; case '\uE037': return 'F7'; case '\uE038': return 'F8'; case '\uE039': return 'F9'; case '\uE03A': return 'F10'; case '\uE03B': return 'F11'; case '\uE03C': return 'F12'; case '\uE019': return 'NumpadEqual'; case '\uE01A': case '\uE05C': return 'Numpad0'; case '\uE01B': case '\uE056': return 'Numpad1'; case '\uE01C': case '\uE05B': return 'Numpad2'; case '\uE01D': case '\uE055': return 'Numpad3'; case '\uE01E': case '\uE058': return 'Numpad4'; case '\uE01F': return 'Numpad5'; case '\uE020': case '\uE05A': return 'Numpad6'; case '\uE021': case '\uE057': return 'Numpad7'; case '\uE022': case '\uE059': return 'Nump