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
JavaScript
(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