Skip to content

Commit

Permalink
Bug 1674653 - [devtools] Resend requests with fission r=ochameau,devt…
Browse files Browse the repository at this point in the history
…ools-backward-compat-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D95474
  • Loading branch information
bomsy committed Dec 12, 2020
1 parent 7fabc9e commit ce646bb
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 21 deletions.
7 changes: 5 additions & 2 deletions devtools/client/netmonitor/src/actions/requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,13 @@ function sendCustomRequest(connector, requestId = null) {
data.body = request.requestPostData.postData.text;
}

const response = await connector.sendHTTPRequest(data);
// @backward-compat { version 85 } Introduced `channelId` to eventually
// replace `actor`.
const { channelId, actor } = await connector.sendHTTPRequest(data);

dispatch({
type: SEND_CUSTOM_REQUEST,
id: response.eventActor.actor,
id: channelId || actor,
});
};
}
Expand Down
14 changes: 12 additions & 2 deletions devtools/client/netmonitor/src/connector/firefox-connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,18 @@ class FirefoxConnector {
*
* @param {object} data data payload would like to sent to backend
*/
sendHTTPRequest(data) {
return this.webConsoleFront.sendHTTPRequest(data);
async sendHTTPRequest(data) {
if (this.hasResourceWatcherSupport && this.currentTarget) {
const networkContentFront = await this.currentTarget.getFront(
"networkContent"
);
const { channelId } = await networkContentFront.sendHTTPRequest(data);
return { channelId };
}
const {
eventActor: { actor },
} = await this.webConsoleFront.sendHTTPRequest(data);
return { actor };
}

/**
Expand Down
26 changes: 21 additions & 5 deletions devtools/client/netmonitor/src/reducers/requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ function Requests() {
requests: [],
// Selected request ID
selectedId: null,
// @backward-compact { version 85 } The preselectedId can either be
// the actor id on old servers, or the resourceId on new ones.
preselectedId: null,
// True if the monitor is recording HTTP traffic
recording: true,
Expand Down Expand Up @@ -171,8 +173,16 @@ function addRequest(state, action) {
}

// Select the request if it was preselected and there is no other selection.
if (state.preselectedId && state.preselectedId === action.id) {
nextState.selectedId = state.selectedId || state.preselectedId;
if (state.preselectedId) {
if (state.preselectedId === action.id) {
nextState.selectedId = state.selectedId || state.preselectedId;
}
// @backward-compact { version 85 } The preselectedId can be resourceId
// instead of actor id when a custom request is created, and could not be
// selected immediately because it was not yet in the request map.
else if (state.preselectedId === newRequest.resourceId) {
nextState.selectedId = action.id;
}
nextState.preselectedId = null;
}

Expand Down Expand Up @@ -272,20 +282,26 @@ function closeCustomRequest(state) {
return state;
}

// Find the cloned requests to be removed
const removedRequest = requests.find(needle => needle.id === selectedId);

// If the custom request is already in the Map, select it immediately,
// and reset `preselectedId` attribute.
const hasPreselectedId =
preselectedId && requests.find(needle => needle.id === preselectedId);
// @backward-compact { version 85 } The preselectId can also be a resourceId
// or an actor id.
const customRequest = requests.find(
needle => needle.id === preselectedId || needle.resourceId === preselectedId
);
const hasPreselectedId = preselectedId && customRequest;

return {
...state,
// Only custom requests can be removed
[removedRequest?.isCustom && "requests"]: requests.filter(
item => item.id !== selectedId
),
preselectedId: hasPreselectedId ? null : preselectedId,
selectedId: hasPreselectedId ? preselectedId : null,
selectedId: hasPreselectedId ? customRequest.id : null,
};
}

Expand Down
3 changes: 2 additions & 1 deletion devtools/client/netmonitor/test/browser_net_resend_xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ add_task(async function() {
);
const originalRequest = getSelectedRequest(store.getState());

const waitForResentRequestEvent = waitForNetworkEvents(monitor, 1);
// Context Menu > "Resend"
EventUtils.sendMouseEvent({ type: "contextmenu" }, firstRequest);
getContextMenuItem(monitor, "request-list-context-resend-only").click();

await performRequests(monitor, tab, 2);
await waitForResentRequestEvent;

// Selects request that was resent
const selectedRequest = getSelectedRequest(store.getState());
Expand Down
102 changes: 94 additions & 8 deletions devtools/server/actors/network-monitor/network-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@
const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
const { networkContentSpec } = require("devtools/shared/specs/network-content");

const { Cc, Ci } = require("chrome");

loader.lazyRequireGetter(
this,
"NetUtil",
"resource://gre/modules/NetUtil.jsm",
true
);

loader.lazyRequireGetter(
this,
"stringToCauseType",
"devtools/server/actors/network-monitor/network-observer",
true
);

loader.lazyRequireGetter(
this,
"WebConsoleUtils",
Expand Down Expand Up @@ -36,21 +52,91 @@ const NetworkContentActor = ActorClassWithSpec(networkContentSpec, {
Actor.prototype.destroy.call(this, conn);
},

get networkEventStackTraceWatcher() {
return getResourceWatcher(this.targetActor, NETWORK_EVENT_STACKTRACE);
},

/**
* The "getStackTrace" packet type handler.
* Send an HTTP request
*
* @return object
* @param {Object} request
* The details of the HTTP Request.
* @return {Number}
* The channel id for the request
*/
async sendHTTPRequest(request) {
const { url, method, headers, body, cause } = request;
// Set the loadingNode and loadGroup to the target document - otherwise the
// request won't show up in the opened netmonitor.
const doc = this.targetActor.window.document;

const channel = NetUtil.newChannel({
uri: NetUtil.newURI(url),
loadingNode: doc,
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
contentPolicyType:
stringToCauseType(cause.type) || Ci.nsIContentPolicy.TYPE_OTHER,
});

channel.QueryInterface(Ci.nsIHttpChannel);

channel.loadGroup = doc.documentLoadGroup;
channel.loadFlags |=
Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIRequest.INHIBIT_CACHING |
Ci.nsIRequest.LOAD_ANONYMOUS;

channel.requestMethod = method;
if (headers) {
for (const { name, value } of headers) {
if (name.toLowerCase() == "referer") {
// The referer header and referrerInfo object should always match. So
// if we want to set the header from privileged context, we should set
// referrerInfo. The referrer header will get set internally.
channel.setNewReferrerInfo(
value,
Ci.nsIReferrerInfo.UNSAFE_URL,
true
);
} else {
channel.setRequestHeader(name, value, false);
}
}
}

if (body) {
channel.QueryInterface(Ci.nsIUploadChannel2);
const bodyStream = Cc[
"@mozilla.org/io/string-input-stream;1"
].createInstance(Ci.nsIStringInputStream);
bodyStream.setData(body, body.length);
channel.explicitSetUploadStream(bodyStream, null, -1, method, false);
}

return new Promise(resolve => {
// Make sure the fetch has completed before sending the channel id,
// so that there is a higher possibilty that the request get into the
// redux store beforehand (but this does not gurantee that).
NetUtil.asyncFetch(channel, () =>
resolve({ channelId: channel.channelId })
);
});
},

/**
* Gets the stacktrace for the specified network resource.
* @param {Number} resourceId
* The id for the network resource
* @return {Object}
* The response packet - stack trace.
*/
getStackTrace(resourceId) {
const networkEventStackTraceWatcher = getResourceWatcher(
this.targetActor,
NETWORK_EVENT_STACKTRACE
);
if (!networkEventStackTraceWatcher) {
if (!this.networkEventStackTraceWatcher) {
throw new Error("Not listening for network event stacktraces");
}
const stacktrace = networkEventStackTraceWatcher.getStackTrace(resourceId);
const stacktrace = this.networkEventStackTraceWatcher.getStackTrace(
resourceId
);
return {
stacktrace: WebConsoleUtils.removeFramesAboveDebuggerEval(stacktrace),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
asResource() {
return {
resourceType: NETWORK_EVENT,
// The browsingContextID is used by the ResourceWatcher on the client to find the related Target Front.
// The browsingContextID is used by the ResourceWatcher on the client
// to find the related Target Front.
browsingContextID: this.networkEventWatcher.watcherActor.browserElement
.browsingContext.id,
resourceId: this._channelId,
Expand Down
5 changes: 3 additions & 2 deletions devtools/server/actors/resources/network-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class NetworkEventWatcher {
* Start watching for all network events related to a given Watcher Actor.
*
* @param WatcherActor watcherActor
* The watcher actor from which we should observe network events
* The watcher actor in the parent process from which we should
* observe network events.
* @param Object options
* Dictionary object with following attributes:
* - onAvailable: mandatory function
Expand Down Expand Up @@ -91,7 +92,7 @@ class NetworkEventWatcher {
onNetworkEvent(event) {
const { channelId } = event;

if (this.networkEvents.get(channelId)) {
if (this.networkEvents.has(channelId)) {
throw new Error(
`Got notified about channel ${channelId} more than once.`
);
Expand Down
6 changes: 6 additions & 0 deletions devtools/shared/specs/network-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ const { generateActorSpec, RetVal, Arg } = require("devtools/shared/protocol");
const networkContentSpec = generateActorSpec({
typeName: "networkContent",
methods: {
sendHTTPRequest: {
request: {
request: Arg(0, "json"),
},
response: RetVal("number"),
},
getStackTrace: {
request: { resourceId: Arg(0) },
// stacktrace is an "array:string", but not always.
Expand Down

0 comments on commit ce646bb

Please sign in to comment.