forked from mirrors/gecko-dev
325 lines
9.4 KiB
JavaScript
325 lines
9.4 KiB
JavaScript
/* 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, {
|
|
NetworkHelper:
|
|
"resource://devtools/shared/network-observer/NetworkHelper.sys.mjs",
|
|
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 {
|
|
#alreadyCompleted;
|
|
#channel;
|
|
#contextId;
|
|
#navigationId;
|
|
#navigationManager;
|
|
#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 alreadyCompleted() {
|
|
return this.#alreadyCompleted;
|
|
}
|
|
|
|
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() {
|
|
const charset = lazy.NetworkUtils.getCharset(this.#channel);
|
|
const sentBody = lazy.NetworkHelper.readPostTextFromRequest(
|
|
this.#channel,
|
|
charset
|
|
);
|
|
return sentBody ? sentBody.length : 0;
|
|
}
|
|
|
|
get redirectCount() {
|
|
return this.#redirectCount;
|
|
}
|
|
|
|
get requestId() {
|
|
return this.#requestId;
|
|
}
|
|
|
|
get serializedURL() {
|
|
return this.#channel.URI.spec;
|
|
}
|
|
|
|
get wrappedChannel() {
|
|
return this.#wrappedChannel;
|
|
}
|
|
|
|
set alreadyCompleted(value) {
|
|
this.#alreadyCompleted = value;
|
|
}
|
|
|
|
/**
|
|
* Add information about raw headers, collected from NetworkObserver events.
|
|
*
|
|
* @param {string} rawHeaders
|
|
* The raw headers.
|
|
*/
|
|
addRawHeaders(rawHeaders) {
|
|
this.#rawHeaders = rawHeaders || "";
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Set the request post body
|
|
*
|
|
* @param {string} body
|
|
* The body to set.
|
|
*/
|
|
setRequestBody(body) {
|
|
// Update the requestObserversCalled flag to allow modifying the request,
|
|
// and reset once done.
|
|
this.#channel.requestObserversCalled = false;
|
|
|
|
try {
|
|
this.#channel.QueryInterface(Ci.nsIUploadChannel2);
|
|
const bodyStream = Cc[
|
|
"@mozilla.org/io/string-input-stream;1"
|
|
].createInstance(Ci.nsIStringInputStream);
|
|
bodyStream.setData(body, body.length);
|
|
this.#channel.explicitSetUploadStream(
|
|
bodyStream,
|
|
null,
|
|
-1,
|
|
this.#channel.requestMethod,
|
|
false
|
|
);
|
|
} finally {
|
|
// Make sure to reset the flag once the modification was attempted.
|
|
this.#channel.requestObserversCalled = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set a request header
|
|
*
|
|
* @param {string} name
|
|
* The header's name.
|
|
* @param {string} value
|
|
* The header's value.
|
|
*/
|
|
setRequestHeader(name, value) {
|
|
this.#channel.setRequestHeader(name, value, false);
|
|
}
|
|
|
|
/**
|
|
* Update the request's method.
|
|
*
|
|
* @param {string} method
|
|
* The method to set.
|
|
*/
|
|
setRequestMethod(method) {
|
|
// Update the requestObserversCalled flag to allow modifying the request,
|
|
// and reset once done.
|
|
this.#channel.requestObserversCalled = false;
|
|
|
|
try {
|
|
this.#channel.requestMethod = method;
|
|
} finally {
|
|
// Make sure to reset the flag once the modification was attempted.
|
|
this.#channel.requestObserversCalled = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|