mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1893117 - [remote] Introduce NetworkRequest and NetworkResponse classes r=webdriver-reviewers,Sasha
Differential Revision: https://phabricator.services.mozilla.com/D208450
This commit is contained in:
parent
8a79370a86
commit
f324714930
@ -23,6 +23,8 @@ remote.jar:
|
||||
content/shared/MobileTabBrowser.sys.mjs (shared/MobileTabBrowser.sys.mjs)
|
||||
content/shared/Navigate.sys.mjs (shared/Navigate.sys.mjs)
|
||||
content/shared/NavigationManager.sys.mjs (shared/NavigationManager.sys.mjs)
|
||||
content/shared/NetworkRequest.sys.mjs (shared/NetworkRequest.sys.mjs)
|
||||
content/shared/NetworkResponse.sys.mjs (shared/NetworkResponse.sys.mjs)
|
||||
content/shared/PDF.sys.mjs (shared/PDF.sys.mjs)
|
||||
content/shared/Prompt.sys.mjs (shared/Prompt.sys.mjs)
|
||||
content/shared/Realm.sys.mjs (shared/Realm.sys.mjs)
|
||||
|
254
remote/shared/NetworkRequest.sys.mjs
Normal file
254
remote/shared/NetworkRequest.sys.mjs
Normal file
@ -0,0 +1,254 @@
|
||||
/* 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, {
|
||||
NetworkUtils:
|
||||
"resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
|
||||
|
||||
notifyNavigationStarted:
|
||||
"chrome://remote/content/shared/NavigationManager.sys.mjs",
|
||||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
});
|
||||
|
||||
/**
|
||||
* The NetworkRequest class is a wrapper around the internal channel which
|
||||
* provides getters and methods closer to fetch's response concept
|
||||
* (https://fetch.spec.whatwg.org/#concept-response).
|
||||
*/
|
||||
export class NetworkRequest {
|
||||
#channel;
|
||||
#contextId;
|
||||
#navigationId;
|
||||
#navigationManager;
|
||||
#postData;
|
||||
#rawHeaders;
|
||||
#redirectCount;
|
||||
#requestId;
|
||||
#timedChannel;
|
||||
#wrappedChannel;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {nsIChannel} channel
|
||||
* The channel for the request.
|
||||
* @param {object} params
|
||||
* @param {NavigationManager} params.navigationManager
|
||||
* The NavigationManager where navigations for the current session are
|
||||
* monitored.
|
||||
* @param {string=} params.rawHeaders
|
||||
* The request's raw (ie potentially compressed) headers
|
||||
*/
|
||||
constructor(channel, params) {
|
||||
const { navigationManager, rawHeaders = "" } = params;
|
||||
|
||||
this.#channel = channel;
|
||||
this.#navigationManager = navigationManager;
|
||||
this.#rawHeaders = rawHeaders;
|
||||
|
||||
this.#timedChannel = this.#channel.QueryInterface(Ci.nsITimedChannel);
|
||||
this.#wrappedChannel = ChannelWrapper.get(channel);
|
||||
|
||||
this.#redirectCount = this.#timedChannel.redirectCount;
|
||||
// The wrappedChannel id remains identical across redirects, whereas
|
||||
// nsIChannel.channelId is different for each and every request.
|
||||
this.#requestId = this.#wrappedChannel.id.toString();
|
||||
|
||||
this.#contextId = this.#getContextId();
|
||||
this.#navigationId = this.#getNavigationId();
|
||||
}
|
||||
|
||||
get contextId() {
|
||||
return this.#contextId;
|
||||
}
|
||||
|
||||
get errorText() {
|
||||
// TODO: Update with a proper error text. Bug 1873037.
|
||||
return ChromeUtils.getXPCOMErrorName(this.#channel.status);
|
||||
}
|
||||
|
||||
get headersSize() {
|
||||
// TODO: rawHeaders will not be updated after modifying the headers via
|
||||
// request interception. Need to find another way to retrieve the
|
||||
// information dynamically.
|
||||
return this.#rawHeaders.length;
|
||||
}
|
||||
|
||||
get method() {
|
||||
return this.#channel.requestMethod;
|
||||
}
|
||||
|
||||
get navigationId() {
|
||||
return this.#navigationId;
|
||||
}
|
||||
|
||||
get postDataSize() {
|
||||
return this.#postData ? this.postData.size : 0;
|
||||
}
|
||||
|
||||
get redirectCount() {
|
||||
return this.#redirectCount;
|
||||
}
|
||||
|
||||
get requestId() {
|
||||
return this.#requestId;
|
||||
}
|
||||
|
||||
get serializedURL() {
|
||||
return this.#channel.URI.spec;
|
||||
}
|
||||
|
||||
get wrappedChannel() {
|
||||
return this.#wrappedChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Fetch timings for the NetworkRequest.
|
||||
*
|
||||
* @returns {object}
|
||||
* Object with keys corresponding to fetch timing names, and their
|
||||
* corresponding values.
|
||||
*/
|
||||
getFetchTimings() {
|
||||
const {
|
||||
channelCreationTime,
|
||||
redirectStartTime,
|
||||
redirectEndTime,
|
||||
dispatchFetchEventStartTime,
|
||||
cacheReadStartTime,
|
||||
domainLookupStartTime,
|
||||
domainLookupEndTime,
|
||||
connectStartTime,
|
||||
connectEndTime,
|
||||
secureConnectionStartTime,
|
||||
requestStartTime,
|
||||
responseStartTime,
|
||||
responseEndTime,
|
||||
} = this.#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
|
||||
// timeOrigin for the global which initiated the request. This is not
|
||||
// available in the parent process, so for now we will use 0.
|
||||
const timeOrigin = 0;
|
||||
|
||||
return {
|
||||
timeOrigin,
|
||||
requestTime: this.#convertTimestamp(channelCreationTime, timeOrigin),
|
||||
redirectStart: this.#convertTimestamp(redirectStartTime, timeOrigin),
|
||||
redirectEnd: this.#convertTimestamp(redirectEndTime, timeOrigin),
|
||||
fetchStart: this.#convertTimestamp(fetchStartTime, timeOrigin),
|
||||
dnsStart: this.#convertTimestamp(domainLookupStartTime, timeOrigin),
|
||||
dnsEnd: this.#convertTimestamp(domainLookupEndTime, timeOrigin),
|
||||
connectStart: this.#convertTimestamp(connectStartTime, timeOrigin),
|
||||
connectEnd: this.#convertTimestamp(connectEndTime, timeOrigin),
|
||||
tlsStart: this.#convertTimestamp(secureConnectionStartTime, timeOrigin),
|
||||
tlsEnd: this.#convertTimestamp(connectEndTime, timeOrigin),
|
||||
requestStart: this.#convertTimestamp(requestStartTime, timeOrigin),
|
||||
responseStart: this.#convertTimestamp(responseStartTime, timeOrigin),
|
||||
responseEnd: this.#convertTimestamp(responseEndTime, timeOrigin),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list of headers for the NetworkRequest.
|
||||
*
|
||||
* @returns {Array.Array}
|
||||
* Array of (name, value) tuples.
|
||||
*/
|
||||
getHeadersList() {
|
||||
const headers = [];
|
||||
|
||||
this.#channel.visitRequestHeaders({
|
||||
visitHeader(name, value) {
|
||||
// The `Proxy-Authorization` header even though it appears on the channel is not
|
||||
// actually sent to the server for non CONNECT requests after the HTTP/HTTPS tunnel
|
||||
// is setup by the proxy.
|
||||
if (name == "Proxy-Authorization") {
|
||||
return;
|
||||
}
|
||||
headers.push([name, value]);
|
||||
},
|
||||
});
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the postData for this NetworkRequest. This is currently forwarded
|
||||
* by the DevTools' NetworkObserver.
|
||||
*
|
||||
* TODO: We should read this information dynamically from the channel so that
|
||||
* we can get updated information in case it was modified via network
|
||||
* interception.
|
||||
*
|
||||
* @param {object} postData
|
||||
* The request POST data.
|
||||
*/
|
||||
setPostData(postData) {
|
||||
this.#postData = postData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
#getContextId() {
|
||||
const id = lazy.NetworkUtils.getChannelBrowsingContextID(this.#channel);
|
||||
const browsingContext = BrowsingContext.get(id);
|
||||
return lazy.TabManager.getIdForBrowsingContext(browsingContext);
|
||||
}
|
||||
|
||||
#getNavigationId() {
|
||||
if (!this.#channel.isMainDocumentChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(
|
||||
this.#contextId
|
||||
);
|
||||
|
||||
let navigation =
|
||||
this.#navigationManager.getNavigationForBrowsingContext(browsingContext);
|
||||
|
||||
// `onBeforeRequestSent` might be too early for the NavigationManager.
|
||||
// If there is no ongoing navigation, create one ourselves.
|
||||
// TODO: Bug 1835704 to detect navigations earlier and avoid this.
|
||||
if (!navigation || navigation.finished) {
|
||||
navigation = lazy.notifyNavigationStarted({
|
||||
contextDetails: { context: browsingContext },
|
||||
url: this.serializedURL,
|
||||
});
|
||||
}
|
||||
|
||||
return navigation ? navigation.navigationId : null;
|
||||
}
|
||||
}
|
131
remote/shared/NetworkResponse.sys.mjs
Normal file
131
remote/shared/NetworkResponse.sys.mjs
Normal file
@ -0,0 +1,131 @@
|
||||
/* 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, {
|
||||
NetworkUtils:
|
||||
"resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
|
||||
});
|
||||
|
||||
/**
|
||||
* The NetworkResponse class is a wrapper around the internal channel which
|
||||
* provides getters and methods closer to fetch's response concept
|
||||
* (https://fetch.spec.whatwg.org/#concept-response).
|
||||
*/
|
||||
export class NetworkResponse {
|
||||
#channel;
|
||||
#decodedBodySize;
|
||||
#encodedBodySize;
|
||||
#fromCache;
|
||||
#headersTransmittedSize;
|
||||
#status;
|
||||
#statusMessage;
|
||||
#totalTransmittedSize;
|
||||
#wrappedChannel;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {nsIChannel} channel
|
||||
* The channel for the response.
|
||||
* @param {object} params
|
||||
* @param {boolean} params.fromCache
|
||||
* Whether the response was read from the cache or not.
|
||||
* @param {string=} params.rawHeaders
|
||||
* The response's raw (ie potentially compressed) headers
|
||||
*/
|
||||
constructor(channel, params) {
|
||||
this.#channel = channel;
|
||||
const { fromCache, rawHeaders = "" } = params;
|
||||
this.#fromCache = fromCache;
|
||||
this.#wrappedChannel = ChannelWrapper.get(channel);
|
||||
|
||||
this.#decodedBodySize = 0;
|
||||
this.#encodedBodySize = 0;
|
||||
this.#headersTransmittedSize = rawHeaders.length;
|
||||
this.#totalTransmittedSize = rawHeaders.length;
|
||||
|
||||
// TODO: responseStatus and responseStatusText are sometimes inconsistent.
|
||||
// For instance, they might be (304, Not Modified) when retrieved during the
|
||||
// responseStarted event, and then (200, OK) during the responseCompleted
|
||||
// event.
|
||||
// For now consider them as immutable and store them on startup.
|
||||
this.#status = this.#channel.responseStatus;
|
||||
this.#statusMessage = this.#channel.responseStatusText;
|
||||
}
|
||||
|
||||
get decodedBodySize() {
|
||||
return this.#decodedBodySize;
|
||||
}
|
||||
|
||||
get encodedBodySize() {
|
||||
return this.#encodedBodySize;
|
||||
}
|
||||
|
||||
get headersTransmittedSize() {
|
||||
return this.#headersTransmittedSize;
|
||||
}
|
||||
|
||||
get fromCache() {
|
||||
return this.#fromCache;
|
||||
}
|
||||
|
||||
get protocol() {
|
||||
return lazy.NetworkUtils.getProtocol(this.#channel);
|
||||
}
|
||||
|
||||
get serializedURL() {
|
||||
return this.#channel.URI.spec;
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this.#status;
|
||||
}
|
||||
|
||||
get statusMessage() {
|
||||
return this.#statusMessage;
|
||||
}
|
||||
|
||||
get totalTransmittedSize() {
|
||||
return this.#totalTransmittedSize;
|
||||
}
|
||||
|
||||
addResponseContent(responseContent) {
|
||||
this.#decodedBodySize = responseContent.decodedBodySize;
|
||||
this.#encodedBodySize = responseContent.bodySize;
|
||||
this.#totalTransmittedSize = responseContent.transferredSize;
|
||||
}
|
||||
|
||||
getComputedMimeType() {
|
||||
// TODO: DevTools NetworkObserver is computing a similar value in
|
||||
// addResponseContent, but uses an inconsistent implementation in
|
||||
// addResponseStart. This approach can only be used as early as in
|
||||
// addResponseHeaders. We should move this logic to the NetworkObserver and
|
||||
// expose mimeType in addResponseStart. Bug 1809670.
|
||||
let mimeType = "";
|
||||
|
||||
try {
|
||||
mimeType = this.#wrappedChannel.contentType;
|
||||
const contentCharset = this.#channel.contentCharset;
|
||||
if (contentCharset) {
|
||||
mimeType += `;charset=${contentCharset}`;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore exceptions when reading contentType/contentCharset
|
||||
}
|
||||
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
getHeadersList() {
|
||||
const headers = [];
|
||||
|
||||
this.#channel.visitOriginalResponseHeaders({
|
||||
visitHeader(name, value) {
|
||||
headers.push([name, value]);
|
||||
},
|
||||
});
|
||||
|
||||
return headers;
|
||||
}
|
||||
}
|
@ -4,10 +4,8 @@
|
||||
|
||||
const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
NetworkUtils:
|
||||
"resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
|
||||
|
||||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
NetworkRequest: "chrome://remote/content/shared/NetworkRequest.sys.mjs",
|
||||
NetworkResponse: "chrome://remote/content/shared/NetworkResponse.sys.mjs",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -18,17 +16,10 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
* NetworkListener instance which created it.
|
||||
*/
|
||||
export class NetworkEventRecord {
|
||||
#contextId;
|
||||
#fromCache;
|
||||
#isMainDocumentChannel;
|
||||
#networkListener;
|
||||
#redirectCount;
|
||||
#requestChannel;
|
||||
#requestData;
|
||||
#requestId;
|
||||
#responseChannel;
|
||||
#responseData;
|
||||
#wrappedChannel;
|
||||
#request;
|
||||
#response;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -39,56 +30,21 @@ export class NetworkEventRecord {
|
||||
* The nsIChannel behind this network event.
|
||||
* @param {NetworkListener} networkListener
|
||||
* The NetworkListener which created this NetworkEventRecord.
|
||||
* @param {NavigationManager} navigationManager
|
||||
* The NavigationManager which belongs to the same session as this
|
||||
* NetworkEventRecord.
|
||||
*/
|
||||
constructor(networkEvent, channel, networkListener) {
|
||||
this.#requestChannel = channel;
|
||||
this.#responseChannel = null;
|
||||
constructor(networkEvent, channel, networkListener, navigationManager) {
|
||||
this.#request = new lazy.NetworkRequest(channel, {
|
||||
navigationManager,
|
||||
rawHeaders: networkEvent.rawHeaders,
|
||||
});
|
||||
this.#response = null;
|
||||
|
||||
this.#fromCache = networkEvent.fromCache;
|
||||
this.#isMainDocumentChannel = channel.isMainDocumentChannel;
|
||||
|
||||
this.#wrappedChannel = ChannelWrapper.get(channel);
|
||||
|
||||
this.#networkListener = networkListener;
|
||||
|
||||
// The context ids computed by TabManager have the lifecycle of a navigable
|
||||
// and can be reused for all the events emitted from this record.
|
||||
this.#contextId = this.#getContextId();
|
||||
|
||||
// The wrappedChannel id remains identical across redirects, whereas
|
||||
// nsIChannel.channelId is different for each and every request.
|
||||
this.#requestId = this.#wrappedChannel.id.toString();
|
||||
|
||||
const { cookies, headers } =
|
||||
lazy.NetworkUtils.fetchRequestHeadersAndCookies(channel);
|
||||
|
||||
// See the RequestData type definition for the full list of properties that
|
||||
// should be set on this object.
|
||||
this.#requestData = {
|
||||
bodySize: null,
|
||||
cookies,
|
||||
headers,
|
||||
headersSize: networkEvent.rawHeaders ? networkEvent.rawHeaders.length : 0,
|
||||
method: channel.requestMethod,
|
||||
request: this.#requestId,
|
||||
timings: {},
|
||||
url: channel.URI.spec,
|
||||
};
|
||||
|
||||
// See the ResponseData type definition for the full list of properties that
|
||||
// should be set on this object.
|
||||
this.#responseData = {
|
||||
// encoded size (body)
|
||||
bodySize: null,
|
||||
content: {
|
||||
// decoded size
|
||||
size: null,
|
||||
},
|
||||
// encoded size (headers)
|
||||
headersSize: null,
|
||||
url: channel.URI.spec,
|
||||
};
|
||||
|
||||
// NetworkObserver creates a network event when request headers have been
|
||||
// parsed.
|
||||
// According to the BiDi spec, we should emit beforeRequestSent when adding
|
||||
@ -113,8 +69,7 @@ export class NetworkEventRecord {
|
||||
* The request POST data.
|
||||
*/
|
||||
addRequestPostData(postData) {
|
||||
// Only the postData size is needed for RemoteAgent consumers.
|
||||
this.#requestData.bodySize = postData.size;
|
||||
this.#request.setPostData(postData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,25 +85,11 @@ export class NetworkEventRecord {
|
||||
* @param {string} options.rawHeaders
|
||||
*/
|
||||
addResponseStart(options) {
|
||||
const { channel, fromCache, rawHeaders = "" } = options;
|
||||
this.#responseChannel = channel;
|
||||
|
||||
const { headers } =
|
||||
lazy.NetworkUtils.fetchResponseHeadersAndCookies(channel);
|
||||
|
||||
const headersSize = rawHeaders.length;
|
||||
this.#responseData = {
|
||||
...this.#responseData,
|
||||
bodySize: 0,
|
||||
bytesReceived: headersSize,
|
||||
const { channel, fromCache, rawHeaders } = options;
|
||||
this.#response = new lazy.NetworkResponse(channel, {
|
||||
rawHeaders,
|
||||
fromCache: this.#fromCache || !!fromCache,
|
||||
headers,
|
||||
headersSize,
|
||||
mimeType: this.#getMimeType(),
|
||||
protocol: lazy.NetworkUtils.getProtocol(channel),
|
||||
status: channel.responseStatus,
|
||||
statusText: channel.responseStatusText,
|
||||
};
|
||||
});
|
||||
|
||||
// This should be triggered when all headers have been received, matching
|
||||
// the WebDriverBiDi response started trigger in `4.6. HTTP-network fetch`
|
||||
@ -189,25 +130,16 @@ export class NetworkEventRecord {
|
||||
*
|
||||
* Required API for a NetworkObserver event owner.
|
||||
*
|
||||
* @param {object} response
|
||||
* @param {object} responseContent
|
||||
* An object which represents the response content.
|
||||
* @param {object} responseInfo
|
||||
* Additional meta data about the response.
|
||||
*/
|
||||
addResponseContent(response, responseInfo) {
|
||||
// Update content-related sizes with the latest data from addResponseContent.
|
||||
this.#responseData = {
|
||||
...this.#responseData,
|
||||
bodySize: response.bodySize,
|
||||
bytesReceived: response.transferredSize,
|
||||
content: {
|
||||
size: response.decodedBodySize,
|
||||
},
|
||||
};
|
||||
|
||||
addResponseContent(responseContent, responseInfo) {
|
||||
if (responseInfo.blockedReason) {
|
||||
this.#emitFetchError();
|
||||
} else {
|
||||
this.#response.addResponseContent(responseContent);
|
||||
this.#emitResponseCompleted();
|
||||
}
|
||||
}
|
||||
@ -234,201 +166,37 @@ export class NetworkEventRecord {
|
||||
this.#emitAuthRequired(authCallbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
#emitAuthRequired(authCallbacks) {
|
||||
this.#updateDataFromTimedChannel();
|
||||
|
||||
this.#networkListener.emit("auth-required", {
|
||||
authCallbacks,
|
||||
contextId: this.#contextId,
|
||||
isNavigationRequest: this.#isMainDocumentChannel,
|
||||
redirectCount: this.#redirectCount,
|
||||
requestChannel: this.#requestChannel,
|
||||
requestData: this.#requestData,
|
||||
responseChannel: this.#responseChannel,
|
||||
responseData: this.#responseData,
|
||||
timestamp: Date.now(),
|
||||
request: this.#request,
|
||||
response: this.#response,
|
||||
});
|
||||
}
|
||||
|
||||
#emitBeforeRequestSent() {
|
||||
this.#updateDataFromTimedChannel();
|
||||
|
||||
this.#networkListener.emit("before-request-sent", {
|
||||
contextId: this.#contextId,
|
||||
isNavigationRequest: this.#isMainDocumentChannel,
|
||||
redirectCount: this.#redirectCount,
|
||||
requestChannel: this.#requestChannel,
|
||||
requestData: this.#requestData,
|
||||
timestamp: Date.now(),
|
||||
request: this.#request,
|
||||
});
|
||||
}
|
||||
|
||||
#emitFetchError() {
|
||||
this.#updateDataFromTimedChannel();
|
||||
|
||||
this.#networkListener.emit("fetch-error", {
|
||||
contextId: this.#contextId,
|
||||
// TODO: Update with a proper error text. Bug 1873037.
|
||||
errorText: ChromeUtils.getXPCOMErrorName(this.#requestChannel.status),
|
||||
isNavigationRequest: this.#isMainDocumentChannel,
|
||||
redirectCount: this.#redirectCount,
|
||||
requestChannel: this.#requestChannel,
|
||||
requestData: this.#requestData,
|
||||
timestamp: Date.now(),
|
||||
request: this.#request,
|
||||
});
|
||||
}
|
||||
|
||||
#emitResponseCompleted() {
|
||||
this.#updateDataFromTimedChannel();
|
||||
|
||||
this.#networkListener.emit("response-completed", {
|
||||
contextId: this.#contextId,
|
||||
isNavigationRequest: this.#isMainDocumentChannel,
|
||||
redirectCount: this.#redirectCount,
|
||||
requestChannel: this.#requestChannel,
|
||||
requestData: this.#requestData,
|
||||
responseChannel: this.#responseChannel,
|
||||
responseData: this.#responseData,
|
||||
timestamp: Date.now(),
|
||||
request: this.#request,
|
||||
response: this.#response,
|
||||
});
|
||||
}
|
||||
|
||||
#emitResponseStarted() {
|
||||
this.#updateDataFromTimedChannel();
|
||||
|
||||
this.#networkListener.emit("response-started", {
|
||||
contextId: this.#contextId,
|
||||
isNavigationRequest: this.#isMainDocumentChannel,
|
||||
redirectCount: this.#redirectCount,
|
||||
requestChannel: this.#requestChannel,
|
||||
requestData: this.#requestData,
|
||||
responseChannel: this.#responseChannel,
|
||||
responseData: this.#responseData,
|
||||
timestamp: Date.now(),
|
||||
request: this.#request,
|
||||
response: this.#response,
|
||||
});
|
||||
}
|
||||
|
||||
#getBrowsingContext() {
|
||||
const id = lazy.NetworkUtils.getChannelBrowsingContextID(
|
||||
this.#requestChannel
|
||||
);
|
||||
return BrowsingContext.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the navigable id for the current browsing context associated to
|
||||
* the requests' channel. Network events are recorded in the parent process
|
||||
* so we always expect to be able to use TabManager.getIdForBrowsingContext.
|
||||
*
|
||||
* @returns {string}
|
||||
* The navigable id corresponding to the given browsing context.
|
||||
*/
|
||||
#getContextId() {
|
||||
return lazy.TabManager.getIdForBrowsingContext(this.#getBrowsingContext());
|
||||
}
|
||||
|
||||
#getMimeType() {
|
||||
// TODO: DevTools NetworkObserver is computing a similar value in
|
||||
// addResponseContent, but uses an inconsistent implementation in
|
||||
// addResponseStart. This approach can only be used as early as in
|
||||
// addResponseHeaders. We should move this logic to the NetworkObserver and
|
||||
// expose mimeType in addResponseStart. Bug 1809670.
|
||||
let mimeType = "";
|
||||
|
||||
try {
|
||||
mimeType = this.#wrappedChannel.contentType;
|
||||
const contentCharset = this.#requestChannel.contentCharset;
|
||||
if (contentCharset) {
|
||||
mimeType += `;charset=${contentCharset}`;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore exceptions when reading contentType/contentCharset
|
||||
}
|
||||
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
#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
|
||||
// timeOrigin for the global which initiated the request. This is not
|
||||
// available in the parent process, so for now we will use 0.
|
||||
const timeOrigin = 0;
|
||||
|
||||
return {
|
||||
timeOrigin,
|
||||
requestTime: this.#convertTimestamp(channelCreationTime, timeOrigin),
|
||||
redirectStart: this.#convertTimestamp(redirectStartTime, timeOrigin),
|
||||
redirectEnd: this.#convertTimestamp(redirectEndTime, timeOrigin),
|
||||
fetchStart: this.#convertTimestamp(fetchStartTime, timeOrigin),
|
||||
dnsStart: this.#convertTimestamp(domainLookupStartTime, timeOrigin),
|
||||
dnsEnd: this.#convertTimestamp(domainLookupEndTime, timeOrigin),
|
||||
connectStart: this.#convertTimestamp(connectStartTime, timeOrigin),
|
||||
connectEnd: this.#convertTimestamp(connectEndTime, timeOrigin),
|
||||
tlsStart: this.#convertTimestamp(secureConnectionStartTime, timeOrigin),
|
||||
tlsEnd: this.#convertTimestamp(connectEndTime, timeOrigin),
|
||||
requestStart: this.#convertTimestamp(requestStartTime, timeOrigin),
|
||||
responseStart: this.#convertTimestamp(responseStartTime, timeOrigin),
|
||||
responseEnd: this.#convertTimestamp(responseEndTime, timeOrigin),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the timings and the redirect count from the nsITimedChannel
|
||||
* corresponding to the current channel. This should be called before emitting
|
||||
* any event from this class.
|
||||
*/
|
||||
#updateDataFromTimedChannel() {
|
||||
const timedChannel = this.#requestChannel.QueryInterface(
|
||||
Ci.nsITimedChannel
|
||||
);
|
||||
this.#redirectCount = timedChannel.redirectCount;
|
||||
this.#requestData.timings = this.#getTimingsFromTimedChannel(timedChannel);
|
||||
}
|
||||
}
|
||||
|
@ -44,11 +44,13 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
export class NetworkListener {
|
||||
#devtoolsNetworkObserver;
|
||||
#listening;
|
||||
#navigationManager;
|
||||
|
||||
constructor() {
|
||||
constructor(navigationManager) {
|
||||
lazy.EventEmitter.decorate(this);
|
||||
|
||||
this.#listening = false;
|
||||
this.#navigationManager = navigationManager;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -104,6 +106,11 @@ export class NetworkListener {
|
||||
};
|
||||
|
||||
#onNetworkEvent = (networkEvent, channel) => {
|
||||
return new lazy.NetworkEventRecord(networkEvent, channel, this);
|
||||
return new lazy.NetworkEventRecord(
|
||||
networkEvent,
|
||||
channel,
|
||||
this,
|
||||
this.#navigationManager
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
* 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 { NavigationManager } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/NavigationManager.sys.mjs"
|
||||
);
|
||||
const { NetworkListener } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/listeners/NetworkListener.sys.mjs"
|
||||
);
|
||||
@ -10,7 +13,10 @@ const { TabManager } = ChromeUtils.importESModule(
|
||||
);
|
||||
|
||||
add_task(async function test_beforeRequestSent() {
|
||||
const listener = new NetworkListener();
|
||||
const navigationManager = new NavigationManager();
|
||||
navigationManager.startMonitoring();
|
||||
|
||||
const listener = new NetworkListener(navigationManager);
|
||||
const events = [];
|
||||
const onEvent = (name, data) => events.push(data);
|
||||
listener.on("before-request-sent", onEvent);
|
||||
@ -54,10 +60,14 @@ add_task(async function test_beforeRequestSent() {
|
||||
gBrowser.removeTab(tab2);
|
||||
listener.off("before-request-sent", onEvent);
|
||||
listener.destroy();
|
||||
navigationManager.destroy();
|
||||
});
|
||||
|
||||
add_task(async function test_beforeRequestSent_newTab() {
|
||||
const listener = new NetworkListener();
|
||||
const navigationManager = new NavigationManager();
|
||||
navigationManager.startMonitoring();
|
||||
|
||||
const listener = new NetworkListener(navigationManager);
|
||||
const onBeforeRequestSent = listener.once("before-request-sent");
|
||||
listener.startListening();
|
||||
|
||||
@ -76,10 +86,14 @@ add_task(async function test_beforeRequestSent_newTab() {
|
||||
"https://example.com/document-builder.sjs?html=tab"
|
||||
);
|
||||
gBrowser.removeTab(tab);
|
||||
navigationManager.destroy();
|
||||
});
|
||||
|
||||
add_task(async function test_fetchError() {
|
||||
const listener = new NetworkListener();
|
||||
const navigationManager = new NavigationManager();
|
||||
navigationManager.startMonitoring();
|
||||
|
||||
const listener = new NetworkListener(navigationManager);
|
||||
const onFetchError = listener.once("fetch-error");
|
||||
listener.startListening();
|
||||
|
||||
@ -90,11 +104,16 @@ add_task(async function test_fetchError() {
|
||||
const event = await onFetchError;
|
||||
|
||||
assertNetworkEvent(event, contextId, "https://not_a_valid_url/");
|
||||
is(event.errorText, "NS_ERROR_UNKNOWN_HOST");
|
||||
is(event.request.errorText, "NS_ERROR_UNKNOWN_HOST");
|
||||
gBrowser.removeTab(tab);
|
||||
navigationManager.destroy();
|
||||
});
|
||||
|
||||
function assertNetworkEvent(event, expectedContextId, expectedUrl) {
|
||||
is(event.contextId, expectedContextId, "Event has the expected context id");
|
||||
is(event.requestData.url, expectedUrl, "Event has the expected url");
|
||||
is(
|
||||
event.request.contextId,
|
||||
expectedContextId,
|
||||
"Event has the expected context id"
|
||||
);
|
||||
is(event.request.serializedURL, expectedUrl, "Event has the expected url");
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
|
||||
matchURLPattern:
|
||||
"chrome://remote/content/shared/webdriver/URLPattern.sys.mjs",
|
||||
notifyNavigationStarted:
|
||||
"chrome://remote/content/shared/NavigationManager.sys.mjs",
|
||||
NetworkListener:
|
||||
"chrome://remote/content/shared/listeners/NetworkListener.sys.mjs",
|
||||
parseChallengeHeader:
|
||||
@ -309,7 +307,9 @@ class NetworkModule extends Module {
|
||||
// Set of event names which have active subscriptions
|
||||
this.#subscribedEvents = new Set();
|
||||
|
||||
this.#networkListener = new lazy.NetworkListener();
|
||||
this.#networkListener = new lazy.NetworkListener(
|
||||
this.messageHandler.navigationManager
|
||||
);
|
||||
this.#networkListener.on("auth-required", this.#onAuthRequired);
|
||||
this.#networkListener.on("before-request-sent", this.#onBeforeRequestSent);
|
||||
this.#networkListener.on("fetch-error", this.#onFetchError);
|
||||
@ -549,8 +549,7 @@ class NetworkModule extends Module {
|
||||
);
|
||||
}
|
||||
|
||||
const wrapper = ChannelWrapper.get(request);
|
||||
wrapper.resume();
|
||||
request.wrappedChannel.resume();
|
||||
|
||||
resolveBlockedEvent();
|
||||
}
|
||||
@ -684,8 +683,7 @@ class NetworkModule extends Module {
|
||||
await authCallbacks.provideAuthCredentials();
|
||||
}
|
||||
} else {
|
||||
const wrapper = ChannelWrapper.get(request);
|
||||
wrapper.resume();
|
||||
request.wrappedChannel.resume();
|
||||
}
|
||||
|
||||
resolveBlockedEvent();
|
||||
@ -803,9 +801,8 @@ class NetworkModule extends Module {
|
||||
);
|
||||
}
|
||||
|
||||
const wrapper = ChannelWrapper.get(request);
|
||||
wrapper.resume();
|
||||
wrapper.cancel(
|
||||
request.wrappedChannel.resume();
|
||||
request.wrappedChannel.cancel(
|
||||
Cr.NS_ERROR_ABORT,
|
||||
Ci.nsILoadInfo.BLOCKING_REASON_WEBDRIVER_BIDI
|
||||
);
|
||||
@ -933,8 +930,7 @@ class NetworkModule extends Module {
|
||||
if (phase === InterceptPhase.AuthRequired) {
|
||||
await authCallbacks.provideAuthCredentials();
|
||||
} else {
|
||||
const wrapper = ChannelWrapper.get(request);
|
||||
wrapper.resume();
|
||||
request.wrappedChannel.resume();
|
||||
}
|
||||
|
||||
resolveBlockedEvent();
|
||||
@ -987,11 +983,7 @@ class NetworkModule extends Module {
|
||||
* The response channel.
|
||||
*/
|
||||
#addBlockedRequest(requestId, phase, options = {}) {
|
||||
const {
|
||||
authCallbacks,
|
||||
requestChannel: request,
|
||||
responseChannel: response,
|
||||
} = options;
|
||||
const { authCallbacks, request, response } = options;
|
||||
const { promise: blockedEventPromise, resolve: resolveBlockedEvent } =
|
||||
Promise.withResolvers();
|
||||
|
||||
@ -1117,14 +1109,14 @@ class NetworkModule extends Module {
|
||||
}
|
||||
}
|
||||
|
||||
#extractChallenges(responseData) {
|
||||
#extractChallenges(response) {
|
||||
let headerName;
|
||||
|
||||
// Using case-insensitive match for header names, so we use the lowercase
|
||||
// version of the "WWW-Authenticate" / "Proxy-Authenticate" strings.
|
||||
if (responseData.status === 401) {
|
||||
if (response.status === 401) {
|
||||
headerName = "www-authenticate";
|
||||
} else if (responseData.status === 407) {
|
||||
} else if (response.status === 407) {
|
||||
headerName = "proxy-authenticate";
|
||||
} else {
|
||||
return null;
|
||||
@ -1132,10 +1124,10 @@ class NetworkModule extends Module {
|
||||
|
||||
const challenges = [];
|
||||
|
||||
for (const header of responseData.headers) {
|
||||
if (header.name.toLowerCase() === headerName) {
|
||||
for (const [name, value] of response.getHeadersList()) {
|
||||
if (name.toLowerCase() === headerName) {
|
||||
// A single header can contain several challenges.
|
||||
const headerChallenges = lazy.parseChallengeHeader(header.value);
|
||||
const headerChallenges = lazy.parseChallengeHeader(value);
|
||||
for (const headerChallenge of headerChallenges) {
|
||||
const realmParam = headerChallenge.params.find(
|
||||
param => param.name == "realm"
|
||||
@ -1177,7 +1169,7 @@ class NetworkModule extends Module {
|
||||
};
|
||||
}
|
||||
|
||||
#getNetworkIntercepts(event, requestData, contextId) {
|
||||
#getNetworkIntercepts(event, request, topContextId) {
|
||||
const intercepts = [];
|
||||
|
||||
let phase;
|
||||
@ -1197,17 +1189,11 @@ class NetworkModule extends Module {
|
||||
return intercepts;
|
||||
}
|
||||
|
||||
// Retrieve the top browsing context id for this network event.
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
|
||||
const topLevelContextId = lazy.TabManager.getIdForBrowsingContext(
|
||||
browsingContext.top
|
||||
);
|
||||
|
||||
const url = requestData.url;
|
||||
const url = request.serializedURL;
|
||||
for (const [interceptId, intercept] of this.#interceptMap) {
|
||||
if (
|
||||
intercept.contexts !== null &&
|
||||
!intercept.contexts.includes(topLevelContextId)
|
||||
!intercept.contexts.includes(topContextId)
|
||||
) {
|
||||
// Skip this intercept if the event's context does not match the list
|
||||
// of contexts for this intercept.
|
||||
@ -1228,31 +1214,96 @@ class NetworkModule extends Module {
|
||||
return intercepts;
|
||||
}
|
||||
|
||||
#getNavigationId(eventName, isNavigationRequest, browsingContext, url) {
|
||||
if (!isNavigationRequest) {
|
||||
// Not a navigation request return null.
|
||||
return null;
|
||||
#getRequestData(request) {
|
||||
const requestId = request.requestId;
|
||||
|
||||
// "Let url be the result of running the URL serializer with request’s URL"
|
||||
// request.serializedURL is already serialized.
|
||||
const url = request.serializedURL;
|
||||
const method = request.method;
|
||||
|
||||
const bodySize = request.postDataSize;
|
||||
const headersSize = request.headersSize;
|
||||
const headers = [];
|
||||
const cookies = [];
|
||||
|
||||
for (const [name, value] of request.getHeadersList()) {
|
||||
headers.push(this.#serializeHeader(name, value));
|
||||
if (name.toLowerCase() == "cookie") {
|
||||
// TODO: Retrieve the actual cookies from the cookie store.
|
||||
const headerCookies = value.split(";");
|
||||
for (const cookie of headerCookies) {
|
||||
const equal = cookie.indexOf("=");
|
||||
const cookieName = cookie.substr(0, equal);
|
||||
const cookieValue = cookie.substr(equal + 1);
|
||||
const serializedCookie = this.#serializeHeader(
|
||||
unescape(cookieName.trim()),
|
||||
unescape(cookieValue.trim())
|
||||
);
|
||||
cookies.push(serializedCookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let navigation =
|
||||
this.messageHandler.navigationManager.getNavigationForBrowsingContext(
|
||||
browsingContext
|
||||
);
|
||||
const timings = request.getFetchTimings();
|
||||
|
||||
// `onBeforeRequestSent` might be too early for the NavigationManager.
|
||||
// If there is no ongoing navigation, create one ourselves.
|
||||
// TODO: Bug 1835704 to detect navigations earlier and avoid this.
|
||||
if (
|
||||
eventName === "network.beforeRequestSent" &&
|
||||
(!navigation || navigation.finished)
|
||||
) {
|
||||
navigation = lazy.notifyNavigationStarted({
|
||||
contextDetails: { context: browsingContext },
|
||||
url,
|
||||
});
|
||||
return {
|
||||
request: requestId,
|
||||
url,
|
||||
method,
|
||||
bodySize,
|
||||
headersSize,
|
||||
headers,
|
||||
cookies,
|
||||
timings,
|
||||
};
|
||||
}
|
||||
|
||||
#getResponseContentInfo(response) {
|
||||
return {
|
||||
size: response.decodedBodySize,
|
||||
};
|
||||
}
|
||||
|
||||
#getResponseData(response) {
|
||||
const url = response.serializedURL;
|
||||
const protocol = response.protocol;
|
||||
const status = response.status;
|
||||
const statusText = response.statusMessage;
|
||||
// TODO: Ideally we should have a `isCacheStateLocal` getter
|
||||
// const fromCache = response.isCacheStateLocal();
|
||||
const fromCache = response.fromCache;
|
||||
const mimeType = response.getComputedMimeType();
|
||||
const headers = [];
|
||||
for (const [name, value] of response.getHeadersList()) {
|
||||
headers.push(this.#serializeHeader(name, value));
|
||||
}
|
||||
|
||||
return navigation ? navigation.navigationId : null;
|
||||
const bytesReceived = response.totalTransmittedSize;
|
||||
const headersSize = response.headersTransmittedSize;
|
||||
const bodySize = response.encodedBodySize;
|
||||
const content = this.#getResponseContentInfo(response);
|
||||
const authChallenges = this.#extractChallenges(response);
|
||||
|
||||
const params = {
|
||||
url,
|
||||
protocol,
|
||||
status,
|
||||
statusText,
|
||||
fromCache,
|
||||
headers,
|
||||
mimeType,
|
||||
bytesReceived,
|
||||
headersSize,
|
||||
bodySize,
|
||||
content,
|
||||
};
|
||||
|
||||
if (authChallenges !== null) {
|
||||
params.authChallenges = authChallenges;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
#getSuspendMarkerText(requestData, phase) {
|
||||
@ -1260,21 +1311,13 @@ class NetworkModule extends Module {
|
||||
}
|
||||
|
||||
#onAuthRequired = (name, data) => {
|
||||
const {
|
||||
authCallbacks,
|
||||
contextId,
|
||||
isNavigationRequest,
|
||||
redirectCount,
|
||||
requestChannel,
|
||||
requestData,
|
||||
responseChannel,
|
||||
responseData,
|
||||
timestamp,
|
||||
} = data;
|
||||
const { authCallbacks, request, response } = data;
|
||||
|
||||
let isBlocked = false;
|
||||
try {
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(
|
||||
request.contextId
|
||||
);
|
||||
if (!browsingContext) {
|
||||
// Do not emit events if the context id does not match any existing
|
||||
// browsing context.
|
||||
@ -1283,18 +1326,9 @@ class NetworkModule extends Module {
|
||||
|
||||
const protocolEventName = "network.authRequired";
|
||||
|
||||
// Process the navigation to create potentially missing navigation ids
|
||||
// before the early return below.
|
||||
const navigation = this.#getNavigationId(
|
||||
protocolEventName,
|
||||
isNavigationRequest,
|
||||
browsingContext,
|
||||
requestData.url
|
||||
);
|
||||
|
||||
const isListening = this.messageHandler.eventsDispatcher.hasListener(
|
||||
protocolEventName,
|
||||
{ contextId }
|
||||
{ contextId: request.contextId }
|
||||
);
|
||||
if (!isListening) {
|
||||
// If there are no listeners subscribed to this event and this context,
|
||||
@ -1302,23 +1336,16 @@ class NetworkModule extends Module {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseParameters = this.#processNetworkEvent(protocolEventName, {
|
||||
contextId,
|
||||
navigation,
|
||||
redirectCount,
|
||||
requestData,
|
||||
timestamp,
|
||||
});
|
||||
const baseParameters = this.#processNetworkEvent(
|
||||
protocolEventName,
|
||||
request
|
||||
);
|
||||
|
||||
const authRequiredEvent = this.#serializeNetworkEvent({
|
||||
const responseData = this.#getResponseData(response);
|
||||
const authRequiredEvent = {
|
||||
...baseParameters,
|
||||
response: responseData,
|
||||
});
|
||||
|
||||
const authChallenges = this.#extractChallenges(responseData);
|
||||
// authChallenges should never be null for a request which triggered an
|
||||
// authRequired event.
|
||||
authRequiredEvent.response.authChallenges = authChallenges;
|
||||
};
|
||||
|
||||
this.emitEvent(
|
||||
protocolEventName,
|
||||
@ -1337,8 +1364,8 @@ class NetworkModule extends Module {
|
||||
InterceptPhase.AuthRequired,
|
||||
{
|
||||
authCallbacks,
|
||||
requestChannel,
|
||||
responseChannel,
|
||||
request,
|
||||
response,
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1352,16 +1379,11 @@ class NetworkModule extends Module {
|
||||
};
|
||||
|
||||
#onBeforeRequestSent = (name, data) => {
|
||||
const {
|
||||
contextId,
|
||||
isNavigationRequest,
|
||||
redirectCount,
|
||||
requestChannel,
|
||||
requestData,
|
||||
timestamp,
|
||||
} = data;
|
||||
const { request } = data;
|
||||
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(
|
||||
request.contextId
|
||||
);
|
||||
if (!browsingContext) {
|
||||
// Do not emit events if the context id does not match any existing
|
||||
// browsing context.
|
||||
@ -1371,15 +1393,6 @@ class NetworkModule extends Module {
|
||||
const internalEventName = "network._beforeRequestSent";
|
||||
const protocolEventName = "network.beforeRequestSent";
|
||||
|
||||
// Process the navigation to create potentially missing navigation ids
|
||||
// before the early return below.
|
||||
const navigation = this.#getNavigationId(
|
||||
protocolEventName,
|
||||
isNavigationRequest,
|
||||
browsingContext,
|
||||
requestData.url
|
||||
);
|
||||
|
||||
// Always emit internal events, they are used to support the browsingContext
|
||||
// navigate command.
|
||||
// Bug 1861922: Replace internal events with a Network listener helper
|
||||
@ -1387,15 +1400,15 @@ class NetworkModule extends Module {
|
||||
this.emitEvent(
|
||||
internalEventName,
|
||||
{
|
||||
navigation,
|
||||
url: requestData.url,
|
||||
navigation: request.navigationId,
|
||||
url: request.serializedURL,
|
||||
},
|
||||
this.#getContextInfo(browsingContext)
|
||||
);
|
||||
|
||||
const isListening = this.messageHandler.eventsDispatcher.hasListener(
|
||||
protocolEventName,
|
||||
{ contextId }
|
||||
{ contextId: request.contextId }
|
||||
);
|
||||
if (!isListening) {
|
||||
// If there are no listeners subscribed to this event and this context,
|
||||
@ -1403,23 +1416,20 @@ class NetworkModule extends Module {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseParameters = this.#processNetworkEvent(protocolEventName, {
|
||||
contextId,
|
||||
navigation,
|
||||
redirectCount,
|
||||
requestData,
|
||||
timestamp,
|
||||
});
|
||||
const baseParameters = this.#processNetworkEvent(
|
||||
protocolEventName,
|
||||
request
|
||||
);
|
||||
|
||||
// Bug 1805479: Handle the initiator, including stacktrace details.
|
||||
const initiator = {
|
||||
type: InitiatorType.Other,
|
||||
};
|
||||
|
||||
const beforeRequestSentEvent = this.#serializeNetworkEvent({
|
||||
const beforeRequestSentEvent = {
|
||||
...baseParameters,
|
||||
initiator,
|
||||
});
|
||||
};
|
||||
|
||||
this.emitEvent(
|
||||
protocolEventName,
|
||||
@ -1430,32 +1440,26 @@ class NetworkModule extends Module {
|
||||
if (beforeRequestSentEvent.isBlocked) {
|
||||
// TODO: Requests suspended in beforeRequestSent still reach the server at
|
||||
// the moment. https://bugzilla.mozilla.org/show_bug.cgi?id=1849686
|
||||
const wrapper = ChannelWrapper.get(requestChannel);
|
||||
wrapper.suspend(
|
||||
this.#getSuspendMarkerText(requestData, "beforeRequestSent")
|
||||
request.wrappedChannel.suspend(
|
||||
this.#getSuspendMarkerText(request, "beforeRequestSent")
|
||||
);
|
||||
|
||||
this.#addBlockedRequest(
|
||||
beforeRequestSentEvent.request.request,
|
||||
InterceptPhase.BeforeRequestSent,
|
||||
{
|
||||
requestChannel,
|
||||
request,
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
#onFetchError = (name, data) => {
|
||||
const {
|
||||
contextId,
|
||||
errorText,
|
||||
isNavigationRequest,
|
||||
redirectCount,
|
||||
requestData,
|
||||
timestamp,
|
||||
} = data;
|
||||
const { request } = data;
|
||||
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(
|
||||
request.contextId
|
||||
);
|
||||
if (!browsingContext) {
|
||||
// Do not emit events if the context id does not match any existing
|
||||
// browsing context.
|
||||
@ -1465,15 +1469,6 @@ class NetworkModule extends Module {
|
||||
const internalEventName = "network._fetchError";
|
||||
const protocolEventName = "network.fetchError";
|
||||
|
||||
// Process the navigation to create potentially missing navigation ids
|
||||
// before the early return below.
|
||||
const navigation = this.#getNavigationId(
|
||||
protocolEventName,
|
||||
isNavigationRequest,
|
||||
browsingContext,
|
||||
requestData.url
|
||||
);
|
||||
|
||||
// Always emit internal events, they are used to support the browsingContext
|
||||
// navigate command.
|
||||
// Bug 1861922: Replace internal events with a Network listener helper
|
||||
@ -1481,15 +1476,15 @@ class NetworkModule extends Module {
|
||||
this.emitEvent(
|
||||
internalEventName,
|
||||
{
|
||||
navigation,
|
||||
url: requestData.url,
|
||||
navigation: request.navigationId,
|
||||
url: request.serializedURL,
|
||||
},
|
||||
this.#getContextInfo(browsingContext)
|
||||
);
|
||||
|
||||
const isListening = this.messageHandler.eventsDispatcher.hasListener(
|
||||
protocolEventName,
|
||||
{ contextId }
|
||||
{ contextId: request.contextId }
|
||||
);
|
||||
if (!isListening) {
|
||||
// If there are no listeners subscribed to this event and this context,
|
||||
@ -1497,18 +1492,15 @@ class NetworkModule extends Module {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseParameters = this.#processNetworkEvent(protocolEventName, {
|
||||
contextId,
|
||||
navigation,
|
||||
redirectCount,
|
||||
requestData,
|
||||
timestamp,
|
||||
});
|
||||
const baseParameters = this.#processNetworkEvent(
|
||||
protocolEventName,
|
||||
request
|
||||
);
|
||||
|
||||
const fetchErrorEvent = this.#serializeNetworkEvent({
|
||||
const fetchErrorEvent = {
|
||||
...baseParameters,
|
||||
errorText,
|
||||
});
|
||||
errorText: request.errorText,
|
||||
};
|
||||
|
||||
this.emitEvent(
|
||||
protocolEventName,
|
||||
@ -1518,18 +1510,11 @@ class NetworkModule extends Module {
|
||||
};
|
||||
|
||||
#onResponseEvent = (name, data) => {
|
||||
const {
|
||||
contextId,
|
||||
isNavigationRequest,
|
||||
redirectCount,
|
||||
requestChannel,
|
||||
requestData,
|
||||
responseChannel,
|
||||
responseData,
|
||||
timestamp,
|
||||
} = data;
|
||||
const { request, response } = data;
|
||||
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(
|
||||
request.contextId
|
||||
);
|
||||
if (!browsingContext) {
|
||||
// Do not emit events if the context id does not match any existing
|
||||
// browsing context.
|
||||
@ -1546,15 +1531,6 @@ class NetworkModule extends Module {
|
||||
? "network._responseStarted"
|
||||
: "network._responseCompleted";
|
||||
|
||||
// Process the navigation to create potentially missing navigation ids
|
||||
// before the early return below.
|
||||
const navigation = this.#getNavigationId(
|
||||
protocolEventName,
|
||||
isNavigationRequest,
|
||||
browsingContext,
|
||||
requestData.url
|
||||
);
|
||||
|
||||
// Always emit internal events, they are used to support the browsingContext
|
||||
// navigate command.
|
||||
// Bug 1861922: Replace internal events with a Network listener helper
|
||||
@ -1562,15 +1538,15 @@ class NetworkModule extends Module {
|
||||
this.emitEvent(
|
||||
internalEventName,
|
||||
{
|
||||
navigation,
|
||||
url: requestData.url,
|
||||
navigation: request.navigationId,
|
||||
url: request.serializedURL,
|
||||
},
|
||||
this.#getContextInfo(browsingContext)
|
||||
);
|
||||
|
||||
const isListening = this.messageHandler.eventsDispatcher.hasListener(
|
||||
protocolEventName,
|
||||
{ contextId }
|
||||
{ contextId: request.contextId }
|
||||
);
|
||||
if (!isListening) {
|
||||
// If there are no listeners subscribed to this event and this context,
|
||||
@ -1578,23 +1554,17 @@ class NetworkModule extends Module {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseParameters = this.#processNetworkEvent(protocolEventName, {
|
||||
contextId,
|
||||
navigation,
|
||||
redirectCount,
|
||||
requestData,
|
||||
timestamp,
|
||||
});
|
||||
const baseParameters = this.#processNetworkEvent(
|
||||
protocolEventName,
|
||||
request
|
||||
);
|
||||
|
||||
const responseEvent = this.#serializeNetworkEvent({
|
||||
const responseData = this.#getResponseData(response);
|
||||
|
||||
const responseEvent = {
|
||||
...baseParameters,
|
||||
response: responseData,
|
||||
});
|
||||
|
||||
const authChallenges = this.#extractChallenges(responseData);
|
||||
if (authChallenges !== null) {
|
||||
responseEvent.response.authChallenges = authChallenges;
|
||||
}
|
||||
};
|
||||
|
||||
this.emitEvent(
|
||||
protocolEventName,
|
||||
@ -1606,51 +1576,40 @@ class NetworkModule extends Module {
|
||||
protocolEventName === "network.responseStarted" &&
|
||||
responseEvent.isBlocked
|
||||
) {
|
||||
const wrapper = ChannelWrapper.get(requestChannel);
|
||||
wrapper.suspend(
|
||||
this.#getSuspendMarkerText(requestData, "responseStarted")
|
||||
request.wrappedChannel.suspend(
|
||||
this.#getSuspendMarkerText(request, "responseStarted")
|
||||
);
|
||||
|
||||
this.#addBlockedRequest(
|
||||
responseEvent.request.request,
|
||||
InterceptPhase.ResponseStarted,
|
||||
{
|
||||
requestChannel,
|
||||
responseChannel,
|
||||
request,
|
||||
response,
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Process the network event data for a given network event name and create
|
||||
* the corresponding base parameters.
|
||||
*
|
||||
* @param {string} eventName
|
||||
* One of the supported network event names.
|
||||
* @param {object} data
|
||||
* @param {string} data.contextId
|
||||
* The browsing context id for the network event.
|
||||
* @param {string|null} data.navigation
|
||||
* The navigation id if this is a network event for a navigation request.
|
||||
* @param {number} data.redirectCount
|
||||
* The redirect count for the network event.
|
||||
* @param {RequestData} data.requestData
|
||||
* The network.RequestData information for the network event.
|
||||
* @param {number} data.timestamp
|
||||
* The timestamp when the network event was created.
|
||||
*/
|
||||
#processNetworkEvent(eventName, data) {
|
||||
const { contextId, navigation, redirectCount, requestData, timestamp } =
|
||||
data;
|
||||
const intercepts = this.#getNetworkIntercepts(
|
||||
eventName,
|
||||
requestData,
|
||||
contextId
|
||||
);
|
||||
const isBlocked = !!intercepts.length;
|
||||
#processNetworkEvent(event, request) {
|
||||
const requestData = this.#getRequestData(request);
|
||||
const navigation = request.navigationId;
|
||||
let contextId = null;
|
||||
let topContextId = null;
|
||||
if (request.contextId) {
|
||||
// Retrieve the top browsing context id for this network event.
|
||||
contextId = request.contextId;
|
||||
const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
|
||||
topContextId = lazy.TabManager.getIdForBrowsingContext(
|
||||
browsingContext.top
|
||||
);
|
||||
}
|
||||
|
||||
const baseParameters = {
|
||||
const intercepts = this.#getNetworkIntercepts(event, request, topContextId);
|
||||
const redirectCount = request.redirectCount;
|
||||
const timestamp = Date.now();
|
||||
const isBlocked = !!intercepts.length;
|
||||
const params = {
|
||||
context: contextId,
|
||||
isBlocked,
|
||||
navigation,
|
||||
@ -1660,51 +1619,17 @@ class NetworkModule extends Module {
|
||||
};
|
||||
|
||||
if (isBlocked) {
|
||||
baseParameters.intercepts = intercepts;
|
||||
params.intercepts = intercepts;
|
||||
}
|
||||
|
||||
return baseParameters;
|
||||
return params;
|
||||
}
|
||||
|
||||
#serializeHeadersOrCookies(headersOrCookies) {
|
||||
return headersOrCookies.map(item => ({
|
||||
name: item.name,
|
||||
value: this.#serializeStringAsBytesValue(item.value),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize in-place all cookies and headers arrays found in a given network
|
||||
* event payload.
|
||||
*
|
||||
* @param {object} networkEvent
|
||||
* The network event parameters object to serialize.
|
||||
* @returns {object}
|
||||
* The serialized network event parameters.
|
||||
*/
|
||||
#serializeNetworkEvent(networkEvent) {
|
||||
// Make a shallow copy of networkEvent before serializing the headers and
|
||||
// cookies arrays in request/response.
|
||||
const serialized = { ...networkEvent };
|
||||
|
||||
// Make a shallow copy of the request data.
|
||||
serialized.request = { ...networkEvent.request };
|
||||
serialized.request.cookies = this.#serializeHeadersOrCookies(
|
||||
networkEvent.request.cookies
|
||||
);
|
||||
serialized.request.headers = this.#serializeHeadersOrCookies(
|
||||
networkEvent.request.headers
|
||||
);
|
||||
|
||||
if (networkEvent.response?.headers) {
|
||||
// Make a shallow copy of the response data.
|
||||
serialized.response = { ...networkEvent.response };
|
||||
serialized.response.headers = this.#serializeHeadersOrCookies(
|
||||
networkEvent.response.headers
|
||||
);
|
||||
}
|
||||
|
||||
return serialized;
|
||||
#serializeHeader(name, value) {
|
||||
return {
|
||||
name,
|
||||
value: this.#serializeStringAsBytesValue(value),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user