初始化项目

This commit is contained in:
2026-09-01 22:15:50 +08:00
commit b5a26a8848
11217 changed files with 1710987 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
Name: mitt
URL: https://github.com/developit/mitt
Version: 3.0.1
License: MIT
MIT License
Copyright (c) 2021 Jason Miller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------- DEPENDENCY DIVIDER --------------------
Name: urlpattern-polyfill
URL: https://github.com/kenchris/urlpattern-polyfill
Version: 10.0.0
License: MIT
Copyright 2020 Intel Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-------------------- DEPENDENCY DIVIDER --------------------
Name: zod
URL: https://zod.dev
Version: 3.23.8
License: MIT
MIT License
Copyright (c) 2020 Colin McDonnell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,28 @@
/**
* 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.
*/
/**
* @fileoverview The entry point to the BiDi Mapper namespace.
* Other modules should only access exports defined in this file.
* XXX: Add ESlint rule for this (https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-restricted-paths.md)
*/
export { BidiServer, MapperOptions } from './BidiServer.js';
export type { CdpConnection } from '../cdp/CdpConnection.js';
export type { CdpClient } from '../cdp/CdpClient.js';
export { EventEmitter } from '../utils/EventEmitter.js';
export type { BidiTransport } from './BidiTransport.js';
export { OutgoingMessage } from './OutgoingMessage.js';
export type { BidiCommandParameterParser } from './BidiParser.js';
+31
View File
@@ -0,0 +1,31 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutgoingMessage = exports.EventEmitter = exports.BidiServer = void 0;
/**
* @fileoverview The entry point to the BiDi Mapper namespace.
* Other modules should only access exports defined in this file.
* XXX: Add ESlint rule for this (https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-restricted-paths.md)
*/
var BidiServer_js_1 = require("./BidiServer.js");
Object.defineProperty(exports, "BidiServer", { enumerable: true, get: function () { return BidiServer_js_1.BidiServer; } });
var EventEmitter_js_1 = require("../utils/EventEmitter.js");
Object.defineProperty(exports, "EventEmitter", { enumerable: true, get: function () { return EventEmitter_js_1.EventEmitter; } });
var OutgoingMessage_js_1 = require("./OutgoingMessage.js");
Object.defineProperty(exports, "OutgoingMessage", { enumerable: true, get: function () { return OutgoingMessage_js_1.OutgoingMessage; } });
//# sourceMappingURL=BidiMapper.js.map
@@ -0,0 +1 @@
{"version":3,"file":"BidiMapper.js","sourceRoot":"","sources":["../../../src/bidiMapper/BidiMapper.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAEH;;;;GAIG;AACH,iDAA0D;AAAlD,2GAAA,UAAU,OAAA;AAGlB,4DAAsD;AAA9C,+GAAA,YAAY,OAAA;AAEpB,2DAAqD;AAA7C,qHAAA,eAAe,OAAA"}
@@ -0,0 +1,57 @@
/**
* 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.
*/
import type { Browser, BrowsingContext, Cdp, Input, Network, Script, Session, Storage, Permissions } from '../protocol/protocol.js';
import type { BidiCommandParameterParser } from './BidiParser.js';
export declare class BidiNoOpParser implements BidiCommandParameterParser {
parseRemoveUserContextParams(params: unknown): Browser.RemoveUserContextParameters;
parseActivateParams(params: unknown): BrowsingContext.ActivateParameters;
parseCaptureScreenshotParams(params: unknown): BrowsingContext.CaptureScreenshotParameters;
parseCloseParams(params: unknown): BrowsingContext.CloseParameters;
parseCreateParams(params: unknown): BrowsingContext.CreateParameters;
parseGetTreeParams(params: unknown): BrowsingContext.GetTreeParameters;
parseHandleUserPromptParams(params: unknown): BrowsingContext.HandleUserPromptParameters;
parseLocateNodesParams(params: unknown): BrowsingContext.LocateNodesParameters;
parseNavigateParams(params: unknown): BrowsingContext.NavigateParameters;
parsePrintParams(params: unknown): BrowsingContext.PrintParameters;
parseReloadParams(params: unknown): BrowsingContext.ReloadParameters;
parseSetViewportParams(params: unknown): BrowsingContext.SetViewportParameters;
parseTraverseHistoryParams(params: unknown): BrowsingContext.TraverseHistoryParameters;
parseGetSessionParams(params: unknown): Cdp.GetSessionParameters;
parseResolveRealmParams(params: unknown): Cdp.ResolveRealmParameters;
parseSendCommandParams(params: unknown): Cdp.SendCommandParameters;
parseAddPreloadScriptParams(params: unknown): Script.AddPreloadScriptParameters;
parseCallFunctionParams(params: unknown): Script.CallFunctionParameters;
parseDisownParams(params: unknown): Script.DisownParameters;
parseEvaluateParams(params: unknown): Script.EvaluateParameters;
parseGetRealmsParams(params: unknown): Script.GetRealmsParameters;
parseRemovePreloadScriptParams(params: unknown): Script.RemovePreloadScriptParameters;
parsePerformActionsParams(params: unknown): Input.PerformActionsParameters;
parseReleaseActionsParams(params: unknown): Input.ReleaseActionsParameters;
parseSetFilesParams(params: unknown): Input.SetFilesParameters;
parseAddInterceptParams(params: unknown): Network.AddInterceptParameters;
parseContinueRequestParams(params: unknown): Network.ContinueRequestParameters;
parseContinueResponseParams(params: unknown): Network.ContinueResponseParameters;
parseContinueWithAuthParams(params: unknown): Network.ContinueWithAuthParameters;
parseFailRequestParams(params: unknown): Network.FailRequestParameters;
parseProvideResponseParams(params: unknown): Network.ProvideResponseParameters;
parseRemoveInterceptParams(params: unknown): Network.RemoveInterceptParameters;
parseSetPermissionsParams(params: unknown): Permissions.SetPermissionParameters;
parseSubscribeParams(params: unknown): Session.SubscriptionRequest;
parseDeleteCookiesParams(params: unknown): Storage.DeleteCookiesParameters;
parseGetCookiesParams(params: unknown): Storage.GetCookiesParameters;
parseSetCookieParams(params: unknown): Storage.SetCookieParameters;
}
@@ -0,0 +1,160 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BidiNoOpParser = void 0;
class BidiNoOpParser {
// Browser domain
// keep-sorted start block=yes
parseRemoveUserContextParams(params) {
return params;
}
// keep-sorted end
// Browsing Context domain
// keep-sorted start block=yes
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;
}
// keep-sorted end
// CDP domain
// keep-sorted start block=yes
parseGetSessionParams(params) {
return params;
}
parseResolveRealmParams(params) {
return params;
}
parseSendCommandParams(params) {
return params;
}
// keep-sorted end
// Script domain
// keep-sorted start block=yes
parseAddPreloadScriptParams(params) {
return params;
}
parseCallFunctionParams(params) {
return params;
}
parseDisownParams(params) {
return params;
}
parseEvaluateParams(params) {
return params;
}
parseGetRealmsParams(params) {
return params;
}
parseRemovePreloadScriptParams(params) {
return params;
}
// keep-sorted end
// Input domain
// keep-sorted start block=yes
parsePerformActionsParams(params) {
return params;
}
parseReleaseActionsParams(params) {
return params;
}
parseSetFilesParams(params) {
return params;
}
// keep-sorted end
// Network domain
// keep-sorted start block=yes
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;
}
// keep-sorted end
// Permissions domain
// keep-sorted start block=yes
parseSetPermissionsParams(params) {
return params;
}
// keep-sorted end
// Session domain
// keep-sorted start block=yes
parseSubscribeParams(params) {
return params;
}
// keep-sorted end
// Storage domain
// keep-sorted start block=yes
parseDeleteCookiesParams(params) {
return params;
}
parseGetCookiesParams(params) {
return params;
}
parseSetCookieParams(params) {
return params;
}
}
exports.BidiNoOpParser = BidiNoOpParser;
//# sourceMappingURL=BidiNoOpParser.js.map
@@ -0,0 +1 @@
{"version":3,"file":"BidiNoOpParser.js","sourceRoot":"","sources":["../../../src/bidiMapper/BidiNoOpParser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
/**
* 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.
*/
import type { Browser, BrowsingContext, Cdp, Input, Network, Permissions, Script, Session, Storage } from '../protocol/protocol.js';
export interface BidiCommandParameterParser {
parseRemoveUserContextParams(params: unknown): Browser.RemoveUserContextParameters;
parseActivateParams(params: unknown): BrowsingContext.ActivateParameters;
parseCaptureScreenshotParams(params: unknown): BrowsingContext.CaptureScreenshotParameters;
parseCloseParams(params: unknown): BrowsingContext.CloseParameters;
parseCreateParams(params: unknown): BrowsingContext.CreateParameters;
parseGetTreeParams(params: unknown): BrowsingContext.GetTreeParameters;
parseHandleUserPromptParams(params: unknown): BrowsingContext.HandleUserPromptParameters;
parseLocateNodesParams(params: unknown): BrowsingContext.LocateNodesParameters;
parseNavigateParams(params: unknown): BrowsingContext.NavigateParameters;
parsePrintParams(params: unknown): BrowsingContext.PrintParameters;
parseReloadParams(params: unknown): BrowsingContext.ReloadParameters;
parseSetViewportParams(params: unknown): BrowsingContext.SetViewportParameters;
parseTraverseHistoryParams(params: unknown): BrowsingContext.TraverseHistoryParameters;
parseGetSessionParams(params: unknown): Cdp.GetSessionParameters;
parseResolveRealmParams(params: unknown): Cdp.ResolveRealmParameters;
parseSendCommandParams(params: unknown): Cdp.SendCommandParameters;
parsePerformActionsParams(params: unknown): Input.PerformActionsParameters;
parseReleaseActionsParams(params: unknown): Input.ReleaseActionsParameters;
parseSetFilesParams(params: unknown): Input.SetFilesParameters;
parseSetPermissionsParams(params: unknown): Permissions.SetPermissionParameters;
parseAddInterceptParams(params: unknown): Network.AddInterceptParameters;
parseContinueRequestParams(params: unknown): Network.ContinueRequestParameters;
parseContinueResponseParams(params: unknown): Network.ContinueResponseParameters;
parseContinueWithAuthParams(params: unknown): Network.ContinueWithAuthParameters;
parseFailRequestParams(params: unknown): Network.FailRequestParameters;
parseProvideResponseParams(params: unknown): Network.ProvideResponseParameters;
parseRemoveInterceptParams(params: unknown): Network.RemoveInterceptParameters;
parseAddPreloadScriptParams(params: unknown): Script.AddPreloadScriptParameters;
parseCallFunctionParams(params: unknown): Script.CallFunctionParameters;
parseDisownParams(params: unknown): Script.DisownParameters;
parseEvaluateParams(params: unknown): Script.EvaluateParameters;
parseGetRealmsParams(params: unknown): Script.GetRealmsParameters;
parseRemovePreloadScriptParams(params: unknown): Script.RemovePreloadScriptParameters;
parseSubscribeParams(params: unknown): Session.SubscriptionRequest;
parseDeleteCookiesParams(params: unknown): Storage.DeleteCookiesParameters;
parseGetCookiesParams(params: unknown): Storage.GetCookiesParameters;
parseSetCookieParams(params: unknown): Storage.SetCookieParameters;
}
+19
View File
@@ -0,0 +1,19 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=BidiParser.js.map
@@ -0,0 +1 @@
{"version":3,"file":"BidiParser.js","sourceRoot":"","sources":["../../../src/bidiMapper/BidiParser.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,46 @@
/**
* 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.
*/
import type { CdpClient } from '../cdp/CdpClient';
import type { CdpConnection } from '../cdp/CdpConnection.js';
import type { ChromiumBidi, Session } from '../protocol/protocol.js';
import { EventEmitter } from '../utils/EventEmitter.js';
import { type LoggerFn } from '../utils/log.js';
import type { Result } from '../utils/result.js';
import type { BidiCommandParameterParser } from './BidiParser.js';
import type { BidiTransport } from './BidiTransport.js';
import type { OutgoingMessage } from './OutgoingMessage.js';
type BidiServerEvent = {
message: ChromiumBidi.Command;
};
export type MapperOptions = {
acceptInsecureCerts?: boolean;
unhandledPromptBehavior?: Session.UserPromptHandler;
};
export declare class BidiServer extends EventEmitter<BidiServerEvent> {
#private;
private constructor();
/**
* Creates and starts BiDi Mapper instance.
*/
static createAndStart(bidiTransport: BidiTransport, cdpConnection: CdpConnection, browserCdpClient: CdpClient, selfTargetId: string, parser?: BidiCommandParameterParser, logger?: LoggerFn): Promise<BidiServer>;
/**
* Sends BiDi message.
*/
emitOutgoingMessage(messageEntry: Promise<Result<OutgoingMessage>>, event: string): void;
close(): void;
}
export {};
+123
View File
@@ -0,0 +1,123 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BidiServer = void 0;
const EventEmitter_js_1 = require("../utils/EventEmitter.js");
const log_js_1 = require("../utils/log.js");
const ProcessingQueue_js_1 = require("../utils/ProcessingQueue.js");
const CommandProcessor_js_1 = require("./CommandProcessor.js");
const CdpTargetManager_js_1 = require("./modules/cdp/CdpTargetManager.js");
const BrowsingContextStorage_js_1 = require("./modules/context/BrowsingContextStorage.js");
const NetworkStorage_js_1 = require("./modules/network/NetworkStorage.js");
const PreloadScriptStorage_js_1 = require("./modules/script/PreloadScriptStorage.js");
const RealmStorage_js_1 = require("./modules/script/RealmStorage.js");
const EventManager_js_1 = require("./modules/session/EventManager.js");
class BidiServer extends EventEmitter_js_1.EventEmitter {
#messageQueue;
#transport;
#commandProcessor;
#eventManager;
#browsingContextStorage = new BrowsingContextStorage_js_1.BrowsingContextStorage();
#realmStorage = new RealmStorage_js_1.RealmStorage();
#preloadScriptStorage = new PreloadScriptStorage_js_1.PreloadScriptStorage();
#logger;
#handleIncomingMessage = (message) => {
void this.#commandProcessor.processCommand(message).catch((error) => {
this.#logger?.(log_js_1.LogType.debugError, error);
});
};
#processOutgoingMessage = async (messageEntry) => {
const message = messageEntry.message;
if (messageEntry.channel !== null) {
message['channel'] = messageEntry.channel;
}
await this.#transport.sendMessage(message);
};
constructor(bidiTransport, cdpConnection, browserCdpClient, selfTargetId, defaultUserContextId, parser, logger) {
super();
this.#logger = logger;
this.#messageQueue = new ProcessingQueue_js_1.ProcessingQueue(this.#processOutgoingMessage, this.#logger);
this.#transport = bidiTransport;
this.#transport.setOnMessage(this.#handleIncomingMessage);
this.#eventManager = new EventManager_js_1.EventManager(this.#browsingContextStorage);
const networkStorage = new NetworkStorage_js_1.NetworkStorage(this.#eventManager, this.#browsingContextStorage, browserCdpClient, logger);
this.#commandProcessor = new CommandProcessor_js_1.CommandProcessor(cdpConnection, browserCdpClient, this.#eventManager, this.#browsingContextStorage, this.#realmStorage, this.#preloadScriptStorage, networkStorage, parser, async (options) => {
// This is required to ignore certificate errors when service worker is fetched.
await browserCdpClient.sendCommand('Security.setIgnoreCertificateErrors', {
ignore: options.acceptInsecureCerts ?? false,
});
new CdpTargetManager_js_1.CdpTargetManager(cdpConnection, browserCdpClient, selfTargetId, this.#eventManager, this.#browsingContextStorage, this.#realmStorage, networkStorage, this.#preloadScriptStorage, defaultUserContextId, options?.unhandledPromptBehavior, logger);
// Needed to get events about new targets.
await browserCdpClient.sendCommand('Target.setDiscoverTargets', {
discover: true,
});
// Needed to automatically attach to new targets.
await browserCdpClient.sendCommand('Target.setAutoAttach', {
autoAttach: true,
waitForDebuggerOnStart: true,
flatten: true,
});
await this.#topLevelContextsLoaded();
}, this.#logger);
this.#eventManager.on("event" /* EventManagerEvents.Event */, ({ message, event }) => {
this.emitOutgoingMessage(message, event);
});
this.#commandProcessor.on("response" /* CommandProcessorEvents.Response */, ({ message, event }) => {
this.emitOutgoingMessage(message, event);
});
}
/**
* Creates and starts BiDi Mapper instance.
*/
static async createAndStart(bidiTransport, cdpConnection, browserCdpClient, selfTargetId, parser, logger) {
// The default context is not exposed in Target.getBrowserContexts but can
// be observed via Target.getTargets. To determine the default browser
// context, we check which one is mentioned in Target.getTargets and not in
// Target.getBrowserContexts.
const [{ browserContextIds }, { targetInfos }] = await Promise.all([
browserCdpClient.sendCommand('Target.getBrowserContexts'),
browserCdpClient.sendCommand('Target.getTargets'),
]);
let defaultUserContextId = 'default';
for (const info of targetInfos) {
if (info.browserContextId &&
!browserContextIds.includes(info.browserContextId)) {
defaultUserContextId = info.browserContextId;
break;
}
}
const server = new BidiServer(bidiTransport, cdpConnection, browserCdpClient, selfTargetId, defaultUserContextId, parser, logger);
return server;
}
/**
* Sends BiDi message.
*/
emitOutgoingMessage(messageEntry, event) {
this.#messageQueue.add(messageEntry, event);
}
close() {
this.#transport.close();
}
async #topLevelContextsLoaded() {
await Promise.all(this.#browsingContextStorage
.getTopLevelContexts()
.map((c) => c.lifecycleLoaded()));
}
}
exports.BidiServer = BidiServer;
//# sourceMappingURL=BidiServer.js.map
@@ -0,0 +1 @@
{"version":3,"file":"BidiServer.js","sourceRoot":"","sources":["../../../src/bidiMapper/BidiServer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
/**
* 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.
*/
import type { ChromiumBidi } from '../protocol/protocol.js';
export interface BidiTransport {
setOnMessage: (handler: (message: ChromiumBidi.Command) => Promise<void> | void) => void;
sendMessage: (message: ChromiumBidi.Message) => Promise<void> | void;
close(): void;
}
@@ -0,0 +1,19 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=BidiTransport.js.map
@@ -0,0 +1 @@
{"version":3,"file":"BidiTransport.js","sourceRoot":"","sources":["../../../src/bidiMapper/BidiTransport.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,45 @@
/**
* 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.
*/
import type { CdpClient } from '../cdp/CdpClient';
import type { CdpConnection } from '../cdp/CdpConnection.js';
import { type ChromiumBidi } from '../protocol/protocol.js';
import { EventEmitter } from '../utils/EventEmitter.js';
import { type LoggerFn } from '../utils/log.js';
import type { Result } from '../utils/result.js';
import type { BidiCommandParameterParser } from './BidiParser.js';
import type { MapperOptions } from './BidiServer.js';
import type { BrowsingContextStorage } from './modules/context/BrowsingContextStorage.js';
import type { NetworkStorage } from './modules/network/NetworkStorage.js';
import type { PreloadScriptStorage } from './modules/script/PreloadScriptStorage.js';
import type { RealmStorage } from './modules/script/RealmStorage.js';
import type { EventManager } from './modules/session/EventManager.js';
import { OutgoingMessage } from './OutgoingMessage.js';
export declare const enum CommandProcessorEvents {
Response = "response"
}
type CommandProcessorEventsMap = {
[CommandProcessorEvents.Response]: {
message: Promise<Result<OutgoingMessage>>;
event: string;
};
};
export declare class CommandProcessor extends EventEmitter<CommandProcessorEventsMap> {
#private;
constructor(cdpConnection: CdpConnection, browserCdpClient: CdpClient, eventManager: EventManager, browsingContextStorage: BrowsingContextStorage, realmStorage: RealmStorage, preloadScriptStorage: PreloadScriptStorage, networkStorage: NetworkStorage, parser: BidiCommandParameterParser | undefined, initConnection: (options: MapperOptions) => Promise<void>, logger?: LoggerFn);
processCommand(command: ChromiumBidi.Command): Promise<void>;
}
export {};
@@ -0,0 +1,235 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandProcessor = void 0;
const protocol_js_1 = require("../protocol/protocol.js");
const EventEmitter_js_1 = require("../utils/EventEmitter.js");
const log_js_1 = require("../utils/log.js");
const BidiNoOpParser_js_1 = require("./BidiNoOpParser.js");
const BrowserProcessor_js_1 = require("./modules/browser/BrowserProcessor.js");
const CdpProcessor_js_1 = require("./modules/cdp/CdpProcessor.js");
const BrowsingContextProcessor_js_1 = require("./modules/context/BrowsingContextProcessor.js");
const InputProcessor_js_1 = require("./modules/input/InputProcessor.js");
const NetworkProcessor_js_1 = require("./modules/network/NetworkProcessor.js");
const PermissionsProcessor_js_1 = require("./modules/permissions/PermissionsProcessor.js");
const ScriptProcessor_js_1 = require("./modules/script/ScriptProcessor.js");
const SessionProcessor_js_1 = require("./modules/session/SessionProcessor.js");
const StorageProcessor_js_1 = require("./modules/storage/StorageProcessor.js");
const OutgoingMessage_js_1 = require("./OutgoingMessage.js");
class CommandProcessor extends EventEmitter_js_1.EventEmitter {
// keep-sorted start
#browserProcessor;
#browsingContextProcessor;
#cdpProcessor;
#inputProcessor;
#networkProcessor;
#permissionsProcessor;
#scriptProcessor;
#sessionProcessor;
#storageProcessor;
// keep-sorted end
#parser;
#logger;
constructor(cdpConnection, browserCdpClient, eventManager, browsingContextStorage, realmStorage, preloadScriptStorage, networkStorage, parser = new BidiNoOpParser_js_1.BidiNoOpParser(), initConnection, logger) {
super();
this.#parser = parser;
this.#logger = logger;
// keep-sorted start block=yes
this.#browserProcessor = new BrowserProcessor_js_1.BrowserProcessor(browserCdpClient);
this.#browsingContextProcessor = new BrowsingContextProcessor_js_1.BrowsingContextProcessor(browserCdpClient, browsingContextStorage, eventManager);
this.#cdpProcessor = new CdpProcessor_js_1.CdpProcessor(browsingContextStorage, realmStorage, cdpConnection, browserCdpClient);
this.#inputProcessor = new InputProcessor_js_1.InputProcessor(browsingContextStorage, realmStorage);
this.#networkProcessor = new NetworkProcessor_js_1.NetworkProcessor(browsingContextStorage, networkStorage);
this.#permissionsProcessor = new PermissionsProcessor_js_1.PermissionsProcessor(browserCdpClient);
this.#scriptProcessor = new ScriptProcessor_js_1.ScriptProcessor(browsingContextStorage, realmStorage, preloadScriptStorage, logger);
this.#sessionProcessor = new SessionProcessor_js_1.SessionProcessor(eventManager, browserCdpClient, initConnection);
this.#storageProcessor = new StorageProcessor_js_1.StorageProcessor(browserCdpClient, browsingContextStorage, logger);
// keep-sorted end
}
async #processCommand(command) {
switch (command.method) {
case 'session.end':
// TODO: Implement.
break;
// Browser domain
// keep-sorted start block=yes
case 'browser.close':
return this.#browserProcessor.close();
case 'browser.createUserContext':
return await this.#browserProcessor.createUserContext(command.params);
case 'browser.getUserContexts':
return await this.#browserProcessor.getUserContexts();
case 'browser.removeUserContext':
return await this.#browserProcessor.removeUserContext(this.#parser.parseRemoveUserContextParams(command.params));
// keep-sorted end
// Browsing Context domain
// keep-sorted start block=yes
case 'browsingContext.activate':
return await this.#browsingContextProcessor.activate(this.#parser.parseActivateParams(command.params));
case 'browsingContext.captureScreenshot':
return await this.#browsingContextProcessor.captureScreenshot(this.#parser.parseCaptureScreenshotParams(command.params));
case 'browsingContext.close':
return await this.#browsingContextProcessor.close(this.#parser.parseCloseParams(command.params));
case 'browsingContext.create':
return await this.#browsingContextProcessor.create(this.#parser.parseCreateParams(command.params));
case 'browsingContext.getTree':
return this.#browsingContextProcessor.getTree(this.#parser.parseGetTreeParams(command.params));
case 'browsingContext.handleUserPrompt':
return await this.#browsingContextProcessor.handleUserPrompt(this.#parser.parseHandleUserPromptParams(command.params));
case 'browsingContext.locateNodes':
return await this.#browsingContextProcessor.locateNodes(this.#parser.parseLocateNodesParams(command.params));
case 'browsingContext.navigate':
return await this.#browsingContextProcessor.navigate(this.#parser.parseNavigateParams(command.params));
case 'browsingContext.print':
return await this.#browsingContextProcessor.print(this.#parser.parsePrintParams(command.params));
case 'browsingContext.reload':
return await this.#browsingContextProcessor.reload(this.#parser.parseReloadParams(command.params));
case 'browsingContext.setViewport':
return await this.#browsingContextProcessor.setViewport(this.#parser.parseSetViewportParams(command.params));
case 'browsingContext.traverseHistory':
return await this.#browsingContextProcessor.traverseHistory(this.#parser.parseTraverseHistoryParams(command.params));
// keep-sorted end
// CDP domain
// keep-sorted start block=yes
case 'cdp.getSession':
return this.#cdpProcessor.getSession(this.#parser.parseGetSessionParams(command.params));
case 'cdp.resolveRealm':
return this.#cdpProcessor.resolveRealm(this.#parser.parseResolveRealmParams(command.params));
case 'cdp.sendCommand':
return await this.#cdpProcessor.sendCommand(this.#parser.parseSendCommandParams(command.params));
// keep-sorted end
// Input domain
// keep-sorted start block=yes
case 'input.performActions':
return await this.#inputProcessor.performActions(this.#parser.parsePerformActionsParams(command.params));
case 'input.releaseActions':
return await this.#inputProcessor.releaseActions(this.#parser.parseReleaseActionsParams(command.params));
case 'input.setFiles':
return await this.#inputProcessor.setFiles(this.#parser.parseSetFilesParams(command.params));
// keep-sorted end
// Network domain
// keep-sorted start block=yes
case 'network.addIntercept':
return await this.#networkProcessor.addIntercept(this.#parser.parseAddInterceptParams(command.params));
case 'network.continueRequest':
return await this.#networkProcessor.continueRequest(this.#parser.parseContinueRequestParams(command.params));
case 'network.continueResponse':
return await this.#networkProcessor.continueResponse(this.#parser.parseContinueResponseParams(command.params));
case 'network.continueWithAuth':
return await this.#networkProcessor.continueWithAuth(this.#parser.parseContinueWithAuthParams(command.params));
case 'network.failRequest':
return await this.#networkProcessor.failRequest(this.#parser.parseFailRequestParams(command.params));
case 'network.provideResponse':
return await this.#networkProcessor.provideResponse(this.#parser.parseProvideResponseParams(command.params));
case 'network.removeIntercept':
return await this.#networkProcessor.removeIntercept(this.#parser.parseRemoveInterceptParams(command.params));
case 'network.setCacheBehavior':
throw new protocol_js_1.UnknownErrorException("Method 'network.setCacheBehavior' is not implemented.");
// keep-sorted end
// Permissions domain
// keep-sorted start block=yes
case 'permissions.setPermission':
return await this.#permissionsProcessor.setPermissions(this.#parser.parseSetPermissionsParams(command.params));
// keep-sorted end
// Script domain
// keep-sorted start block=yes
case 'script.addPreloadScript':
return await this.#scriptProcessor.addPreloadScript(this.#parser.parseAddPreloadScriptParams(command.params));
case 'script.callFunction':
return await this.#scriptProcessor.callFunction(this.#parser.parseCallFunctionParams(this.#processTargetParams(command.params)));
case 'script.disown':
return await this.#scriptProcessor.disown(this.#parser.parseDisownParams(this.#processTargetParams(command.params)));
case 'script.evaluate':
return await this.#scriptProcessor.evaluate(this.#parser.parseEvaluateParams(this.#processTargetParams(command.params)));
case 'script.getRealms':
return this.#scriptProcessor.getRealms(this.#parser.parseGetRealmsParams(command.params));
case 'script.removePreloadScript':
return await this.#scriptProcessor.removePreloadScript(this.#parser.parseRemovePreloadScriptParams(command.params));
// keep-sorted end
// Session domain
// keep-sorted start block=yes
case 'session.new':
return await this.#sessionProcessor.new(command.params);
case 'session.status':
return this.#sessionProcessor.status();
case 'session.subscribe':
return await this.#sessionProcessor.subscribe(this.#parser.parseSubscribeParams(command.params), command.channel);
case 'session.unsubscribe':
return await this.#sessionProcessor.unsubscribe(this.#parser.parseSubscribeParams(command.params), command.channel);
// keep-sorted end
// Storage domain
// keep-sorted start block=yes
case 'storage.deleteCookies':
return await this.#storageProcessor.deleteCookies(this.#parser.parseDeleteCookiesParams(command.params));
case 'storage.getCookies':
return await this.#storageProcessor.getCookies(this.#parser.parseGetCookiesParams(command.params));
case 'storage.setCookie':
return await this.#storageProcessor.setCookie(this.#parser.parseSetCookieParams(command.params));
// keep-sorted end
}
// Intentionally kept outside the switch statement to ensure that
// ESLint @typescript-eslint/switch-exhaustiveness-check triggers if a new
// command is added.
throw new protocol_js_1.UnknownCommandException(`Unknown command '${command.method}'.`);
}
// Workaround for as zod.union always take the first schema
// https://github.com/w3c/webdriver-bidi/issues/635
#processTargetParams(params) {
if (typeof params === 'object' &&
params &&
'target' in params &&
typeof params.target === 'object' &&
params.target &&
'context' in params.target) {
delete params.target['realm'];
}
return params;
}
async processCommand(command) {
try {
const result = await this.#processCommand(command);
const response = {
type: 'success',
id: command.id,
result,
};
this.emit("response" /* CommandProcessorEvents.Response */, {
message: OutgoingMessage_js_1.OutgoingMessage.createResolved(response, command.channel),
event: command.method,
});
}
catch (e) {
if (e instanceof protocol_js_1.Exception) {
this.emit("response" /* CommandProcessorEvents.Response */, {
message: OutgoingMessage_js_1.OutgoingMessage.createResolved(e.toErrorResponse(command.id), command.channel),
event: command.method,
});
}
else {
const error = e;
this.#logger?.(log_js_1.LogType.bidi, error);
this.emit("response" /* CommandProcessorEvents.Response */, {
message: OutgoingMessage_js_1.OutgoingMessage.createResolved(new protocol_js_1.UnknownErrorException(error.message, error.stack).toErrorResponse(command.id), command.channel),
event: command.method,
});
}
}
}
}
exports.CommandProcessor = CommandProcessor;
//# sourceMappingURL=CommandProcessor.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
/**
* 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.
*/
import type { BidiPlusChannel } from '../protocol/chromium-bidi.js';
import type { ChromiumBidi } from '../protocol/protocol.js';
import type { Result } from '../utils/result.js';
export declare class OutgoingMessage {
#private;
private constructor();
static createFromPromise(messagePromise: Promise<Result<ChromiumBidi.Message>>, channel: BidiPlusChannel): Promise<Result<OutgoingMessage>>;
static createResolved(message: ChromiumBidi.Message, channel?: BidiPlusChannel): Promise<Result<OutgoingMessage>>;
get message(): ChromiumBidi.Message;
get channel(): BidiPlusChannel;
}
@@ -0,0 +1,52 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutgoingMessage = void 0;
class OutgoingMessage {
#message;
#channel;
constructor(message, channel = null) {
this.#message = message;
this.#channel = channel;
}
static createFromPromise(messagePromise, channel) {
return messagePromise.then((message) => {
if (message.kind === 'success') {
return {
kind: 'success',
value: new OutgoingMessage(message.value, channel),
};
}
return message;
});
}
static createResolved(message, channel) {
return Promise.resolve({
kind: 'success',
value: new OutgoingMessage(message, channel),
});
}
get message() {
return this.#message;
}
get channel() {
return this.#channel;
}
}
exports.OutgoingMessage = OutgoingMessage;
//# sourceMappingURL=OutgoingMessage.js.map
@@ -0,0 +1 @@
{"version":3,"file":"OutgoingMessage.js","sourceRoot":"","sources":["../../../src/bidiMapper/OutgoingMessage.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAMH,MAAa,eAAe;IACjB,QAAQ,CAAuB;IAC/B,QAAQ,CAAkB;IAEnC,YACE,OAA6B,EAC7B,UAA2B,IAAI;QAE/B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,MAAM,CAAC,iBAAiB,CACtB,cAAqD,EACrD,OAAwB;QAExB,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;YACrC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC/B,OAAO;oBACL,IAAI,EAAE,SAAS;oBACf,KAAK,EAAE,IAAI,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC;iBACnD,CAAC;YACJ,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,cAAc,CACnB,OAA6B,EAC7B,OAAyB;QAEzB,OAAO,OAAO,CAAC,OAAO,CAAC;YACrB,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,IAAI,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC;SAC7C,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF;AA5CD,0CA4CC"}
@@ -0,0 +1,26 @@
/**
* 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.
*/
import { type EmptyResult, type Browser } from '../../../protocol/protocol.js';
import type { CdpClient } from '../../BidiMapper.js';
export declare class BrowserProcessor {
#private;
constructor(browserCdpClient: CdpClient);
close(): EmptyResult;
createUserContext(params: Record<string, any>): Promise<Browser.CreateUserContextResult>;
removeUserContext(params: Browser.RemoveUserContextParameters): Promise<EmptyResult>;
getUserContexts(): Promise<Browser.GetUserContextsResult>;
}
@@ -0,0 +1,81 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserProcessor = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
class BrowserProcessor {
#browserCdpClient;
constructor(browserCdpClient) {
this.#browserCdpClient = browserCdpClient;
}
close() {
// Ensure that it is put at the end of the event loop.
// This way we send back the response before closing the tab.
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 protocol_js_1.InvalidArgumentException('`default` user context cannot be removed');
}
try {
await this.#browserCdpClient.sendCommand('Target.disposeBrowserContext', {
browserContextId: userContext,
});
}
catch (err) {
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/protocol/target_handler.cc;l=1424;drc=c686e8f4fd379312469fe018f5c390e9c8f20d0d
if (err.message.startsWith('Failed to find context with id')) {
throw new protocol_js_1.NoSuchUserContextException(err.message);
}
throw err;
}
return {};
}
async getUserContexts() {
const result = await this.#browserCdpClient.sendCommand('Target.getBrowserContexts');
return {
userContexts: [
{
userContext: 'default',
},
...result.browserContextIds.map((id) => {
return {
userContext: id,
};
}),
],
};
}
}
exports.BrowserProcessor = BrowserProcessor;
//# sourceMappingURL=BrowserProcessor.js.map
@@ -0,0 +1 @@
{"version":3,"file":"BrowserProcessor.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/browser/BrowserProcessor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAIH,+DAKuC;AAGvC,MAAa,gBAAgB;IAClB,iBAAiB,CAAY;IAEtC,YAAY,gBAA2B;QACrC,IAAI,CAAC,iBAAiB,GAAG,gBAAgB,CAAC;IAC5C,CAAC;IAED,KAAK;QACH,sDAAsD;QACtD,6DAA6D;QAC7D,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC;QAEzE,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,iBAAiB,CACrB,MAA2B;QAE3B,MAAM,OAAO,GAAgD;YAC3D,WAAW,EAAE,MAAM,CAAC,kBAAkB,CAAC,IAAI,SAAS;SACrD,CAAC;QACF,MAAM,eAAe,GACnB,MAAM,CAAC,sBAAsB,CAAC,IAAI,SAAS,CAAC;QAC9C,IAAI,eAAe,EAAE,CAAC;YACpB,OAAO,CAAC,eAAe,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CACtD,6BAA6B,EAC7B,OAAO,CACR,CAAC;QACF,OAAO;YACL,WAAW,EAAE,OAAO,CAAC,gBAAgB;SACtC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,iBAAiB,CACrB,MAA2C;QAE3C,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;QACvC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,IAAI,sCAAwB,CAChC,0CAA0C,CAC3C,CAAC;QACJ,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,8BAA8B,EAAE;gBACvE,gBAAgB,EAAE,WAAW;aAC9B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,mKAAmK;YACnK,IAAK,GAAa,CAAC,OAAO,CAAC,UAAU,CAAC,gCAAgC,CAAC,EAAE,CAAC;gBACxE,MAAM,IAAI,wCAA0B,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC;YAC/D,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CACrD,2BAA2B,CAC5B,CAAC;QACF,OAAO;YACL,YAAY,EAAE;gBACZ;oBACE,WAAW,EAAE,SAAS;iBACvB;gBACD,GAAG,MAAM,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;oBACrC,OAAO;wBACL,WAAW,EAAE,EAAE;qBAChB,CAAC;gBACJ,CAAC,CAAC;aACH;SACF,CAAC;IACJ,CAAC;CACF;AA3ED,4CA2EC"}
@@ -0,0 +1,27 @@
/**
* 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.
*/
import { type Cdp } from '../../../protocol/protocol.js';
import type { CdpClient, CdpConnection } from '../../BidiMapper.js';
import type { BrowsingContextStorage } from '../context/BrowsingContextStorage.js';
import type { RealmStorage } from '../script/RealmStorage.js';
export declare class CdpProcessor {
#private;
constructor(browsingContextStorage: BrowsingContextStorage, realmStorage: RealmStorage, cdpConnection: CdpConnection, browserCdpClient: CdpClient);
getSession(params: Cdp.GetSessionParameters): Cdp.GetSessionResult;
resolveRealm(params: Cdp.ResolveRealmParameters): Cdp.ResolveRealmResult;
sendCommand(params: Cdp.SendCommandParameters): Promise<Cdp.SendCommandResult>;
}
@@ -0,0 +1,60 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CdpProcessor = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
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 protocol_js_1.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,
};
}
}
exports.CdpProcessor = CdpProcessor;
//# sourceMappingURL=CdpProcessor.js.map
@@ -0,0 +1 @@
{"version":3,"file":"CdpProcessor.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/cdp/CdpProcessor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAEH,+DAA8E;AAK9E,MAAa,YAAY;IACd,uBAAuB,CAAyB;IAChD,aAAa,CAAe;IAC5B,cAAc,CAAgB;IAC9B,iBAAiB,CAAY;IAEtC,YACE,sBAA8C,EAC9C,YAA0B,EAC1B,aAA4B,EAC5B,gBAA2B;QAE3B,IAAI,CAAC,uBAAuB,GAAG,sBAAsB,CAAC;QACtD,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC;QAClC,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;QACpC,IAAI,CAAC,iBAAiB,GAAG,gBAAgB,CAAC;IAC5C,CAAC;IAED,UAAU,CAAC,MAAgC;QACzC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC/B,MAAM,SAAS,GACb,IAAI,CAAC,uBAAuB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC;QAC1E,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,EAAC,OAAO,EAAE,SAAS,EAAC,CAAC;IAC9B,CAAC;IAED,YAAY,CAAC,MAAkC;QAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAC,OAAO,EAAE,OAAO,EAAC,CAAC,CAAC;QAC9D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,mCAAqB,CAAC,wBAAwB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,EAAC,kBAAkB,EAAE,KAAK,CAAC,kBAAkB,EAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,WAAW,CACf,MAAiC;QAEjC,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO;YAC3B,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC;YAClD,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACtE,OAAO;YACL,MAAM;YACN,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC;IACJ,CAAC;CACF;AAjDD,oCAiDC"}
@@ -0,0 +1,36 @@
import type { Protocol } from 'devtools-protocol';
import type { CdpClient } from '../../../cdp/CdpClient.js';
import type { ChromiumBidi, Session } from '../../../protocol/protocol.js';
import { Deferred } from '../../../utils/Deferred.js';
import type { LoggerFn } from '../../../utils/log.js';
import type { Result } from '../../../utils/result.js';
import type { BrowsingContextStorage } from '../context/BrowsingContextStorage.js';
import type { NetworkStorage } from '../network/NetworkStorage.js';
import type { ChannelProxy } from '../script/ChannelProxy.js';
import type { PreloadScriptStorage } from '../script/PreloadScriptStorage.js';
import type { RealmStorage } from '../script/RealmStorage.js';
import type { EventManager } from '../session/EventManager.js';
export declare class CdpTarget {
#private;
static create(targetId: Protocol.Target.TargetID, cdpClient: CdpClient, browserCdpClient: CdpClient, realmStorage: RealmStorage, eventManager: EventManager, preloadScriptStorage: PreloadScriptStorage, browsingContextStorage: BrowsingContextStorage, networkStorage: NetworkStorage, unhandledPromptBehavior?: Session.UserPromptHandler, logger?: LoggerFn): CdpTarget;
constructor(targetId: Protocol.Target.TargetID, cdpClient: CdpClient, browserCdpClient: CdpClient, eventManager: EventManager, realmStorage: RealmStorage, preloadScriptStorage: PreloadScriptStorage, browsingContextStorage: BrowsingContextStorage, networkStorage: NetworkStorage, unhandledPromptBehavior?: Session.UserPromptHandler, logger?: LoggerFn);
/** Returns a deferred that resolves when the target is unblocked. */
get unblocked(): Deferred<Result<void>>;
get id(): Protocol.Target.TargetID;
get cdpClient(): CdpClient;
get browserCdpClient(): CdpClient;
/** Needed for CDP escape path. */
get cdpSessionId(): Protocol.Target.SessionID;
toggleFetchIfNeeded(): Promise<void>;
/**
* Toggles both Network and Fetch domains.
*/
toggleNetworkIfNeeded(): Promise<void>;
/**
* All the ProxyChannels from all the preload scripts of the given
* BrowsingContext.
*/
getChannels(): ChannelProxy[];
get topLevelId(): string;
isSubscribedTo(moduleOrEvent: ChromiumBidi.EventNames): boolean;
}
@@ -0,0 +1,236 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CdpTarget = void 0;
const chromium_bidi_js_1 = require("../../../protocol/chromium-bidi.js");
const Deferred_js_1 = require("../../../utils/Deferred.js");
const log_js_1 = require("../../../utils/log.js");
const BrowsingContextImpl_js_1 = require("../context/BrowsingContextImpl.js");
const LogManager_js_1 = require("../log/LogManager.js");
class CdpTarget {
#id;
#cdpClient;
#browserCdpClient;
#realmStorage;
#eventManager;
#preloadScriptStorage;
#browsingContextStorage;
#networkStorage;
#unblocked = new Deferred_js_1.Deferred();
#unhandledPromptBehavior;
#logger;
#networkDomainEnabled = false;
#fetchDomainStages = {
request: false,
response: false,
auth: false,
};
static create(targetId, cdpClient, browserCdpClient, realmStorage, eventManager, preloadScriptStorage, browsingContextStorage, networkStorage, unhandledPromptBehavior, logger) {
const cdpTarget = new CdpTarget(targetId, cdpClient, browserCdpClient, eventManager, realmStorage, preloadScriptStorage, browsingContextStorage, networkStorage, unhandledPromptBehavior, logger);
LogManager_js_1.LogManager.create(cdpTarget, realmStorage, eventManager, logger);
cdpTarget.#setEventListeners();
// No need to await.
// Deferred will be resolved when the target is unblocked.
void cdpTarget.#unblock();
return cdpTarget;
}
constructor(targetId, cdpClient, browserCdpClient, eventManager, realmStorage, preloadScriptStorage, browsingContextStorage, networkStorage, unhandledPromptBehavior, logger) {
this.#id = targetId;
this.#cdpClient = cdpClient;
this.#browserCdpClient = browserCdpClient;
this.#eventManager = eventManager;
this.#realmStorage = realmStorage;
this.#preloadScriptStorage = preloadScriptStorage;
this.#networkStorage = networkStorage;
this.#browsingContextStorage = browsingContextStorage;
this.#unhandledPromptBehavior = unhandledPromptBehavior;
this.#logger = logger;
}
/** Returns a deferred that resolves when the target is unblocked. */
get unblocked() {
return this.#unblocked;
}
get id() {
return this.#id;
}
get cdpClient() {
return this.#cdpClient;
}
get browserCdpClient() {
return this.#browserCdpClient;
}
/** Needed for CDP escape path. */
get cdpSessionId() {
// SAFETY we got the client by it's id for creating
return this.#cdpClient.sessionId;
}
/**
* Enables all the required CDP domains and unblocks the target.
*/
async #unblock() {
try {
await Promise.all([
this.#cdpClient.sendCommand('Page.enable'),
// There can be some existing frames in the target, if reconnecting to an
// existing browser instance, e.g. via Puppeteer. Need to restore the browsing
// contexts for the frames to correctly handle further events, like
// `Runtime.executionContextCreated`.
// It's important to schedule this task together with enabling domains commands to
// prepare the tree before the events (e.g. Runtime.executionContextCreated) start
// coming.
// https://github.com/GoogleChromeLabs/chromium-bidi/issues/2282
this.#cdpClient
.sendCommand('Page.getFrameTree')
.then((frameTree) => this.#restoreFrameTreeState(frameTree.frameTree)),
this.#cdpClient.sendCommand('Runtime.enable'),
this.#cdpClient.sendCommand('Page.setLifecycleEventsEnabled', {
enabled: true,
}),
this.toggleNetworkIfNeeded(),
this.#cdpClient.sendCommand('Target.setAutoAttach', {
autoAttach: true,
waitForDebuggerOnStart: true,
flatten: true,
}),
this.#initAndEvaluatePreloadScripts(),
this.#cdpClient.sendCommand('Runtime.runIfWaitingForDebugger'),
]);
}
catch (error) {
this.#logger?.(log_js_1.LogType.debugError, 'Failed to unblock target', error);
// The target might have been closed before the initialization finished.
if (!this.#cdpClient.isCloseError(error)) {
this.#unblocked.resolve({
kind: 'error',
error,
});
return;
}
}
this.#unblocked.resolve({
kind: 'success',
value: undefined,
});
}
#restoreFrameTreeState(frameTree) {
const frame = frameTree.frame;
const maybeContext = this.#browsingContextStorage.findContext(frame.id);
if (maybeContext !== undefined) {
// Restoring parent of already known browsing context. This means the target is
// OOPiF and the BiDi session was connected to already existing browser instance.
if (maybeContext.parentId === null &&
frame.parentId !== null &&
frame.parentId !== undefined) {
maybeContext.parentId = frame.parentId;
}
}
if (maybeContext === undefined && frame.parentId !== undefined) {
// Restore not yet known nested frames. The top-level frame is created when the
// target is attached.
const parentBrowsingContext = this.#browsingContextStorage.getContext(frame.parentId);
BrowsingContextImpl_js_1.BrowsingContextImpl.create(frame.id, frame.parentId, parentBrowsingContext.userContext, parentBrowsingContext.cdpTarget, this.#eventManager, this.#browsingContextStorage, this.#realmStorage, frame.url, undefined, this.#unhandledPromptBehavior, this.#logger);
}
frameTree.childFrames?.map((frameTree) => this.#restoreFrameTreeState(frameTree));
}
async toggleFetchIfNeeded() {
const stages = this.#networkStorage.getInterceptionStages(this.topLevelId);
if (
// Only toggle interception when Network is enabled
!this.#networkDomainEnabled ||
(this.#fetchDomainStages.request === stages.request &&
this.#fetchDomainStages.response === stages.response &&
this.#fetchDomainStages.auth === stages.auth)) {
return;
}
const patterns = [];
this.#fetchDomainStages = stages;
if (stages.request || stages.auth) {
// CDP quirk we need request interception when we intercept auth
patterns.push({
urlPattern: '*',
requestStage: 'Request',
});
}
if (stages.response) {
patterns.push({
urlPattern: '*',
requestStage: 'Response',
});
}
if (patterns.length) {
await this.#cdpClient.sendCommand('Fetch.enable', {
patterns,
handleAuthRequests: stages.auth,
});
}
else {
await this.#cdpClient.sendCommand('Fetch.disable');
}
}
/**
* Toggles both Network and Fetch domains.
*/
async toggleNetworkIfNeeded() {
const enabled = this.isSubscribedTo(chromium_bidi_js_1.BiDiModule.Network);
if (enabled === this.#networkDomainEnabled) {
return;
}
this.#networkDomainEnabled = enabled;
try {
await Promise.all([
this.#cdpClient.sendCommand(enabled ? 'Network.enable' : 'Network.disable'),
this.toggleFetchIfNeeded(),
]);
}
catch (err) {
this.#logger?.(log_js_1.LogType.debugError, err);
this.#networkDomainEnabled = !enabled;
}
}
#setEventListeners() {
this.#cdpClient.on('*', (event, params) => {
// We may encounter uses for EventEmitter other than CDP events,
// which we want to skip.
if (typeof event !== 'string') {
return;
}
this.#eventManager.registerEvent({
type: 'event',
method: `cdp.${event}`,
params: {
event,
params,
session: this.cdpSessionId,
},
}, this.id);
});
}
/**
* All the ProxyChannels from all the preload scripts of the given
* BrowsingContext.
*/
getChannels() {
return this.#preloadScriptStorage
.find()
.flatMap((script) => script.channels);
}
/** Loads all top-level preload scripts. */
async #initAndEvaluatePreloadScripts() {
await Promise.all(this.#preloadScriptStorage
.find({
// Needed for OOPIF
targetId: this.topLevelId,
global: true,
})
.map((script) => {
return script.initInTarget(this, true);
}));
}
get topLevelId() {
return (this.#browsingContextStorage.findTopLevelContextId(this.id) ?? this.id);
}
isSubscribedTo(moduleOrEvent) {
return this.#eventManager.subscriptionManager.isSubscribedTo(moduleOrEvent, this.topLevelId);
}
}
exports.CdpTarget = CdpTarget;
//# sourceMappingURL=CdpTarget.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
import type { CdpClient } from '../../../cdp/CdpClient.js';
import type { CdpConnection } from '../../../cdp/CdpConnection.js';
import type { Browser, Session } from '../../../protocol/protocol.js';
import { type LoggerFn } from '../../../utils/log.js';
import type { BrowsingContextStorage } from '../context/BrowsingContextStorage.js';
import type { NetworkStorage } from '../network/NetworkStorage.js';
import type { PreloadScriptStorage } from '../script/PreloadScriptStorage.js';
import type { RealmStorage } from '../script/RealmStorage.js';
import type { EventManager } from '../session/EventManager.js';
export declare class CdpTargetManager {
#private;
constructor(cdpConnection: CdpConnection, browserCdpClient: CdpClient, selfTargetId: string, eventManager: EventManager, browsingContextStorage: BrowsingContextStorage, realmStorage: RealmStorage, networkStorage: NetworkStorage, preloadScriptStorage: PreloadScriptStorage, defaultUserContextId: Browser.UserContext, unhandledPromptBehavior?: Session.UserPromptHandler, logger?: LoggerFn);
}
@@ -0,0 +1,212 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CdpTargetManager = void 0;
const log_js_1 = require("../../../utils/log.js");
const BrowsingContextImpl_js_1 = require("../context/BrowsingContextImpl.js");
const WorkerRealm_js_1 = require("../script/WorkerRealm.js");
const CdpTarget_js_1 = require("./CdpTarget.js");
const cdpToBidiTargetTypes = {
service_worker: 'service-worker',
shared_worker: 'shared-worker',
worker: 'dedicated-worker',
};
class CdpTargetManager {
#browserCdpClient;
#cdpConnection;
#targetKeysToBeIgnoredByAutoAttach = new Set();
#selfTargetId;
#eventManager;
#browsingContextStorage;
#networkStorage;
#preloadScriptStorage;
#realmStorage;
#defaultUserContextId;
#logger;
#unhandledPromptBehavior;
constructor(cdpConnection, browserCdpClient, selfTargetId, eventManager, browsingContextStorage, realmStorage, networkStorage, preloadScriptStorage, defaultUserContextId, unhandledPromptBehavior, logger) {
this.#cdpConnection = cdpConnection;
this.#browserCdpClient = browserCdpClient;
this.#targetKeysToBeIgnoredByAutoAttach.add(selfTargetId);
this.#selfTargetId = selfTargetId;
this.#eventManager = eventManager;
this.#browsingContextStorage = browsingContextStorage;
this.#preloadScriptStorage = preloadScriptStorage;
this.#networkStorage = networkStorage;
this.#realmStorage = realmStorage;
this.#defaultUserContextId = defaultUserContextId;
this.#unhandledPromptBehavior = unhandledPromptBehavior;
this.#logger = logger;
this.#setEventListeners(browserCdpClient);
}
/**
* This method is called for each CDP session, since this class is responsible
* for creating and destroying all targets and browsing contexts.
*/
#setEventListeners(cdpClient) {
cdpClient.on('Target.attachedToTarget', (params) => {
this.#handleAttachedToTargetEvent(params, cdpClient);
});
cdpClient.on('Target.detachedFromTarget', this.#handleDetachedFromTargetEvent.bind(this));
cdpClient.on('Target.targetInfoChanged', this.#handleTargetInfoChangedEvent.bind(this));
cdpClient.on('Inspector.targetCrashed', () => {
this.#handleTargetCrashedEvent(cdpClient);
});
cdpClient.on('Page.frameAttached', this.#handleFrameAttachedEvent.bind(this));
cdpClient.on('Page.frameDetached', this.#handleFrameDetachedEvent.bind(this));
}
#handleFrameAttachedEvent(params) {
const parentBrowsingContext = this.#browsingContextStorage.findContext(params.parentFrameId);
if (parentBrowsingContext !== undefined) {
BrowsingContextImpl_js_1.BrowsingContextImpl.create(params.frameId, params.parentFrameId, parentBrowsingContext.userContext, parentBrowsingContext.cdpTarget, this.#eventManager, this.#browsingContextStorage, this.#realmStorage,
// At this point, we don't know the URL of the frame yet, so it will be updated
// later.
'about:blank', undefined, this.#unhandledPromptBehavior, this.#logger);
}
}
#handleFrameDetachedEvent(params) {
// In case of OOPiF no need in deleting BrowsingContext.
if (params.reason === 'swap') {
return;
}
this.#browsingContextStorage.findContext(params.frameId)?.dispose();
}
#handleAttachedToTargetEvent(params, parentSessionCdpClient) {
const { sessionId, targetInfo } = params;
const targetCdpClient = this.#cdpConnection.getCdpClient(sessionId);
const detach = async () => {
// Detaches and resumes the target suppressing errors.
await targetCdpClient
.sendCommand('Runtime.runIfWaitingForDebugger')
.then(() => parentSessionCdpClient.sendCommand('Target.detachFromTarget', params))
.catch((error) => this.#logger?.(log_js_1.LogType.debugError, error));
};
if (this.#selfTargetId !== targetInfo.targetId) {
// Service workers are special case because they attach to the
// browser target and the page target (so twice per worker) during
// the regular auto-attach and might hang if the CDP session on
// the browser level is not detached. The logic to detach the
// right session is handled in the switch below.
const targetKey = targetInfo.type === 'service_worker'
? `${parentSessionCdpClient.sessionId}_${targetInfo.targetId}`
: targetInfo.targetId;
// Mapper generally only needs one session per target. If we
// receive additional auto-attached sessions, that is very likely
// coming from custom CDP sessions.
if (this.#targetKeysToBeIgnoredByAutoAttach.has(targetKey)) {
// Return to leave the session untouched.
return;
}
this.#targetKeysToBeIgnoredByAutoAttach.add(targetKey);
}
switch (targetInfo.type) {
case 'page':
case 'iframe': {
if (this.#selfTargetId === targetInfo.targetId) {
void detach();
return;
}
const cdpTarget = this.#createCdpTarget(targetCdpClient, targetInfo);
const maybeContext = this.#browsingContextStorage.findContext(targetInfo.targetId);
if (maybeContext && targetInfo.type === 'iframe') {
// OOPiF.
maybeContext.updateCdpTarget(cdpTarget);
}
else {
const userContext = targetInfo.browserContextId &&
targetInfo.browserContextId !== this.#defaultUserContextId
? targetInfo.browserContextId
: 'default';
// New context.
BrowsingContextImpl_js_1.BrowsingContextImpl.create(targetInfo.targetId, null, userContext, cdpTarget, this.#eventManager, this.#browsingContextStorage, this.#realmStorage,
// Hack: when a new target created, CDP emits targetInfoChanged with an empty
// url, and navigates it to about:blank later. When the event is emitted for
// an existing target (reconnect), the url is already known, and navigation
// events will not be emitted anymore. Replacing empty url with `about:blank`
// allows to handle both cases in the same way.
// "7.3.2.1 Creating browsing contexts".
// https://html.spec.whatwg.org/multipage/document-sequences.html#creating-browsing-contexts
// TODO: check who to deal with non-null creator and its `creatorOrigin`.
targetInfo.url === '' ? 'about:blank' : targetInfo.url, targetInfo.openerFrameId ?? targetInfo.openerId, this.#unhandledPromptBehavior, this.#logger);
}
return;
}
case 'service_worker':
case 'worker': {
const realm = this.#realmStorage.findRealm({
cdpSessionId: parentSessionCdpClient.sessionId,
});
// If there is no browsing context, this worker is already terminated.
if (!realm) {
void detach();
return;
}
const cdpTarget = this.#createCdpTarget(targetCdpClient, targetInfo);
this.#handleWorkerTarget(cdpToBidiTargetTypes[targetInfo.type], cdpTarget, realm);
return;
}
// In CDP, we only emit shared workers on the browser and not the set of
// frames that use the shared worker. If we change this in the future to
// behave like service workers (emits on both browser and frame targets),
// we can remove this block and merge service workers with the above one.
case 'shared_worker': {
const cdpTarget = this.#createCdpTarget(targetCdpClient, targetInfo);
this.#handleWorkerTarget(cdpToBidiTargetTypes[targetInfo.type], cdpTarget);
return;
}
}
// DevTools or some other not supported by BiDi target. Just release
// debugger and ignore them.
void detach();
}
#createCdpTarget(targetCdpClient, targetInfo) {
this.#setEventListeners(targetCdpClient);
const target = CdpTarget_js_1.CdpTarget.create(targetInfo.targetId, targetCdpClient, this.#browserCdpClient, this.#realmStorage, this.#eventManager, this.#preloadScriptStorage, this.#browsingContextStorage, this.#networkStorage, this.#unhandledPromptBehavior, this.#logger);
this.#networkStorage.onCdpTargetCreated(target);
return target;
}
#workers = new Map();
#handleWorkerTarget(realmType, cdpTarget, ownerRealm) {
cdpTarget.cdpClient.on('Runtime.executionContextCreated', (params) => {
const { uniqueId, id, origin } = params.context;
const workerRealm = new WorkerRealm_js_1.WorkerRealm(cdpTarget.cdpClient, this.#eventManager, id, this.#logger, (0, BrowsingContextImpl_js_1.serializeOrigin)(origin), ownerRealm ? [ownerRealm] : [], uniqueId, this.#realmStorage, realmType);
this.#workers.set(cdpTarget.cdpSessionId, workerRealm);
});
}
#handleDetachedFromTargetEvent({ sessionId, targetId, }) {
if (targetId) {
this.#preloadScriptStorage.find({ targetId }).map((preloadScript) => {
preloadScript.dispose(targetId);
});
}
const context = this.#browsingContextStorage.findContextBySession(sessionId);
if (context) {
context.dispose();
return;
}
const worker = this.#workers.get(sessionId);
if (worker) {
this.#realmStorage.deleteRealms({
cdpSessionId: worker.cdpClient.sessionId,
});
}
}
#handleTargetInfoChangedEvent(params) {
const context = this.#browsingContextStorage.findContext(params.targetInfo.targetId);
if (context) {
context.onTargetInfoChanged(params);
}
}
#handleTargetCrashedEvent(cdpClient) {
// This is primarily used for service and shared workers. CDP tends to not
// signal they closed gracefully and instead says they crashed to signal
// they are closed.
const realms = this.#realmStorage.findRealms({
cdpSessionId: cdpClient.sessionId,
});
for (const realm of realms) {
realm.dispose();
}
}
}
exports.CdpTargetManager = CdpTargetManager;
//# sourceMappingURL=CdpTargetManager.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,82 @@
/**
* 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.
*/
import type { Protocol } from 'devtools-protocol';
import { BrowsingContext, Session } from '../../../protocol/protocol.js';
import { type LoggerFn } from '../../../utils/log.js';
import type { CdpTarget } from '../cdp/CdpTarget.js';
import type { Realm } from '../script/Realm.js';
import type { RealmStorage } from '../script/RealmStorage.js';
import type { EventManager } from '../session/EventManager.js';
import type { BrowsingContextStorage } from './BrowsingContextStorage.js';
export declare class BrowsingContextImpl {
#private;
static readonly LOGGER_PREFIX: "debug:browsingContext";
readonly userContext: string;
private constructor();
static create(id: BrowsingContext.BrowsingContext, parentId: BrowsingContext.BrowsingContext | null, userContext: string, cdpTarget: CdpTarget, eventManager: EventManager, browsingContextStorage: BrowsingContextStorage, realmStorage: RealmStorage, url: string, originalOpener?: string, unhandledPromptBehavior?: Session.UserPromptHandler, logger?: LoggerFn): BrowsingContextImpl;
static getTimestamp(): number;
/**
* @see https://html.spec.whatwg.org/multipage/document-sequences.html#navigable
*/
get navigableId(): string | undefined;
/**
* Virtual navigation ID. Required, as CDP `loaderId` cannot be mapped 1:1 to all the
* navigations (e.g. same document navigations). Updated after each navigation,
* including same-document ones.
*/
get virtualNavigationId(): string;
dispose(): void;
/** Returns the ID of this context. */
get id(): BrowsingContext.BrowsingContext;
/** Returns the parent context ID. */
get parentId(): BrowsingContext.BrowsingContext | null;
/** Sets the parent context ID and updates parent's children. */
set parentId(parentId: BrowsingContext.BrowsingContext | null);
/** Returns the parent context. */
get parent(): BrowsingContextImpl | null;
/** Returns all direct children contexts. */
get directChildren(): BrowsingContextImpl[];
/** Returns all children contexts, flattened. */
get allChildren(): BrowsingContextImpl[];
/**
* Returns true if this is a top-level context.
* This is the case whenever the parent context ID is null.
*/
isTopLevelContext(): boolean;
get top(): BrowsingContextImpl;
addChild(childId: BrowsingContext.BrowsingContext): void;
get cdpTarget(): CdpTarget;
updateCdpTarget(cdpTarget: CdpTarget): void;
get url(): string;
lifecycleLoaded(): Promise<void>;
targetUnblockedOrThrow(): Promise<void>;
getOrCreateSandbox(sandbox: string | undefined): Promise<Realm>;
serializeToBidiValue(maxDepth?: number, addParentField?: boolean): BrowsingContext.Info;
onTargetInfoChanged(params: Protocol.Target.TargetInfoChangedEvent): void;
navigate(url: string, wait: BrowsingContext.ReadinessState): Promise<BrowsingContext.NavigateResult>;
reload(ignoreCache: boolean, wait: BrowsingContext.ReadinessState): Promise<BrowsingContext.NavigateResult>;
setViewport(viewport?: BrowsingContext.Viewport | null, devicePixelRatio?: number | null): Promise<void>;
handleUserPrompt(accept?: boolean, userText?: string): Promise<void>;
activate(): Promise<void>;
captureScreenshot(params: BrowsingContext.CaptureScreenshotParameters): Promise<BrowsingContext.CaptureScreenshotResult>;
print(params: BrowsingContext.PrintParameters): Promise<BrowsingContext.PrintResult>;
close(): Promise<void>;
traverseHistory(delta: number): Promise<void>;
toggleModulesIfNeeded(): Promise<void>;
locateNodes(params: BrowsingContext.LocateNodesParameters): Promise<BrowsingContext.LocateNodesResult>;
}
export declare function serializeOrigin(origin: string): string;
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
import type { CdpClient } from '../../../cdp/CdpClient.js';
import { BrowsingContext, type EmptyResult } from '../../../protocol/protocol.js';
import type { EventManager } from '../session/EventManager.js';
import type { BrowsingContextStorage } from './BrowsingContextStorage.js';
export declare class BrowsingContextProcessor {
#private;
constructor(browserCdpClient: CdpClient, browsingContextStorage: BrowsingContextStorage, eventManager: EventManager);
getTree(params: BrowsingContext.GetTreeParameters): BrowsingContext.GetTreeResult;
create(params: BrowsingContext.CreateParameters): Promise<BrowsingContext.CreateResult>;
navigate(params: BrowsingContext.NavigateParameters): Promise<BrowsingContext.NavigateResult>;
reload(params: BrowsingContext.ReloadParameters): Promise<EmptyResult>;
activate(params: BrowsingContext.ActivateParameters): Promise<EmptyResult>;
captureScreenshot(params: BrowsingContext.CaptureScreenshotParameters): Promise<BrowsingContext.CaptureScreenshotResult>;
print(params: BrowsingContext.PrintParameters): Promise<BrowsingContext.PrintResult>;
setViewport(params: BrowsingContext.SetViewportParameters): Promise<EmptyResult>;
traverseHistory(params: BrowsingContext.TraverseHistoryParameters): Promise<BrowsingContext.TraverseHistoryResult>;
handleUserPrompt(params: BrowsingContext.HandleUserPromptParameters): Promise<EmptyResult>;
close(params: BrowsingContext.CloseParameters): Promise<EmptyResult>;
locateNodes(params: BrowsingContext.LocateNodesParameters): Promise<BrowsingContext.LocateNodesResult>;
}
@@ -0,0 +1,196 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowsingContextProcessor = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
class BrowsingContextProcessor {
#browserCdpClient;
#browsingContextStorage;
#eventManager;
constructor(browserCdpClient, browsingContextStorage, eventManager) {
this.#browserCdpClient = browserCdpClient;
this.#browsingContextStorage = browsingContextStorage;
this.#eventManager = eventManager;
this.#eventManager.addSubscribeHook(protocol_js_1.ChromiumBidi.BrowsingContext.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 protocol_js_1.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" /* BrowsingContext.CreateType.Tab */:
newWindow = false;
break;
case "window" /* BrowsingContext.CreateType.Window */:
newWindow = true;
break;
}
if (!existingContexts.length) {
// If there are no contexts in the given user context, we need to set
// newWindow to true as newWindow=false will be rejected.
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 (
// See https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/devtools/protocol/target_handler.cc;l=90;drc=e80392ac11e48a691f4309964cab83a3a59e01c8
err.message.startsWith('Failed to find browser context with id') ||
// See https://source.chromium.org/chromium/chromium/src/+/main:headless/lib/browser/protocol/target_handler.cc;l=49;drc=e80392ac11e48a691f4309964cab83a3a59e01c8
err.message === 'browserContextId') {
throw new protocol_js_1.NoSuchUserContextException(`The context ${userContext} was not found`);
}
throw err;
}
// Wait for the new tab to be loaded to avoid race conditions in the
// `browsingContext` events, when the `browsingContext.domContentLoaded` and
// `browsingContext.load` events from the initial `about:blank` navigation
// are emitted after the next navigation is started.
// Details: https://github.com/web-platform-tests/wpt/issues/35846
const contextId = result.targetId;
const context = this.#browsingContextStorage.getContext(contextId);
await context.lifecycleLoaded();
return { context: context.id };
}
navigate(params) {
const context = this.#browsingContextStorage.getContext(params.context);
return context.navigate(params.url, params.wait ?? "none" /* BrowsingContext.ReadinessState.None */);
}
reload(params) {
const context = this.#browsingContextStorage.getContext(params.context);
return context.reload(params.ignoreCache ?? false, params.wait ?? "none" /* BrowsingContext.ReadinessState.None */);
}
async activate(params) {
const context = this.#browsingContextStorage.getContext(params.context);
if (!context.isTopLevelContext()) {
throw new protocol_js_1.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 protocol_js_1.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 protocol_js_1.InvalidArgumentException(`No browsing context with id ${params.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) {
// Heuristically determine the error
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/protocol/page_handler.cc;l=1085?q=%22No%20dialog%20is%20showing%22&ss=chromium
if (error.message?.includes('No dialog is showing')) {
throw new protocol_js_1.NoSuchAlertException('No dialog is showing');
}
throw error;
}
return {};
}
async close(params) {
const context = this.#browsingContextStorage.getContext(params.context);
if (!context.isTopLevelContext()) {
throw new protocol_js_1.InvalidArgumentException(`Non top-level browsing context ${context.id} cannot be closed.`);
}
try {
const detachedFromTargetPromise = new Promise((resolve) => {
const onContextDestroyed = (event) => {
if (event.targetId === params.context) {
this.#browserCdpClient.off('Target.detachedFromTarget', onContextDestroyed);
resolve();
}
};
this.#browserCdpClient.on('Target.detachedFromTarget', onContextDestroyed);
});
if (params.promptUnload) {
await context.close();
}
else {
await this.#browserCdpClient.sendCommand('Target.closeTarget', {
targetId: params.context,
});
}
// Sometimes CDP command finishes before `detachedFromTarget` event,
// sometimes after. Wait for the CDP command to be finished, and then wait
// for `detachedFromTarget` if it hasn't emitted.
await detachedFromTargetPromise;
}
catch (error) {
// Swallow error that arise from the page being destroyed
// Example is navigating to faulty SSL certificate
if (!(error.code === -32000 /* CdpErrorConstants.GENERIC_ERROR */ &&
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: protocol_js_1.ChromiumBidi.BrowsingContext.EventNames.ContextCreated,
params: context.serializeToBidiValue(),
}, context.id);
});
return Promise.resolve();
}
}
exports.BrowsingContextProcessor = BrowsingContextProcessor;
//# sourceMappingURL=BrowsingContextProcessor.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,42 @@
/**
* 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.
*/
import { type BrowsingContext } from '../../../protocol/protocol.js';
import type { BrowsingContextImpl } from './BrowsingContextImpl.js';
/** Container class for browsing contexts. */
export declare class BrowsingContextStorage {
#private;
/** Gets all top-level contexts, i.e. those with no parent. */
getTopLevelContexts(): BrowsingContextImpl[];
/** Gets all contexts. */
getAllContexts(): BrowsingContextImpl[];
/** Deletes the context with the given ID. */
deleteContextById(id: BrowsingContext.BrowsingContext): void;
/** Deletes the given context. */
deleteContext(context: BrowsingContextImpl): void;
/** Tracks the given context. */
addContext(context: BrowsingContextImpl): void;
/** Returns true whether there is an existing context with the given ID. */
hasContext(id: BrowsingContext.BrowsingContext): boolean;
/** Gets the context with the given ID, if any. */
findContext(id: BrowsingContext.BrowsingContext): BrowsingContextImpl | undefined;
/** Returns the top-level context ID of the given context, if any. */
findTopLevelContextId(id: BrowsingContext.BrowsingContext | null): BrowsingContext.BrowsingContext | null;
findContextBySession(sessionId: string): BrowsingContextImpl | undefined;
/** Gets the context with the given ID, if any, otherwise throws. */
getContext(id: BrowsingContext.BrowsingContext): BrowsingContextImpl;
verifyTopLevelContextsList(contexts: BrowsingContext.BrowsingContext[] | undefined): Set<BrowsingContextImpl>;
}
@@ -0,0 +1,99 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowsingContextStorage = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
/** Container class for browsing contexts. */
class BrowsingContextStorage {
/** Map from context ID to context implementation. */
#contexts = new Map();
/** Gets all top-level contexts, i.e. those with no parent. */
getTopLevelContexts() {
return this.getAllContexts().filter((context) => context.isTopLevelContext());
}
/** Gets all contexts. */
getAllContexts() {
return Array.from(this.#contexts.values());
}
/** Deletes the context with the given ID. */
deleteContextById(id) {
this.#contexts.delete(id);
}
/** Deletes the given context. */
deleteContext(context) {
this.#contexts.delete(context.id);
}
/** Tracks the given context. */
addContext(context) {
this.#contexts.set(context.id, context);
}
/** Returns true whether there is an existing context with the given ID. */
hasContext(id) {
return this.#contexts.has(id);
}
/** Gets the context with the given ID, if any. */
findContext(id) {
return this.#contexts.get(id);
}
/** Returns the top-level context ID of the given context, if any. */
findTopLevelContextId(id) {
if (id === null) {
return null;
}
const maybeContext = this.findContext(id);
const parentId = maybeContext?.parentId ?? null;
if (parentId === null) {
return id;
}
return this.findTopLevelContextId(parentId);
}
findContextBySession(sessionId) {
for (const context of this.#contexts.values()) {
if (context.cdpTarget.cdpSessionId === sessionId) {
return context;
}
}
return;
}
/** Gets the context with the given ID, if any, otherwise throws. */
getContext(id) {
const result = this.findContext(id);
if (result === undefined) {
throw new protocol_js_1.NoSuchFrameException(`Context ${id} not found`);
}
return result;
}
verifyTopLevelContextsList(contexts) {
const foundContexts = new Set();
if (!contexts) {
return foundContexts;
}
for (const contextId of contexts) {
const context = this.getContext(contextId);
if (context.isTopLevelContext()) {
foundContexts.add(context);
}
else {
throw new protocol_js_1.InvalidArgumentException(`Non top-level context '${contextId}' given.`);
}
}
return foundContexts;
}
}
exports.BrowsingContextStorage = BrowsingContextStorage;
//# sourceMappingURL=BrowsingContextStorage.js.map
@@ -0,0 +1 @@
{"version":3,"file":"BrowsingContextStorage.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/context/BrowsingContextStorage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
/**
* 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.
*/
import type { BrowsingContextImpl } from '../context/BrowsingContextImpl.js';
import type { ActionOption } from './ActionOption.js';
import type { InputState } from './InputState.js';
export declare class ActionDispatcher {
#private;
static isMacOS: (context: BrowsingContextImpl) => Promise<boolean>;
constructor(inputState: InputState, context: BrowsingContextImpl, isMacOS: boolean);
dispatchActions(optionsByTick: readonly (readonly Readonly<ActionOption>[])[]): Promise<void>;
dispatchTickActions(options: readonly Readonly<ActionOption>[]): Promise<void>;
}
@@ -0,0 +1,713 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActionDispatcher = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const assert_js_1 = require("../../../utils/assert.js");
const GraphemeTools_1 = require("../../../utils/GraphemeTools");
const InputSource_js_1 = require("./InputSource.js");
const keyUtils_js_1 = require("./keyUtils.js");
const USKeyboardLayout_js_1 = require("./USKeyboardLayout.js");
/** https://w3c.github.io/webdriver/#dfn-center-point */
const CALCULATE_IN_VIEW_CENTER_PT_DECL = ((i) => {
const t = i.getClientRects()[0], e = Math.max(0, Math.min(t.x, t.x + t.width)), n = Math.min(window.innerWidth, Math.max(t.x, t.x + t.width)), h = Math.max(0, Math.min(t.y, t.y + t.height)), m = Math.min(window.innerHeight, Math.max(t.y, t.y + t.height));
return [e + ((n - e) >> 1), h + ((m - h) >> 1)];
}).toString();
const IS_MAC_DECL = (() => {
return navigator.platform.toLowerCase().includes('mac');
}).toString();
async function getElementCenter(context, element) {
const sandbox = await context.getOrCreateSandbox(undefined);
const result = await sandbox.callFunction(CALCULATE_IN_VIEW_CENTER_PT_DECL, false, { type: 'undefined' }, [element]);
if (result.type === 'exception') {
throw new protocol_js_1.NoSuchElementException(`Origin element ${element.sharedId} was not found`);
}
(0, assert_js_1.assert)(result.result.type === 'array');
(0, assert_js_1.assert)(result.result.value?.[0]?.type === 'number');
(0, assert_js_1.assert)(result.result.value?.[1]?.type === 'number');
const { result: { value: [{ value: x }, { value: y }], }, } = result;
return { x: x, y: y };
}
class ActionDispatcher {
static isMacOS = async (context) => {
const result = await (await context.getOrCreateSandbox(undefined)).callFunction(IS_MAC_DECL, false);
(0, assert_js_1.assert)(result.type !== 'exception');
(0, assert_js_1.assert)(result.result.type === 'boolean');
return result.result.value;
};
#tickStart = 0;
#tickDuration = 0;
#inputState;
#context;
#isMacOS;
constructor(inputState, context, isMacOS) {
this.#inputState = inputState;
this.#context = context;
this.#isMacOS = isMacOS;
}
async dispatchActions(optionsByTick) {
await this.#inputState.queue.run(async () => {
for (const options of optionsByTick) {
await this.dispatchTickActions(options);
}
});
}
async dispatchTickActions(options) {
this.#tickStart = performance.now();
this.#tickDuration = 0;
for (const { action } of options) {
if ('duration' in action && action.duration !== undefined) {
this.#tickDuration = Math.max(this.#tickDuration, action.duration);
}
}
const promises = [
new Promise((resolve) => setTimeout(resolve, this.#tickDuration)),
];
for (const option of options) {
// In theory we have to wait for each action to happen, but CDP is serial,
// so as an optimization, we queue all CDP commands at once and await all
// of them.
promises.push(this.#dispatchAction(option));
}
await Promise.all(promises);
}
async #dispatchAction({ id, action }) {
const source = this.#inputState.get(id);
const keyState = this.#inputState.getGlobalKeyState();
switch (action.type) {
case 'keyDown': {
// SAFETY: The source is validated before.
await this.#dispatchKeyDownAction(source, action);
this.#inputState.cancelList.push({
id,
action: {
...action,
type: 'keyUp',
},
});
break;
}
case 'keyUp': {
// SAFETY: The source is validated before.
await this.#dispatchKeyUpAction(source, action);
break;
}
case 'pause': {
// TODO: Implement waiting on the input source.
break;
}
case 'pointerDown': {
// SAFETY: The source is validated before.
await this.#dispatchPointerDownAction(source, keyState, action);
this.#inputState.cancelList.push({
id,
action: {
...action,
type: 'pointerUp',
},
});
break;
}
case 'pointerMove': {
// SAFETY: The source is validated before.
await this.#dispatchPointerMoveAction(source, keyState, action);
break;
}
case 'pointerUp': {
// SAFETY: The source is validated before.
await this.#dispatchPointerUpAction(source, keyState, action);
break;
}
case 'scroll': {
// SAFETY: The source is validated before.
await this.#dispatchScrollAction(source, keyState, action);
break;
}
}
}
async #dispatchPointerDownAction(source, keyState, action) {
const { button } = action;
if (source.pressed.has(button)) {
return;
}
source.pressed.add(button);
const { x, y, subtype: pointerType } = source;
const { width, height, pressure, twist, tangentialPressure } = action;
const { tiltX, tiltY } = getTilt(action);
// --- Platform-specific code begins here ---
const { modifiers } = keyState;
const { radiusX, radiusY } = getRadii(width ?? 1, height ?? 1);
switch (pointerType) {
case "mouse" /* Input.PointerType.Mouse */:
case "pen" /* Input.PointerType.Pen */:
// TODO: Implement width and height when available.
await this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchMouseEvent', {
type: 'mousePressed',
x,
y,
modifiers,
button: getCdpButton(button),
buttons: source.buttons,
clickCount: source.setClickCount(button, new InputSource_js_1.PointerSource.ClickContext(x, y, performance.now())),
pointerType,
tangentialPressure,
tiltX,
tiltY,
twist,
force: pressure,
});
break;
case "touch" /* Input.PointerType.Touch */:
await this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchTouchEvent', {
type: 'touchStart',
touchPoints: [
{
x,
y,
radiusX,
radiusY,
tangentialPressure,
tiltX,
tiltY,
twist,
force: pressure,
id: source.pointerId,
},
],
modifiers,
});
break;
}
source.radiusX = radiusX;
source.radiusY = radiusY;
source.force = pressure;
// --- Platform-specific code ends here ---
}
#dispatchPointerUpAction(source, keyState, action) {
const { button } = action;
if (!source.pressed.has(button)) {
return;
}
source.pressed.delete(button);
const { x, y, force, radiusX, radiusY, subtype: pointerType } = source;
// --- Platform-specific code begins here ---
const { modifiers } = keyState;
switch (pointerType) {
case "mouse" /* Input.PointerType.Mouse */:
case "pen" /* Input.PointerType.Pen */:
// TODO: Implement width and height when available.
return this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchMouseEvent', {
type: 'mouseReleased',
x,
y,
modifiers,
button: getCdpButton(button),
buttons: source.buttons,
clickCount: source.getClickCount(button),
pointerType,
});
case "touch" /* Input.PointerType.Touch */:
return this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [
{
x,
y,
id: source.pointerId,
force,
radiusX,
radiusY,
},
],
modifiers,
});
}
// --- Platform-specific code ends here ---
}
async #dispatchPointerMoveAction(source, keyState, action) {
const { x: startX, y: startY, subtype: pointerType } = source;
const { width, height, pressure, twist, tangentialPressure, x: offsetX, y: offsetY, origin = 'viewport', duration = this.#tickDuration, } = action;
const { tiltX, tiltY } = getTilt(action);
const { radiusX, radiusY } = getRadii(width ?? 1, height ?? 1);
const { targetX, targetY } = await this.#getCoordinateFromOrigin(origin, offsetX, offsetY, startX, startY);
if (targetX < 0 || targetY < 0) {
throw new protocol_js_1.MoveTargetOutOfBoundsException(`Cannot move beyond viewport (x: ${targetX}, y: ${targetY})`);
}
let last;
do {
const ratio = duration > 0 ? (performance.now() - this.#tickStart) / duration : 1;
last = ratio >= 1;
let x;
let y;
if (last) {
x = targetX;
y = targetY;
}
else {
x = Math.round(ratio * (targetX - startX) + startX);
y = Math.round(ratio * (targetY - startY) + startY);
}
if (source.x !== x || source.y !== y) {
// --- Platform-specific code begins here ---
const { modifiers } = keyState;
switch (pointerType) {
case "mouse" /* Input.PointerType.Mouse */:
// TODO: Implement width and height when available.
await this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchMouseEvent', {
type: 'mouseMoved',
x,
y,
modifiers,
clickCount: 0,
button: getCdpButton(source.pressed.values().next().value ?? 5),
buttons: source.buttons,
pointerType,
tangentialPressure,
tiltX,
tiltY,
twist,
force: pressure,
});
break;
case "pen" /* Input.PointerType.Pen */:
if (source.pressed.size !== 0) {
// TODO: Implement width and height when available.
await this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchMouseEvent', {
type: 'mouseMoved',
x,
y,
modifiers,
clickCount: 0,
button: getCdpButton(source.pressed.values().next().value ?? 5),
buttons: source.buttons,
pointerType,
tangentialPressure,
tiltX,
tiltY,
twist,
force: pressure,
});
}
break;
case "touch" /* Input.PointerType.Touch */:
if (source.pressed.size !== 0) {
await this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchTouchEvent', {
type: 'touchMove',
touchPoints: [
{
x,
y,
radiusX,
radiusY,
tangentialPressure,
tiltX,
tiltY,
twist,
force: pressure,
id: source.pointerId,
},
],
modifiers,
});
}
break;
}
// --- Platform-specific code ends here ---
source.x = x;
source.y = y;
source.radiusX = radiusX;
source.radiusY = radiusY;
source.force = pressure;
}
} while (!last);
}
async #getCoordinateFromOrigin(origin, offsetX, offsetY, startX, startY) {
let targetX;
let targetY;
switch (origin) {
case 'viewport':
targetX = offsetX;
targetY = offsetY;
break;
case 'pointer':
targetX = startX + offsetX;
targetY = startY + offsetY;
break;
default: {
const { x: posX, y: posY } = await getElementCenter(this.#context, origin.element);
// SAFETY: These can never be special numbers.
targetX = posX + offsetX;
targetY = posY + offsetY;
break;
}
}
return { targetX, targetY };
}
async #dispatchScrollAction(_source, keyState, action) {
const { deltaX: targetDeltaX, deltaY: targetDeltaY, x: offsetX, y: offsetY, origin = 'viewport', duration = this.#tickDuration, } = action;
if (origin === 'pointer') {
throw new protocol_js_1.InvalidArgumentException('"pointer" origin is invalid for scrolling.');
}
const { targetX, targetY } = await this.#getCoordinateFromOrigin(origin, offsetX, offsetY, 0, 0);
if (targetX < 0 || targetY < 0) {
throw new protocol_js_1.MoveTargetOutOfBoundsException(`Cannot move beyond viewport (x: ${targetX}, y: ${targetY})`);
}
let currentDeltaX = 0;
let currentDeltaY = 0;
let last;
do {
const ratio = duration > 0 ? (performance.now() - this.#tickStart) / duration : 1;
last = ratio >= 1;
let deltaX;
let deltaY;
if (last) {
deltaX = targetDeltaX - currentDeltaX;
deltaY = targetDeltaY - currentDeltaY;
}
else {
deltaX = Math.round(ratio * targetDeltaX - currentDeltaX);
deltaY = Math.round(ratio * targetDeltaY - currentDeltaY);
}
if (deltaX !== 0 || deltaY !== 0) {
// --- Platform-specific code begins here ---
const { modifiers } = keyState;
await this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchMouseEvent', {
type: 'mouseWheel',
deltaX,
deltaY,
x: targetX,
y: targetY,
modifiers,
});
// --- Platform-specific code ends here ---
currentDeltaX += deltaX;
currentDeltaY += deltaY;
}
} while (!last);
}
async #dispatchKeyDownAction(source, action) {
const rawKey = action.value;
if (!(0, GraphemeTools_1.isSingleGrapheme)(rawKey)) {
// https://w3c.github.io/webdriver/#dfn-process-a-key-action
// WebDriver spec allows a grapheme to be used.
throw new protocol_js_1.InvalidArgumentException(`Invalid key value: ${rawKey}`);
}
const isGrapheme = (0, GraphemeTools_1.isSingleComplexGrapheme)(rawKey);
const key = (0, keyUtils_js_1.getNormalizedKey)(rawKey);
const repeat = source.pressed.has(key);
const code = (0, keyUtils_js_1.getKeyCode)(rawKey);
const location = (0, keyUtils_js_1.getKeyLocation)(rawKey);
switch (key) {
case 'Alt':
source.alt = true;
break;
case 'Shift':
source.shift = true;
break;
case 'Control':
source.ctrl = true;
break;
case 'Meta':
source.meta = true;
break;
}
source.pressed.add(key);
const { modifiers } = source;
// --- Platform-specific code begins here ---
// The spread is a little hack so JS gives us an array of unicode characters
// to measure.
const unmodifiedText = getKeyEventUnmodifiedText(key, source, isGrapheme);
const text = getKeyEventText(code ?? '', source) ?? unmodifiedText;
let command;
// The following commands need to be declared because Chromium doesn't
// handle them. See
// https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/blink/renderer/core/editing/editing_behavior.cc;l=169;drc=b8143cf1dfd24842890fcd831c4f5d909bef4fc4;bpv=0;bpt=1.
if (this.#isMacOS && source.meta) {
switch (code) {
case 'KeyA':
command = 'SelectAll';
break;
case 'KeyC':
command = 'Copy';
break;
case 'KeyV':
command = source.shift ? 'PasteAndMatchStyle' : 'Paste';
break;
case 'KeyX':
command = 'Cut';
break;
case 'KeyZ':
command = source.shift ? 'Redo' : 'Undo';
break;
default:
// Intentionally empty.
}
}
const promises = [
this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchKeyEvent', {
type: text ? 'keyDown' : 'rawKeyDown',
windowsVirtualKeyCode: USKeyboardLayout_js_1.KeyToKeyCode[key],
key,
code,
text,
unmodifiedText,
autoRepeat: repeat,
isSystemKey: source.alt || undefined,
location: location < 3 ? location : undefined,
isKeypad: location === 3,
modifiers,
commands: command ? [command] : undefined,
}),
];
// Drag cancelling happens on escape.
if (key === 'Escape') {
if (!source.alt &&
((this.#isMacOS && !source.ctrl && !source.meta) || !this.#isMacOS)) {
promises.push(this.#context.cdpTarget.cdpClient.sendCommand('Input.cancelDragging'));
}
}
await Promise.all(promises);
// --- Platform-specific code ends here ---
}
#dispatchKeyUpAction(source, action) {
const rawKey = action.value;
if (!(0, GraphemeTools_1.isSingleGrapheme)(rawKey)) {
// https://w3c.github.io/webdriver/#dfn-process-a-key-action
// WebDriver spec allows a grapheme to be used.
throw new protocol_js_1.InvalidArgumentException(`Invalid key value: ${rawKey}`);
}
const isGrapheme = (0, GraphemeTools_1.isSingleComplexGrapheme)(rawKey);
const key = (0, keyUtils_js_1.getNormalizedKey)(rawKey);
if (!source.pressed.has(key)) {
return;
}
const code = (0, keyUtils_js_1.getKeyCode)(rawKey);
const location = (0, keyUtils_js_1.getKeyLocation)(rawKey);
switch (key) {
case 'Alt':
source.alt = false;
break;
case 'Shift':
source.shift = false;
break;
case 'Control':
source.ctrl = false;
break;
case 'Meta':
source.meta = false;
break;
}
source.pressed.delete(key);
const { modifiers } = source;
// --- Platform-specific code begins here ---
// The spread is a little hack so JS gives us an array of unicode characters
// to measure.
const unmodifiedText = getKeyEventUnmodifiedText(key, source, isGrapheme);
const text = getKeyEventText(code ?? '', source) ?? unmodifiedText;
return this.#context.cdpTarget.cdpClient.sendCommand('Input.dispatchKeyEvent', {
type: 'keyUp',
windowsVirtualKeyCode: USKeyboardLayout_js_1.KeyToKeyCode[key],
key,
code,
text,
unmodifiedText,
location: location < 3 ? location : undefined,
isSystemKey: source.alt || undefined,
isKeypad: location === 3,
modifiers,
});
// --- Platform-specific code ends here ---
}
}
exports.ActionDispatcher = ActionDispatcher;
/**
* Translates a non-grapheme key to either an `undefined` for a special keys, or a single
* character modified by shift if needed.
*/
const getKeyEventUnmodifiedText = (key, source, isGrapheme) => {
if (isGrapheme) {
// Graphemes should be presented as text in the CDP command.
return key;
}
if (key === 'Enter') {
return '\r';
}
// If key is not a single character, it is a normalized key value, and should be
// presented as key, not text in the CDP command.
return [...key].length === 1
? source.shift
? key.toLocaleUpperCase('en-US')
: key
: undefined;
};
const getKeyEventText = (code, source) => {
if (source.ctrl) {
switch (code) {
case 'Digit2':
if (source.shift) {
return '\x00';
}
break;
case 'KeyA':
return '\x01';
case 'KeyB':
return '\x02';
case 'KeyC':
return '\x03';
case 'KeyD':
return '\x04';
case 'KeyE':
return '\x05';
case 'KeyF':
return '\x06';
case 'KeyG':
return '\x07';
case 'KeyH':
return '\x08';
case 'KeyI':
return '\x09';
case 'KeyJ':
return '\x0A';
case 'KeyK':
return '\x0B';
case 'KeyL':
return '\x0C';
case 'KeyM':
return '\x0D';
case 'KeyN':
return '\x0E';
case 'KeyO':
return '\x0F';
case 'KeyP':
return '\x10';
case 'KeyQ':
return '\x11';
case 'KeyR':
return '\x12';
case 'KeyS':
return '\x13';
case 'KeyT':
return '\x14';
case 'KeyU':
return '\x15';
case 'KeyV':
return '\x16';
case 'KeyW':
return '\x17';
case 'KeyX':
return '\x18';
case 'KeyY':
return '\x19';
case 'KeyZ':
return '\x1A';
case 'BracketLeft':
return '\x1B';
case 'Backslash':
return '\x1C';
case 'BracketRight':
return '\x1D';
case 'Digit6':
if (source.shift) {
return '\x1E';
}
break;
case 'Minus':
return '\x1F';
}
return '';
}
if (source.alt) {
return '';
}
return;
};
function getCdpButton(button) {
switch (button) {
case 0:
return 'left';
case 1:
return 'middle';
case 2:
return 'right';
case 3:
return 'back';
case 4:
return 'forward';
default:
return 'none';
}
}
function getTilt(action) {
// https://w3c.github.io/pointerevents/#converting-between-tiltx-tilty-and-altitudeangle-azimuthangle
const altitudeAngle = action.altitudeAngle ?? 0;
const azimuthAngle = action.azimuthAngle ?? 0;
let tiltXRadians = 0;
let tiltYRadians = 0;
if (altitudeAngle === 0) {
// the pen is in the X-Y plane
if (azimuthAngle === 0 || azimuthAngle === 2 * Math.PI) {
// pen is on positive X axis
tiltXRadians = Math.PI / 2;
}
if (azimuthAngle === Math.PI / 2) {
// pen is on positive Y axis
tiltYRadians = Math.PI / 2;
}
if (azimuthAngle === Math.PI) {
// pen is on negative X axis
tiltXRadians = -Math.PI / 2;
}
if (azimuthAngle === (3 * Math.PI) / 2) {
// pen is on negative Y axis
tiltYRadians = -Math.PI / 2;
}
if (azimuthAngle > 0 && azimuthAngle < Math.PI / 2) {
tiltXRadians = Math.PI / 2;
tiltYRadians = Math.PI / 2;
}
if (azimuthAngle > Math.PI / 2 && azimuthAngle < Math.PI) {
tiltXRadians = -Math.PI / 2;
tiltYRadians = Math.PI / 2;
}
if (azimuthAngle > Math.PI && azimuthAngle < (3 * Math.PI) / 2) {
tiltXRadians = -Math.PI / 2;
tiltYRadians = -Math.PI / 2;
}
if (azimuthAngle > (3 * Math.PI) / 2 && azimuthAngle < 2 * Math.PI) {
tiltXRadians = Math.PI / 2;
tiltYRadians = -Math.PI / 2;
}
}
if (altitudeAngle !== 0) {
const tanAlt = Math.tan(altitudeAngle);
tiltXRadians = Math.atan(Math.cos(azimuthAngle) / tanAlt);
tiltYRadians = Math.atan(Math.sin(azimuthAngle) / tanAlt);
}
const factor = 180 / Math.PI;
return {
tiltX: Math.round(tiltXRadians * factor),
tiltY: Math.round(tiltYRadians * factor),
};
}
function getRadii(width, height) {
return {
radiusX: width ? width / 2 : 0.5,
radiusY: height ? height / 2 : 0.5,
};
}
//# sourceMappingURL=ActionDispatcher.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,22 @@
/**
* 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.
*/
import type { Input } from '../../../protocol/protocol.js';
export type ActionOption = ActionOptionFor<Input.NoneSourceAction | Input.KeySourceAction | Input.PointerSourceAction | Input.WheelSourceAction>;
export interface ActionOptionFor<A> {
id: string;
action: A;
}
@@ -0,0 +1,19 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=ActionOption.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ActionOption.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/input/ActionOption.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,10 @@
import { Input, type EmptyResult } from '../../../protocol/protocol.js';
import type { BrowsingContextStorage } from '../context/BrowsingContextStorage.js';
import type { RealmStorage } from '../script/RealmStorage.js';
export declare class InputProcessor {
#private;
constructor(browsingContextStorage: BrowsingContextStorage, realmStorage: RealmStorage);
performActions(params: Input.PerformActionsParameters): Promise<EmptyResult>;
releaseActions(params: Input.ReleaseActionsParameters): Promise<EmptyResult>;
setFiles(params: Input.SetFilesParameters): Promise<EmptyResult>;
}
@@ -0,0 +1,194 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputProcessor = void 0;
/*
* 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.
*/
const protocol_js_1 = require("../../../protocol/protocol.js");
const assert_js_1 = require("../../../utils/assert.js");
const ActionDispatcher_js_1 = require("../input/ActionDispatcher.js");
const InputStateManager_js_1 = require("../input/InputStateManager.js");
class InputProcessor {
#browsingContextStorage;
#realmStorage;
#inputStateManager = new InputStateManager_js_1.InputStateManager();
constructor(browsingContextStorage, realmStorage) {
this.#browsingContextStorage = browsingContextStorage;
this.#realmStorage = realmStorage;
}
async performActions(params) {
const context = this.#browsingContextStorage.getContext(params.context);
const inputState = this.#inputStateManager.get(context.top);
const actionsByTick = this.#getActionsByTick(params, inputState);
const dispatcher = new ActionDispatcher_js_1.ActionDispatcher(inputState, context, await ActionDispatcher_js_1.ActionDispatcher.isMacOS(context).catch(() => false));
await dispatcher.dispatchActions(actionsByTick);
return {};
}
async releaseActions(params) {
const context = this.#browsingContextStorage.getContext(params.context);
const topContext = context.top;
const inputState = this.#inputStateManager.get(topContext);
const dispatcher = new ActionDispatcher_js_1.ActionDispatcher(inputState, context, await ActionDispatcher_js_1.ActionDispatcher.isMacOS(context).catch(() => false));
await dispatcher.dispatchTickActions(inputState.cancelList.reverse());
this.#inputStateManager.delete(topContext);
return {};
}
async setFiles(params) {
const context = this.#browsingContextStorage.getContext(params.context);
const realm = await context.getOrCreateSandbox(undefined);
let result;
try {
result = await realm.callFunction(String(function getFiles(fileListLength) {
if (!(this instanceof HTMLInputElement)) {
if (this instanceof Element) {
return 1 /* ErrorCode.Element */;
}
return 0 /* ErrorCode.Node */;
}
if (this.type !== 'file') {
return 2 /* ErrorCode.Type */;
}
if (this.disabled) {
return 3 /* ErrorCode.Disabled */;
}
if (fileListLength > 1 && !this.multiple) {
return 4 /* ErrorCode.Multiple */;
}
return;
}), false, params.element, [{ type: 'number', value: params.files.length }]);
}
catch {
throw new protocol_js_1.NoSuchNodeException(`Could not find element ${params.element.sharedId}`);
}
(0, assert_js_1.assert)(result.type === 'success');
if (result.result.type === 'number') {
switch (result.result.value) {
case 0 /* ErrorCode.Node */: {
throw new protocol_js_1.NoSuchElementException(`Could not find element ${params.element.sharedId}`);
}
case 1 /* ErrorCode.Element */: {
throw new protocol_js_1.UnableToSetFileInputException(`Element ${params.element.sharedId} is not a input`);
}
case 2 /* ErrorCode.Type */: {
throw new protocol_js_1.UnableToSetFileInputException(`Input element ${params.element.sharedId} is not a file type`);
}
case 3 /* ErrorCode.Disabled */: {
throw new protocol_js_1.UnableToSetFileInputException(`Input element ${params.element.sharedId} is disabled`);
}
case 4 /* ErrorCode.Multiple */: {
throw new protocol_js_1.UnableToSetFileInputException(`Cannot set multiple files on a non-multiple input element`);
}
}
}
/**
* The zero-length array is a special case, it seems that
* DOM.setFileInputFiles does not actually update the files in that case, so
* the solution is to eval the element value to a new FileList directly.
*/
if (params.files.length === 0) {
// XXX: These events should converted to trusted events. Perhaps do this
// in `DOM.setFileInputFiles`?
await realm.callFunction(String(function dispatchEvent() {
if (this.files?.length === 0) {
this.dispatchEvent(new Event('cancel', {
bubbles: true,
}));
return;
}
this.files = new DataTransfer().files;
// Dispatch events for this case because it should behave akin to a user action.
this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true }));
}), false, params.element);
return {};
}
// Our goal here is to iterate over the input element files and get their
// file paths.
const paths = [];
for (let i = 0; i < params.files.length; ++i) {
const result = await realm.callFunction(String(function getFiles(index) {
return this.files?.item(index);
}), false, params.element, [{ type: 'number', value: 0 }], "root" /* Script.ResultOwnership.Root */);
(0, assert_js_1.assert)(result.type === 'success');
if (result.result.type !== 'object') {
break;
}
const { handle } = result.result;
(0, assert_js_1.assert)(handle !== undefined);
const { path } = await realm.cdpClient.sendCommand('DOM.getFileInfo', {
objectId: handle,
});
paths.push(path);
// Cleanup the handle.
void realm.disown(handle).catch(undefined);
}
paths.sort();
// We create a new array so we preserve the order of the original files.
const sortedFiles = [...params.files].sort();
if (paths.length !== params.files.length ||
sortedFiles.some((path, index) => {
return paths[index] !== path;
})) {
const { objectId } = await realm.deserializeForCdp(params.element);
// This cannot throw since this was just used in `callFunction` above.
(0, assert_js_1.assert)(objectId !== undefined);
await realm.cdpClient.sendCommand('DOM.setFileInputFiles', {
files: params.files,
objectId,
});
}
else {
// XXX: We should dispatch a trusted event.
await realm.callFunction(String(function dispatchEvent() {
this.dispatchEvent(new Event('cancel', {
bubbles: true,
}));
}), false, params.element);
}
return {};
}
#getActionsByTick(params, inputState) {
const actionsByTick = [];
for (const action of params.actions) {
switch (action.type) {
case "pointer" /* SourceType.Pointer */: {
action.parameters ??= { pointerType: "mouse" /* Input.PointerType.Mouse */ };
action.parameters.pointerType ??= "mouse" /* Input.PointerType.Mouse */;
const source = inputState.getOrCreate(action.id, "pointer" /* SourceType.Pointer */, action.parameters.pointerType);
if (source.subtype !== action.parameters.pointerType) {
throw new protocol_js_1.InvalidArgumentException(`Expected input source ${action.id} to be ${source.subtype}; got ${action.parameters.pointerType}.`);
}
break;
}
default:
inputState.getOrCreate(action.id, action.type);
}
const actions = action.actions.map((item) => ({
id: action.id,
action: item,
}));
for (let i = 0; i < actions.length; i++) {
if (actionsByTick.length === i) {
actionsByTick.push([]);
}
actionsByTick[i].push(actions[i]);
}
}
return actionsByTick;
}
}
exports.InputProcessor = InputProcessor;
//# sourceMappingURL=InputProcessor.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,72 @@
/**
* 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.
*/
import type { Input } from '../../../protocol/protocol.js';
export declare const enum SourceType {
Key = "key",
Pointer = "pointer",
Wheel = "wheel",
None = "none"
}
export declare class NoneSource {
type: SourceType.None;
}
export declare class KeySource {
#private;
type: SourceType.Key;
pressed: Set<string>;
get modifiers(): number;
get alt(): boolean;
set alt(value: boolean);
get ctrl(): boolean;
set ctrl(value: boolean);
get meta(): boolean;
set meta(value: boolean);
get shift(): boolean;
set shift(value: boolean);
}
export declare class PointerSource {
#private;
type: SourceType.Pointer;
subtype: Input.PointerType;
pointerId: number;
pressed: Set<number>;
x: number;
y: number;
radiusX?: number;
radiusY?: number;
force?: number;
constructor(id: number, subtype: Input.PointerType);
get buttons(): number;
static ClickContext: {
new (x: number, y: number, time: number): {
count: number;
"__#91668@#x": number;
"__#91668@#y": number;
"__#91668@#time": number;
compare(context: any): boolean;
};
"__#91668@#DOUBLE_CLICK_TIME_MS": number;
"__#91668@#MAX_DOUBLE_CLICK_RADIUS": number;
};
setClickCount(button: number, context: InstanceType<typeof PointerSource.ClickContext>): number;
getClickCount(button: number): number;
}
export declare class WheelSource {
type: SourceType.Wheel;
}
export type InputSource = NoneSource | KeySource | PointerSource | WheelSource;
export type InputSourceFor<Type extends SourceType> = Type extends SourceType.Key ? KeySource : Type extends SourceType.Pointer ? PointerSource : Type extends SourceType.Wheel ? WheelSource : NoneSource;
@@ -0,0 +1,151 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.WheelSource = exports.PointerSource = exports.KeySource = exports.NoneSource = void 0;
class NoneSource {
type = "none" /* SourceType.None */;
}
exports.NoneSource = NoneSource;
class KeySource {
type = "key" /* SourceType.Key */;
pressed = new Set();
// This is a bitfield that matches the modifiers parameter of
// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchKeyEvent
#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;
}
}
}
exports.KeySource = KeySource;
class PointerSource {
type = "pointer" /* SourceType.Pointer */;
subtype;
pointerId;
pressed = new Set();
x = 0;
y = 0;
radiusX;
radiusY;
force;
constructor(id, subtype) {
this.pointerId = id;
this.subtype = subtype;
}
// This is a bitfield that matches the buttons parameter of
// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchMouseEvent
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;
}
// --- Platform-specific code starts here ---
// Input.dispatchMouseEvent doesn't know the concept of double click, so we
// need to create the logic, similar to how it's done for OSes:
// https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/events/event.cc;l=479
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 (
// The click needs to be within a certain amount of ms.
context.#time - this.#time > ClickContext.#DOUBLE_CLICK_TIME_MS ||
// The click needs to be within a certain square radius.
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;
}
}
exports.PointerSource = PointerSource;
class WheelSource {
type = "wheel" /* SourceType.Wheel */;
}
exports.WheelSource = WheelSource;
//# sourceMappingURL=InputSource.js.map
@@ -0,0 +1 @@
{"version":3,"file":"InputSource.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/input/InputSource.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
/**
* 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.
*/
import { Input } from '../../../protocol/protocol.js';
import { Mutex } from '../../../utils/Mutex.js';
import type { ActionOption } from './ActionOption.js';
import { KeySource, PointerSource, SourceType, type InputSource, type InputSourceFor } from './InputSource.js';
export declare class InputState {
#private;
cancelList: ActionOption[];
getOrCreate(id: string, type: SourceType.Pointer, subtype: Input.PointerType): PointerSource;
getOrCreate<Type extends SourceType>(id: string, type: Type): InputSourceFor<Type>;
get(id: string): InputSource;
getGlobalKeyState(): KeySource;
get queue(): Mutex;
}
@@ -0,0 +1,93 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputState = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const Mutex_js_1 = require("../../../utils/Mutex.js");
const InputSource_js_1 = require("./InputSource.js");
class InputState {
cancelList = [];
#sources = new Map();
#mutex = new Mutex_js_1.Mutex();
getOrCreate(id, type, subtype) {
let source = this.#sources.get(id);
if (!source) {
switch (type) {
case "none" /* SourceType.None */:
source = new InputSource_js_1.NoneSource();
break;
case "key" /* SourceType.Key */:
source = new InputSource_js_1.KeySource();
break;
case "pointer" /* SourceType.Pointer */: {
let pointerId = subtype === "mouse" /* Input.PointerType.Mouse */ ? 0 : 2;
const pointerIds = new Set();
for (const [, source] of this.#sources) {
if (source.type === "pointer" /* SourceType.Pointer */) {
pointerIds.add(source.pointerId);
}
}
while (pointerIds.has(pointerId)) {
++pointerId;
}
source = new InputSource_js_1.PointerSource(pointerId, subtype);
break;
}
case "wheel" /* SourceType.Wheel */:
source = new InputSource_js_1.WheelSource();
break;
default:
throw new protocol_js_1.InvalidArgumentException(`Expected "${"none" /* SourceType.None */}", "${"key" /* SourceType.Key */}", "${"pointer" /* SourceType.Pointer */}", or "${"wheel" /* SourceType.Wheel */}". Found unknown source type ${type}.`);
}
this.#sources.set(id, source);
return source;
}
if (source.type !== type) {
throw new protocol_js_1.InvalidArgumentException(`Input source type of ${id} is ${source.type}, but received ${type}.`);
}
return source;
}
get(id) {
const source = this.#sources.get(id);
if (!source) {
throw new protocol_js_1.UnknownErrorException(`Internal error.`);
}
return source;
}
getGlobalKeyState() {
const state = new InputSource_js_1.KeySource();
for (const [, source] of this.#sources) {
if (source.type !== "key" /* SourceType.Key */) {
continue;
}
for (const pressed of source.pressed) {
state.pressed.add(pressed);
}
state.alt ||= source.alt;
state.ctrl ||= source.ctrl;
state.meta ||= source.meta;
state.shift ||= source.shift;
}
return state;
}
get queue() {
return this.#mutex;
}
}
exports.InputState = InputState;
//# sourceMappingURL=InputState.js.map
@@ -0,0 +1 @@
{"version":3,"file":"InputState.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/input/InputState.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
/**
* 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.
*/
import type { BrowsingContextImpl } from '../context/BrowsingContextImpl.js';
import { InputState } from './InputState.js';
export declare class InputStateManager extends WeakMap<BrowsingContextImpl, InputState> {
get(context: BrowsingContextImpl): InputState;
}
@@ -0,0 +1,34 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputStateManager = void 0;
const assert_js_1 = require("../../../utils/assert.js");
const InputState_js_1 = require("./InputState.js");
// We use a weak map here as specified here:
// https://www.w3.org/TR/webdriver/#dfn-browsing-context-input-state-map
class InputStateManager extends WeakMap {
get(context) {
(0, assert_js_1.assert)(context.isTopLevelContext());
if (!this.has(context)) {
this.set(context, new InputState_js_1.InputState());
}
return super.get(context);
}
}
exports.InputStateManager = InputStateManager;
//# sourceMappingURL=InputStateManager.js.map
@@ -0,0 +1 @@
{"version":3,"file":"InputStateManager.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/input/InputStateManager.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAEH,wDAAgD;AAGhD,mDAA2C;AAE3C,4CAA4C;AAC5C,wEAAwE;AACxE,MAAa,iBAAkB,SAAQ,OAGtC;IACU,GAAG,CAAC,OAA4B;QACvC,IAAA,kBAAM,EAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAEpC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,0BAAU,EAAE,CAAC,CAAC;QACtC,CAAC;QAED,OAAO,KAAK,CAAC,GAAG,CAAC,OAAO,CAAE,CAAC;IAC7B,CAAC;CACF;AAbD,8CAaC"}
@@ -0,0 +1,17 @@
/**
* 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.
*/
export declare const KeyToKeyCode: Record<string, number | undefined>;
@@ -0,0 +1,274 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.KeyToKeyCode = void 0;
// TODO: Remove this once https://crrev.com/c/4548290 is stably in Chromium.
// `Input.dispatchKeyboardEvent` will automatically handle these conversions.
exports.KeyToKeyCode = {
'0': 48,
'1': 49,
'2': 50,
'3': 51,
'4': 52,
'5': 53,
'6': 54,
'7': 55,
'8': 56,
'9': 57,
Abort: 3,
Help: 6,
Backspace: 8,
Tab: 9,
Numpad5: 12,
NumpadEnter: 13,
Enter: 13,
'\\r': 13,
'\\n': 13,
ShiftLeft: 16,
ShiftRight: 16,
ControlLeft: 17,
ControlRight: 17,
AltLeft: 18,
AltRight: 18,
Pause: 19,
CapsLock: 20,
Escape: 27,
Convert: 28,
NonConvert: 29,
Space: 32,
Numpad9: 33,
PageUp: 33,
Numpad3: 34,
PageDown: 34,
End: 35,
Numpad1: 35,
Home: 36,
Numpad7: 36,
ArrowLeft: 37,
Numpad4: 37,
Numpad8: 38,
ArrowUp: 38,
ArrowRight: 39,
Numpad6: 39,
Numpad2: 40,
ArrowDown: 40,
Select: 41,
Open: 43,
PrintScreen: 44,
Insert: 45,
Numpad0: 45,
Delete: 46,
NumpadDecimal: 46,
Digit0: 48,
Digit1: 49,
Digit2: 50,
Digit3: 51,
Digit4: 52,
Digit5: 53,
Digit6: 54,
Digit7: 55,
Digit8: 56,
Digit9: 57,
KeyA: 65,
KeyB: 66,
KeyC: 67,
KeyD: 68,
KeyE: 69,
KeyF: 70,
KeyG: 71,
KeyH: 72,
KeyI: 73,
KeyJ: 74,
KeyK: 75,
KeyL: 76,
KeyM: 77,
KeyN: 78,
KeyO: 79,
KeyP: 80,
KeyQ: 81,
KeyR: 82,
KeyS: 83,
KeyT: 84,
KeyU: 85,
KeyV: 86,
KeyW: 87,
KeyX: 88,
KeyY: 89,
KeyZ: 90,
MetaLeft: 91,
MetaRight: 92,
ContextMenu: 93,
NumpadMultiply: 106,
NumpadAdd: 107,
NumpadSubtract: 109,
NumpadDivide: 111,
F1: 112,
F2: 113,
F3: 114,
F4: 115,
F5: 116,
F6: 117,
F7: 118,
F8: 119,
F9: 120,
F10: 121,
F11: 122,
F12: 123,
F13: 124,
F14: 125,
F15: 126,
F16: 127,
F17: 128,
F18: 129,
F19: 130,
F20: 131,
F21: 132,
F22: 133,
F23: 134,
F24: 135,
NumLock: 144,
ScrollLock: 145,
AudioVolumeMute: 173,
AudioVolumeDown: 174,
AudioVolumeUp: 175,
MediaTrackNext: 176,
MediaTrackPrevious: 177,
MediaStop: 178,
MediaPlayPause: 179,
Semicolon: 186,
Equal: 187,
NumpadEqual: 187,
Comma: 188,
Minus: 189,
Period: 190,
Slash: 191,
Backquote: 192,
BracketLeft: 219,
Backslash: 220,
BracketRight: 221,
Quote: 222,
AltGraph: 225,
Props: 247,
Cancel: 3,
Clear: 12,
Shift: 16,
Control: 17,
Alt: 18,
Accept: 30,
ModeChange: 31,
' ': 32,
Print: 42,
Execute: 43,
'\\u0000': 46,
a: 65,
b: 66,
c: 67,
d: 68,
e: 69,
f: 70,
g: 71,
h: 72,
i: 73,
j: 74,
k: 75,
l: 76,
m: 77,
n: 78,
o: 79,
p: 80,
q: 81,
r: 82,
s: 83,
t: 84,
u: 85,
v: 86,
w: 87,
x: 88,
y: 89,
z: 90,
Meta: 91,
'*': 106,
'+': 107,
'-': 109,
'/': 111,
';': 186,
'=': 187,
',': 188,
'.': 190,
'`': 192,
'[': 219,
'\\\\': 220,
']': 221,
"'": 222,
Attn: 246,
CrSel: 247,
ExSel: 248,
EraseEof: 249,
Play: 250,
ZoomOut: 251,
')': 48,
'!': 49,
'@': 50,
'#': 51,
$: 52,
'%': 53,
'^': 54,
'&': 55,
'(': 57,
A: 65,
B: 66,
C: 67,
D: 68,
E: 69,
F: 70,
G: 71,
H: 72,
I: 73,
J: 74,
K: 75,
L: 76,
M: 77,
N: 78,
O: 79,
P: 80,
Q: 81,
R: 82,
S: 83,
T: 84,
U: 85,
V: 86,
W: 87,
X: 88,
Y: 89,
Z: 90,
':': 186,
'<': 188,
_: 189,
'>': 190,
'?': 191,
'~': 192,
'{': 219,
'|': 220,
'}': 221,
'"': 222,
Camera: 44,
EndCall: 95,
VolumeDown: 182,
VolumeUp: 183,
};
//# sourceMappingURL=USKeyboardLayout.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,31 @@
/**
* 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.
*/
/**
* Returns the normalized key value for a given key according to the table:
* https://w3c.github.io/webdriver/#dfn-normalized-key-value
*/
export declare function getNormalizedKey(value: string): string;
/**
* Returns the key code for a given key according to the table:
* https://w3c.github.io/webdriver/#dfn-shifted-character
*/
export declare function getKeyCode(key: string): string | undefined;
/**
* Returns the location of the key according to the table:
* https://w3c.github.io/webdriver/#dfn-key-location
*/
export declare function getKeyLocation(key: string): 0 | 1 | 2 | 3;
@@ -0,0 +1,497 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNormalizedKey = getNormalizedKey;
exports.getKeyCode = getKeyCode;
exports.getKeyLocation = getKeyLocation;
/**
* Returns the normalized key value for a given key according to the table:
* https://w3c.github.io/webdriver/#dfn-normalized-key-value
*/
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';
// Specification declares the '\uE006' to be `Return`, but it is not supported by
// Chrome, so fall back to `Enter`, which aligns with WPT.
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;
}
}
/**
* Returns the key code for a given key according to the table:
* https://w3c.github.io/webdriver/#dfn-shifted-character
*/
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';
// The spec declares the '<' to be `IntlBackslash` as well, but it is already covered
// in the `Comma` above.
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 'Numpad8';
case '\uE023':
case '\uE054':
return 'Numpad9';
case '\uE025':
return 'NumpadAdd';
case '\uE026':
return 'NumpadComma';
case '\uE028':
case '\uE05D':
return 'NumpadDecimal';
case '\uE029':
return 'NumpadDivide';
case '\uE007':
return 'NumpadEnter';
case '\uE024':
return 'NumpadMultiply';
case '\uE027':
return 'NumpadSubtract';
default:
return;
}
}
/**
* Returns the location of the key according to the table:
* https://w3c.github.io/webdriver/#dfn-key-location
*/
function getKeyLocation(key) {
switch (key) {
case '\uE007':
case '\uE008':
case '\uE009':
case '\uE00A':
case '\uE03D':
return 1;
case '\uE019':
case '\uE01A':
case '\uE01B':
case '\uE01C':
case '\uE01D':
case '\uE01E':
case '\uE01F':
case '\uE020':
case '\uE021':
case '\uE022':
case '\uE023':
case '\uE024':
case '\uE025':
case '\uE026':
case '\uE027':
case '\uE028':
case '\uE029':
case '\uE054':
case '\uE055':
case '\uE056':
case '\uE057':
case '\uE058':
case '\uE059':
case '\uE05A':
case '\uE05B':
case '\uE05C':
case '\uE05D':
return 3;
case '\uE050':
case '\uE051':
case '\uE052':
case '\uE053':
return 2;
default:
return 0;
}
}
//# sourceMappingURL=keyUtils.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
import { type LoggerFn } from '../../../utils/log.js';
import type { CdpTarget } from '../cdp/CdpTarget.js';
import type { RealmStorage } from '../script/RealmStorage.js';
import type { EventManager } from '../session/EventManager.js';
export declare class LogManager {
#private;
private constructor();
static create(cdpTarget: CdpTarget, realmStorage: RealmStorage, eventManager: EventManager, logger?: LoggerFn): LogManager;
}
@@ -0,0 +1,138 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LogManager = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const log_js_1 = require("../../../utils/log.js");
const logHelper_js_1 = require("./logHelper.js");
/** Converts CDP StackTrace object to BiDi StackTrace object. */
function getBidiStackTrace(cdpStackTrace) {
const stackFrames = cdpStackTrace?.callFrames.map((callFrame) => {
return {
columnNumber: callFrame.columnNumber,
functionName: callFrame.functionName,
lineNumber: callFrame.lineNumber,
url: callFrame.url,
};
});
return stackFrames ? { callFrames: stackFrames } : undefined;
}
function getLogLevel(consoleApiType) {
if (["error" /* Log.Level.Error */, 'assert'].includes(consoleApiType)) {
return "error" /* Log.Level.Error */;
}
if (["debug" /* Log.Level.Debug */, 'trace'].includes(consoleApiType)) {
return "debug" /* Log.Level.Debug */;
}
if (["warn" /* Log.Level.Warn */, 'warning'].includes(consoleApiType)) {
return "warn" /* Log.Level.Warn */;
}
return "info" /* Log.Level.Info */;
}
class LogManager {
#eventManager;
#realmStorage;
#cdpTarget;
#logger;
constructor(cdpTarget, realmStorage, eventManager, logger) {
this.#cdpTarget = cdpTarget;
this.#realmStorage = realmStorage;
this.#eventManager = eventManager;
this.#logger = logger;
}
static create(cdpTarget, realmStorage, eventManager, logger) {
const logManager = new LogManager(cdpTarget, realmStorage, eventManager, logger);
logManager.#initializeEntryAddedEventListener();
return logManager;
}
#initializeEntryAddedEventListener() {
this.#cdpTarget.cdpClient.on('Runtime.consoleAPICalled', (params) => {
// Try to find realm by `cdpSessionId` and `executionContextId`,
// if provided.
const realm = this.#realmStorage.findRealm({
cdpSessionId: this.#cdpTarget.cdpSessionId,
executionContextId: params.executionContextId,
});
if (realm === undefined) {
// Ignore exceptions not attached to any realm.
this.#logger?.(log_js_1.LogType.cdp, params);
return;
}
const argsPromise = realm === undefined
? Promise.resolve(params.args)
: // Properly serialize arguments if possible.
Promise.all(params.args.map((arg) => {
return realm.serializeCdpObject(arg, "none" /* Script.ResultOwnership.None */);
}));
for (const browsingContext of realm.associatedBrowsingContexts) {
this.#eventManager.registerPromiseEvent(argsPromise.then((args) => ({
kind: 'success',
value: {
type: 'event',
method: protocol_js_1.ChromiumBidi.Log.EventNames.LogEntryAdded,
params: {
level: getLogLevel(params.type),
source: realm.source,
text: (0, logHelper_js_1.getRemoteValuesText)(args, true),
timestamp: Math.round(params.timestamp),
stackTrace: getBidiStackTrace(params.stackTrace),
type: 'console',
// Console method is `warn`, not `warning`.
method: params.type === 'warning' ? 'warn' : params.type,
args,
},
},
}), (error) => ({
kind: 'error',
error,
})), browsingContext.id, protocol_js_1.ChromiumBidi.Log.EventNames.LogEntryAdded);
}
});
this.#cdpTarget.cdpClient.on('Runtime.exceptionThrown', (params) => {
// Try to find realm by `cdpSessionId` and `executionContextId`,
// if provided.
const realm = this.#realmStorage.findRealm({
cdpSessionId: this.#cdpTarget.cdpSessionId,
executionContextId: params.exceptionDetails.executionContextId,
});
if (realm === undefined) {
// Ignore exceptions not attached to any realm.
this.#logger?.(log_js_1.LogType.cdp, params);
return;
}
for (const browsingContext of realm.associatedBrowsingContexts) {
this.#eventManager.registerPromiseEvent(LogManager.#getExceptionText(params, realm).then((text) => ({
kind: 'success',
value: {
type: 'event',
method: protocol_js_1.ChromiumBidi.Log.EventNames.LogEntryAdded,
params: {
level: "error" /* Log.Level.Error */,
source: realm.source,
text,
timestamp: Math.round(params.timestamp),
stackTrace: getBidiStackTrace(params.exceptionDetails.stackTrace),
type: 'javascript',
},
},
}), (error) => ({
kind: 'error',
error,
})), browsingContext.id, protocol_js_1.ChromiumBidi.Log.EventNames.LogEntryAdded);
}
});
}
/**
* Try the best to get the exception text.
*/
static async #getExceptionText(params, realm) {
if (!params.exceptionDetails.exception) {
return params.exceptionDetails.text;
}
if (realm === undefined) {
return JSON.stringify(params.exceptionDetails.exception);
}
return await realm.stringifyObject(params.exceptionDetails.exception);
}
}
exports.LogManager = LogManager;
//# sourceMappingURL=LogManager.js.map
@@ -0,0 +1 @@
{"version":3,"file":"LogManager.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/log/LogManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
/**
* 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.
*/
import type { Script } from '../../../protocol/protocol.js';
/**
* @param args input remote values to be format printed
* @return parsed text of the remote values in specific format
*/
export declare function logMessageFormatter(args: Script.RemoteValue[]): string;
export declare function getRemoteValuesText(args: Script.RemoteValue[], formatText: boolean): string;
@@ -0,0 +1,173 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.logMessageFormatter = logMessageFormatter;
exports.getRemoteValuesText = getRemoteValuesText;
const assert_js_1 = require("../../../utils/assert.js");
const specifiers = ['%s', '%d', '%i', '%f', '%o', '%O', '%c'];
function isFormatSpecifier(str) {
return specifiers.some((spec) => str.includes(spec));
}
/**
* @param args input remote values to be format printed
* @return parsed text of the remote values in specific format
*/
function logMessageFormatter(args) {
let output = '';
const argFormat = args[0].value.toString();
const argValues = args.slice(1, undefined);
const tokens = argFormat.split(new RegExp(specifiers.map((spec) => `(${spec})`).join('|'), 'g'));
for (const token of tokens) {
if (token === undefined || token === '') {
continue;
}
if (isFormatSpecifier(token)) {
const arg = argValues.shift();
// raise an exception when less value is provided
(0, assert_js_1.assert)(arg, `Less value is provided: "${getRemoteValuesText(args, false)}"`);
if (token === '%s') {
output += stringFromArg(arg);
}
else if (token === '%d' || token === '%i') {
if (arg.type === 'bigint' ||
arg.type === 'number' ||
arg.type === 'string') {
output += parseInt(arg.value.toString(), 10);
}
else {
output += 'NaN';
}
}
else if (token === '%f') {
if (arg.type === 'bigint' ||
arg.type === 'number' ||
arg.type === 'string') {
output += parseFloat(arg.value.toString());
}
else {
output += 'NaN';
}
}
else {
// %o, %O, %c
output += toJson(arg);
}
}
else {
output += token;
}
}
// raise an exception when more value is provided
if (argValues.length > 0) {
throw new Error(`More value is provided: "${getRemoteValuesText(args, false)}"`);
}
return output;
}
/**
* @param arg input remote value to be parsed
* @return parsed text of the remote value
*
* input: {"type": "number", "value": 1}
* output: 1
*
* input: {"type": "string", "value": "abc"}
* output: "abc"
*
* input: {"type": "object", "value": [["id", {"type": "number", "value": 1}]]}
* output: '{"id": 1}'
*
* input: {"type": "object", "value": [["font-size", {"type": "string", "value": "20px"}]]}
* output: '{"font-size": "20px"}'
*/
function toJson(arg) {
// arg type validation
if (arg.type !== 'array' &&
arg.type !== 'bigint' &&
arg.type !== 'date' &&
arg.type !== 'number' &&
arg.type !== 'object' &&
arg.type !== 'string') {
return stringFromArg(arg);
}
if (arg.type === 'bigint') {
return `${arg.value.toString()}n`;
}
if (arg.type === 'number') {
return arg.value.toString();
}
if (['date', 'string'].includes(arg.type)) {
return JSON.stringify(arg.value);
}
if (arg.type === 'object') {
return `{${arg.value
.map((pair) => {
return `${JSON.stringify(pair[0])}:${toJson(pair[1])}`;
})
.join(',')}}`;
}
if (arg.type === 'array') {
return `[${arg.value?.map((val) => toJson(val)).join(',') ?? ''}]`;
}
// eslint-disable-next-line @typescript-eslint/no-base-to-string
throw Error(`Invalid value type: ${arg}`);
}
function stringFromArg(arg) {
if (!Object.hasOwn(arg, 'value')) {
return arg.type;
}
switch (arg.type) {
case 'string':
case 'number':
case 'boolean':
case 'bigint':
return String(arg.value);
case 'regexp':
return `/${arg.value.pattern}/${arg.value.flags ?? ''}`;
case 'date':
return new Date(arg.value).toString();
case 'object':
return `Object(${arg.value?.length ?? ''})`;
case 'array':
return `Array(${arg.value?.length ?? ''})`;
case 'map':
return `Map(${arg.value?.length})`;
case 'set':
return `Set(${arg.value?.length})`;
default:
return arg.type;
}
}
function getRemoteValuesText(args, formatText) {
const arg = args[0];
if (!arg) {
return '';
}
// if args[0] is a format specifier, format the args as output
if (arg.type === 'string' &&
isFormatSpecifier(arg.value.toString()) &&
formatText) {
return logMessageFormatter(args);
}
// if args[0] is not a format specifier, just join the args with \u0020 (unicode 'SPACE')
return args
.map((arg) => {
return stringFromArg(arg);
})
.join('\u0020');
}
//# sourceMappingURL=logHelper.js.map
@@ -0,0 +1 @@
{"version":3,"file":"logHelper.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/log/logHelper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
/**
* 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.
*/
import { Network, type EmptyResult } from '../../../protocol/protocol.js';
import type { BrowsingContextStorage } from '../context/BrowsingContextStorage.js';
import type { NetworkStorage } from './NetworkStorage.js';
/** Dispatches Network domain commands. */
export declare class NetworkProcessor {
#private;
constructor(browsingContextStorage: BrowsingContextStorage, networkStorage: NetworkStorage);
addIntercept(params: Network.AddInterceptParameters): Promise<Network.AddInterceptResult>;
continueRequest(params: Network.ContinueRequestParameters): Promise<EmptyResult>;
continueResponse(params: Network.ContinueResponseParameters): Promise<EmptyResult>;
continueWithAuth(params: Network.ContinueWithAuthParameters): Promise<EmptyResult>;
failRequest({ request: networkId, }: Network.FailRequestParameters): Promise<EmptyResult>;
provideResponse(params: Network.ProvideResponseParameters): Promise<EmptyResult>;
removeIntercept(params: Network.RemoveInterceptParameters): Promise<EmptyResult>;
/**
* Validate https://fetch.spec.whatwg.org/#header-value
*/
static validateHeaders(headers: Network.Header[]): void;
static isMethodValid(method: string): boolean;
/**
* Attempts to parse the given url.
* Throws an InvalidArgumentException if the url is invalid.
*/
static parseUrlString(url: string): URL;
static parseUrlPatterns(urlPatterns: Network.UrlPattern[]): Network.UrlPattern[];
static wrapInterceptionError(error: any): any;
}
@@ -0,0 +1,287 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkProcessor = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
/** Dispatches Network domain commands. */
class NetworkProcessor {
#browsingContextStorage;
#networkStorage;
constructor(browsingContextStorage, networkStorage) {
this.#browsingContextStorage = browsingContextStorage;
this.#networkStorage = networkStorage;
}
async addIntercept(params) {
this.#browsingContextStorage.verifyTopLevelContextsList(params.contexts);
const urlPatterns = params.urlPatterns ?? [];
const parsedUrlPatterns = NetworkProcessor.parseUrlPatterns(urlPatterns);
const intercept = this.#networkStorage.addIntercept({
urlPatterns: parsedUrlPatterns,
phases: params.phases,
contexts: params.contexts,
});
await Promise.all(this.#browsingContextStorage.getAllContexts().map((context) => {
return context.cdpTarget.toggleFetchIfNeeded();
}));
return {
intercept,
};
}
async continueRequest(params) {
if (params.url !== undefined) {
NetworkProcessor.parseUrlString(params.url);
}
if (params.method !== undefined) {
if (!NetworkProcessor.isMethodValid(params.method)) {
throw new protocol_js_1.InvalidArgumentException(`Method '${params.method}' is invalid.`);
}
}
if (params.headers) {
NetworkProcessor.validateHeaders(params.headers);
}
const request = this.#getBlockedRequestOrFail(params.request, [
"beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */,
]);
try {
await request.continueRequest(params);
}
catch (error) {
throw NetworkProcessor.wrapInterceptionError(error);
}
return {};
}
async continueResponse(params) {
if (params.headers) {
NetworkProcessor.validateHeaders(params.headers);
}
const request = this.#getBlockedRequestOrFail(params.request, [
"authRequired" /* Network.InterceptPhase.AuthRequired */,
"responseStarted" /* Network.InterceptPhase.ResponseStarted */,
]);
try {
await request.continueResponse(params);
}
catch (error) {
throw NetworkProcessor.wrapInterceptionError(error);
}
return {};
}
async continueWithAuth(params) {
const networkId = params.request;
const request = this.#getBlockedRequestOrFail(networkId, [
"authRequired" /* Network.InterceptPhase.AuthRequired */,
]);
await request.continueWithAuth(params);
return {};
}
async failRequest({ request: networkId, }) {
const request = this.#getRequestOrFail(networkId);
if (request.interceptPhase === "authRequired" /* Network.InterceptPhase.AuthRequired */) {
throw new protocol_js_1.InvalidArgumentException(`Request '${networkId}' in 'authRequired' phase cannot be failed`);
}
if (!request.interceptPhase) {
throw new protocol_js_1.NoSuchRequestException(`No blocked request found for network id '${networkId}'`);
}
await request.failRequest('Failed');
return {};
}
async provideResponse(params) {
if (params.headers) {
NetworkProcessor.validateHeaders(params.headers);
}
const request = this.#getBlockedRequestOrFail(params.request, [
"beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */,
"responseStarted" /* Network.InterceptPhase.ResponseStarted */,
"authRequired" /* Network.InterceptPhase.AuthRequired */,
]);
try {
await request.provideResponse(params);
}
catch (error) {
throw NetworkProcessor.wrapInterceptionError(error);
}
return {};
}
async removeIntercept(params) {
this.#networkStorage.removeIntercept(params.intercept);
await Promise.all(this.#browsingContextStorage.getAllContexts().map((context) => {
return context.cdpTarget.toggleFetchIfNeeded();
}));
return {};
}
#getRequestOrFail(id) {
const request = this.#networkStorage.getRequestById(id);
if (!request) {
throw new protocol_js_1.NoSuchRequestException(`Network request with ID '${id}' doesn't exist`);
}
return request;
}
#getBlockedRequestOrFail(id, phases) {
const request = this.#getRequestOrFail(id);
if (!request.interceptPhase) {
throw new protocol_js_1.NoSuchRequestException(`No blocked request found for network id '${id}'`);
}
if (request.interceptPhase && !phases.includes(request.interceptPhase)) {
throw new protocol_js_1.InvalidArgumentException(`Blocked request for network id '${id}' is in '${request.interceptPhase}' phase`);
}
return request;
}
/**
* Validate https://fetch.spec.whatwg.org/#header-value
*/
static validateHeaders(headers) {
for (const header of headers) {
let headerValue;
if (header.value.type === 'string') {
headerValue = header.value.value;
}
else {
headerValue = atob(header.value.value);
}
if (headerValue !== headerValue.trim() ||
headerValue.includes('\n') ||
headerValue.includes('\0')) {
throw new protocol_js_1.InvalidArgumentException(`Header value '${headerValue}' is not acceptable value`);
}
}
}
static isMethodValid(method) {
// https://httpwg.org/specs/rfc9110.html#method.overview
return /^[!#$%&'*+\-.^_`|~a-zA-Z\d]+$/.test(method);
}
/**
* Attempts to parse the given url.
* Throws an InvalidArgumentException if the url is invalid.
*/
static parseUrlString(url) {
try {
return new URL(url);
}
catch (error) {
throw new protocol_js_1.InvalidArgumentException(`Invalid URL '${url}': ${error}`);
}
}
static parseUrlPatterns(urlPatterns) {
return urlPatterns.map((urlPattern) => {
switch (urlPattern.type) {
case 'string': {
NetworkProcessor.parseUrlString(urlPattern.pattern);
return urlPattern;
}
case 'pattern':
// No params signifies intercept all
if (urlPattern.protocol === undefined &&
urlPattern.hostname === undefined &&
urlPattern.port === undefined &&
urlPattern.pathname === undefined &&
urlPattern.search === undefined) {
return urlPattern;
}
if (urlPattern.protocol) {
urlPattern.protocol = unescapeURLPattern(urlPattern.protocol);
if (!urlPattern.protocol.match(/^[a-zA-Z+-.]+$/)) {
throw new protocol_js_1.InvalidArgumentException('Forbidden characters');
}
}
if (urlPattern.hostname) {
urlPattern.hostname = unescapeURLPattern(urlPattern.hostname);
}
if (urlPattern.port) {
urlPattern.port = unescapeURLPattern(urlPattern.port);
}
if (urlPattern.pathname) {
urlPattern.pathname = unescapeURLPattern(urlPattern.pathname);
if (urlPattern.pathname[0] !== '/') {
urlPattern.pathname = `/${urlPattern.pathname}`;
}
if (urlPattern.pathname.includes('#') ||
urlPattern.pathname.includes('?')) {
throw new protocol_js_1.InvalidArgumentException('Forbidden characters');
}
}
else if (urlPattern.pathname === '') {
urlPattern.pathname = '/';
}
if (urlPattern.search) {
urlPattern.search = unescapeURLPattern(urlPattern.search);
if (urlPattern.search[0] !== '?') {
urlPattern.search = `?${urlPattern.search}`;
}
if (urlPattern.search.includes('#')) {
throw new protocol_js_1.InvalidArgumentException('Forbidden characters');
}
}
if (urlPattern.protocol === '') {
throw new protocol_js_1.InvalidArgumentException(`URL pattern must specify a protocol`);
}
if (urlPattern.hostname === '') {
throw new protocol_js_1.InvalidArgumentException(`URL pattern must specify a hostname`);
}
if ((urlPattern.hostname?.length ?? 0) > 0) {
if (urlPattern.protocol?.match(/^file/i)) {
throw new protocol_js_1.InvalidArgumentException(`URL pattern protocol cannot be 'file'`);
}
if (urlPattern.hostname?.includes(':')) {
throw new protocol_js_1.InvalidArgumentException(`URL pattern hostname must not contain a colon`);
}
}
if (urlPattern.port === '') {
throw new protocol_js_1.InvalidArgumentException(`URL pattern must specify a port`);
}
try {
new URLPattern(urlPattern);
}
catch (error) {
throw new protocol_js_1.InvalidArgumentException(`${error}`);
}
return urlPattern;
}
});
}
static wrapInterceptionError(error) {
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/protocol/fetch_handler.cc;l=169
if (error?.message.includes('Invalid header')) {
return new protocol_js_1.InvalidArgumentException('Invalid header');
}
return error;
}
}
exports.NetworkProcessor = NetworkProcessor;
/**
* See https://w3c.github.io/webdriver-bidi/#unescape-url-pattern
*/
function unescapeURLPattern(pattern) {
const forbidden = new Set(['(', ')', '*', '{', '}']);
let result = '';
let isEscaped = false;
for (const c of pattern) {
if (!isEscaped) {
if (forbidden.has(c)) {
throw new protocol_js_1.InvalidArgumentException('Forbidden characters');
}
if (c === '\\') {
isEscaped = true;
continue;
}
}
result += c;
isEscaped = false;
}
return result;
}
//# sourceMappingURL=NetworkProcessor.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,48 @@
/**
* @fileoverview `NetworkRequest` represents a single network request and keeps
* track of all the related CDP events.
*/
import type { Protocol } from 'devtools-protocol';
import { Network } from '../../../protocol/protocol.js';
import { Deferred } from '../../../utils/Deferred.js';
import { type LoggerFn } from '../../../utils/log.js';
import type { CdpTarget } from '../cdp/CdpTarget.js';
import type { EventManager } from '../session/EventManager.js';
import type { NetworkStorage } from './NetworkStorage.js';
/** Abstracts one individual network request. */
export declare class NetworkRequest {
#private;
static unknownParameter: string;
waitNextPhase: Deferred<void>;
constructor(id: Network.Request, eventManager: EventManager, networkStorage: NetworkStorage, cdpTarget: CdpTarget, redirectCount?: number, logger?: LoggerFn);
get id(): string;
get fetchId(): string | undefined;
/**
* When blocked returns the phase for it
*/
get interceptPhase(): Network.InterceptPhase | undefined;
get url(): string;
get redirectCount(): number;
get cdpTarget(): CdpTarget;
get cdpClient(): import("../../BidiMapper.js").CdpClient;
isRedirecting(): boolean;
handleRedirect(event: Protocol.Network.RequestWillBeSentEvent): void;
onRequestWillBeSentEvent(event: Protocol.Network.RequestWillBeSentEvent): void;
onRequestWillBeSentExtraInfoEvent(event: Protocol.Network.RequestWillBeSentExtraInfoEvent): void;
onResponseReceivedExtraInfoEvent(event: Protocol.Network.ResponseReceivedExtraInfoEvent): void;
onResponseReceivedEvent(event: Protocol.Network.ResponseReceivedEvent): void;
onServedFromCache(): void;
onLoadingFailedEvent(event: Protocol.Network.LoadingFailedEvent): void;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-failRequest */
failRequest(errorReason: Protocol.Network.ErrorReason): Promise<void>;
onRequestPaused(event: Protocol.Fetch.RequestPausedEvent): void;
onAuthRequired(event: Protocol.Fetch.AuthRequiredEvent): void;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueRequest */
continueRequest(overrides?: Omit<Network.ContinueRequestParameters, 'request'>): Promise<void>;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueResponse */
continueResponse(overrides?: Omit<Network.ContinueResponseParameters, 'request'>): Promise<void>;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueWithAuth */
continueWithAuth(authChallenge: Omit<Network.ContinueWithAuthParameters, 'request'>): Promise<void>;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-provideResponse */
provideResponse(overrides: Omit<Network.ProvideResponseParameters, 'request'>): Promise<void>;
}
@@ -0,0 +1,708 @@
"use strict";
/*
* 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.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkRequest = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const assert_js_1 = require("../../../utils/assert.js");
const Deferred_js_1 = require("../../../utils/Deferred.js");
const log_js_1 = require("../../../utils/log.js");
const NetworkUtils_js_1 = require("./NetworkUtils.js");
const REALM_REGEX = /(?<=realm=").*(?=")/;
/** Abstracts one individual network request. */
class NetworkRequest {
static unknownParameter = 'UNKNOWN';
/**
* Each network request has an associated request id, which is a string
* uniquely identifying that request.
*
* The identifier for a request resulting from a redirect matches that of the
* request that initiated it.
*/
#id;
#fetchId;
/**
* Indicates the network intercept phase, if the request is currently blocked.
* Undefined necessarily implies that the request is not blocked.
*/
#interceptPhase;
#servedFromCache = false;
#redirectCount;
#request = {};
#requestOverrides;
#response = {};
#eventManager;
#networkStorage;
#cdpTarget;
#logger;
#emittedEvents = {
[protocol_js_1.ChromiumBidi.Network.EventNames.AuthRequired]: false,
[protocol_js_1.ChromiumBidi.Network.EventNames.BeforeRequestSent]: false,
[protocol_js_1.ChromiumBidi.Network.EventNames.FetchError]: false,
[protocol_js_1.ChromiumBidi.Network.EventNames.ResponseCompleted]: false,
[protocol_js_1.ChromiumBidi.Network.EventNames.ResponseStarted]: false,
};
waitNextPhase = new Deferred_js_1.Deferred();
constructor(id, eventManager, networkStorage, cdpTarget, redirectCount = 0, logger) {
this.#id = id;
this.#eventManager = eventManager;
this.#networkStorage = networkStorage;
this.#cdpTarget = cdpTarget;
this.#redirectCount = redirectCount;
this.#logger = logger;
}
get id() {
return this.#id;
}
get fetchId() {
return this.#fetchId;
}
/**
* When blocked returns the phase for it
*/
get interceptPhase() {
return this.#interceptPhase;
}
get url() {
const fragment = this.#request.info?.request.urlFragment ??
this.#request.paused?.request.urlFragment ??
'';
const url = this.#response.info?.url ??
this.#response.paused?.request.url ??
this.#requestOverrides?.url ??
this.#request.auth?.request.url ??
this.#request.info?.request.url ??
this.#request.paused?.request.url ??
NetworkRequest.unknownParameter;
return `${url}${fragment}`;
}
get redirectCount() {
return this.#redirectCount;
}
get cdpTarget() {
return this.#cdpTarget;
}
get cdpClient() {
return this.#cdpTarget.cdpClient;
}
isRedirecting() {
return Boolean(this.#request.info);
}
#isDataUrl() {
return this.url.startsWith('data:');
}
get #method() {
return (this.#requestOverrides?.method ??
this.#request.info?.request.method ??
this.#request.paused?.request.method ??
this.#request.auth?.request.method ??
this.#response.paused?.request.method);
}
get #navigationId() {
// Heuristic to determine if this is a navigation request, and if not return null.
if (!this.#request.info ||
!this.#request.info.loaderId ||
// When we navigate all CDP network events have `loaderId`
// CDP's `loaderId` and `requestId` match when
// that request triggered the loading
this.#request.info.loaderId !== this.#request.info.requestId) {
return null;
}
// Get virtual navigation ID from the browsing context.
return this.#networkStorage.getVirtualNavigationId(this.#context ?? undefined);
}
get #cookies() {
let cookies = [];
if (this.#request.extraInfo) {
cookies = this.#request.extraInfo.associatedCookies
.filter(({ blockedReasons }) => {
return !Array.isArray(blockedReasons) || blockedReasons.length === 0;
})
.map(({ cookie }) => (0, NetworkUtils_js_1.cdpToBiDiCookie)(cookie));
}
return cookies;
}
get #bodySize() {
let bodySize = 0;
if (typeof this.#requestOverrides?.bodySize === 'number') {
bodySize = this.#requestOverrides.bodySize;
}
else {
bodySize = (0, NetworkUtils_js_1.bidiBodySizeFromCdpPostDataEntries)(this.#request.info?.request.postDataEntries ?? []);
}
return bodySize;
}
get #context() {
return (this.#response.paused?.frameId ??
this.#request.info?.frameId ??
this.#request.paused?.frameId ??
this.#request.auth?.frameId ??
null);
}
/** Returns the HTTP status code associated with this request if any. */
get #statusCode() {
return (this.#response.paused?.responseStatusCode ??
this.#response.extraInfo?.statusCode ??
this.#response.info?.status);
}
get #requestHeaders() {
let headers = [];
if (this.#requestOverrides?.headers) {
headers = this.#requestOverrides.headers;
}
else {
headers = [
...(0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpNetworkHeaders)(this.#request.info?.request.headers),
...(0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpNetworkHeaders)(this.#request.extraInfo?.headers),
];
}
return headers;
}
get #authChallenges() {
// TODO: get headers from Fetch.requestPaused
if (!this.#response.info) {
return;
}
if (!(this.#statusCode === 401 || this.#statusCode === 407)) {
return undefined;
}
const headerName = this.#statusCode === 401 ? 'WWW-Authenticate' : 'Proxy-Authenticate';
const authChallenges = [];
for (const [header, value] of Object.entries(this.#response.info.headers)) {
// TODO: Do a proper match based on https://httpwg.org/specs/rfc9110.html#credentials
// Or verify this works
if (header.localeCompare(headerName, undefined, { sensitivity: 'base' }) === 0) {
authChallenges.push({
scheme: value.split(' ').at(0) ?? '',
realm: value.match(REALM_REGEX)?.at(0) ?? '',
});
}
}
return authChallenges;
}
get #timings() {
return {
// TODO: Verify this is correct
timeOrigin: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.requestTime),
requestTime: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.requestTime),
redirectStart: 0,
redirectEnd: 0,
// TODO: Verify this is correct
// https://source.chromium.org/chromium/chromium/src/+/main:net/base/load_timing_info.h;l=145
fetchStart: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.requestTime),
dnsStart: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.dnsStart),
dnsEnd: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.dnsEnd),
connectStart: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.connectStart),
connectEnd: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.connectEnd),
tlsStart: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.sslStart),
requestStart: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.sendStart),
// https://source.chromium.org/chromium/chromium/src/+/main:net/base/load_timing_info.h;l=196
responseStart: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.receiveHeadersStart),
responseEnd: (0, NetworkUtils_js_1.getTiming)(this.#response.info?.timing?.receiveHeadersEnd),
};
}
#phaseChanged() {
this.waitNextPhase.resolve();
this.waitNextPhase = new Deferred_js_1.Deferred();
}
#interceptsInPhase(phase) {
if (!this.#cdpTarget.isSubscribedTo(`network.${phase}`)) {
return new Set();
}
return this.#networkStorage.getInterceptsForPhase(this, phase);
}
#isBlockedInPhase(phase) {
return this.#interceptsInPhase(phase).size > 0;
}
handleRedirect(event) {
// TODO: use event.redirectResponse;
// Temporary workaround to emit ResponseCompleted event for redirects
this.#response.hasExtraInfo = false;
this.#response.info = event.redirectResponse;
this.#emitEventsIfReady({
wasRedirected: true,
});
}
#emitEventsIfReady(options = {}) {
const requestExtraInfoCompleted =
// Flush redirects
options.wasRedirected ||
options.hasFailed ||
this.#isDataUrl() ||
Boolean(this.#request.extraInfo) ||
// Requests from cache don't have extra info
this.#servedFromCache ||
// Sometimes there is no extra info and the response
// is the only place we can find out
Boolean(this.#response.info && !this.#response.hasExtraInfo);
const noInterceptionExpected =
// We can't intercept data urls from CDP
this.#isDataUrl() ||
// Cached requests never hit the network
this.#servedFromCache;
const requestInterceptionExpected = !noInterceptionExpected &&
this.#isBlockedInPhase("beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */);
const requestInterceptionCompleted = !requestInterceptionExpected ||
(requestInterceptionExpected && Boolean(this.#request.paused));
if (Boolean(this.#request.info) &&
(requestInterceptionExpected
? requestInterceptionCompleted
: requestExtraInfoCompleted)) {
this.#emitEvent(this.#getBeforeRequestEvent.bind(this));
}
const responseExtraInfoCompleted = Boolean(this.#response.extraInfo) ||
// Response from cache don't have extra info
this.#servedFromCache ||
// Don't expect extra info if the flag is false
Boolean(this.#response.info && !this.#response.hasExtraInfo);
const responseInterceptionExpected = !noInterceptionExpected &&
this.#isBlockedInPhase("responseStarted" /* Network.InterceptPhase.ResponseStarted */);
if (this.#response.info ||
(responseInterceptionExpected && Boolean(this.#response.paused))) {
this.#emitEvent(this.#getResponseStartedEvent.bind(this));
}
const responseInterceptionCompleted = !responseInterceptionExpected ||
(responseInterceptionExpected && Boolean(this.#response.paused));
if (Boolean(this.#response.info) &&
responseExtraInfoCompleted &&
responseInterceptionCompleted) {
this.#emitEvent(this.#getResponseReceivedEvent.bind(this));
this.#networkStorage.deleteRequest(this.id);
}
}
onRequestWillBeSentEvent(event) {
this.#request.info = event;
this.#emitEventsIfReady();
}
onRequestWillBeSentExtraInfoEvent(event) {
this.#request.extraInfo = event;
this.#emitEventsIfReady();
}
onResponseReceivedExtraInfoEvent(event) {
if (event.statusCode >= 300 &&
event.statusCode <= 399 &&
this.#request.info &&
event.headers['location'] === this.#request.info.request.url) {
// We received the Response Extra info for the redirect
// Too late so we need to skip it as it will
// fire wrongly for the last one
return;
}
this.#response.extraInfo = event;
this.#emitEventsIfReady();
}
onResponseReceivedEvent(event) {
this.#response.hasExtraInfo = event.hasExtraInfo;
this.#response.info = event.response;
this.#emitEventsIfReady();
}
onServedFromCache() {
this.#servedFromCache = true;
this.#emitEventsIfReady();
}
onLoadingFailedEvent(event) {
this.#emitEventsIfReady({
hasFailed: true,
});
this.#emitEvent(() => {
return {
method: protocol_js_1.ChromiumBidi.Network.EventNames.FetchError,
params: {
...this.#getBaseEventParams(),
errorText: event.errorText,
},
};
});
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-failRequest */
async failRequest(errorReason) {
(0, assert_js_1.assert)(this.#fetchId, 'Network Interception not set-up.');
await this.cdpClient.sendCommand('Fetch.failRequest', {
requestId: this.#fetchId,
errorReason,
});
this.#interceptPhase = undefined;
}
onRequestPaused(event) {
this.#fetchId = event.requestId;
// CDP https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#event-requestPaused
if (event.responseStatusCode || event.responseErrorReason) {
this.#response.paused = event;
if (this.#isBlockedInPhase("responseStarted" /* Network.InterceptPhase.ResponseStarted */) &&
// CDP may emit multiple events for a single request
!this.#emittedEvents[protocol_js_1.ChromiumBidi.Network.EventNames.ResponseStarted] &&
// Continue all response that have not enabled Network domain
this.#fetchId !== this.id) {
this.#interceptPhase = "responseStarted" /* Network.InterceptPhase.ResponseStarted */;
}
else {
void this.#continueResponse();
}
}
else {
this.#request.paused = event;
if (this.#isBlockedInPhase("beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */) &&
// CDP may emit multiple events for a single request
!this.#emittedEvents[protocol_js_1.ChromiumBidi.Network.EventNames.BeforeRequestSent] &&
// Continue all requests that have not enabled Network domain
this.#fetchId !== this.id) {
this.#interceptPhase = "beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */;
}
else {
void this.#continueRequest();
}
}
this.#emitEventsIfReady();
}
onAuthRequired(event) {
this.#fetchId = event.requestId;
this.#request.auth = event;
if (this.#isBlockedInPhase("authRequired" /* Network.InterceptPhase.AuthRequired */) &&
// Continue all auth requests that have not enabled Network domain
this.#fetchId !== this.id) {
this.#interceptPhase = "authRequired" /* Network.InterceptPhase.AuthRequired */;
}
else {
void this.#continueWithAuth({
response: 'Default',
});
}
this.#emitEvent(() => {
return {
method: protocol_js_1.ChromiumBidi.Network.EventNames.AuthRequired,
params: {
...this.#getBaseEventParams("authRequired" /* Network.InterceptPhase.AuthRequired */),
response: this.#getResponseEventParams(),
},
};
});
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueRequest */
async continueRequest(overrides = {}) {
const overrideHeaders = this.#getOverrideHeader(overrides.headers, overrides.cookies);
const headers = (0, NetworkUtils_js_1.cdpFetchHeadersFromBidiNetworkHeaders)(overrideHeaders);
const postData = getCdpBodyFromBiDiBytesValue(overrides.body);
await this.#continueRequest({
url: overrides.url,
method: overrides.method,
headers,
postData,
});
this.#requestOverrides = {
url: overrides.url,
method: overrides.method,
headers: overrides.headers,
cookies: overrides.cookies,
bodySize: getSizeFromBiDiBytesValue(overrides.body),
};
}
async #continueRequest(overrides = {}) {
(0, assert_js_1.assert)(this.#fetchId, 'Network Interception not set-up.');
await this.cdpClient.sendCommand('Fetch.continueRequest', {
requestId: this.#fetchId,
url: overrides.url,
method: overrides.method,
headers: overrides.headers,
postData: overrides.postData,
});
this.#interceptPhase = undefined;
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueResponse */
async continueResponse(overrides = {}) {
if (this.interceptPhase === "authRequired" /* Network.InterceptPhase.AuthRequired */) {
if (overrides.credentials) {
await Promise.all([
this.waitNextPhase,
await this.#continueWithAuth({
response: 'ProvideCredentials',
username: overrides.credentials.username,
password: overrides.credentials.password,
}),
]);
}
else {
// We need to use `ProvideCredentials`
// As `Default` may cancel the request
return await this.#continueWithAuth({
response: 'ProvideCredentials',
});
}
}
if (this.#interceptPhase === "responseStarted" /* Network.InterceptPhase.ResponseStarted */) {
const overrideHeaders = this.#getOverrideHeader(overrides.headers, overrides.cookies);
const responseHeaders = (0, NetworkUtils_js_1.cdpFetchHeadersFromBidiNetworkHeaders)(overrideHeaders);
await this.#continueResponse({
responseCode: overrides.statusCode,
responsePhrase: overrides.reasonPhrase,
responseHeaders,
});
}
}
async #continueResponse({ responseCode, responsePhrase, responseHeaders, } = {}) {
(0, assert_js_1.assert)(this.#fetchId, 'Network Interception not set-up.');
await this.cdpClient.sendCommand('Fetch.continueResponse', {
requestId: this.#fetchId,
responseCode,
responsePhrase,
responseHeaders,
});
this.#interceptPhase = undefined;
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueWithAuth */
async continueWithAuth(authChallenge) {
let username;
let password;
if (authChallenge.action === 'provideCredentials') {
const { credentials } = authChallenge;
username = credentials.username;
password = credentials.password;
}
const response = (0, NetworkUtils_js_1.cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction)(authChallenge.action);
await this.#continueWithAuth({
response,
username,
password,
});
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-provideResponse */
async provideResponse(overrides) {
(0, assert_js_1.assert)(this.#fetchId, 'Network Interception not set-up.');
// We need to pass through if the request is already in
// AuthRequired phase
if (this.interceptPhase === "authRequired" /* Network.InterceptPhase.AuthRequired */) {
// We need to use `ProvideCredentials`
// As `Default` may cancel the request
return await this.#continueWithAuth({
response: 'ProvideCredentials',
});
}
// If we don't modify the response
// just continue the request
if (!overrides.body && !overrides.headers) {
return await this.#continueRequest();
}
const overrideHeaders = this.#getOverrideHeader(overrides.headers, overrides.cookies);
const responseHeaders = (0, NetworkUtils_js_1.cdpFetchHeadersFromBidiNetworkHeaders)(overrideHeaders);
const responseCode = overrides.statusCode ?? this.#statusCode ?? 200;
await this.cdpClient.sendCommand('Fetch.fulfillRequest', {
requestId: this.#fetchId,
responseCode,
responsePhrase: overrides.reasonPhrase,
responseHeaders,
body: getCdpBodyFromBiDiBytesValue(overrides.body),
});
this.#interceptPhase = undefined;
}
async #continueWithAuth(authChallengeResponse) {
(0, assert_js_1.assert)(this.#fetchId, 'Network Interception not set-up.');
await this.cdpClient.sendCommand('Fetch.continueWithAuth', {
requestId: this.#fetchId,
authChallengeResponse,
});
this.#interceptPhase = undefined;
}
#emitEvent(getEvent) {
let event;
try {
event = getEvent();
}
catch (error) {
this.#logger?.(log_js_1.LogType.debugError, error);
return;
}
if (this.#isIgnoredEvent() ||
(this.#emittedEvents[event.method] &&
// Special case this event can be emitted multiple times
event.method !== protocol_js_1.ChromiumBidi.Network.EventNames.AuthRequired)) {
return;
}
this.#phaseChanged();
this.#emittedEvents[event.method] = true;
this.#eventManager.registerEvent(Object.assign(event, {
type: 'event',
}), this.#context);
}
#getBaseEventParams(phase) {
const interceptProps = {
isBlocked: false,
};
if (phase) {
const blockedBy = this.#interceptsInPhase(phase);
interceptProps.isBlocked = blockedBy.size > 0;
if (interceptProps.isBlocked) {
interceptProps.intercepts = [...blockedBy];
}
}
return {
context: this.#context,
navigation: this.#navigationId,
redirectCount: this.#redirectCount,
request: this.#getRequestData(),
// Timestamp should be in milliseconds, while CDP provides it in seconds.
timestamp: Math.round((0, NetworkUtils_js_1.getTiming)(this.#request.info?.wallTime) * 1000),
// Contains isBlocked and intercepts
...interceptProps,
};
}
#getResponseEventParams() {
// Chromium sends wrong extraInfo events for responses served from cache.
// See https://github.com/puppeteer/puppeteer/issues/9965 and
// https://crbug.com/1340398.
if (this.#response.info?.fromDiskCache) {
this.#response.extraInfo = undefined;
}
const headers = [
...(0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpNetworkHeaders)(this.#response.info?.headers),
...(0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpNetworkHeaders)(this.#response.extraInfo?.headers),
// TODO: Verify how to dedupe these
// ...bidiNetworkHeadersFromCdpNetworkHeadersEntries(
// this.#response.paused?.responseHeaders
// ),
];
const authChallenges = this.#authChallenges;
return {
url: this.url,
protocol: this.#response.info?.protocol ?? '',
status: this.#statusCode ?? -1, // TODO: Throw an exception or use some other status code?
statusText: this.#response.info?.statusText ||
this.#response.paused?.responseStatusText ||
'',
fromCache: this.#response.info?.fromDiskCache ||
this.#response.info?.fromPrefetchCache ||
this.#servedFromCache,
headers,
mimeType: this.#response.info?.mimeType || '',
bytesReceived: this.#response.info?.encodedDataLength || 0,
headersSize: (0, NetworkUtils_js_1.computeHeadersSize)(headers),
// TODO: consider removing from spec.
bodySize: 0,
content: {
// TODO: consider removing from spec.
size: 0,
},
...(authChallenges ? { authChallenges } : {}),
// @ts-expect-error this is a CDP-specific extension.
'goog:securityDetails': this.#response.info?.securityDetails,
};
}
#getRequestData() {
const headers = this.#requestHeaders;
return {
request: this.#id,
url: this.url,
method: this.#method ?? NetworkRequest.unknownParameter,
headers,
cookies: this.#cookies,
headersSize: (0, NetworkUtils_js_1.computeHeadersSize)(headers),
bodySize: this.#bodySize,
timings: this.#timings,
// @ts-expect-error CDP-specific attribute.
'goog:postData': this.#request.info?.request?.postData,
'goog:hasPostData': this.#request.info?.request?.hasPostData,
'goog:resourceType': this.#request.info?.type,
};
}
#getBeforeRequestEvent() {
(0, assert_js_1.assert)(this.#request.info, 'RequestWillBeSentEvent is not set');
return {
method: protocol_js_1.ChromiumBidi.Network.EventNames.BeforeRequestSent,
params: {
...this.#getBaseEventParams("beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */),
initiator: {
type: NetworkRequest.#getInitiatorType(this.#request.info.initiator.type),
columnNumber: this.#request.info.initiator.columnNumber,
lineNumber: this.#request.info.initiator.lineNumber,
stackTrace: this.#request.info.initiator.stack,
request: this.#request.info.initiator.requestId,
},
},
};
}
#getResponseStartedEvent() {
return {
method: protocol_js_1.ChromiumBidi.Network.EventNames.ResponseStarted,
params: {
...this.#getBaseEventParams("responseStarted" /* Network.InterceptPhase.ResponseStarted */),
response: this.#getResponseEventParams(),
},
};
}
#getResponseReceivedEvent() {
return {
method: protocol_js_1.ChromiumBidi.Network.EventNames.ResponseCompleted,
params: {
...this.#getBaseEventParams(),
response: this.#getResponseEventParams(),
},
};
}
#isIgnoredEvent() {
const faviconUrl = '/favicon.ico';
return (this.#request.paused?.request.url.endsWith(faviconUrl) ??
this.#request.info?.request.url.endsWith(faviconUrl) ??
false);
}
#getOverrideHeader(headers, cookies) {
if (!headers && !cookies) {
return undefined;
}
let overrideHeaders = headers;
const cookieHeader = (0, NetworkUtils_js_1.networkHeaderFromCookieHeaders)(cookies);
if (cookieHeader && !overrideHeaders) {
overrideHeaders = this.#requestHeaders;
}
if (cookieHeader && overrideHeaders) {
overrideHeaders.filter((header) => header.name.localeCompare('cookie', undefined, {
sensitivity: 'base',
}) !== 0);
overrideHeaders.push(cookieHeader);
}
return overrideHeaders;
}
static #getInitiatorType(initiatorType) {
switch (initiatorType) {
case 'parser':
case 'script':
case 'preflight':
return initiatorType;
default:
return 'other';
}
}
}
exports.NetworkRequest = NetworkRequest;
function getCdpBodyFromBiDiBytesValue(body) {
let parsedBody;
if (body?.type === 'string') {
parsedBody = btoa(body.value);
}
else if (body?.type === 'base64') {
parsedBody = body.value;
}
return parsedBody;
}
function getSizeFromBiDiBytesValue(body) {
if (body?.type === 'string') {
return body.value.length;
}
else if (body?.type === 'base64') {
return atob(body.value).length;
}
return 0;
}
//# sourceMappingURL=NetworkRequest.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,44 @@
import { type BrowsingContext, Network } from '../../../protocol/protocol.js';
import type { LoggerFn } from '../../../utils/log.js';
import type { CdpClient } from '../../BidiMapper.js';
import type { CdpTarget } from '../cdp/CdpTarget.js';
import type { BrowsingContextStorage } from '../context/BrowsingContextStorage';
import type { EventManager } from '../session/EventManager.js';
import { NetworkRequest } from './NetworkRequest.js';
type NetworkInterception = Omit<Network.AddInterceptParameters, 'urlPatterns'> & {
urlPatterns: Network.UrlPattern[];
};
/** Stores network and intercept maps. */
export declare class NetworkStorage {
#private;
constructor(eventManager: EventManager, browsingContextStorage: BrowsingContextStorage, browserClient: CdpClient, logger?: LoggerFn);
onCdpTargetCreated(cdpTarget: CdpTarget): void;
getInterceptionStages(browsingContextId: BrowsingContext.BrowsingContext): {
request: boolean;
response: boolean;
auth: boolean;
};
getInterceptsForPhase(request: NetworkRequest, phase: Network.InterceptPhase): Set<Network.Intercept>;
disposeRequestMap(sessionId: string): void;
/**
* Adds the given entry to the intercept map.
* URL patterns are assumed to be parsed.
*
* @return The intercept ID.
*/
addIntercept(value: NetworkInterception): Network.Intercept;
/**
* Removes the given intercept from the intercept map.
* Throws NoSuchInterceptException if the intercept does not exist.
*/
removeIntercept(intercept: Network.Intercept): void;
getRequestById(id: Network.Request): NetworkRequest | undefined;
getRequestByFetchId(fetchId: Network.Request): NetworkRequest | undefined;
addRequest(request: NetworkRequest): void;
deleteRequest(id: Network.Request): void;
/**
* Gets the virtual navigation ID for the given navigable ID.
*/
getVirtualNavigationId(contextId: string | undefined): string | null;
}
export {};
@@ -0,0 +1,210 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkStorage = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const uuid_js_1 = require("../../../utils/uuid.js");
const NetworkRequest_js_1 = require("./NetworkRequest.js");
const NetworkUtils_js_1 = require("./NetworkUtils.js");
/** Stores network and intercept maps. */
class NetworkStorage {
#browsingContextStorage;
#eventManager;
#logger;
/**
* A map from network request ID to Network Request objects.
* Needed as long as information about requests comes from different events.
*/
#requests = new Map();
/** A map from intercept ID to track active network intercepts. */
#intercepts = new Map();
constructor(eventManager, browsingContextStorage, browserClient, logger) {
this.#browsingContextStorage = browsingContextStorage;
this.#eventManager = eventManager;
browserClient.on('Target.detachedFromTarget', ({ sessionId }) => {
this.disposeRequestMap(sessionId);
});
this.#logger = logger;
}
/**
* Gets the network request with the given ID, if any.
* Otherwise, creates a new network request with the given ID and cdp target.
*/
#getOrCreateNetworkRequest(id, cdpTarget, redirectCount) {
let request = this.getRequestById(id);
if (request) {
return request;
}
request = new NetworkRequest_js_1.NetworkRequest(id, this.#eventManager, this, cdpTarget, redirectCount, this.#logger);
this.addRequest(request);
return request;
}
onCdpTargetCreated(cdpTarget) {
const cdpClient = cdpTarget.cdpClient;
// TODO: Wrap into object
const listeners = [
[
'Network.requestWillBeSent',
(params) => {
const request = this.getRequestById(params.requestId);
if (request && request.isRedirecting()) {
request.handleRedirect(params);
this.deleteRequest(params.requestId);
this.#getOrCreateNetworkRequest(params.requestId, cdpTarget, request.redirectCount + 1).onRequestWillBeSentEvent(params);
}
else {
this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onRequestWillBeSentEvent(params);
}
},
],
[
'Network.requestWillBeSentExtraInfo',
(params) => {
this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onRequestWillBeSentExtraInfoEvent(params);
},
],
[
'Network.responseReceived',
(params) => {
this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onResponseReceivedEvent(params);
},
],
[
'Network.responseReceivedExtraInfo',
(params) => {
this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onResponseReceivedExtraInfoEvent(params);
},
],
[
'Network.requestServedFromCache',
(params) => {
this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onServedFromCache();
},
],
[
'Network.loadingFailed',
(params) => {
this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onLoadingFailedEvent(params);
},
],
[
'Fetch.requestPaused',
(event) => {
this.#getOrCreateNetworkRequest(
// CDP quirk if the Network domain is not present this is undefined
event.networkId ?? event.requestId, cdpTarget).onRequestPaused(event);
},
],
[
'Fetch.authRequired',
(event) => {
let request = this.getRequestByFetchId(event.requestId);
if (!request) {
request = this.#getOrCreateNetworkRequest(event.requestId, cdpTarget);
}
request.onAuthRequired(event);
},
],
];
for (const [event, listener] of listeners) {
cdpClient.on(event, listener);
}
}
getInterceptionStages(browsingContextId) {
const stages = {
request: false,
response: false,
auth: false,
};
for (const intercept of this.#intercepts.values()) {
if (intercept.contexts &&
!intercept.contexts.includes(browsingContextId)) {
continue;
}
stages.request ||= intercept.phases.includes("beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */);
stages.response ||= intercept.phases.includes("responseStarted" /* Network.InterceptPhase.ResponseStarted */);
stages.auth ||= intercept.phases.includes("authRequired" /* Network.InterceptPhase.AuthRequired */);
}
return stages;
}
getInterceptsForPhase(request, phase) {
if (request.url === NetworkRequest_js_1.NetworkRequest.unknownParameter) {
return new Set();
}
const intercepts = new Set();
for (const [interceptId, intercept] of this.#intercepts.entries()) {
if (!intercept.phases.includes(phase) ||
(intercept.contexts &&
!intercept.contexts.includes(request.cdpTarget.topLevelId))) {
continue;
}
if (intercept.urlPatterns.length === 0) {
intercepts.add(interceptId);
continue;
}
for (const pattern of intercept.urlPatterns) {
if ((0, NetworkUtils_js_1.matchUrlPattern)(pattern, request.url)) {
intercepts.add(interceptId);
break;
}
}
}
return intercepts;
}
disposeRequestMap(sessionId) {
for (const request of this.#requests.values()) {
if (request.cdpClient.sessionId === sessionId) {
this.#requests.delete(request.id);
}
}
}
/**
* Adds the given entry to the intercept map.
* URL patterns are assumed to be parsed.
*
* @return The intercept ID.
*/
addIntercept(value) {
const interceptId = (0, uuid_js_1.uuidv4)();
this.#intercepts.set(interceptId, value);
return interceptId;
}
/**
* Removes the given intercept from the intercept map.
* Throws NoSuchInterceptException if the intercept does not exist.
*/
removeIntercept(intercept) {
if (!this.#intercepts.has(intercept)) {
throw new protocol_js_1.NoSuchInterceptException(`Intercept '${intercept}' does not exist.`);
}
this.#intercepts.delete(intercept);
}
getRequestById(id) {
return this.#requests.get(id);
}
getRequestByFetchId(fetchId) {
for (const request of this.#requests.values()) {
if (request.fetchId === fetchId) {
return request;
}
}
return;
}
addRequest(request) {
this.#requests.set(request.id, request);
}
deleteRequest(id) {
this.#requests.delete(id);
}
/**
* Gets the virtual navigation ID for the given navigable ID.
*/
getVirtualNavigationId(contextId) {
if (contextId === undefined) {
return null;
}
return (this.#browsingContextStorage.findContext(contextId)
?.virtualNavigationId ?? null);
}
}
exports.NetworkStorage = NetworkStorage;
//# sourceMappingURL=NetworkStorage.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,51 @@
/**
* @fileoverview Utility functions for the Network domain.
*/
import type { Protocol } from 'devtools-protocol';
import { Network, type Storage } from '../../../protocol/protocol.js';
export declare function computeHeadersSize(headers: Network.Header[]): number;
/** Converts from CDP Network domain headers to BiDi network headers. */
export declare function bidiNetworkHeadersFromCdpNetworkHeaders(headers?: Protocol.Network.Headers): Network.Header[];
/** Converts from CDP Fetch domain headers to BiDi network headers. */
export declare function bidiNetworkHeadersFromCdpNetworkHeadersEntries(headers?: Protocol.Fetch.HeaderEntry[]): Network.Header[];
/** Converts from Bidi network headers to CDP Network domain headers. */
export declare function cdpNetworkHeadersFromBidiNetworkHeaders(headers?: Network.Header[]): Protocol.Network.Headers | undefined;
/** Converts from CDP Fetch domain header entries to Bidi network headers. */
export declare function bidiNetworkHeadersFromCdpFetchHeaders(headers?: Protocol.Fetch.HeaderEntry[]): Network.Header[];
/** Converts from Bidi network headers to CDP Fetch domain header entries. */
export declare function cdpFetchHeadersFromBidiNetworkHeaders(headers?: Network.Header[]): Protocol.Fetch.HeaderEntry[] | undefined;
export declare function networkHeaderFromCookieHeaders(headers?: Network.CookieHeader[]): Network.Header | undefined;
/** Converts from Bidi auth action to CDP auth challenge response. */
export declare function cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction(action: 'default' | 'cancel' | 'provideCredentials'): "Default" | "CancelAuth" | "ProvideCredentials";
/**
* Converts from CDP Network domain cookie to BiDi network cookie.
* * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-Cookie
* * https://w3c.github.io/webdriver-bidi/#type-network-Cookie
*/
export declare function cdpToBiDiCookie(cookie: Protocol.Network.Cookie): Network.Cookie;
/**
* Decodes a byte value to a string.
* @param {Network.BytesValue} value
* @return {string}
*/
export declare function deserializeByteValue(value: Network.BytesValue): string;
/**
* Converts from BiDi set network cookie params to CDP Network domain cookie.
* * https://w3c.github.io/webdriver-bidi/#type-network-Cookie
* * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-CookieParam
*/
export declare function bidiToCdpCookie(params: Storage.SetCookieParameters, partitionKey: Storage.PartitionKey): Protocol.Network.CookieParam;
export declare function sameSiteBiDiToCdp(sameSite: Network.SameSite): Protocol.Network.CookieSameSite;
/**
* Returns true if the given protocol is special.
* Special protocols are those that have a default port.
*
* Example inputs: 'http', 'http:'
*
* @see https://url.spec.whatwg.org/#special-scheme
*/
export declare function isSpecialScheme(protocol: string): boolean;
/** Matches the given URLPattern against the given URL. */
export declare function matchUrlPattern(urlPattern: Network.UrlPattern, url: string | undefined): boolean;
export declare function bidiBodySizeFromCdpPostDataEntries(entries: Protocol.Network.PostDataEntry[]): number;
export declare function getTiming(timing: number | undefined): number;
@@ -0,0 +1,297 @@
"use strict";
/*
* 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.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.computeHeadersSize = computeHeadersSize;
exports.bidiNetworkHeadersFromCdpNetworkHeaders = bidiNetworkHeadersFromCdpNetworkHeaders;
exports.bidiNetworkHeadersFromCdpNetworkHeadersEntries = bidiNetworkHeadersFromCdpNetworkHeadersEntries;
exports.cdpNetworkHeadersFromBidiNetworkHeaders = cdpNetworkHeadersFromBidiNetworkHeaders;
exports.bidiNetworkHeadersFromCdpFetchHeaders = bidiNetworkHeadersFromCdpFetchHeaders;
exports.cdpFetchHeadersFromBidiNetworkHeaders = cdpFetchHeadersFromBidiNetworkHeaders;
exports.networkHeaderFromCookieHeaders = networkHeaderFromCookieHeaders;
exports.cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction = cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction;
exports.cdpToBiDiCookie = cdpToBiDiCookie;
exports.deserializeByteValue = deserializeByteValue;
exports.bidiToCdpCookie = bidiToCdpCookie;
exports.sameSiteBiDiToCdp = sameSiteBiDiToCdp;
exports.isSpecialScheme = isSpecialScheme;
exports.matchUrlPattern = matchUrlPattern;
exports.bidiBodySizeFromCdpPostDataEntries = bidiBodySizeFromCdpPostDataEntries;
exports.getTiming = getTiming;
const ErrorResponse_js_1 = require("../../../protocol/ErrorResponse.js");
const Base64_js_1 = require("../../../utils/Base64.js");
const UrlPattern_js_1 = require("../../../utils/UrlPattern.js");
function computeHeadersSize(headers) {
const requestHeaders = headers.reduce((acc, header) => {
return `${acc}${header.name}: ${header.value.value}\r\n`;
}, '');
return new TextEncoder().encode(requestHeaders).length;
}
/** Converts from CDP Network domain headers to BiDi network headers. */
function bidiNetworkHeadersFromCdpNetworkHeaders(headers) {
if (!headers) {
return [];
}
return Object.entries(headers).map(([name, value]) => ({
name,
value: {
type: 'string',
value,
},
}));
}
/** Converts from CDP Fetch domain headers to BiDi network headers. */
function bidiNetworkHeadersFromCdpNetworkHeadersEntries(headers) {
if (!headers) {
return [];
}
return headers.map(({ name, value }) => ({
name,
value: {
type: 'string',
value,
},
}));
}
/** Converts from Bidi network headers to CDP Network domain headers. */
function cdpNetworkHeadersFromBidiNetworkHeaders(headers) {
if (headers === undefined) {
return undefined;
}
return headers.reduce((result, header) => {
// TODO: Distinguish between string and bytes?
result[header.name] = header.value.value;
return result;
}, {});
}
/** Converts from CDP Fetch domain header entries to Bidi network headers. */
function bidiNetworkHeadersFromCdpFetchHeaders(headers) {
if (!headers) {
return [];
}
return headers.map(({ name, value }) => ({
name,
value: {
type: 'string',
value,
},
}));
}
/** Converts from Bidi network headers to CDP Fetch domain header entries. */
function cdpFetchHeadersFromBidiNetworkHeaders(headers) {
if (headers === undefined) {
return undefined;
}
return headers.map(({ name, value }) => ({
name,
value: value.value,
}));
}
function networkHeaderFromCookieHeaders(headers) {
if (headers === undefined) {
return undefined;
}
const value = headers.reduce((acc, value, index) => {
if (index > 0) {
acc += ';';
}
const cookieValue = value.value.type === 'base64'
? btoa(value.value.value)
: value.value.value;
acc += `${value.name}=${cookieValue}`;
return acc;
}, '');
return {
name: 'Cookie',
value: {
type: 'string',
value,
},
};
}
/** Converts from Bidi auth action to CDP auth challenge response. */
function cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction(action) {
switch (action) {
case 'default':
return 'Default';
case 'cancel':
return 'CancelAuth';
case 'provideCredentials':
return 'ProvideCredentials';
}
}
/**
* Converts from CDP Network domain cookie to BiDi network cookie.
* * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-Cookie
* * https://w3c.github.io/webdriver-bidi/#type-network-Cookie
*/
function cdpToBiDiCookie(cookie) {
const result = {
name: cookie.name,
value: { type: 'string', value: cookie.value },
domain: cookie.domain,
path: cookie.path,
size: cookie.size,
httpOnly: cookie.httpOnly,
secure: cookie.secure,
sameSite: cookie.sameSite === undefined
? "none" /* Network.SameSite.None */
: sameSiteCdpToBiDi(cookie.sameSite),
...(cookie.expires >= 0 ? { expiry: cookie.expires } : undefined),
};
// Extending with CDP-specific properties with `goog:` prefix.
result[`goog:session`] = cookie.session;
result[`goog:priority`] = cookie.priority;
result[`goog:sameParty`] = cookie.sameParty;
result[`goog:sourceScheme`] = cookie.sourceScheme;
result[`goog:sourcePort`] = cookie.sourcePort;
if (cookie.partitionKey !== undefined) {
result[`goog:partitionKey`] = cookie.partitionKey;
}
if (cookie.partitionKeyOpaque !== undefined) {
result[`goog:partitionKeyOpaque`] = cookie.partitionKeyOpaque;
}
return result;
}
/**
* Decodes a byte value to a string.
* @param {Network.BytesValue} value
* @return {string}
*/
function deserializeByteValue(value) {
if (value.type === 'base64') {
return (0, Base64_js_1.base64ToString)(value.value);
}
return value.value;
}
/**
* Converts from BiDi set network cookie params to CDP Network domain cookie.
* * https://w3c.github.io/webdriver-bidi/#type-network-Cookie
* * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-CookieParam
*/
function bidiToCdpCookie(params, partitionKey) {
const deserializedValue = deserializeByteValue(params.cookie.value);
const result = {
name: params.cookie.name,
value: deserializedValue,
domain: params.cookie.domain,
path: params.cookie.path ?? '/',
secure: params.cookie.secure ?? false,
httpOnly: params.cookie.httpOnly ?? false,
...(partitionKey.sourceOrigin !== undefined && {
partitionKey: {
hasCrossSiteAncestor: false,
// CDP's `partitionKey.topLevelSite` is the BiDi's `partition.sourceOrigin`.
topLevelSite: partitionKey.sourceOrigin,
},
}),
...(params.cookie.expiry !== undefined && {
expires: params.cookie.expiry,
}),
...(params.cookie.sameSite !== undefined && {
sameSite: sameSiteBiDiToCdp(params.cookie.sameSite),
}),
};
// Extending with CDP-specific properties with `goog:` prefix.
if (params.cookie[`goog:url`] !== undefined) {
result.url = params.cookie[`goog:url`];
}
if (params.cookie[`goog:priority`] !== undefined) {
result.priority = params.cookie[`goog:priority`];
}
if (params.cookie[`goog:sameParty`] !== undefined) {
result.sameParty = params.cookie[`goog:sameParty`];
}
if (params.cookie[`goog:sourceScheme`] !== undefined) {
result.sourceScheme = params.cookie[`goog:sourceScheme`];
}
if (params.cookie[`goog:sourcePort`] !== undefined) {
result.sourcePort = params.cookie[`goog:sourcePort`];
}
return result;
}
function sameSiteCdpToBiDi(sameSite) {
switch (sameSite) {
case 'Strict':
return "strict" /* Network.SameSite.Strict */;
case 'None':
return "none" /* Network.SameSite.None */;
case 'Lax':
return "lax" /* Network.SameSite.Lax */;
default:
// Defaults to `Lax`:
// https://web.dev/articles/samesite-cookies-explained#samesitelax_by_default
return "lax" /* Network.SameSite.Lax */;
}
}
function sameSiteBiDiToCdp(sameSite) {
switch (sameSite) {
case "strict" /* Network.SameSite.Strict */:
return 'Strict';
case "lax" /* Network.SameSite.Lax */:
return 'Lax';
case "none" /* Network.SameSite.None */:
return 'None';
}
throw new ErrorResponse_js_1.InvalidArgumentException(`Unknown 'sameSite' value ${sameSite}`);
}
/**
* Returns true if the given protocol is special.
* Special protocols are those that have a default port.
*
* Example inputs: 'http', 'http:'
*
* @see https://url.spec.whatwg.org/#special-scheme
*/
function isSpecialScheme(protocol) {
return ['ftp', 'file', 'http', 'https', 'ws', 'wss'].includes(protocol.replace(/:$/, ''));
}
/** Matches the given URLPattern against the given URL. */
function matchUrlPattern(urlPattern, url) {
switch (urlPattern.type) {
case 'string': {
const pattern = new UrlPattern_js_1.URLPattern(urlPattern.pattern);
return new UrlPattern_js_1.URLPattern({
protocol: pattern.protocol,
hostname: pattern.hostname,
port: pattern.port,
pathname: pattern.pathname,
search: pattern.search,
}).test(url);
}
case 'pattern':
return new UrlPattern_js_1.URLPattern(urlPattern).test(url);
}
}
function bidiBodySizeFromCdpPostDataEntries(entries) {
let size = 0;
for (const entry of entries) {
size += atob(entry.bytes ?? '').length;
}
return size;
}
function getTiming(timing) {
if (!timing) {
return 0;
}
if (timing < 0) {
return 0;
}
return timing;
}
//# sourceMappingURL=NetworkUtils.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,23 @@
/**
* 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.
*/
import type { CdpClient } from '../../../cdp/CdpClient.js';
import { type EmptyResult, type Permissions } from '../../../protocol/protocol.js';
export declare class PermissionsProcessor {
#private;
constructor(browserCdpClient: CdpClient);
setPermissions(params: Permissions.SetPermissionParameters): Promise<EmptyResult>;
}
@@ -0,0 +1,54 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PermissionsProcessor = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
class PermissionsProcessor {
#browserCdpClient;
constructor(browserCdpClient) {
this.#browserCdpClient = browserCdpClient;
}
async setPermissions(params) {
try {
const userContextId = params['goog:userContext'] ||
params.userContext;
await this.#browserCdpClient.sendCommand('Browser.setPermission', {
origin: params.origin,
browserContextId: userContextId && userContextId !== 'default'
? userContextId
: undefined,
permission: {
name: params.descriptor.name,
},
setting: params.state,
});
}
catch (err) {
if (err.message ===
`Permission can't be granted to opaque origins.`) {
// Return success if the origin is not valid (does not match any
// existing origins).
return {};
}
throw new protocol_js_1.InvalidArgumentException(err.message);
}
return {};
}
}
exports.PermissionsProcessor = PermissionsProcessor;
//# sourceMappingURL=PermissionsProcessor.js.map
@@ -0,0 +1 @@
{"version":3,"file":"PermissionsProcessor.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/permissions/PermissionsProcessor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAGH,+DAIuC;AAEvC,MAAa,oBAAoB;IAC/B,iBAAiB,CAAY;IAE7B,YAAY,gBAA2B;QACrC,IAAI,CAAC,iBAAiB,GAAG,gBAAgB,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,MAA2C;QAE3C,IAAI,CAAC;YACH,MAAM,aAAa,GAChB,MAAwC,CAAC,kBAAkB,CAAC;gBAC7D,MAAM,CAAC,WAAW,CAAC;YACrB,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,uBAAuB,EAAE;gBAChE,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,gBAAgB,EACd,aAAa,IAAI,aAAa,KAAK,SAAS;oBAC1C,CAAC,CAAC,aAAa;oBACf,CAAC,CAAC,SAAS;gBACf,UAAU,EAAE;oBACV,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI;iBAC7B;gBACD,OAAO,EAAE,MAAM,CAAC,KAAK;aACtB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IACG,GAAa,CAAC,OAAO;gBACtB,gDAAgD,EAChD,CAAC;gBACD,gEAAgE;gBAChE,qBAAqB;gBACrB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,IAAI,sCAAwB,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;CACF;AAtCD,oDAsCC"}
@@ -0,0 +1,30 @@
import { Script } from '../../../protocol/protocol.js';
import { type LoggerFn } from '../../../utils/log.js';
import type { EventManager } from '../session/EventManager.js';
import type { Realm } from './Realm.js';
/**
* Used to send messages from realm to BiDi user.
*/
export declare class ChannelProxy {
#private;
constructor(channel: Script.ChannelProperties, logger?: LoggerFn);
/**
* Creates a channel proxy in the given realm, initialises listener and
* returns a handle to `sendMessage` delegate.
*/
init(realm: Realm, eventManager: EventManager): Promise<Script.Handle>;
/** Gets a ChannelProxy from window and returns its handle. */
startListenerFromWindow(realm: Realm, eventManager: EventManager): Promise<void>;
/**
* String to be evaluated to create a ProxyChannel and put it to window.
* Returns the delegate `sendMessage`. Used to provide an argument for preload
* script. Does the following:
* 1. Creates a ChannelProxy.
* 2. Puts the ChannelProxy to window['${this.#id}'] or resolves the promise
* by calling delegate stored in window['${this.#id}'].
* This is needed because `#getHandleFromWindow` can be called before or
* after this method.
* 3. Returns the delegate `sendMessage` of the created ChannelProxy.
*/
getEvalInWindowStr(): string;
}
@@ -0,0 +1,235 @@
"use strict";
/*
* 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.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChannelProxy = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const log_js_1 = require("../../../utils/log.js");
const uuid_js_1 = require("../../../utils/uuid.js");
/**
* Used to send messages from realm to BiDi user.
*/
class ChannelProxy {
#properties;
#id = (0, uuid_js_1.uuidv4)();
#logger;
constructor(channel, logger) {
this.#properties = channel;
this.#logger = logger;
}
/**
* Creates a channel proxy in the given realm, initialises listener and
* returns a handle to `sendMessage` delegate.
*/
async init(realm, eventManager) {
const channelHandle = await ChannelProxy.#createAndGetHandleInRealm(realm);
const sendMessageHandle = await ChannelProxy.#createSendMessageHandle(realm, channelHandle);
void this.#startListener(realm, channelHandle, eventManager);
return sendMessageHandle;
}
/** Gets a ChannelProxy from window and returns its handle. */
async startListenerFromWindow(realm, eventManager) {
try {
const channelHandle = await this.#getHandleFromWindow(realm);
void this.#startListener(realm, channelHandle, eventManager);
}
catch (error) {
this.#logger?.(log_js_1.LogType.debugError, error);
}
}
/**
* Evaluation string which creates a ChannelProxy object on the client side.
*/
static #createChannelProxyEvalStr() {
const functionStr = String(() => {
const queue = [];
let queueNonEmptyResolver = null;
return {
/**
* Gets a promise, which is resolved as soon as a message occurs
* in the queue.
*/
async getMessage() {
const onMessage = queue.length > 0
? Promise.resolve()
: new Promise((resolve) => {
queueNonEmptyResolver = resolve;
});
await onMessage;
return queue.shift();
},
/**
* Adds a message to the queue.
* Resolves the pending promise if needed.
*/
sendMessage(message) {
queue.push(message);
if (queueNonEmptyResolver !== null) {
queueNonEmptyResolver();
queueNonEmptyResolver = null;
}
},
};
});
return `(${functionStr})()`;
}
/** Creates a ChannelProxy in the given realm. */
static async #createAndGetHandleInRealm(realm) {
const createChannelHandleResult = await realm.cdpClient.sendCommand('Runtime.evaluate', {
expression: this.#createChannelProxyEvalStr(),
contextId: realm.executionContextId,
serializationOptions: {
serialization: "idOnly" /* Protocol.Runtime.SerializationOptionsSerialization.IdOnly */,
},
});
if (createChannelHandleResult.exceptionDetails ||
createChannelHandleResult.result.objectId === undefined) {
throw new Error(`Cannot create channel`);
}
return createChannelHandleResult.result.objectId;
}
/** Gets a handle to `sendMessage` delegate from the ChannelProxy handle. */
static async #createSendMessageHandle(realm, channelHandle) {
const sendMessageArgResult = await realm.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: String((channelHandle) => {
return channelHandle.sendMessage;
}),
arguments: [{ objectId: channelHandle }],
executionContextId: realm.executionContextId,
serializationOptions: {
serialization: "idOnly" /* Protocol.Runtime.SerializationOptionsSerialization.IdOnly */,
},
});
// TODO: check for exceptionDetails.
return sendMessageArgResult.result.objectId;
}
/** Starts listening for the channel events of the provided ChannelProxy. */
async #startListener(realm, channelHandle, eventManager) {
// noinspection InfiniteLoopJS
for (;;) {
try {
const message = await realm.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: String(async (channelHandle) => await channelHandle.getMessage()),
arguments: [
{
objectId: channelHandle,
},
],
awaitPromise: true,
executionContextId: realm.executionContextId,
serializationOptions: {
serialization: "deep" /* Protocol.Runtime.SerializationOptionsSerialization.Deep */,
maxDepth: this.#properties.serializationOptions?.maxObjectDepth ??
undefined,
},
});
if (message.exceptionDetails) {
throw new Error('Runtime.callFunctionOn in ChannelProxy', {
cause: message.exceptionDetails,
});
}
for (const browsingContext of realm.associatedBrowsingContexts) {
eventManager.registerEvent({
type: 'event',
method: protocol_js_1.ChromiumBidi.Script.EventNames.Message,
params: {
channel: this.#properties.channel,
data: realm.cdpToBidiValue(message, this.#properties.ownership ?? "none" /* Script.ResultOwnership.None */),
source: realm.source,
},
}, browsingContext.id);
}
}
catch (error) {
// If an error is thrown, then the channel is permanently broken, so we
// exit the loop.
this.#logger?.(log_js_1.LogType.debugError, error);
break;
}
}
}
/**
* Returns a handle of ChannelProxy from window's property which was set there
* by `getEvalInWindowStr`. If window property is not set yet, sets a promise
* resolver to the window property, so that `getEvalInWindowStr` can resolve
* the promise later on with the channel.
* This is needed because `getEvalInWindowStr` can be called before or
* after this method.
*/
async #getHandleFromWindow(realm) {
const channelHandleResult = await realm.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: String((id) => {
const w = window;
if (w[id] === undefined) {
// The channelProxy is not created yet. Create a promise, put the
// resolver to window property and return the promise.
// `getEvalInWindowStr` will resolve the promise later.
return new Promise((resolve) => (w[id] = resolve));
}
// The channelProxy is already created by `getEvalInWindowStr` and
// is set into window property. Return it.
const channelProxy = w[id];
delete w[id];
return channelProxy;
}),
arguments: [{ value: this.#id }],
executionContextId: realm.executionContextId,
awaitPromise: true,
serializationOptions: {
serialization: "idOnly" /* Protocol.Runtime.SerializationOptionsSerialization.IdOnly */,
},
});
if (channelHandleResult.exceptionDetails !== undefined ||
channelHandleResult.result.objectId === undefined) {
throw new Error(`ChannelHandle not found in window["${this.#id}"]`);
}
return channelHandleResult.result.objectId;
}
/**
* String to be evaluated to create a ProxyChannel and put it to window.
* Returns the delegate `sendMessage`. Used to provide an argument for preload
* script. Does the following:
* 1. Creates a ChannelProxy.
* 2. Puts the ChannelProxy to window['${this.#id}'] or resolves the promise
* by calling delegate stored in window['${this.#id}'].
* This is needed because `#getHandleFromWindow` can be called before or
* after this method.
* 3. Returns the delegate `sendMessage` of the created ChannelProxy.
*/
getEvalInWindowStr() {
const delegate = String((id, channelProxy) => {
const w = window;
if (w[id] === undefined) {
// `#getHandleFromWindow` is not initialized yet, and will get the
// channelProxy later.
w[id] = channelProxy;
}
else {
// `#getHandleFromWindow` is already set a delegate to window property
// and is waiting for it to be called with the channelProxy.
w[id](channelProxy);
delete w[id];
}
return channelProxy.sendMessage;
});
const channelProxyEval = ChannelProxy.#createChannelProxyEvalStr();
return `(${delegate})('${this.#id}',${channelProxyEval})`;
}
}
exports.ChannelProxy = ChannelProxy;
//# sourceMappingURL=ChannelProxy.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,41 @@
import type { Protocol } from 'devtools-protocol';
import type { BrowsingContext, Script } from '../../../protocol/protocol.js';
import type { LoggerFn } from '../../../utils/log.js';
import type { CdpTarget } from '../cdp/CdpTarget.js';
import { ChannelProxy } from './ChannelProxy.js';
/**
* BiDi IDs are generated by the server and are unique within contexts.
*
* CDP preload script IDs are generated by the client and are unique
* within sessions.
*
* The mapping between BiDi and CDP preload script IDs is 1:many.
* BiDi IDs are needed by the mapper to keep track of potential multiple CDP IDs
* in the client.
*/
export declare class PreloadScript {
#private;
get id(): string;
get targetIds(): Set<Protocol.Target.TargetID>;
constructor(params: Script.AddPreloadScriptParameters, logger?: LoggerFn);
/** Channels of the preload script. */
get channels(): ChannelProxy[];
/** Contexts of the preload script, if any */
get contexts(): BrowsingContext.BrowsingContext[] | undefined;
/**
* Adds the script to the given CDP targets by calling the
* `Page.addScriptToEvaluateOnNewDocument` command.
*/
initInTargets(cdpTargets: Iterable<CdpTarget>, runImmediately: boolean): Promise<void>;
/**
* Adds the script to the given CDP target by calling the
* `Page.addScriptToEvaluateOnNewDocument` command.
*/
initInTarget(cdpTarget: CdpTarget, runImmediately: boolean): Promise<void>;
/**
* Removes this script from all CDP targets.
*/
remove(): Promise<void>;
/** Removes the provided cdp target from the list of cdp preload scripts. */
dispose(cdpTargetId: Protocol.Target.TargetID): void;
}
@@ -0,0 +1,126 @@
"use strict";
/*
* 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.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PreloadScript = void 0;
const uuid_js_1 = require("../../../utils/uuid.js");
const ChannelProxy_js_1 = require("./ChannelProxy.js");
/**
* BiDi IDs are generated by the server and are unique within contexts.
*
* CDP preload script IDs are generated by the client and are unique
* within sessions.
*
* The mapping between BiDi and CDP preload script IDs is 1:many.
* BiDi IDs are needed by the mapper to keep track of potential multiple CDP IDs
* in the client.
*/
class PreloadScript {
/** BiDi ID, an automatically generated UUID. */
#id = (0, uuid_js_1.uuidv4)();
/** CDP preload scripts. */
#cdpPreloadScripts = [];
/** The script itself, in a format expected by the spec i.e. a function. */
#functionDeclaration;
/** Targets, in which the preload script is initialized. */
#targetIds = new Set();
/** Channels to be added as arguments to functionDeclaration. */
#channels;
/** The script sandbox / world name. */
#sandbox;
/** The browsing contexts to execute the preload scripts in, if any. */
#contexts;
get id() {
return this.#id;
}
get targetIds() {
return this.#targetIds;
}
constructor(params, logger) {
this.#channels =
params.arguments?.map((a) => new ChannelProxy_js_1.ChannelProxy(a.value, logger)) ?? [];
this.#functionDeclaration = params.functionDeclaration;
this.#sandbox = params.sandbox;
this.#contexts = params.contexts;
}
/** Channels of the preload script. */
get channels() {
return this.#channels;
}
/** Contexts of the preload script, if any */
get contexts() {
return this.#contexts;
}
/**
* String to be evaluated. Wraps user-provided function so that the following
* steps are run:
* 1. Create channels.
* 2. Store the created channels in window.
* 3. Call the user-provided function with channels as arguments.
*/
#getEvaluateString() {
const channelsArgStr = `[${this.channels
.map((c) => c.getEvalInWindowStr())
.join(', ')}]`;
return `(()=>{(${this.#functionDeclaration})(...${channelsArgStr})})()`;
}
/**
* Adds the script to the given CDP targets by calling the
* `Page.addScriptToEvaluateOnNewDocument` command.
*/
async initInTargets(cdpTargets, runImmediately) {
await Promise.all(Array.from(cdpTargets).map((cdpTarget) => this.initInTarget(cdpTarget, runImmediately)));
}
/**
* Adds the script to the given CDP target by calling the
* `Page.addScriptToEvaluateOnNewDocument` command.
*/
async initInTarget(cdpTarget, runImmediately) {
const addCdpPreloadScriptResult = await cdpTarget.cdpClient.sendCommand('Page.addScriptToEvaluateOnNewDocument', {
source: this.#getEvaluateString(),
worldName: this.#sandbox,
runImmediately,
});
this.#cdpPreloadScripts.push({
target: cdpTarget,
preloadScriptId: addCdpPreloadScriptResult.identifier,
});
this.#targetIds.add(cdpTarget.id);
}
/**
* Removes this script from all CDP targets.
*/
async remove() {
await Promise.all([
this.#cdpPreloadScripts.map(async (cdpPreloadScript) => {
const cdpTarget = cdpPreloadScript.target;
const cdpPreloadScriptId = cdpPreloadScript.preloadScriptId;
return await cdpTarget.cdpClient.sendCommand('Page.removeScriptToEvaluateOnNewDocument', {
identifier: cdpPreloadScriptId,
});
}),
]);
}
/** Removes the provided cdp target from the list of cdp preload scripts. */
dispose(cdpTargetId) {
this.#cdpPreloadScripts = this.#cdpPreloadScripts.filter((cdpPreloadScript) => cdpPreloadScript.target?.id !== cdpTargetId);
this.#targetIds.delete(cdpTargetId);
}
}
exports.PreloadScript = PreloadScript;
//# sourceMappingURL=PreloadScript.js.map
@@ -0,0 +1 @@
{"version":3,"file":"PreloadScript.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/script/PreloadScript.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
import type { CdpTarget } from '../cdp/CdpTarget.js';
import type { PreloadScript } from './PreloadScript.js';
/** PreloadScripts can be filtered by BiDi ID or target ID. */
export type PreloadScriptFilter = Partial<{
id: PreloadScript['id'];
targetId: CdpTarget['id'];
global: boolean;
}>;
/**
* Container class for preload scripts.
*/
export declare class PreloadScriptStorage {
#private;
/**
* Finds all entries that match the given filter (OR logic).
*/
find(filter?: PreloadScriptFilter): PreloadScript[];
add(preloadScript: PreloadScript): void;
/** Deletes all BiDi preload script entries that match the given filter. */
remove(filter?: PreloadScriptFilter): void;
}
@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PreloadScriptStorage = void 0;
/**
* Container class for preload scripts.
*/
class PreloadScriptStorage {
/** Tracks all BiDi preload scripts. */
#scripts = new Set();
/**
* Finds all entries that match the given filter (OR logic).
*/
find(filter) {
if (!filter) {
return [...this.#scripts];
}
return [...this.#scripts].filter((script) => {
if (filter.id !== undefined && filter.id === script.id) {
return true;
}
if (filter.targetId !== undefined &&
script.targetIds.has(filter.targetId)) {
return true;
}
if (filter.global !== undefined &&
// Global scripts have no contexts
((filter.global && script.contexts === undefined) ||
// Non global scripts always have contexts
(!filter.global && script.contexts !== undefined))) {
return true;
}
return false;
});
}
add(preloadScript) {
this.#scripts.add(preloadScript);
}
/** Deletes all BiDi preload script entries that match the given filter. */
remove(filter) {
for (const preloadScript of this.find(filter)) {
this.#scripts.delete(preloadScript);
}
}
}
exports.PreloadScriptStorage = PreloadScriptStorage;
//# sourceMappingURL=PreloadScriptStorage.js.map
@@ -0,0 +1 @@
{"version":3,"file":"PreloadScriptStorage.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/modules/script/PreloadScriptStorage.ts"],"names":[],"mappings":";;;AA2BA;;GAEG;AACH,MAAa,oBAAoB;IAC/B,wCAAwC;IAC/B,QAAQ,GAAG,IAAI,GAAG,EAAiB,CAAC;IAE7C;;OAEG;IACH,IAAI,CAAC,MAA4B;QAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE;YAC1C,IAAI,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC;gBACvD,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IACE,MAAM,CAAC,QAAQ,KAAK,SAAS;gBAC7B,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EACrC,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IACE,MAAM,CAAC,MAAM,KAAK,SAAS;gBAC3B,kCAAkC;gBAClC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC;oBAC/C,0CAA0C;oBAC1C,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,EACpD,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,aAA4B;QAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACnC,CAAC;IAED,2EAA2E;IAC3E,MAAM,CAAC,MAA4B;QACjC,KAAK,MAAM,aAAa,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;CACF;AA7CD,oDA6CC"}
@@ -0,0 +1,65 @@
/**
* 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.
*/
import { Protocol } from 'devtools-protocol';
import type { CdpClient } from '../../../cdp/CdpClient.js';
import { Script } from '../../../protocol/protocol.js';
import { type LoggerFn } from '../../../utils/log.js';
import type { BrowsingContextImpl } from '../context/BrowsingContextImpl.js';
import type { EventManager } from '../session/EventManager.js';
import type { RealmStorage } from './RealmStorage.js';
export declare abstract class Realm {
#private;
constructor(cdpClient: CdpClient, eventManager: EventManager, executionContextId: Protocol.Runtime.ExecutionContextId, logger: LoggerFn | undefined, origin: string, realmId: Script.Realm, realmStorage: RealmStorage);
cdpToBidiValue(cdpValue: Protocol.Runtime.CallFunctionOnResponse | Protocol.Runtime.EvaluateResponse, resultOwnership: Script.ResultOwnership): Script.RemoteValue;
/**
* Relies on the CDP to implement proper BiDi serialization, except:
* * CDP integer property `backendNodeId` is replaced with `sharedId` of
* `{documentId}_element_{backendNodeId}`;
* * CDP integer property `weakLocalObjectReference` is replaced with UUID `internalId`
* using unique-per serialization `internalIdMap`.
* * CDP type `platformobject` is replaced with `object`.
* @param deepSerializedValue - CDP value to be converted to BiDi.
* @param internalIdMap - Map from CDP integer `weakLocalObjectReference` to BiDi UUID
* `internalId`.
*/
protected serializeForBiDi(deepSerializedValue: Protocol.Runtime.DeepSerializedValue, internalIdMap: Map<number, string>): Script.RemoteValue;
get realmId(): Script.Realm;
get executionContextId(): Protocol.Runtime.ExecutionContextId;
get origin(): string;
get source(): Script.Source;
get cdpClient(): CdpClient;
abstract get associatedBrowsingContexts(): BrowsingContextImpl[];
abstract get realmType(): Script.RealmType;
protected get baseInfo(): Script.BaseRealmInfo;
abstract get realmInfo(): Script.RealmInfo;
evaluate(expression: string, awaitPromise: boolean, resultOwnership?: Script.ResultOwnership, serializationOptions?: Script.SerializationOptions, userActivation?: boolean, includeCommandLineApi?: boolean): Promise<Script.EvaluateResult>;
protected initialize(): void;
/**
* Serializes a given CDP object into BiDi, keeping references in the
* target's `globalThis`.
*/
serializeCdpObject(cdpRemoteObject: Protocol.Runtime.RemoteObject, resultOwnership: Script.ResultOwnership): Promise<Script.RemoteValue>;
/**
* Gets the string representation of an object. This is equivalent to
* calling `toString()` on the object value.
*/
stringifyObject(cdpRemoteObject: Protocol.Runtime.RemoteObject): Promise<string>;
callFunction(functionDeclaration: string, awaitPromise: boolean, thisLocalValue?: Script.LocalValue, argumentsLocalValues?: Script.LocalValue[], resultOwnership?: Script.ResultOwnership, serializationOptions?: Script.SerializationOptions, userActivation?: boolean): Promise<Script.EvaluateResult>;
deserializeForCdp(localValue: Script.LocalValue): Promise<Protocol.Runtime.CallArgument>;
disown(handle: Script.Handle): Promise<void>;
dispose(): void;
}
@@ -0,0 +1,470 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Realm = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const log_js_1 = require("../../../utils/log.js");
const uuid_js_1 = require("../../../utils/uuid.js");
const ChannelProxy_js_1 = require("./ChannelProxy.js");
class Realm {
#cdpClient;
#eventManager;
#executionContextId;
#logger;
#origin;
#realmId;
#realmStorage;
constructor(cdpClient, eventManager, executionContextId, logger, origin, realmId, realmStorage) {
this.#cdpClient = cdpClient;
this.#eventManager = eventManager;
this.#executionContextId = executionContextId;
this.#logger = logger;
this.#origin = origin;
this.#realmId = realmId;
this.#realmStorage = realmStorage;
this.#realmStorage.addRealm(this);
}
cdpToBidiValue(cdpValue, resultOwnership) {
const bidiValue = this.serializeForBiDi(cdpValue.result.deepSerializedValue, new Map());
if (cdpValue.result.objectId) {
const objectId = cdpValue.result.objectId;
if (resultOwnership === "root" /* Script.ResultOwnership.Root */) {
// Extend BiDi value with `handle` based on required `resultOwnership`
// and CDP response but not on the actual BiDi type.
bidiValue.handle = objectId;
// Remember all the handles sent to client.
this.#realmStorage.knownHandlesToRealmMap.set(objectId, this.realmId);
}
else {
// No need to await for the object to be released.
void this.#releaseObject(objectId).catch((error) => this.#logger?.(log_js_1.LogType.debugError, error));
}
}
return bidiValue;
}
/**
* Relies on the CDP to implement proper BiDi serialization, except:
* * CDP integer property `backendNodeId` is replaced with `sharedId` of
* `{documentId}_element_{backendNodeId}`;
* * CDP integer property `weakLocalObjectReference` is replaced with UUID `internalId`
* using unique-per serialization `internalIdMap`.
* * CDP type `platformobject` is replaced with `object`.
* @param deepSerializedValue - CDP value to be converted to BiDi.
* @param internalIdMap - Map from CDP integer `weakLocalObjectReference` to BiDi UUID
* `internalId`.
*/
serializeForBiDi(deepSerializedValue, internalIdMap) {
if (Object.hasOwn(deepSerializedValue, 'weakLocalObjectReference')) {
const weakLocalObjectReference = deepSerializedValue.weakLocalObjectReference;
if (!internalIdMap.has(weakLocalObjectReference)) {
internalIdMap.set(weakLocalObjectReference, (0, uuid_js_1.uuidv4)());
}
deepSerializedValue.internalId = internalIdMap.get(weakLocalObjectReference);
delete deepSerializedValue['weakLocalObjectReference'];
}
// Platform object is a special case. It should have only `{type: object}`
// without `value` field.
if (deepSerializedValue.type === 'platformobject') {
return { type: 'object' };
}
const bidiValue = deepSerializedValue.value;
if (bidiValue === undefined) {
return deepSerializedValue;
}
// Recursively update the nested values.
if (['array', 'set', 'htmlcollection', 'nodelist'].includes(deepSerializedValue.type)) {
for (const i in bidiValue) {
bidiValue[i] = this.serializeForBiDi(bidiValue[i], internalIdMap);
}
}
if (['object', 'map'].includes(deepSerializedValue.type)) {
for (const i in bidiValue) {
bidiValue[i] = [
this.serializeForBiDi(bidiValue[i][0], internalIdMap),
this.serializeForBiDi(bidiValue[i][1], internalIdMap),
];
}
}
return deepSerializedValue;
}
get realmId() {
return this.#realmId;
}
get executionContextId() {
return this.#executionContextId;
}
get origin() {
return this.#origin;
}
get source() {
return {
realm: this.realmId,
};
}
get cdpClient() {
return this.#cdpClient;
}
get baseInfo() {
return {
realm: this.realmId,
origin: this.origin,
};
}
async evaluate(expression, awaitPromise, resultOwnership = "none" /* Script.ResultOwnership.None */, serializationOptions = {}, userActivation = false, includeCommandLineApi = false) {
const cdpEvaluateResult = await this.cdpClient.sendCommand('Runtime.evaluate', {
contextId: this.executionContextId,
expression,
awaitPromise,
serializationOptions: Realm.#getSerializationOptions("deep" /* Protocol.Runtime.SerializationOptionsSerialization.Deep */, serializationOptions),
userGesture: userActivation,
includeCommandLineAPI: includeCommandLineApi,
});
if (cdpEvaluateResult.exceptionDetails) {
return await this.#getExceptionResult(cdpEvaluateResult.exceptionDetails, 0, resultOwnership);
}
return {
realm: this.realmId,
result: this.cdpToBidiValue(cdpEvaluateResult, resultOwnership),
type: 'success',
};
}
#registerEvent(event) {
if (this.associatedBrowsingContexts.length === 0) {
this.#eventManager.registerEvent(event, null);
}
else {
for (const browsingContext of this.associatedBrowsingContexts) {
this.#eventManager.registerEvent(event, browsingContext.id);
}
}
}
initialize() {
this.#registerEvent({
type: 'event',
method: protocol_js_1.ChromiumBidi.Script.EventNames.RealmCreated,
params: this.realmInfo,
});
}
/**
* Serializes a given CDP object into BiDi, keeping references in the
* target's `globalThis`.
*/
async serializeCdpObject(cdpRemoteObject, resultOwnership) {
const argument = Realm.#cdpRemoteObjectToCallArgument(cdpRemoteObject);
const cdpValue = await this.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: String((remoteObject) => remoteObject),
awaitPromise: false,
arguments: [argument],
serializationOptions: {
serialization: "deep" /* Protocol.Runtime.SerializationOptionsSerialization.Deep */,
},
executionContextId: this.executionContextId,
});
return this.cdpToBidiValue(cdpValue, resultOwnership);
}
static #cdpRemoteObjectToCallArgument(cdpRemoteObject) {
if (cdpRemoteObject.objectId !== undefined) {
return { objectId: cdpRemoteObject.objectId };
}
if (cdpRemoteObject.unserializableValue !== undefined) {
return { unserializableValue: cdpRemoteObject.unserializableValue };
}
return { value: cdpRemoteObject.value };
}
/**
* Gets the string representation of an object. This is equivalent to
* calling `toString()` on the object value.
*/
async stringifyObject(cdpRemoteObject) {
const { result } = await this.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: String((remoteObject) => String(remoteObject)),
awaitPromise: false,
arguments: [cdpRemoteObject],
returnByValue: true,
executionContextId: this.executionContextId,
});
return result.value;
}
async #flattenKeyValuePairs(mappingLocalValue) {
const keyValueArray = await Promise.all(mappingLocalValue.map(async ([key, value]) => {
let keyArg;
if (typeof key === 'string') {
// Key is a string.
keyArg = { value: key };
}
else {
// Key is a serialized value.
keyArg = await this.deserializeForCdp(key);
}
const valueArg = await this.deserializeForCdp(value);
return [keyArg, valueArg];
}));
return keyValueArray.flat();
}
async #flattenValueList(listLocalValue) {
return await Promise.all(listLocalValue.map((localValue) => this.deserializeForCdp(localValue)));
}
async #serializeCdpExceptionDetails(cdpExceptionDetails, lineOffset, resultOwnership) {
const callFrames = cdpExceptionDetails.stackTrace?.callFrames.map((frame) => ({
url: frame.url,
functionName: frame.functionName,
lineNumber: frame.lineNumber - lineOffset,
columnNumber: frame.columnNumber,
})) ?? [];
// Exception should always be there.
const exception = cdpExceptionDetails.exception;
return {
exception: await this.serializeCdpObject(exception, resultOwnership),
columnNumber: cdpExceptionDetails.columnNumber,
lineNumber: cdpExceptionDetails.lineNumber - lineOffset,
stackTrace: {
callFrames,
},
text: (await this.stringifyObject(exception)) || cdpExceptionDetails.text,
};
}
async callFunction(functionDeclaration, awaitPromise, thisLocalValue = {
type: 'undefined',
}, argumentsLocalValues = [], resultOwnership = "none" /* Script.ResultOwnership.None */, serializationOptions = {}, userActivation = false) {
const callFunctionAndSerializeScript = `(...args) => {
function callFunction(f, args) {
const deserializedThis = args.shift();
const deserializedArgs = args;
return f.apply(deserializedThis, deserializedArgs);
}
return callFunction((
${functionDeclaration}
), args);
}`;
const thisAndArgumentsList = [
await this.deserializeForCdp(thisLocalValue),
...(await Promise.all(argumentsLocalValues.map(async (argumentLocalValue) => await this.deserializeForCdp(argumentLocalValue)))),
];
let cdpCallFunctionResult;
try {
cdpCallFunctionResult = await this.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: callFunctionAndSerializeScript,
awaitPromise,
arguments: thisAndArgumentsList,
serializationOptions: Realm.#getSerializationOptions("deep" /* Protocol.Runtime.SerializationOptionsSerialization.Deep */, serializationOptions),
executionContextId: this.executionContextId,
userGesture: userActivation,
});
}
catch (error) {
// Heuristic to determine if the problem is in the argument.
// The check can be done on the `deserialization` step, but this approach
// helps to save round-trips.
if (error.code === -32000 /* CdpErrorConstants.GENERIC_ERROR */ &&
[
'Could not find object with given id',
'Argument should belong to the same JavaScript world as target object',
'Invalid remote object id',
].includes(error.message)) {
throw new protocol_js_1.NoSuchHandleException('Handle was not found.');
}
throw error;
}
if (cdpCallFunctionResult.exceptionDetails) {
return await this.#getExceptionResult(cdpCallFunctionResult.exceptionDetails, 1, resultOwnership);
}
return {
type: 'success',
result: this.cdpToBidiValue(cdpCallFunctionResult, resultOwnership),
realm: this.realmId,
};
}
async deserializeForCdp(localValue) {
if ('handle' in localValue && localValue.handle) {
return { objectId: localValue.handle };
// We tried to find a handle value but failed
// This allows us to have exhaustive switch on `localValue.type`
}
else if ('handle' in localValue || 'sharedId' in localValue) {
throw new protocol_js_1.NoSuchHandleException('Handle was not found.');
}
switch (localValue.type) {
case 'undefined':
return { unserializableValue: 'undefined' };
case 'null':
return { unserializableValue: 'null' };
case 'string':
return { value: localValue.value };
case 'number':
if (localValue.value === 'NaN') {
return { unserializableValue: 'NaN' };
}
else if (localValue.value === '-0') {
return { unserializableValue: '-0' };
}
else if (localValue.value === 'Infinity') {
return { unserializableValue: 'Infinity' };
}
else if (localValue.value === '-Infinity') {
return { unserializableValue: '-Infinity' };
}
return {
value: localValue.value,
};
case 'boolean':
return { value: Boolean(localValue.value) };
case 'bigint':
return {
unserializableValue: `BigInt(${JSON.stringify(localValue.value)})`,
};
case 'date':
return {
unserializableValue: `new Date(Date.parse(${JSON.stringify(localValue.value)}))`,
};
case 'regexp':
return {
unserializableValue: `new RegExp(${JSON.stringify(localValue.value.pattern)}, ${JSON.stringify(localValue.value.flags)})`,
};
case 'map': {
// TODO: If none of the nested keys and values has a remote
// reference, serialize to `unserializableValue` without CDP roundtrip.
const keyValueArray = await this.#flattenKeyValuePairs(localValue.value);
const { result } = await this.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: String((...args) => {
const result = new Map();
for (let i = 0; i < args.length; i += 2) {
result.set(args[i], args[i + 1]);
}
return result;
}),
awaitPromise: false,
arguments: keyValueArray,
returnByValue: false,
executionContextId: this.executionContextId,
});
// TODO(#375): Release `result.objectId` after using.
return { objectId: result.objectId };
}
case 'object': {
// TODO: If none of the nested keys and values has a remote
// reference, serialize to `unserializableValue` without CDP roundtrip.
const keyValueArray = await this.#flattenKeyValuePairs(localValue.value);
const { result } = await this.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: String((...args) => {
const result = {};
for (let i = 0; i < args.length; i += 2) {
// Key should be either `string`, `number`, or `symbol`.
const key = args[i];
result[key] = args[i + 1];
}
return result;
}),
awaitPromise: false,
arguments: keyValueArray,
returnByValue: false,
executionContextId: this.executionContextId,
});
// TODO(#375): Release `result.objectId` after using.
return { objectId: result.objectId };
}
case 'array': {
// TODO: If none of the nested items has a remote reference,
// serialize to `unserializableValue` without CDP roundtrip.
const args = await this.#flattenValueList(localValue.value);
const { result } = await this.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: String((...args) => args),
awaitPromise: false,
arguments: args,
returnByValue: false,
executionContextId: this.executionContextId,
});
// TODO(#375): Release `result.objectId` after using.
return { objectId: result.objectId };
}
case 'set': {
// TODO: if none of the nested items has a remote reference,
// serialize to `unserializableValue` without CDP roundtrip.
const args = await this.#flattenValueList(localValue.value);
const { result } = await this.cdpClient.sendCommand('Runtime.callFunctionOn', {
functionDeclaration: String((...args) => new Set(args)),
awaitPromise: false,
arguments: args,
returnByValue: false,
executionContextId: this.executionContextId,
});
// TODO(#375): Release `result.objectId` after using.
return { objectId: result.objectId };
}
case 'channel': {
const channelProxy = new ChannelProxy_js_1.ChannelProxy(localValue.value, this.#logger);
const channelProxySendMessageHandle = await channelProxy.init(this, this.#eventManager);
return { objectId: channelProxySendMessageHandle };
}
// TODO(#375): Dispose of nested objects.
}
// Intentionally outside to handle unknown types
throw new Error(`Value ${JSON.stringify(localValue)} is not deserializable.`);
}
async #getExceptionResult(exceptionDetails, lineOffset, resultOwnership) {
return {
exceptionDetails: await this.#serializeCdpExceptionDetails(exceptionDetails, lineOffset, resultOwnership),
realm: this.realmId,
type: 'exception',
};
}
static #getSerializationOptions(serialization, serializationOptions) {
return {
serialization,
additionalParameters: Realm.#getAdditionalSerializationParameters(serializationOptions),
...Realm.#getMaxObjectDepth(serializationOptions),
};
}
static #getAdditionalSerializationParameters(serializationOptions) {
const additionalParameters = {};
if (serializationOptions.maxDomDepth !== undefined) {
additionalParameters['maxNodeDepth'] =
serializationOptions.maxDomDepth === null
? 1000
: serializationOptions.maxDomDepth;
}
if (serializationOptions.includeShadowTree !== undefined) {
additionalParameters['includeShadowTree'] =
serializationOptions.includeShadowTree;
}
return additionalParameters;
}
static #getMaxObjectDepth(serializationOptions) {
return serializationOptions.maxObjectDepth === undefined ||
serializationOptions.maxObjectDepth === null
? {}
: { maxDepth: serializationOptions.maxObjectDepth };
}
async #releaseObject(handle) {
try {
await this.cdpClient.sendCommand('Runtime.releaseObject', {
objectId: handle,
});
}
catch (error) {
// Heuristic to determine if the problem is in the unknown handler.
// Ignore the error if so.
if (!(error.code === -32000 /* CdpErrorConstants.GENERIC_ERROR */ &&
error.message === 'Invalid remote object id')) {
throw error;
}
}
}
async disown(handle) {
// Disowning an object from different realm does nothing.
if (this.#realmStorage.knownHandlesToRealmMap.get(handle) !== this.realmId) {
return;
}
await this.#releaseObject(handle);
this.#realmStorage.knownHandlesToRealmMap.delete(handle);
}
dispose() {
this.#registerEvent({
type: 'event',
method: protocol_js_1.ChromiumBidi.Script.EventNames.RealmDestroyed,
params: {
realm: this.realmId,
},
});
}
}
exports.Realm = Realm;
//# sourceMappingURL=Realm.js.map
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More