summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/resources/network-events-content.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/resources/network-events-content.js')
-rw-r--r--devtools/server/actors/resources/network-events-content.js258
1 files changed, 258 insertions, 0 deletions
diff --git a/devtools/server/actors/resources/network-events-content.js b/devtools/server/actors/resources/network-events-content.js
new file mode 100644
index 0000000000..7d5607faec
--- /dev/null
+++ b/devtools/server/actors/resources/network-events-content.js
@@ -0,0 +1,258 @@
+/* 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/. */
+
+"use strict";
+
+loader.lazyRequireGetter(
+ this,
+ "NetworkEventActor",
+ "resource://devtools/server/actors/network-monitor/network-event-actor.js",
+ true
+);
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ NetworkUtils:
+ "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
+});
+
+/**
+ * Handles network events from the content process
+ * This currently only handles events for requests (js/css) blocked by CSP.
+ */
+class NetworkEventContentWatcher {
+ /**
+ * Start watching for all network events related to a given Target Actor.
+ *
+ * @param TargetActor targetActor
+ * The target actor in the content process from which we should
+ * observe network events.
+ * @param Object options
+ * Dictionary object with following attributes:
+ * - onAvailable: mandatory function
+ * This will be called for each resource.
+ * - onUpdated: optional function
+ * This would be called multiple times for each resource.
+ */
+ async watch(targetActor, { onAvailable, onUpdated }) {
+ this._networkEvents = new Map();
+
+ this.targetActor = targetActor;
+ this.onAvailable = onAvailable;
+ this.onUpdated = onUpdated;
+
+ this.httpFailedOpeningRequest = this.httpFailedOpeningRequest.bind(this);
+ this.httpOnImageCacheResponse = this.httpOnImageCacheResponse.bind(this);
+
+ Services.obs.addObserver(
+ this.httpFailedOpeningRequest,
+ "http-on-failed-opening-request"
+ );
+
+ Services.obs.addObserver(
+ this.httpOnImageCacheResponse,
+ "http-on-image-cache-response"
+ );
+ }
+ /**
+ * Allows clearing of network events
+ */
+ clear() {
+ this._networkEvents.clear();
+ }
+
+ get conn() {
+ return this.targetActor.conn;
+ }
+
+ httpFailedOpeningRequest(subject, topic) {
+ const channel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+ // Ignore preload requests to avoid duplicity request entries in
+ // the Network panel. If a preload fails (for whatever reason)
+ // then the platform kicks off another 'real' request.
+ if (lazy.NetworkUtils.isPreloadRequest(channel)) {
+ return;
+ }
+
+ if (
+ !lazy.NetworkUtils.matchRequest(channel, {
+ targetActor: this.targetActor,
+ })
+ ) {
+ return;
+ }
+
+ this.onNetworkEventAvailable(channel, {
+ networkEventOptions: {
+ blockedReason: channel.loadInfo.requestBlockingReason,
+ },
+ resourceOverrides: null,
+ onNetworkEventUpdate: this.onFailedNetworkEventUpdated.bind(this),
+ });
+ }
+
+ httpOnImageCacheResponse(subject, topic) {
+ if (
+ topic != "http-on-image-cache-response" ||
+ !(subject instanceof Ci.nsIHttpChannel)
+ ) {
+ return;
+ }
+
+ const channel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+ if (
+ !lazy.NetworkUtils.matchRequest(channel, {
+ targetActor: this.targetActor,
+ })
+ ) {
+ return;
+ }
+
+ // Only one network request should be created per URI for images from the cache
+ const hasNetworkEventForURI = Array.from(this._networkEvents.values()).find(
+ networkEvent => networkEvent.url === channel.URI.spec
+ );
+
+ if (hasNetworkEventForURI) {
+ return;
+ }
+
+ this.onNetworkEventAvailable(channel, {
+ networkEventOptions: { fromCache: true },
+ resourceOverrides: {
+ status: 200,
+ statusText: "OK",
+ totalTime: 0,
+ mimeType: channel.contentType,
+ contentSize: channel.contentLength,
+ },
+ onNetworkEventUpdate: this.onImageCacheNetworkEventUpdated.bind(this),
+ });
+ }
+
+ onNetworkEventAvailable(
+ channel,
+ { networkEventOptions, resourceOverrides, onNetworkEventUpdate }
+ ) {
+ const event = lazy.NetworkUtils.createNetworkEvent(
+ channel,
+ networkEventOptions
+ );
+
+ const actor = new NetworkEventActor(
+ this.conn,
+ this.targetActor.sessionContext,
+ {
+ onNetworkEventUpdate,
+ onNetworkEventDestroy: this.onNetworkEventDestroyed.bind(this),
+ },
+ event
+ );
+ this.targetActor.manage(actor);
+
+ const resource = actor.asResource();
+
+ this._networkEvents.set(resource.resourceId, {
+ browsingContextID: resource.browsingContextID,
+ innerWindowId: resource.innerWindowId,
+ resourceId: resource.resourceId,
+ resourceType: resource.resourceType,
+ url: resource.url,
+ types: [],
+ resourceUpdates: {},
+ });
+
+ // Override the default resource property values if need be
+ if (resourceOverrides) {
+ for (const prop in resourceOverrides) {
+ resource[prop] = resourceOverrides[prop];
+ }
+ }
+
+ this.onAvailable([resource]);
+ const {
+ cookies,
+ headers,
+ } = lazy.NetworkUtils.fetchRequestHeadersAndCookies(channel);
+ actor.addRequestHeaders(headers);
+ actor.addRequestCookies(cookies);
+ }
+
+ /*
+ * When an update is needed for a network event.
+ *
+ * @param {Object} updateResource
+ * The resource to be updated
+ * @param {Array} allRequiredUpdates
+ * The updates that are essential to be received before notifying
+ * the client. Other updates may or may not be available.
+ */
+
+ onNetworkEventUpdated(updateResource, allRequiredUpdates) {
+ const networkEvent = this._networkEvents.get(updateResource.resourceId);
+
+ if (!networkEvent) {
+ return;
+ }
+
+ const {
+ browsingContextID,
+ innerWindowId,
+ resourceId,
+ resourceType,
+ resourceUpdates,
+ types,
+ } = networkEvent;
+
+ resourceUpdates[`${updateResource.updateType}Available`] = true;
+ types.push(updateResource.updateType);
+
+ if (allRequiredUpdates.every(header => types.includes(header))) {
+ this.onUpdated([
+ {
+ browsingContextID,
+ innerWindowId,
+ resourceType,
+ resourceId,
+ resourceUpdates,
+ },
+ ]);
+ }
+ }
+
+ onFailedNetworkEventUpdated(updateResource) {
+ this.onNetworkEventUpdated(updateResource, [
+ "requestHeaders",
+ "requestCookies",
+ ]);
+ }
+
+ onImageCacheNetworkEventUpdated(updateResource) {
+ this.onNetworkEventUpdated(updateResource, ["requestHeaders"]);
+ }
+
+ onNetworkEventDestroyed(channelId) {
+ if (this._networkEvents.has(channelId)) {
+ this._networkEvents.delete(channelId);
+ }
+ }
+
+ destroy() {
+ this.clear();
+ Services.obs.removeObserver(
+ this.httpFailedOpeningRequest,
+ "http-on-failed-opening-request"
+ );
+
+ Services.obs.removeObserver(
+ this.httpOnImageCacheResponse,
+ "http-on-image-cache-response"
+ );
+ }
+}
+
+module.exports = NetworkEventContentWatcher;