зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1790368 - [bidi] Implement basic support for network.beforeRequestSent event r=webdriver-reviewers,Sasha,whimboo
Depends on D164147 Differential Revision: https://phabricator.services.mozilla.com/D162037
This commit is contained in:
Родитель
51f558b426
Коммит
7d99823762
|
@ -31,6 +31,8 @@ remote.jar:
|
|||
content/shared/listeners/ConsoleAPIListener.sys.mjs (shared/listeners/ConsoleAPIListener.sys.mjs)
|
||||
content/shared/listeners/ConsoleListener.sys.mjs (shared/listeners/ConsoleListener.sys.mjs)
|
||||
content/shared/listeners/LoadListener.sys.mjs (shared/listeners/LoadListener.sys.mjs)
|
||||
content/shared/listeners/NetworkEventRecord.sys.mjs (shared/listeners/NetworkEventRecord.sys.mjs)
|
||||
content/shared/listeners/NetworkListener.sys.mjs (shared/listeners/NetworkListener.sys.mjs)
|
||||
|
||||
# shared modules (messagehandler architecture)
|
||||
content/shared/messagehandler/Errors.sys.mjs (shared/messagehandler/Errors.sys.mjs)
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
/* 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/. */
|
||||
|
||||
const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
});
|
||||
|
||||
/**
|
||||
* The NetworkEventRecord implements the interface expected from network event
|
||||
* owners for consumers of the DevTools NetworkObserver.
|
||||
*
|
||||
* The NetworkEventRecord emits the before-request-sent event on behalf of the
|
||||
* NetworkListener instance which created it.
|
||||
*/
|
||||
export class NetworkEventRecord {
|
||||
#contextId;
|
||||
#channel;
|
||||
#networkListener;
|
||||
#requestData;
|
||||
#requestId;
|
||||
#responseData;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} networkEvent
|
||||
* The initial network event information (see createNetworkEvent() in
|
||||
* NetworkUtils.sys.mjs).
|
||||
* @param {nsIChannel} channel
|
||||
* The nsIChannel behind this network event.
|
||||
* @param {NetworkListener} networkListener
|
||||
* The NetworkListener which created this NetworkEventRecord.
|
||||
*/
|
||||
constructor(networkEvent, channel, networkListener) {
|
||||
const browsingContext = BrowsingContext.get(networkEvent.browsingContextID);
|
||||
this.#contextId = lazy.TabManager.getIdForBrowsingContext(browsingContext);
|
||||
this.#channel = channel;
|
||||
this.#networkListener = networkListener;
|
||||
|
||||
// The wrappedChannel id remains identical across redirects, whereas
|
||||
// nsIChannel.channelId is different for each and every request.
|
||||
const wrappedChannel = ChannelWrapper.get(channel);
|
||||
this.#requestId = wrappedChannel.id.toString();
|
||||
|
||||
this.#requestData = {
|
||||
bodySize: 0,
|
||||
cookies: [],
|
||||
headers: [],
|
||||
headersSize: networkEvent.headersSize || null,
|
||||
method: networkEvent.method || null,
|
||||
request: this.#requestId,
|
||||
timings: {},
|
||||
url: networkEvent.url || null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This method has to be defined to match the event owner API of the
|
||||
* NetworkObserver. It will only be called once per network event record, so
|
||||
* despite the name we will simply store the headers and rawHeaders.
|
||||
*
|
||||
* Set network request headers.
|
||||
*
|
||||
* @param {Array} headers
|
||||
* The request headers array.
|
||||
* @param {string=} rawHeaders
|
||||
* The raw headers source.
|
||||
*/
|
||||
addRequestHeaders(headers, rawHeaders) {
|
||||
this.#requestData.headers = headers;
|
||||
this.#requestData.rawHeaders = rawHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set network request cookies.
|
||||
*
|
||||
* This method has to be defined to match the event owner API of the
|
||||
* NetworkObserver. It will only be called once per network event record, so
|
||||
* despite the name we will simply store the cookies.
|
||||
*
|
||||
* @param {Array} cookies
|
||||
* The request cookies array.
|
||||
*/
|
||||
addRequestCookies(cookies) {
|
||||
this.#requestData.cookies = cookies;
|
||||
|
||||
// By design, the NetworkObserver will synchronously create a "network event"
|
||||
// then call addRequestHeaders and finally addRequestCookies.
|
||||
// According to the BiDi spec, we should emit beforeRequestSent when adding
|
||||
// request headers, see https://whatpr.org/fetch/1540.html#http-network-or-cache-fetch
|
||||
// step 8.17
|
||||
// Bug 1802181: switch the NetworkObserver to an event-based API.
|
||||
this.#emitBeforeRequestSent();
|
||||
}
|
||||
|
||||
// Expected network event owner API from the NetworkObserver.
|
||||
addEventTimings() {}
|
||||
addRequestPostData() {}
|
||||
addResponseCache() {}
|
||||
addResponseContent() {}
|
||||
addResponseCookies() {}
|
||||
addResponseHeaders() {}
|
||||
addResponseStart() {}
|
||||
addSecurityInfo() {}
|
||||
addServerTimings() {}
|
||||
|
||||
#emitBeforeRequestSent() {
|
||||
const timedChannel = this.#channel.QueryInterface(Ci.nsITimedChannel);
|
||||
this.#requestData.timings = this.#getTimingsFromTimedChannel(timedChannel);
|
||||
|
||||
this.#networkListener.emit("before-request-sent", {
|
||||
contextId: this.#contextId,
|
||||
redirectCount: timedChannel.redirectCount,
|
||||
requestData: this.#requestData,
|
||||
requestId: this.#requestId,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the provided request timing to a timing relative to the beginning
|
||||
* of the request. All timings are numbers representing high definition
|
||||
* timestamps.
|
||||
*
|
||||
* @param {number} timing
|
||||
* High definition timestamp for a request timing relative from the time
|
||||
* origin.
|
||||
* @param {number} requestTime
|
||||
* High definition timestamp for the request start time relative from the
|
||||
* time origin.
|
||||
* @returns {number} High definition timestamp for the request timing relative
|
||||
* to the start time of the request, or 0 if the provided timing was 0.
|
||||
*/
|
||||
#convertTimestamp(timing, requestTime) {
|
||||
if (timing == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return timing - requestTime;
|
||||
}
|
||||
|
||||
#getTimingsFromTimedChannel(timedChannel) {
|
||||
const {
|
||||
channelCreationTime,
|
||||
redirectStartTime,
|
||||
redirectEndTime,
|
||||
dispatchFetchEventStartTime,
|
||||
cacheReadStartTime,
|
||||
domainLookupStartTime,
|
||||
domainLookupEndTime,
|
||||
connectStartTime,
|
||||
connectEndTime,
|
||||
secureConnectionStartTime,
|
||||
requestStartTime,
|
||||
responseStartTime,
|
||||
responseEndTime,
|
||||
} = timedChannel;
|
||||
|
||||
// fetchStart should be the post-redirect start time, which should be the
|
||||
// first non-zero timing from: dispatchFetchEventStart, cacheReadStart and
|
||||
// domainLookupStart. See https://www.w3.org/TR/navigation-timing-2/#processing-model
|
||||
const fetchStartTime =
|
||||
dispatchFetchEventStartTime ||
|
||||
cacheReadStartTime ||
|
||||
domainLookupStartTime;
|
||||
|
||||
// Bug 1805478: Per spec, the origin time should match Performance API's
|
||||
// originTime for the global which initiated the request. This is not
|
||||
// available in the parent process, so for now we will use 0.
|
||||
const originTime = 0;
|
||||
|
||||
return {
|
||||
originTime,
|
||||
requestTime: this.#convertTimestamp(channelCreationTime, originTime),
|
||||
redirectStart: this.#convertTimestamp(redirectStartTime, originTime),
|
||||
redirectEnd: this.#convertTimestamp(redirectEndTime, originTime),
|
||||
fetchStart: this.#convertTimestamp(fetchStartTime, originTime),
|
||||
dnsStart: this.#convertTimestamp(domainLookupStartTime, originTime),
|
||||
dnsEnd: this.#convertTimestamp(domainLookupEndTime, originTime),
|
||||
connectStart: this.#convertTimestamp(connectStartTime, originTime),
|
||||
connectEnd: this.#convertTimestamp(connectEndTime, originTime),
|
||||
tlsStart: this.#convertTimestamp(secureConnectionStartTime, originTime),
|
||||
tlsEnd: this.#convertTimestamp(connectEndTime, originTime),
|
||||
requestStart: this.#convertTimestamp(requestStartTime, originTime),
|
||||
responseStart: this.#convertTimestamp(responseStartTime, originTime),
|
||||
responseEnd: this.#convertTimestamp(responseEndTime, originTime),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
NetworkEventRecord:
|
||||
"chrome://remote/content/shared/listeners/NetworkEventRecord.sys.mjs",
|
||||
NetworkObserver:
|
||||
"resource://devtools/shared/network-observer/NetworkObserver.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
||||
EventEmitter: "resource://gre/modules/EventEmitter.jsm",
|
||||
});
|
||||
|
||||
/**
|
||||
* The NetworkListener listens to all network activity from the parent
|
||||
* process.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* const listener = new NetworkListener();
|
||||
* listener.on("before-request-sent", onBeforeRequestSent);
|
||||
* listener.startListening();
|
||||
*
|
||||
* const onBeforeRequestSent = (eventName, data = {}) => {
|
||||
* const { cntextId, redirectCount, requestData, requestId, timestamp } = data;
|
||||
* ...
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @emits before-request-sent
|
||||
* The NetworkListener emits "before-request-sent" events, with the
|
||||
* following object as payload:
|
||||
* - {number} browsingContextId - The browsing context id of the browsing
|
||||
* context where this request was performed.
|
||||
* - {number} redirectCount - The request's redirect count.
|
||||
* - {RequestData} requestData - The request's data as expected by
|
||||
* WebDriver BiDi.
|
||||
* - {string} requestId - The id of the request, consistent across
|
||||
* redirects.
|
||||
* - {number} timestamp - Timestamp when the event was generated.
|
||||
*/
|
||||
export class NetworkListener {
|
||||
#devtoolsNetworkObserver;
|
||||
#listening;
|
||||
|
||||
constructor() {
|
||||
lazy.EventEmitter.decorate(this);
|
||||
|
||||
this.#listening = false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.stopListening();
|
||||
this.#devtoolsNetworkObserver.destroy();
|
||||
}
|
||||
|
||||
startListening() {
|
||||
if (this.#listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#devtoolsNetworkObserver = new lazy.NetworkObserver({
|
||||
ignoreChannelFunction: this.#ignoreChannelFunction,
|
||||
onNetworkEvent: this.#onNetworkEvent,
|
||||
});
|
||||
|
||||
this.#listening = true;
|
||||
}
|
||||
|
||||
stopListening() {
|
||||
if (!this.#listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#devtoolsNetworkObserver.destroy();
|
||||
this.#devtoolsNetworkObserver = null;
|
||||
|
||||
this.#listening = false;
|
||||
}
|
||||
|
||||
#ignoreChannelFunction = channel => {
|
||||
// Ignore chrome-privileged or DevTools-initiated requests
|
||||
if (
|
||||
channel.loadInfo?.loadingDocument === null &&
|
||||
(channel.loadInfo.loadingPrincipal ===
|
||||
Services.scriptSecurityManager.getSystemPrincipal() ||
|
||||
channel.loadInfo.isInDevToolsContext)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
#onNetworkEvent = (networkEvent, httpActivity) => {
|
||||
return new lazy.NetworkEventRecord(networkEvent, httpActivity, this);
|
||||
};
|
||||
}
|
|
@ -17,6 +17,7 @@ remote.jar:
|
|||
# WebDriver BiDi root modules
|
||||
content/webdriver-bidi/modules/root/browsingContext.sys.mjs (modules/root/browsingContext.sys.mjs)
|
||||
content/webdriver-bidi/modules/root/log.sys.mjs (modules/root/log.sys.mjs)
|
||||
content/webdriver-bidi/modules/root/network.sys.mjs (modules/root/network.sys.mjs)
|
||||
content/webdriver-bidi/modules/root/script.sys.mjs (modules/root/script.sys.mjs)
|
||||
content/webdriver-bidi/modules/root/session.sys.mjs (modules/root/session.sys.mjs)
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ ChromeUtils.defineESModuleGetters(modules.root, {
|
|||
browsingContext:
|
||||
"chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
|
||||
log: "chrome://remote/content/webdriver-bidi/modules/root/log.sys.mjs",
|
||||
network:
|
||||
"chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs",
|
||||
script: "chrome://remote/content/webdriver-bidi/modules/root/script.sys.mjs",
|
||||
session:
|
||||
"chrome://remote/content/webdriver-bidi/modules/root/session.sys.mjs",
|
||||
|
|
|
@ -0,0 +1,229 @@
|
|||
/* 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 {boolean} isRedirect
|
||||
* @property {Navigation=} navigation
|
||||
* @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} bodySize
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parameters for the BeforeRequestSent event
|
||||
*
|
||||
* @typedef {BaseParameters & BeforeRequestSentParametersProperties} BeforeRequestSentParameters
|
||||
*/
|
||||
|
||||
class NetworkModule extends Module {
|
||||
#beforeRequestSentMap;
|
||||
#networkListener;
|
||||
#subscribedEvents;
|
||||
|
||||
constructor(messageHandler) {
|
||||
super(messageHandler);
|
||||
|
||||
// Map of request ids to redirect counts. A WebDriver BiDi request id is
|
||||
// identical for redirects of a given request, this map allows to know if we
|
||||
// already emitted a beforeRequestSent event for a given request with a
|
||||
// specific redirectCount.
|
||||
this.#beforeRequestSentMap = new Map();
|
||||
|
||||
this.#networkListener = new lazy.NetworkListener(messageHandler);
|
||||
|
||||
// Set of event names which have active subscriptions
|
||||
this.#subscribedEvents = new Set();
|
||||
|
||||
this.#networkListener.on("before-request-sent", this.#onBeforeRequestSent);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#networkListener.off("before-request-sent", this.#onBeforeRequestSent);
|
||||
|
||||
this.#beforeRequestSentMap = null;
|
||||
this.#subscribedEvents = null;
|
||||
}
|
||||
|
||||
#getContextInfo(browsingContext) {
|
||||
return {
|
||||
contextId: browsingContext.id,
|
||||
type: lazy.WindowGlobalMessageHandler.type,
|
||||
};
|
||||
}
|
||||
|
||||
#onBeforeRequestSent = (name, data) => {
|
||||
const { contextId, requestData, timestamp, redirectCount } = data;
|
||||
|
||||
const isRedirect = redirectCount > 0;
|
||||
this.#beforeRequestSentMap.set(requestData.requestId, redirectCount);
|
||||
|
||||
// Bug 1805479: Handle the initiator, including stacktrace details.
|
||||
const initiator = {
|
||||
type: InitiatorType.Other,
|
||||
};
|
||||
|
||||
const baseParameters = {
|
||||
context: contextId,
|
||||
isRedirect,
|
||||
redirectCount,
|
||||
// Bug 1805405: Handle the navigation id.
|
||||
navigation: null,
|
||||
request: requestData,
|
||||
timestamp,
|
||||
};
|
||||
|
||||
const beforeRequestSentEvent = {
|
||||
...baseParameters,
|
||||
initiator,
|
||||
};
|
||||
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
|
||||
this.emitEvent(
|
||||
"network.beforeRequestSent",
|
||||
beforeRequestSentEvent,
|
||||
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) {
|
||||
const { category, added = [], removed = [] } = params;
|
||||
if (category === "event") {
|
||||
for (const event of added) {
|
||||
this.#subscribeEvent(event);
|
||||
}
|
||||
for (const event of removed) {
|
||||
this.#unsubscribeEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static get supportedEvents() {
|
||||
return ["network.beforeRequestSent"];
|
||||
}
|
||||
}
|
||||
|
||||
export const network = NetworkModule;
|
Загрузка…
Ссылка в новой задаче