summaryrefslogtreecommitdiffstats
path: root/remote/shared/listeners/NetworkEventRecord.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'remote/shared/listeners/NetworkEventRecord.sys.mjs')
-rw-r--r--remote/shared/listeners/NetworkEventRecord.sys.mjs370
1 files changed, 370 insertions, 0 deletions
diff --git a/remote/shared/listeners/NetworkEventRecord.sys.mjs b/remote/shared/listeners/NetworkEventRecord.sys.mjs
new file mode 100644
index 0000000000..cc3e0d1001
--- /dev/null
+++ b/remote/shared/listeners/NetworkEventRecord.sys.mjs
@@ -0,0 +1,370 @@
+/* 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",
+
+ 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 {
+ #channel;
+ #fromCache;
+ #networkListener;
+ #redirectCount;
+ #requestData;
+ #requestId;
+ #responseData;
+ #wrappedChannel;
+
+ /**
+ *
+ * @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) {
+ this.#channel = channel;
+ this.#fromCache = networkEvent.fromCache;
+
+ this.#wrappedChannel = ChannelWrapper.get(channel);
+
+ this.#networkListener = networkListener;
+
+ // 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
+ // 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();
+ }
+
+ /**
+ * Add network request POST data.
+ *
+ * Required API for a NetworkObserver event owner.
+ *
+ * @param {object} postData
+ * The request POST data.
+ */
+ addRequestPostData(postData) {
+ // Only the postData size is needed for RemoteAgent consumers.
+ this.#requestData.bodySize = postData.size;
+ }
+
+ /**
+ * Add the initial network response information.
+ *
+ * Required API for a NetworkObserver event owner.
+ *
+ *
+ * @param {object} options
+ * @param {nsIChannel} options.channel
+ * The channel.
+ * @param {boolean} options.fromCache
+ * @param {string} options.rawHeaders
+ */
+ addResponseStart(options) {
+ const { channel, fromCache, rawHeaders = "" } = options;
+ const { headers } =
+ lazy.NetworkUtils.fetchResponseHeadersAndCookies(channel);
+
+ const headersSize = rawHeaders.length;
+ this.#responseData = {
+ ...this.#responseData,
+ bodySize: 0,
+ bytesReceived: headersSize,
+ 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`
+ // from the fetch specification, based on the PR visible at
+ // https://github.com/whatwg/fetch/pull/1540
+ this.#emitResponseStarted();
+ }
+
+ /**
+ * Add connection security information.
+ *
+ * Required API for a NetworkObserver event owner.
+ *
+ * Not used for RemoteAgent.
+ *
+ * @param {object} info
+ * The object containing security information.
+ * @param {boolean} isRacing
+ * True if the corresponding channel raced the cache and network requests.
+ */
+ addSecurityInfo(info, isRacing) {}
+
+ /**
+ * Add network event timings.
+ *
+ * Required API for a NetworkObserver event owner.
+ *
+ * Not used for RemoteAgent.
+ *
+ * @param {number} total
+ * The total time for the request.
+ * @param {object} timings
+ * The har-like timings.
+ * @param {object} offsets
+ * The har-like timings, but as offset from the request start.
+ * @param {Array} serverTimings
+ * The server timings.
+ */
+ addEventTimings(total, timings, offsets, serverTimings) {}
+
+ /**
+ * Add response cache entry.
+ *
+ * Required API for a NetworkObserver event owner.
+ *
+ * Not used for RemoteAgent.
+ *
+ * @param {object} options
+ * An object which contains a single responseCache property.
+ */
+ addResponseCache(options) {}
+
+ /**
+ * Add response content.
+ *
+ * Required API for a NetworkObserver event owner.
+ *
+ * @param {object} response
+ * 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,
+ },
+ };
+
+ this.#emitResponseCompleted();
+ }
+
+ /**
+ * Add server timings.
+ *
+ * Required API for a NetworkObserver event owner.
+ *
+ * Not used for RemoteAgent.
+ *
+ * @param {Array} serverTimings
+ * The server timings.
+ */
+ addServerTimings(serverTimings) {}
+
+ #emitBeforeRequestSent() {
+ this.#updateDataFromTimedChannel();
+
+ this.#networkListener.emit("before-request-sent", {
+ contextId: this.#getContextId(),
+ redirectCount: this.#redirectCount,
+ requestData: this.#requestData,
+ timestamp: Date.now(),
+ });
+ }
+
+ #emitResponseCompleted() {
+ this.#updateDataFromTimedChannel();
+
+ this.#networkListener.emit("response-completed", {
+ contextId: this.#getContextId(),
+ redirectCount: this.#redirectCount,
+ requestData: this.#requestData,
+ responseData: this.#responseData,
+ timestamp: Date.now(),
+ });
+ }
+
+ #emitResponseStarted() {
+ this.#updateDataFromTimedChannel();
+
+ this.#networkListener.emit("response-started", {
+ contextId: this.#getContextId(),
+ redirectCount: this.#redirectCount,
+ requestData: this.#requestData,
+ responseData: this.#responseData,
+ 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;
+ }
+
+ /**
+ * Retrieve the context id corresponding to the current channel, this could
+ * change dynamically during a cross group navigation for an iframe, so this
+ * should always be retrieved dynamically.
+ */
+ #getContextId() {
+ const id = lazy.NetworkUtils.getChannelBrowsingContextID(this.#channel);
+ const browsingContext = BrowsingContext.get(id);
+ return lazy.TabManager.getIdForBrowsingContext(browsingContext);
+ }
+
+ #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.#channel.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
+ // 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),
+ };
+ }
+
+ /**
+ * 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.#channel.QueryInterface(Ci.nsITimedChannel);
+ this.#redirectCount = timedChannel.redirectCount;
+ this.#requestData.timings = this.#getTimingsFromTimedChannel(timedChannel);
+ }
+}