Source code

Revision control

Copy as Markdown

Other Tools

/* 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:
notifyNavigationStarted:
});
/**
* The NetworkRequest class is a wrapper around the internal channel which
* provides getters and methods closer to fetch's response concept
*/
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
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;
}
}