diff options
Diffstat (limited to 'remote/webdriver-bidi/modules/root/network.sys.mjs')
-rw-r--r-- | remote/webdriver-bidi/modules/root/network.sys.mjs | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/remote/webdriver-bidi/modules/root/network.sys.mjs b/remote/webdriver-bidi/modules/root/network.sys.mjs new file mode 100644 index 0000000000..b44f20e58f --- /dev/null +++ b/remote/webdriver-bidi/modules/root/network.sys.mjs @@ -0,0 +1,320 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + NetworkListener: + "chrome://remote/content/shared/listeners/NetworkListener.sys.mjs", + TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", + WindowGlobalMessageHandler: + "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs", +}); + +/** + * @typedef {object} BaseParameters + * @property {string=} context + * @property {Navigation=} navigation + * @property {number} redirectCount + * @property {RequestData} request + * @property {number} timestamp + */ + +/** + * @typedef {object} Cookie + * @property {Array<number>=} binaryValue + * @property {string} domain + * @property {number=} expires + * @property {boolean} httpOnly + * @property {string} name + * @property {string} path + * @property {('lax' | 'none' | 'strict')} sameSite + * @property {boolean} secure + * @property {number} size + * @property {string=} value + */ + +/** + * @typedef {object} FetchTimingInfo + * @property {number} originTime + * @property {number} requestTime + * @property {number} redirectStart + * @property {number} redirectEnd + * @property {number} fetchStart + * @property {number} dnsStart + * @property {number} dnsEnd + * @property {number} connectStart + * @property {number} connectEnd + * @property {number} tlsStart + * @property {number} requestStart + * @property {number} responseStart + * @property {number} responseEnd + */ + +/** + * @typedef {object} Header + * @property {Array<number>=} binaryValue + * @property {string} name + * @property {string=} value + */ + +/** + * @typedef {string} InitiatorType + */ + +/** + * Enum of possible initiator types. + * + * @readonly + * @enum {InitiatorType} + */ +const InitiatorType = { + Other: "other", + Parser: "parser", + Preflight: "preflight", + Script: "script", +}; +/** + * @typedef {object} Initiator + * @property {InitiatorType} type + * @property {number=} columnNumber + * @property {number=} lineNumber + * @property {string=} request + * @property {StackTrace=} stackTrace + */ + +/** + * @typedef {object} RequestData + * @property {number|null} bodySize + * Defaults to null. + * @property {Array<Cookie>} cookies + * @property {Array<Header>} headers + * @property {number} headersSize + * @property {string} method + * @property {string} request + * @property {FetchTimingInfo} timings + * @property {string} url + */ + +/** + * @typedef {object} BeforeRequestSentParametersProperties + * @property {Initiator} initiator + */ + +/* eslint-disable jsdoc/valid-types */ +/** + * Parameters for the BeforeRequestSent event + * + * @typedef {BaseParameters & BeforeRequestSentParametersProperties} BeforeRequestSentParameters + */ +/* eslint-enable jsdoc/valid-types */ + +/** + * @typedef {object} ResponseContent + * @property {number|null} size + * Defaults to null. + */ + +/** + * @typedef {object} ResponseData + * @property {string} url + * @property {string} protocol + * @property {number} status + * @property {string} statusText + * @property {boolean} fromCache + * @property {Array<Header>} headers + * @property {string} mimeType + * @property {number} bytesReceived + * @property {number|null} headersSize + * Defaults to null. + * @property {number|null} bodySize + * Defaults to null. + * @property {ResponseContent} content + */ + +/** + * @typedef {object} ResponseStartedParametersProperties + * @property {ResponseData} response + */ + +/* eslint-disable jsdoc/valid-types */ +/** + * Parameters for the ResponseStarted event + * + * @typedef {BaseParameters & ResponseStartedParametersProperties} ResponseStartedParameters + */ +/* eslint-enable jsdoc/valid-types */ + +/** + * @typedef {object} ResponseCompletedParametersProperties + * @property {ResponseData} response + */ + +/* eslint-disable jsdoc/valid-types */ +/** + * Parameters for the ResponseCompleted event + * + * @typedef {BaseParameters & ResponseCompletedParametersProperties} ResponseCompletedParameters + */ +/* eslint-enable jsdoc/valid-types */ + +class NetworkModule extends Module { + #networkListener; + #subscribedEvents; + + constructor(messageHandler) { + super(messageHandler); + + // Set of event names which have active subscriptions + this.#subscribedEvents = new Set(); + + this.#networkListener = new lazy.NetworkListener(); + this.#networkListener.on("before-request-sent", this.#onBeforeRequestSent); + this.#networkListener.on("response-completed", this.#onResponseEvent); + this.#networkListener.on("response-started", this.#onResponseEvent); + } + + destroy() { + this.#networkListener.off("before-request-sent", this.#onBeforeRequestSent); + this.#networkListener.off("response-completed", this.#onResponseEvent); + this.#networkListener.off("response-started", this.#onResponseEvent); + + this.#subscribedEvents = null; + } + + #getContextInfo(browsingContext) { + return { + contextId: browsingContext.id, + type: lazy.WindowGlobalMessageHandler.type, + }; + } + + #onBeforeRequestSent = (name, data) => { + const { contextId, requestData, timestamp, redirectCount } = data; + + // Bug 1805479: Handle the initiator, including stacktrace details. + const initiator = { + type: InitiatorType.Other, + }; + + const baseParameters = { + context: contextId, + // Bug 1805405: Handle the navigation id. + navigation: null, + redirectCount, + request: requestData, + timestamp, + }; + + const beforeRequestSentEvent = { + ...baseParameters, + initiator, + }; + + const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + this.emitEvent( + "network.beforeRequestSent", + beforeRequestSentEvent, + this.#getContextInfo(browsingContext) + ); + }; + + #onResponseEvent = (name, data) => { + const { contextId, requestData, responseData, timestamp, redirectCount } = + data; + + const baseParameters = { + context: contextId, + // Bug 1805405: Handle the navigation id. + navigation: null, + redirectCount, + request: requestData, + timestamp, + }; + + const responseEvent = { + ...baseParameters, + response: responseData, + }; + + const protocolEventName = + name === "response-started" + ? "network.responseStarted" + : "network.responseCompleted"; + + const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + this.emitEvent( + protocolEventName, + responseEvent, + this.#getContextInfo(browsingContext) + ); + }; + + #startListening(event) { + if (this.#subscribedEvents.size == 0) { + this.#networkListener.startListening(); + } + this.#subscribedEvents.add(event); + } + + #stopListening(event) { + this.#subscribedEvents.delete(event); + if (this.#subscribedEvents.size == 0) { + this.#networkListener.stopListening(); + } + } + + #subscribeEvent(event) { + if (this.constructor.supportedEvents.includes(event)) { + this.#startListening(event); + } + } + + #unsubscribeEvent(event) { + if (this.constructor.supportedEvents.includes(event)) { + this.#stopListening(event); + } + } + + /** + * Internal commands + */ + + _applySessionData(params) { + // TODO: Bug 1775231. Move this logic to a shared module or an abstract + // class. + const { category } = params; + if (category === "event") { + const filteredSessionData = params.sessionData.filter(item => + this.messageHandler.matchesContext(item.contextDescriptor) + ); + for (const event of this.#subscribedEvents.values()) { + const hasSessionItem = filteredSessionData.some( + item => item.value === event + ); + // If there are no session items for this context, we should unsubscribe from the event. + if (!hasSessionItem) { + this.#unsubscribeEvent(event); + } + } + + // Subscribe to all events, which have an item in SessionData. + for (const { value } of filteredSessionData) { + this.#subscribeEvent(value); + } + } + } + + static get supportedEvents() { + return [ + "network.beforeRequestSent", + "network.responseCompleted", + "network.responseStarted", + ]; + } +} + +export const network = NetworkModule; |