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,235 lines (1,223 loc) • 835 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["Input"] = "input";
BiDiModule["Log"] = "log";
BiDiModule["Network"] = "network";
BiDiModule["Script"] = "script";
BiDiModule["Session"] = "session";
BiDiModule["Speculation"] = "speculation";
})(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["DownloadEnd"] = "browsingContext.downloadEnd";
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 Input$2;
(function (Input) {
(function (EventNames) {
EventNames["FileDialogOpened"] = "input.fileDialogOpened";
})(Input.EventNames || (Input.EventNames = {}));
})(Input$2 || (Input$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";
EventNames["GattConnectionAttempted"] = "bluetooth.gattConnectionAttempted";
EventNames["CharacteristicEventGenerated"] = "bluetooth.characteristicEventGenerated";
EventNames["DescriptorEventGenerated"] = "bluetooth.descriptorEventGenerated";
})(Bluetooth.EventNames || (Bluetooth.EventNames = {}));
})(Bluetooth$2 || (Bluetooth$2 = {}));
var Speculation;
(function (Speculation) {
(function (EventNames) {
EventNames["PrefetchStatusUpdated"] = "speculation.prefetchStatusUpdated";
})(Speculation.EventNames || (Speculation.EventNames = {}));
})(Speculation || (Speculation = {}));
const EVENT_NAMES = new Set([
...Object.values(BiDiModule),
...Object.values(Bluetooth$2.EventNames),
...Object.values(BrowsingContext$2.EventNames),
...Object.values(Input$2.EventNames),
...Object.values(Log$1.EventNames),
...Object.values(Network$2.EventNames),
...Object.values(Script$2.EventNames),
...Object.values(Speculation.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);
}
}
class NoSuchNetworkCollectorException extends Exception {
constructor(message, stacktrace) {
super("no such network collector" , message, stacktrace);
}
}
class NoSuchNetworkDataException extends Exception {
constructor(message, stacktrace) {
super("no such network data" , 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 {
parseDisableSimulationParameters(params) {
return params;
}
parseHandleRequestDevicePromptParams(params) {
return params;
}
parseSimulateAdapterParameters(params) {
return params;
}
parseSimulateAdvertisementParameters(params) {
return params;
}
parseSimulateCharacteristicParameters(params) {
return params;
}
parseSimulateCharacteristicResponseParameters(params) {
return params;
}
parseSimulateDescriptorParameters(params) {
return params;
}
parseSimulateDescriptorResponseParameters(params) {
return params;
}
parseSimulateGattConnectionResponseParameters(params) {
return params;
}
parseSimulateGattDisconnectionParameters(params) {
return params;
}
parseSimulatePreconnectedPeripheralParameters(params) {
return params;
}
parseSimulateServiceParameters(params) {
return params;
}
parseCreateUserContextParameters(params) {
return params;
}
parseRemoveUserContextParameters(params) {
return params;
}
parseSetClientWindowStateParameters(params) {
return params;
}
parseSetDownloadBehaviorParameters(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;
}
parseSetBypassCspParams(params) {
return params;
}
parseSetViewportParams(params) {
return params;
}
parseTraverseHistoryParams(params) {
return params;
}
parseGetSessionParams(params) {
return params;
}
parseResolveRealmParams(params) {
return params;
}
parseSendCommandParams(params) {
return params;
}
parseSetClientHintsOverrideParams(params) {
return params;
}
parseSetForcedColorsModeThemeOverrideParams(params) {
return params;
}
parseSetGeolocationOverrideParams(params) {
return params;
}
parseSetLocaleOverrideParams(params) {
return params;
}
parseSetNetworkConditionsParams(params) {
return params;
}
parseSetScreenOrientationOverrideParams(params) {
return params;
}
parseSetScreenSettingsOverrideParams(params) {
return params;
}
parseSetScriptingEnabledParams(params) {
return params;
}
parseSetScrollbarTypeOverrideParams(params) {
return params;
}
parseSetTimezoneOverrideParams(params) {
return params;
}
parseSetTouchOverrideParams(params) {
return params;
}
parseSetUserAgentOverrideParams(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;
}
parseAddDataCollectorParams(params) {
return params;
}
parseAddInterceptParams(params) {
return params;
}
parseContinueRequestParams(params) {
return params;
}
parseContinueResponseParams(params) {
return params;
}
parseContinueWithAuthParams(params) {
return params;
}
parseDisownDataParams(params) {
return params;
}
parseFailRequestParams(params) {
return params;
}
parseGetDataParams(params) {
return params;
}
parseProvideResponseParams(params) {
return params;
}
parseRemoveDataCollectorParams(params) {
return params;
}
parseRemoveInterceptParams(params) {
return params;
}
parseSetCacheBehaviorParams(params) {
return params;
}
parseSetExtraHeadersParams(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;
#configStorage;
#userContextStorage;
constructor(browserCdpClient, browsingContextStorage, configStorage, userContextStorage) {
this.#browserCdpClient = browserCdpClient;
this.#browsingContextStorage = browsingContextStorage;
this.#configStorage = configStorage;
this.#userContextStorage = userContextStorage;
}
close() {
setTimeout(() => this.#browserCdpClient.sendCommand('Browser.close').catch(() => { }), 0);
return {};
}
async createUserContext(params) {
const w3cParams = params;
const globalConfig = this.#configStorage.getGlobalConfig();
if (w3cParams.acceptInsecureCerts !== undefined) {
if (w3cParams.acceptInsecureCerts === false &&
globalConfig.acceptInsecureCerts === true)
throw new UnknownErrorException(`Cannot set user context's "acceptInsecureCerts" to false, when a capability "acceptInsecureCerts" is set to true`);
}
const request = {};
if (w3cParams.proxy) {
const proxyStr = getProxyStr(w3cParams.proxy);
if (proxyStr) {
request.proxyServer = proxyStr;
}
if (w3cParams.proxy.noProxy) {
request.proxyBypassList = w3cParams.proxy.noProxy.join(',');
}
}
else {
if (params['goog:proxyServer'] !== undefined) {
request.proxyServer = params['goog:proxyServer'];
}
const proxyBypassList = params['goog:proxyBypassList'] ?? undefined;
if (proxyBypassList) {
request.proxyBypassList = proxyBypassList.join(',');
}
}
const context = await this.#browserCdpClient.sendCommand('Target.createBrowserContext', request);
await this.#applyDownloadBehavior(globalConfig.downloadBehavior ?? null, context.browserContextId);
this.#configStorage.updateUserContextConfig(context.browserContextId, {
acceptInsecureCerts: params['acceptInsecureCerts'],
userPromptHandler: params['unhandledPromptBehavior'],
});
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 setClientWindowState(params) {
const { clientWindow } = params;
const bounds = {
windowState: params.state,
};
if (params.state === 'normal') {
if (params.width !== undefined) {
bounds.width = params.width;
}
if (params.height !== undefined) {
bounds.height = params.height;
}
if (params.x !== undefined) {
bounds.left = params.x;
}
if (params.y !== undefined) {
bounds.top = params.y;
}
}
const windowId = Number.parseInt(clientWindow);
if (isNaN(windowId)) {
throw new InvalidArgumentException('no such client window');
}
await this.#browserCdpClient.sendCommand('Browser.setWindowBounds', {
windowId,
bounds,
});
const result = await this.#browserCdpClient.sendCommand('Browser.getWindowBounds', {
windowId,
});
return {
active: false,
clientWindow: `${windowId}`,
state: result.bounds.windowState ?? 'normal',
height: result.bounds.height ?? 0,
width: result.bounds.width ?? 0,
x: result.bounds.left ?? 0,
y: result.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 };
}
#toCdpDownloadBehavior(downloadBehavior) {
if (downloadBehavior === null)
return {
behavior: 'default',
};
if (downloadBehavior?.type === 'denied')
return {
behavior: 'deny',
};
if (downloadBehavior?.type === 'allowed') {
return {
behavior: 'allow',
downloadPath: downloadBehavior.destinationFolder,
};
}
throw new UnknownErrorException('Unexpected download behavior');
}
async #applyDownloadBehavior(downloadBehavior, userContext) {
await this.#browserCdpClient.sendCommand('Browser.setDownloadBehavior', {
...this.#toCdpDownloadBehavior(downloadBehavior),
browserContextId: userContext === 'default' ? undefined : userContext,
eventsEnabled: true,
});
}
async setDownloadBehavior(params) {
let userContexts;
if (params.userContexts === undefined) {
userContexts = (await this.#userContextStorage.getUserContexts()).map((c) => c.userContext);
}
else {
userContexts = Array.from(await this.#userContextStorage.verifyUserContextIdList(params.userContexts));
}
if (params.userContexts === undefined) {
this.#configStorage.updateGlobalConfig({
downloadBehavior: params.downloadBehavior,
});
}
else {
params.userContexts.map((userContext) => this.#configStorage.updateUserContextConfig(userContext, {
downloadBehavior: params.downloadBehavior,
}));
}
await Promise.all(userContexts.map(async (userContext) => {
const downloadBehavior = this.#configStorage.getActiveConfig(undefined, userContext)
.downloadBehavior ?? null;
await this.#applyDownloadBehavior(downloadBehavior, userContext);
}));
return {};
}
}
function getProxyStr(proxyConfig) {
if (proxyConfig.proxyType === 'direct' ||
proxyConfig.proxyType === 'system') {
return undefined;
}
if (proxyConfig.proxyType === 'pac') {
throw new UnsupportedOperationException(`PAC proxy configuration is not supported per user context`);
}
if (proxyConfig.proxyType === 'autodetect') {
throw new UnsupportedOperationException(`Autodetect proxy is not supported per user context`);
}
if (proxyConfig.proxyType === 'manual') {
const servers = [];
if (proxyConfig.httpProxy !== undefined) {
servers.push(`http=${proxyConfig.httpProxy}`);
}
if (proxyConfig.sslProxy !== undefined) {
servers.push(`https=${proxyConfig.sslProxy}`);
}
if (proxyConfig.socksProxy !== undefined ||
proxyConfig.socksVersion !== undefined) {
if (proxyConfig.socksProxy === undefined) {
throw new InvalidArgumentException(`'socksVersion' cannot be set without 'socksProxy'`);
}
if (proxyConfig.socksVersion === undefined ||
typeof proxyConfig.socksVersion !== 'number' ||
!Number.isInteger(proxyConfig.socksVersion) ||
proxyConfig.socksVersion < 0 ||
proxyConfig.socksVersion > 255) {
throw new InvalidArgumentException(`'socksVersion' must be between 0 and 255`);
}
servers.push(`socks=socks${proxyConfig.socksVersion}://${proxyConfig.socksProxy}`);
}
if (servers.length === 0) {
return undefined;
}
return servers.join(';');
}
throw new UnknownErrorException(`Unknown proxy type`);
}
/**
* 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;
#contextConfigStorage;
#eventManager;
#userContextStorage;
constructor(browserCdpClient, browsingContextStorage, userContextStorage, contextConfigStorage, eventManager) {
this.#contextConfigStorage = contextConfigStorage;
this.#userContextStorage = userContextStorage;
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 maxDimensionSize = 10_000_000;
if ((params.viewport?.height ?? 0) > maxDimensionSize ||
(params.viewport?.width ?? 0) > maxDimensionSize) {
throw new UnsupportedOperationException(`Viewport dimension over ${maxDimensionSize} are not supported`);
}
const config = {};
if (params.devicePixelRatio !== undefined) {
config.devicePixelRatio = params.devicePixelRatio;
}
if (params.viewport !== undefined) {
config.viewport = params.viewport;
}
const impactedTopLevelContexts = await this.#getRelatedTopLevelBrowsingContexts(params.context, params.userContexts);
for (const userContextId of params.userContexts ?? []) {
this.#contextConfigStorage.updateUserContextConfig(userContextId, config);
}
if (params.context !== undefined) {
this.#contextConfigStorage.updateBrowsingContextConfig(params.context, config);
}
await Promise.all(impactedTopLevelContexts.map(async (context) => {
const config = this.#contextConfigStorage.getActiveConfig(context.id, context.userContext);
await context.setViewport(config.viewport ?? null, config.devicePixelRatio ?? null, config.screenOrientation ?? null);
}));
return {};
}
async #getRelatedTopLevelBrowsingContexts(browsingContextId, userContextIds) {
if (browsingContextId === undefined && userContextIds === undefined) {
throw new InvalidArgumentException('Either userContexts or context must be provided');
}
if (browsingContextId !== undefined && userContextIds !== undefined) {
throw new InvalidArgumentException('userContexts and context are mutually exclusive');
}
if (browsingContextId !== undefined) {
const context = this.#browsingContextStorage.getContext(browsingContextId);
if (!context.isTopLevelContext()) {
throw new InvalidArgumentException('Emulating viewport is only supported on the top-level context');
}
return [context];
}
await this.#userContextStorage.verifyUserContextIdList(userContextIds);
const result = [];
for (const userContextId of userContextIds) {
const topLevelBrowsingContexts = this.#browsingContextStorage
.getTopLevelContexts()
.filter((browsingContext) => browsingContext.userContext === userContextId);
result.push(...topLevelBrowsingContexts);
}
return [...new Set(result).values()];
}
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 2025 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 EmulationProcessor {
#userContextStorage;
#browsingContextStorage;
#contextConfigStorage;
constructor(browsingContextStorage, userContextStorage, contextConfigStorage) {
this.#userContextStorage = userContextStorage;
this.#browsingContextStorage = browsingContextStorage;
this.#contextConfigStorage = contextConfigStorage;
}
async setGeolocationOverride(params) {
if ('coordinates' in params && 'error' in params) {
throw new InvalidArgumentException('Coordinates and error cannot be set at the same time');
}
let geolocation = null;
if ('coordinates' in params) {
if ((params.coordinates?.altitude ?? null) === null &&
(params.coordinates?.altitudeAccuracy ?? null) !== null) {
throw new InvalidArgumentException('Geolocation altitudeAccuracy can be set only with altitude');
}
geolocation = params.coordinates;
}
else if ('error' in params) {
if (params.error.type !== 'positionUnavailable') {
throw new InvalidArgumentException(`Unknown geolocation error ${params.error.type}`);
}
geolocation = params.error;
}
else {
throw new InvalidArgumentException(`Coordinates or error should be set`);
}
const browsingContexts = await this.#getRelatedTopLevelBrowsingContexts(params.contexts, params.userContexts);
for (const browsingContextId of params.contexts ?? []) {
this.#contextConfigStorage.updateBrowsingContextConfig(browsingContextId, {
geolocation,
});
}
for (const userContextId of params.userContexts ?? []) {
this.#contextConfigStorage.updateUserContextConfig(userContextId, {
geolocation,
});
}
await Promise.all(browsingContexts.map(async (context) => {
const config = this.#contextConfigStorage.getActiveConfig(context.id, context.userContext);
await context.setGeolocationOverride(config.geolocation ?? null);
}));
return {};
}
async setLocaleOverride(params) {
const locale = params.locale ?? null;
if (locale !== null && !isValidLocale(locale)) {
throw new InvalidArgumentException(`Invalid locale "${locale}"`);
}
const browsingContexts = await this.#getRelatedTopLevelBrowsingContexts(params.contexts, params.userContexts);
for (const browsingContextId of params.contexts ?? []) {
this.#contextConfigStorage.updateBrowsingContextConfig(browsingContextId, {
locale,
});
}
for (const userContextId of params.userContexts ?? []) {
this.#contextConfigStorage.updateUserContextConfig(userContextId, {
locale,
});
}
await Promise.all(browsingContexts.map(async (context) => {