Skip to content

Instantly share code, notes, and snippets.

@wellington1993
Created August 2, 2022 17:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wellington1993/35e927b853aaf2dbd7069c7c34e531b3 to your computer and use it in GitHub Desktop.
Save wellington1993/35e927b853aaf2dbd7069c7c34e531b3 to your computer and use it in GitHub Desktop.
view-source:resource://devtools/server/actors/resources/network-events-content.js
/* 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";
const { Ci } = require("chrome");
const Services = require("Services");
loader.lazyRequireGetter(
this,
"NetworkEventActor",
"devtools/server/actors/network-monitor/network-event-actor",
true
);
loader.lazyRequireGetter(
this,
"NetworkUtils",
"devtools/server/actors/network-monitor/utils/network-utils"
);
/**
* 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 (NetworkUtils.isPreloadRequest(channel)) {
return;
}
if (
!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 (
!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 = 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]);
NetworkUtils.fetchRequestHeadersAndCookies(channel, actor, {});
}
/*
* 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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment