123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- "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 uuid_1 = require("../../../utils/uuid");
- /**
- * Used to send messages from realm to BiDi user.
- */
- class ChannelProxy {
- #properties;
- #id = (0, uuid_1.uuidv4)();
- constructor(channel) {
- if (![0, null, undefined].includes(channel.serializationOptions?.maxDomDepth)) {
- throw new Error('serializationOptions.maxDomDepth other than 0 or null is not supported');
- }
- if (![undefined, 'none'].includes(channel.serializationOptions?.includeShadowTree)) {
- throw new Error('serializationOptions.includeShadowTree other than "none" is not supported');
- }
- this.#properties = channel;
- }
- /**
- * 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) {
- const channelHandle = await this.#getHandleFromWindow(realm);
- void this.#startListener(realm, channelHandle, eventManager);
- }
- /**
- * 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',
- },
- });
- 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',
- },
- });
- // TODO: check for exceptionDetails.
- return sendMessageArgResult.result.objectId;
- }
- /** Starts listening for the channel events of the provided ChannelProxy. */
- async #startListener(realm, channelHandle, eventManager) {
- // TODO(#294): Remove this loop after the realm is destroyed.
- // Rely on the CDP throwing exception in such a case.
- // noinspection InfiniteLoopJS
- for (;;) {
- const message = await realm.cdpClient.sendCommand('Runtime.callFunctionOn', {
- functionDeclaration: String(async (channelHandle) => channelHandle.getMessage()),
- arguments: [
- {
- objectId: channelHandle,
- },
- ],
- awaitPromise: true,
- executionContextId: realm.executionContextId,
- serializationOptions: {
- serialization: 'deep',
- ...(this.#properties.serializationOptions?.maxObjectDepth ===
- undefined ||
- this.#properties.serializationOptions.maxObjectDepth === null
- ? {}
- : {
- maxDepth: this.#properties.serializationOptions.maxObjectDepth,
- }),
- },
- });
- if (message.exceptionDetails) {
- // TODO: add logging.
- // TODO: check if a error should be thrown.
- return;
- }
- eventManager.registerEvent({
- method: protocol_js_1.Script.EventNames.MessageEvent,
- params: {
- channel: this.#properties.channel,
- data: realm.cdpToBidiValue(message, this.#properties.ownership ?? 'none'),
- source: {
- realm: realm.realmId,
- context: realm.browsingContextId,
- },
- },
- }, realm.browsingContextId);
- }
- }
- /**
- * 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',
- },
- });
- 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
|