Bug 1662736 - [devtools] Simplify the network request payload r=ochameau

Differential Revision: https://phabricator.services.mozilla.com/D94457
This commit is contained in:
Hubert Boma Manilla 2020-11-13 15:17:48 +00:00
Родитель 0951f9218a
Коммит a70ec38159
24 изменённых файлов: 423 добавлений и 823 удалений

Просмотреть файл

@ -325,12 +325,12 @@ class FirefoxConnector {
} }
navigate() { navigate() {
if (this.dataProvider.isPayloadQueueEmpty()) { if (!this.dataProvider.hasPendingRequests()) {
this.onReloaded(); this.onReloaded();
return; return;
} }
const listener = () => { const listener = () => {
if (this.dataProvider && !this.dataProvider.isPayloadQueueEmpty()) { if (this.dataProvider && this.dataProvider.hasPendingRequests()) {
return; return;
} }
if (this.owner) { if (this.owner) {

Просмотреть файл

@ -43,8 +43,8 @@ class FirefoxDataProvider {
// Map of the stacktrace information keyed by the actor id's // Map of the stacktrace information keyed by the actor id's
this.stackTraceRequestInfoByActorID = new Map(); this.stackTraceRequestInfoByActorID = new Map();
// Internal properties // For tracking unfinished requests
this.payloadQueue = new Map(); this.pendingRequests = new Set();
// Map[key string => Promise] used by `requestData` to prevent requesting the same // Map[key string => Promise] used by `requestData` to prevent requesting the same
// request data twice. // request data twice.
@ -84,32 +84,14 @@ class FirefoxDataProvider {
* Add a new network request to application state. * Add a new network request to application state.
* *
* @param {string} id request id * @param {string} id request id
* @param {object} data data payload will be added to application state * @param {object} resource resource payload will be added to application state
*/ */
async addRequest(id, data) { async addRequest(id, resource) {
const { startedDateTime, ...payload } = data; // Add to the pending requests which helps when deciding if the request is complete.
this.pendingRequests.add(id);
// Insert blocked reason in the payload queue as well, as we'll need it later
// when deciding if the request is complete.
this.pushRequestToQueue(id, {
blockedReason: payload.blockedReason,
});
if (this.actionsEnabled && this.actions.addRequest) { if (this.actionsEnabled && this.actions.addRequest) {
await this.actions.addRequest( await this.actions.addRequest(id, resource, true);
id,
{
...payload,
// Convert the received date/time string to a unix timestamp.
startedMs: Date.parse(startedDateTime),
// Compatibility code to support Firefox 58 and earlier that always
// send stack-trace immediately on networkEvent message.
// FF59+ supports fetching the traces lazily via requestData.
stacktrace: payload.cause.stacktrace,
},
true
);
} }
this.emit(EVENTS.REQUEST_ADDED, id); this.emit(EVENTS.REQUEST_ADDED, id);
@ -287,25 +269,10 @@ class FirefoxDataProvider {
/** /**
* Public API used by the Toolbox: Tells if there is still any pending request. * Public API used by the Toolbox: Tells if there is still any pending request.
* *
* @return {boolean} returns true if the payload queue is empty * @return {boolean} returns true if pending requests still exist in the queue.
*/ */
isPayloadQueueEmpty() { hasPendingRequests() {
return this.payloadQueue.size === 0; return this.pendingRequests.size > 0;
}
/**
* Merge upcoming networkEventUpdate payload into existing one.
*
* @param {string} id request actor id
* @param {object} payload request data payload
*/
pushRequestToQueue(id, payload) {
let payloadFromQueue = this.payloadQueue.get(id);
if (!payloadFromQueue) {
payloadFromQueue = {};
this.payloadQueue.set(id, payloadFromQueue);
}
Object.assign(payloadFromQueue, payload);
} }
/** /**
@ -359,22 +326,7 @@ class FirefoxDataProvider {
* @param {object} resource The network event resource * @param {object} resource The network event resource
*/ */
async onNetworkResourceAvailable(resource) { async onNetworkResourceAvailable(resource) {
const { const { actor, stacktraceResourceId } = resource;
actor,
cause,
fromCache,
fromServiceWorker,
isXHR,
request: { method, url },
response: { bodySize, ...responseProps },
startedDateTime,
isThirdPartyTrackingResource,
referrerPolicy,
blockedReason,
blockingExtension,
resourceId,
stacktraceResourceId,
} = resource;
// Check if a stacktrace resource exists for this network resource. // Check if a stacktrace resource exists for this network resource.
// The stacktrace event is expected to happen before the network // The stacktrace event is expected to happen before the network
@ -385,8 +337,10 @@ class FirefoxDataProvider {
lastFrame, lastFrame,
targetFront, targetFront,
} = this.stackTraces.get(stacktraceResourceId); } = this.stackTraces.get(stacktraceResourceId);
cause.stacktraceAvailable = stacktraceAvailable;
cause.lastFrame = lastFrame; resource.cause.stacktraceAvailable = stacktraceAvailable;
resource.cause.lastFrame = lastFrame;
this.stackTraces.delete(stacktraceResourceId); this.stackTraces.delete(stacktraceResourceId);
// We retrieve preliminary information about the stacktrace from the // We retrieve preliminary information about the stacktrace from the
// NETWORK_EVENT_STACKTRACE resource via `this.stackTraces` Map, // NETWORK_EVENT_STACKTRACE resource via `this.stackTraces` Map,
@ -398,45 +352,7 @@ class FirefoxDataProvider {
stacktraceResourceId, stacktraceResourceId,
}); });
} }
await this.addRequest(actor, resource);
// For resources from the resource watcher cache no updates are going to be fired
// as the resource already contains all the updated props. These need to be set so
// the UI knows the data is available on the backend.
const available = {};
[
"eventTimings",
"requestHeaders",
"requestPostData",
"responseHeaders",
"responseStart",
"responseContent",
"securityInfo",
"responseCache",
"responseCookies",
].forEach(updateType => {
if (resource.updates.includes(updateType)) {
available[`${updateType}Available`] = true;
}
});
await this.addRequest(actor, {
cause,
fromCache,
fromServiceWorker,
isXHR,
method,
startedDateTime,
url,
isThirdPartyTrackingResource,
referrerPolicy,
blockedReason,
blockingExtension,
resourceId,
mimeType: resource?.content?.mimeType,
contentSize: bodySize,
...responseProps,
...available,
});
this.emitForTests(TEST_EVENTS.NETWORK_EVENT, resource); this.emitForTests(TEST_EVENTS.NETWORK_EVENT, resource);
} }
@ -445,66 +361,23 @@ class FirefoxDataProvider {
* *
* @param {object} resource The updated network event resource. * @param {object} resource The updated network event resource.
*/ */
async onNetworkResourceUpdated(resource, update) { async onNetworkResourceUpdated(resource) {
switch (update.updateType) {
case "securityInfo":
this.pushRequestToQueue(resource.actor, {
securityState: resource.securityState,
isRacing: resource.isRacing,
});
break;
case "responseStart":
this.pushRequestToQueue(resource.actor, {
httpVersion: resource.response.httpVersion,
remoteAddress: resource.response.remoteAddress,
remotePort: resource.response.remotePort,
status: resource.response.status,
statusText: resource.response.statusText,
headersSize: resource.response.headersSize,
waitingTime: resource.response.waitingTime,
});
// Identify the channel as SSE if mimeType is event-stream. // Identify the channel as SSE if mimeType is event-stream.
if (resource.response.content.mimeType?.includes("text/event-stream")) { if (resource?.mimeType?.includes("text/event-stream")) {
await this.setEventStreamFlag(resource.actor); await this.setEventStreamFlag(resource.actor);
} }
this.emitForTests( this.pendingRequests.delete(resource.actor);
TEST_EVENTS.STARTED_RECEIVING_RESPONSE, if (this.actionsEnabled && this.actions.updateRequest) {
resource.actor await this.actions.updateRequest(resource.actor, resource, true);
);
break;
case "responseContent":
this.pushRequestToQueue(resource.actor, {
contentSize: resource.response.bodySize,
transferredSize: resource.response.transferredSize,
mimeType: resource.response.content.mimeType,
blockingExtension: resource.blockingExtension,
blockedReason: resource.blockedReason,
});
break;
case "eventTimings":
// Total time doesn't have to be always set e.g. net provider enhancer
// in Console panel is using this method to fetch data when network log
// is expanded. So, make sure to not push undefined into the payload queue
// (it could overwrite an existing value).
if (typeof resource.totalTime !== "undefined") {
this.pushRequestToQueue(resource.actor, {
totalTime: resource.totalTime,
});
}
break;
} }
// This available field helps knowing when/if updateType property is arrived // This event is fired only once per request, once all the properties are fetched
// and can be requested via `requestData` // from `onNetworkResourceUpdated`. There should be no more RDP requests after this.
this.pushRequestToQueue(resource.actor, { // Note that this event might be consumed by extension so, emit it in production
[`${update.updateType}Available`]: true, // release as well.
});
await this.onPayloadDataReceived(resource);
this.emitForTests(TEST_EVENTS.NETWORK_EVENT_UPDATED, resource.actor); this.emitForTests(TEST_EVENTS.NETWORK_EVENT_UPDATED, resource.actor);
this.emit(EVENTS.PAYLOAD_READY, resource);
} }
/** /**
@ -564,42 +437,6 @@ class FirefoxDataProvider {
// TODO: Emit an event for test here // TODO: Emit an event for test here
} }
/**
* Notify actions when events from onNetworkResourceUpdated are done, updated network event
* resources contain initial network info for each updateType and then we can invoke
* requestData to fetch its corresponded data lazily.
* Once all updateTypes of updated network event resource are available, we flush the merged
* request payload from pending queue and then update the component.
*/
async onPayloadDataReceived(resource) {
const payload = this.payloadQueue.get(resource.actor) || {};
// For blocked requests, we should only expect the request portions and not
// the response portions to be available.
if (!payload.requestHeadersAvailable || !payload.requestCookiesAvailable) {
return;
}
// For unblocked requests, we should wait for all major portions to be available.
if (
!payload.blockedReason &&
(!payload.eventTimingsAvailable || !payload.responseContentAvailable)
) {
return;
}
this.payloadQueue.delete(resource.actor);
if (this.actionsEnabled && this.actions.updateRequest) {
await this.actions.updateRequest(resource.actor, payload, true);
}
// This event is fired only once per request, once all the properties are fetched
// from `onNetworkResourceUpdated`. There should be no more RDP requests after this.
// Note that this event might be consumed by extension so, emit it in production
// release as well.
this.emit(EVENTS.PAYLOAD_READY, resource);
}
/** /**
* Public connector API to lazily request HTTP details from the backend. * Public connector API to lazily request HTTP details from the backend.
* *

Просмотреть файл

@ -170,12 +170,7 @@ HarCollector.prototype = {
for (const resource of resources) { for (const resource of resources) {
trace.log("HarCollector.onNetworkEvent; ", resource); trace.log("HarCollector.onNetworkEvent; ", resource);
const { const { actor, startedDateTime, method, url, isXHR } = resource;
actor,
startedDateTime,
request: { method, url },
isXHR,
} = resource;
const startTime = Date.parse(startedDateTime); const startTime = Date.parse(startedDateTime);
if (this.firstRequestStart == -1) { if (this.firstRequestStart == -1) {
@ -211,7 +206,7 @@ HarCollector.prototype = {
}, },
onResourceUpdated: function(updates) { onResourceUpdated: function(updates) {
for (const { resource, update } of updates) { for (const { resource } of updates) {
// Skip events from unknown actors (not in the list). // Skip events from unknown actors (not in the list).
// It can happen when there are zombie requests received after // It can happen when there are zombie requests received after
// the target is closed or multiple tabs are attached through // the target is closed or multiple tabs are attached through
@ -221,84 +216,84 @@ HarCollector.prototype = {
return; return;
} }
trace.log(
"HarCollector.onNetworkEventUpdate; " + update.updateType,
resource
);
const includeResponseBodies = Services.prefs.getBoolPref( const includeResponseBodies = Services.prefs.getBoolPref(
"devtools.netmonitor.har.includeResponseBodies" "devtools.netmonitor.har.includeResponseBodies"
); );
[
{
type: "eventTimings",
method: "getEventTimings",
callbackName: "onEventTimings",
},
{
type: "requestHeaders",
method: "getRequestHeaders",
callbackName: "onRequestHeaders",
},
{
type: "requestPostData",
method: "getRequestPostData",
callbackName: "onRequestPostData",
},
{
type: "responseHeaders",
method: "getResponseHeaders",
callbackName: "onResponseHeaders",
},
{ type: "responseStart" },
{
type: "responseContent",
method: "getResponseContent",
callbackName: "onResponseContent",
},
{
type: "requestCookies",
method: "getRequestCookies",
callbackName: "onRequestCookies",
},
{
type: "responseCookies",
method: "getResponseCookies",
callbackName: "onResponseCookies",
},
].forEach(updateType => {
trace.log(
"HarCollector.onNetworkEventUpdate; " + updateType.type,
resource
);
let request; let request;
switch (update.updateType) { if (resource[`${updateType.type}Available`]) {
case "requestHeaders": if (updateType.type == "responseStart") {
request = this.getData( file.httpVersion = resource.httpVersion;
resource.actor, file.status = resource.status;
"getRequestHeaders", file.statusText = resource.statusText;
this.onRequestHeaders } else if (updateType.type == "responseContent") {
);
break;
case "requestCookies":
request = this.getData(
resource.actor,
"getRequestCookies",
this.onRequestCookies
);
break;
case "requestPostData":
request = this.getData(
resource.actor,
"getRequestPostData",
this.onRequestPostData
);
break;
case "responseHeaders":
request = this.getData(
resource.actor,
"getResponseHeaders",
this.onResponseHeaders
);
break;
case "responseCookies":
request = this.getData(
resource.actor,
"getResponseCookies",
this.onResponseCookies
);
break;
case "responseStart":
file.httpVersion = resource.response.httpVersion;
file.status = resource.response.status;
file.statusText = resource.response.statusText;
break;
case "responseContent":
file.contentSize = resource.contentSize; file.contentSize = resource.contentSize;
file.mimeType = resource.mimeType; file.mimeType = resource.mimeType;
file.transferredSize = resource.transferredSize; file.transferredSize = resource.transferredSize;
if (includeResponseBodies) { if (includeResponseBodies) {
request = this.getData( request = this.getData(
resource.actor, resource.actor,
"getResponseContent", updateType.method,
this.onResponseContent this[updateType.callbackName]
); );
} }
break; } else {
case "eventTimings":
request = this.getData( request = this.getData(
resource.actor, resource.actor,
"getEventTimings", updateType.method,
this.onEventTimings this[updateType.callbackName]
); );
break; }
} }
if (request) { if (request) {
this.requests.push(request); this.requests.push(request);
} }
this.resetPageLoadTimeout(); this.resetPageLoadTimeout();
});
} }
}, },

Просмотреть файл

@ -5,6 +5,9 @@
"use strict"; "use strict";
const { TIMING_KEYS } = require("devtools/client/netmonitor/src/constants"); const { TIMING_KEYS } = require("devtools/client/netmonitor/src/constants");
const {
getUrlDetails,
} = require("devtools/client/netmonitor/src/utils/request-utils");
var guid = 0; var guid = 0;
@ -47,6 +50,7 @@ HarImporter.prototype = {
startedMs: startedMs, startedMs: startedMs,
method: entry.request.method, method: entry.request.method,
url: entry.request.url, url: entry.request.url,
urlDetails: getUrlDetails(entry.request.url),
isXHR: false, isXHR: false,
cause: { cause: {
loadingDocumentUri: "", loadingDocumentUri: "",

Просмотреть файл

@ -5,7 +5,6 @@
"use strict"; "use strict";
const { const {
getUrlDetails,
processNetworkUpdates, processNetworkUpdates,
} = require("devtools/client/netmonitor/src/utils/request-utils"); } = require("devtools/client/netmonitor/src/utils/request-utils");
const { const {
@ -152,11 +151,12 @@ function requestsReducer(state = Requests(), action) {
function addRequest(state, action) { function addRequest(state, action) {
const nextState = { ...state }; const nextState = { ...state };
// The target front is not used and cannot be serialized by redux
// eslint-disable-next-line no-unused-vars
const { targetFront, ...requestData } = action.data;
const newRequest = { const newRequest = {
id: action.id, id: action.id,
...action.data, ...requestData,
urlDetails: getUrlDetails(action.data.url),
}; };
nextState.requests = [...state.requests, newRequest]; nextState.requests = [...state.requests, newRequest];

Просмотреть файл

@ -585,26 +585,25 @@ async function getMessagePayload(payload, getLongString) {
/** /**
* This helper function is used for additional processing of * This helper function is used for additional processing of
* incoming network update packets. It's used by Network and * incoming network update packets. It makes sure the only valid
* Console panel reducers. * update properties and the values are correct.
* It's used by Network and Console panel reducers.
* @param {object} update
* The new update payload
* @param {object} request
* The current request in the state
*/ */
function processNetworkUpdates(update) { function processNetworkUpdates(update) {
const result = {}; const newRequest = {};
for (const [key, value] of Object.entries(update)) { for (const [key, value] of Object.entries(update)) {
if (UPDATE_PROPS.includes(key)) { if (UPDATE_PROPS.includes(key)) {
result[key] = value; newRequest[key] = value;
if (key == "requestPostData") {
switch (key) { newRequest.requestHeadersFromUploadStream = value.uploadHeaders;
case "totalTime":
result.totalTime = update.totalTime;
break;
case "requestPostData":
result.requestHeadersFromUploadStream = value.uploadHeaders;
break;
} }
} }
} }
return result; return newRequest;
} }
/** /**

Просмотреть файл

@ -80,16 +80,17 @@ function NetworkEventMessage({
source, source,
type, type,
level, level,
request, url,
method,
isXHR, isXHR,
timeStamp, timeStamp,
blockedReason, blockedReason,
httpVersion,
status,
statusText,
totalTime,
} = message; } = message;
const { response = {}, totalTime } = networkMessageUpdate;
const { httpVersion, status, statusText } = response;
const topLevelClasses = ["cm-s-mozilla"]; const topLevelClasses = ["cm-s-mozilla"];
if (isMessageNetworkError(message)) { if (isMessageNetworkError(message)) {
topLevelClasses.push("error"); topLevelClasses.push("error");
@ -139,7 +140,7 @@ function NetworkEventMessage({
const onToggle = (messageId, e) => { const onToggle = (messageId, e) => {
const shouldOpenLink = (isMacOS && e.metaKey) || (!isMacOS && e.ctrlKey); const shouldOpenLink = (isMacOS && e.metaKey) || (!isMacOS && e.ctrlKey);
if (shouldOpenLink) { if (shouldOpenLink) {
serviceContainer.openLink(request.url, e); serviceContainer.openLink(url, e);
e.stopPropagation(); e.stopPropagation();
} else if (open) { } else if (open) {
dispatch(actions.messageClose(messageId)); dispatch(actions.messageClose(messageId));
@ -149,27 +150,24 @@ function NetworkEventMessage({
}; };
// Message body components. // Message body components.
const method = dom.span({ className: "method" }, request.method); const requestMethod = dom.span({ className: "method" }, method);
const xhr = isXHR const xhr = isXHR
? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator")) ? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator"))
: null; : null;
const requestUrl = dom.span( const requestUrl = dom.span({ className: "url", title: url }, url);
{ className: "url", title: request.url },
request.url
);
const statusBody = statusInfo const statusBody = statusInfo
? dom.a({ className: "status" }, statusInfo) ? dom.a({ className: "status" }, statusInfo)
: null; : null;
const messageBody = [xhr, method, requestUrl, statusBody]; const messageBody = [xhr, requestMethod, requestUrl, statusBody];
// API consumed by Net monitor UI components. Most of the method // API consumed by Net monitor UI components. Most of the method
// are not needed in context of the Console panel (atm) and thus // are not needed in context of the Console panel (atm) and thus
// let's just provide empty implementation. // let's just provide empty implementation.
// Individual methods might be implemented step by step as needed. // Individual methods might be implemented step by step as needed.
const connector = { const connector = {
viewSourceInDebugger: (url, line, column) => { viewSourceInDebugger: (srcUrl, line, column) => {
serviceContainer.onViewSourceInDebugger({ url, line, column }); serviceContainer.onViewSourceInDebugger({ url: srcUrl, line, column });
}, },
getLongString: grip => { getLongString: grip => {
return serviceContainer.getLongString(grip); return serviceContainer.getLongString(grip);
@ -210,6 +208,7 @@ function NetworkEventMessage({
}) })
); );
const request = { url, method };
return Message({ return Message({
dispatch, dispatch,
messageId: id, messageId: id,

Просмотреть файл

@ -51,9 +51,7 @@ function enableNetProvider(webConsoleUI) {
const message = updates[action.id]; const message = updates[action.id];
if (message && !message.openedOnce && message.source == "network") { if (message && !message.openedOnce && message.source == "network") {
dataProvider.onNetworkResourceAvailable(message); dataProvider.onNetworkResourceAvailable(message);
message.updates.forEach(updateType => { dataProvider.onNetworkResourceUpdated(message);
dataProvider.onNetworkResourceUpdated(message, { updateType });
});
} }
} }
@ -69,9 +67,7 @@ function enableNetProvider(webConsoleUI) {
const open = allMessages.includes(actor); const open = allMessages.includes(actor);
if (open) { if (open) {
const message = getMessage(newState, actor); const message = getMessage(newState, actor);
message.updates.forEach(updateType => { dataProvider.onNetworkResourceUpdated(message);
dataProvider.onNetworkResourceUpdated(message, { updateType });
});
} }
} }
} }

Просмотреть файл

@ -1359,7 +1359,7 @@ function passSearchFilters(message, filters) {
// Look for a match in location. // Look for a match in location.
isTextInFrame(matchStr, message.frame) || isTextInFrame(matchStr, message.frame) ||
// Look for a match in net events. // Look for a match in net events.
isTextInNetEvent(matchStr, message.request) || isTextInNetEvent(matchStr, message) ||
// Look for a match in stack-trace. // Look for a match in stack-trace.
isTextInStackTrace(matchStr, message.stacktrace) || isTextInStackTrace(matchStr, message.stacktrace) ||
// Look for a match in messageText. // Look for a match in messageText.
@ -1445,12 +1445,10 @@ function isTextInParameter(matchStr, parameter) {
/** /**
* Returns true if given text is included in provided net event grip. * Returns true if given text is included in provided net event grip.
*/ */
function isTextInNetEvent(matchStr, request) { function isTextInNetEvent(matchStr, { method, url } = {}) {
if (!request) { if (!method && !url) {
return false; return false;
} }
const { method, url } = request;
return matchStr(method) || matchStr(url); return matchStr(method) || matchStr(url);
} }

Просмотреть файл

@ -32,6 +32,8 @@ add_task(async function() {
// clear the browser console. // clear the browser console.
await clearOutput(hud); await clearOutput(hud);
await waitForTick();
await safeCloseBrowserConsole();
}); });
async function testMessages(hud) { async function testMessages(hud) {

Просмотреть файл

@ -24,9 +24,9 @@ add_task(async function task() {
const currentTab = gBrowser.selectedTab; const currentTab = gBrowser.selectedTab;
const target = await TargetFactory.forTab(currentTab); const target = await TargetFactory.forTab(currentTab);
const toolbox = gDevTools.getToolbox(target); const toolbox = gDevTools.getToolbox(target);
const panel = toolbox.getCurrentPanel().panelWin;
const monitor = toolbox.getCurrentPanel(); const netReady = panel.api.once("NetMonitor:PayloadReady");
const netReady = monitor.panelWin.api.once("NetMonitor:PayloadReady");
// Fire an XHR POST request. // Fire an XHR POST request.
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() {
@ -46,19 +46,7 @@ add_task(async function task() {
const urlNode = messageNode.querySelector(".url"); const urlNode = messageNode.querySelector(".url");
info("Network message found."); info("Network message found.");
const onReady = new Promise(resolve => { const onReady = hud.ui.once("network-request-payload-ready");
let count = 0;
function onPayloadReady(updateCount) {
count += updateCount;
// Wait for all NETWORK_REQUEST updated events
// Otherwise we may still be having pending request
if (count == 7) {
hud.ui.off("network-request-payload-ready", onPayloadReady);
resolve();
}
}
hud.ui.on("network-request-payload-ready", onPayloadReady);
});
// Expand network log // Expand network log
urlNode.click(); urlNode.click();
@ -66,6 +54,7 @@ add_task(async function task() {
await onReady; await onReady;
info("network-request-payload-ready received"); info("network-request-payload-ready received");
await testNetworkMessage(messageNode); await testNetworkMessage(messageNode);
await waitForLazyRequests(toolbox); await waitForLazyRequests(toolbox);
}); });

Просмотреть файл

@ -27,6 +27,9 @@ registerCleanupFunction(async () => {
}); });
add_task(async function task() { add_task(async function task() {
// Make sure the filter to show all the requests is set
await pushPref("devtools.netmonitor.filters", '["all"]');
// Test that the request appears in the console. // Test that the request appears in the console.
const hud = await openNewTabAndConsole(TEST_URI); const hud = await openNewTabAndConsole(TEST_URI);
const currentTab = gBrowser.selectedTab; const currentTab = gBrowser.selectedTab;
@ -65,11 +68,14 @@ async function testNetmonitor(toolbox) {
); );
store.dispatch(Actions.batchEnable(false)); store.dispatch(Actions.batchEnable(false));
const requestItem = document.querySelector(".request-list-item");
await waitUntil(() => store.getState().requests.requests.length > 0); await waitUntil(() => store.getState().requests.requests.length > 0);
// Lets also wait until all the event timings data requested // Lets also wait until all the event timings data requested
// has completed and the column is rendered. // has completed and the column is rendered.
await waitForDOM(requestItem, ".requests-list-timings-total"); await waitForDOM(
document,
".request-list-item:first-child .requests-list-timings-total"
);
is( is(
store.getState().requests.requests.length, store.getState().requests.requests.length,

Просмотреть файл

@ -40,15 +40,7 @@ add_task(async function() {
let failed = false; let failed = false;
for (const [key, packet] of generatedStubs) { for (const [key, packet] of generatedStubs) {
// packet.updates are handle by the webconsole front, and can be updated after
// we cleaned the packet, so the order isn't guaranteed. Let's sort the array
// here so the test doesn't fail.
const existingPacket = existingStubs.stubPackets.get(key); const existingPacket = existingStubs.stubPackets.get(key);
if (packet.updates && existingPacket.updates) {
packet.updates.sort();
existingPacket.updates.sort();
}
const packetStr = JSON.stringify(packet, null, 2); const packetStr = JSON.stringify(packet, null, 2);
const existingPacketStr = JSON.stringify(existingPacket, null, 2); const existingPacketStr = JSON.stringify(existingPacket, null, 2);
is(packetStr, existingPacketStr, `"${key}" packet has expected value`); is(packetStr, existingPacketStr, `"${key}" packet has expected value`);
@ -67,7 +59,6 @@ async function generateNetworkEventStubs() {
const tab = await addTab(TEST_URI); const tab = await addTab(TEST_URI);
const resourceWatcher = await createResourceWatcherForTab(tab); const resourceWatcher = await createResourceWatcherForTab(tab);
const stacktraces = new Map(); const stacktraces = new Map();
let addNetworkStub = function() {}; let addNetworkStub = function() {};
let addNetworkUpdateStub = function() {}; let addNetworkUpdateStub = function() {};
@ -110,7 +101,6 @@ async function generateNetworkEventStubs() {
); );
for (const [key, code] of getCommands()) { for (const [key, code] of getCommands()) {
const noExpectedUpdates = 7;
const networkEventDone = new Promise(resolve => { const networkEventDone = new Promise(resolve => {
addNetworkStub = resource => { addNetworkStub = resource => {
stubs.set(key, getCleanedPacket(key, getOrderedResource(resource))); stubs.set(key, getCleanedPacket(key, getOrderedResource(resource)));
@ -118,12 +108,8 @@ async function generateNetworkEventStubs() {
}; };
}); });
const networkEventUpdateDone = new Promise(resolve => { const networkEventUpdateDone = new Promise(resolve => {
let updateCount = 0;
addNetworkUpdateStub = resource => { addNetworkUpdateStub = resource => {
const updateKey = `${key} update`; const updateKey = `${key} update`;
// make sure all the updates have been happened
if (updateCount >= noExpectedUpdates) {
// make sure the network event stub contains all the updates
stubs.set(key, getCleanedPacket(key, getOrderedResource(resource))); stubs.set(key, getCleanedPacket(key, getOrderedResource(resource)));
stubs.set( stubs.set(
updateKey, updateKey,
@ -132,11 +118,7 @@ async function generateNetworkEventStubs() {
// Hand-picking only what we need should prevent this. // Hand-picking only what we need should prevent this.
getCleanedPacket(updateKey, getOrderedResource(resource)) getCleanedPacket(updateKey, getOrderedResource(resource))
); );
resolve(); resolve();
} else {
updateCount++;
}
}; };
}); });
@ -169,15 +151,23 @@ async function generateNetworkEventStubs() {
function getOrderedResource(resource) { function getOrderedResource(resource) {
return { return {
resourceType: resource.resourceType, resourceType: resource.resourceType,
_type: resource._type,
timeStamp: resource.timeStamp, timeStamp: resource.timeStamp,
node: resource.node,
actor: resource.actor, actor: resource.actor,
startedDateTime: resource.startedDateTime, startedDateTime: resource.startedDateTime,
request: resource.request, method: resource.method,
url: resource.url,
isXHR: resource.isXHR, isXHR: resource.isXHR,
cause: resource.cause, cause: resource.cause,
response: resource.response, httpVersion: resource.httpVersion,
status: resource.status,
statusText: resource.statusText,
headersSize: resource.headersSize,
remoteAddress: resource.remoteAddress,
remotePort: resource.remotePort,
mimeType: resource.mimeType,
waitingTime: resource.waitingTime,
contentSize: resource.contentSize,
transferredSize: resource.transferredSize,
timings: resource.timings, timings: resource.timings,
private: resource.private, private: resource.private,
fromCache: resource.fromCache, fromCache: resource.fromCache,
@ -185,10 +175,11 @@ function getOrderedResource(resource) {
isThirdPartyTrackingResource: resource.isThirdPartyTrackingResource, isThirdPartyTrackingResource: resource.isThirdPartyTrackingResource,
referrerPolicy: resource.referrerPolicy, referrerPolicy: resource.referrerPolicy,
blockedReason: resource.blockedReason, blockedReason: resource.blockedReason,
blockingExtension: resource.blockingExtension,
channelId: resource.channelId, channelId: resource.channelId,
updates: resource.updates,
totalTime: resource.totalTime, totalTime: resource.totalTime,
securityState: resource.securityState, securityState: resource.securityState,
responseCache: resource.responseCache,
isRacing: resource.isRacing, isRacing: resource.isRacing,
}; };
} }

Просмотреть файл

@ -1704,7 +1704,12 @@ function toggleLayout(hud) {
async function waitForLazyRequests(toolbox) { async function waitForLazyRequests(toolbox) {
const { wrapper } = toolbox.getCurrentPanel().hud.ui; const { wrapper } = toolbox.getCurrentPanel().hud.ui;
return waitUntil(() => { return waitUntil(() => {
return !wrapper.networkDataProvider.lazyRequestData.size; return (
!wrapper.networkDataProvider.lazyRequestData.size &&
// Make sure that batched request updates are all complete
// as they trigger late lazy data requests.
!wrapper.queuedRequestUpdates.length
);
}); });
} }

Просмотреть файл

@ -296,29 +296,8 @@ function getCleanedPacket(key, packet) {
res.actor = existingPacket.actor; res.actor = existingPacket.actor;
} }
if (res?.request?.headersSize && existingPacket?.request?.headersSize) { if (res.waitingTime && existingPacket.waitingTime) {
res.request.headersSize = existingPacket.request.headersSize; res.waitingTime = existingPacket.waitingTime;
}
if (res?.response?.headersSize && existingPacket?.response?.headersSize) {
res.response.headersSize = existingPacket.response.headersSize;
}
if (res?.response?.bodySize && existingPacket?.response?.bodySize) {
res.response.bodySize = existingPacket.response.bodySize;
}
if (
res?.response?.transferredSize &&
existingPacket?.response?.transferredSize
) {
res.response.transferredSize = existingPacket.response.transferredSize;
}
if (res?.response?.waitingTime && existingPacket?.response?.waitingTime) {
res.response.waitingTime = existingPacket.response.waitingTime;
}
if (res.updates && Array.isArray(res.updates)) {
res.updates.sort();
} }
if (res.helperResult) { if (res.helperResult) {

Просмотреть файл

@ -23,15 +23,11 @@ rawPackets.set(`GET request`, {
"timeStamp": 1572867483805, "timeStamp": 1572867483805,
"actor": "server0.conn0.netEvent4", "actor": "server0.conn0.netEvent4",
"startedDateTime": "2019-11-04T11:06:34.542Z", "startedDateTime": "2019-11-04T11:06:34.542Z",
"request": {
"url": "http://example.com/inexistent.html",
"method": "GET", "method": "GET",
"headersSize": 385 "url": "http://example.com/inexistent.html",
},
"isXHR": false, "isXHR": false,
"cause": { "cause": {
"type": "img", "type": "img",
"loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
"stacktraceAvailable": true, "stacktraceAvailable": true,
"lastFrame": { "lastFrame": {
"filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
@ -41,34 +37,20 @@ rawPackets.set(`GET request`, {
"asyncCause": null "asyncCause": null
} }
}, },
"response": {
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"status": "404", "status": "404",
"statusText": "Not Found", "statusText": "Not Found",
"headersSize": 160,
"remoteAddress": "127.0.0.1", "remoteAddress": "127.0.0.1",
"remotePort": 8888, "remotePort": 8888,
"content": { "mimeType": "text/html; charset=utf-8",
"mimeType": "text/html; charset=utf-8"
},
"waitingTime": 1, "waitingTime": 1,
"bodySize": 418, "contentSize": 418,
"transferredSize": 578 "transferredSize": 578,
},
"timings": {}, "timings": {},
"private": false, "private": false,
"isThirdPartyTrackingResource": false, "isThirdPartyTrackingResource": false,
"referrerPolicy": "no-referrer-when-downgrade", "referrerPolicy": "no-referrer-when-downgrade",
"updates": [ "blockedReason": 0,
"eventTimings",
"requestCookies",
"requestHeaders",
"responseContent",
"responseCookies",
"responseHeaders",
"responseStart",
"securityInfo"
],
"totalTime": 2, "totalTime": 2,
"securityState": "insecure", "securityState": "insecure",
"isRacing": false "isRacing": false
@ -79,15 +61,11 @@ rawPackets.set(`GET request update`, {
"timeStamp": 1572867483805, "timeStamp": 1572867483805,
"actor": "server0.conn0.netEvent5", "actor": "server0.conn0.netEvent5",
"startedDateTime": "2020-07-07T14:41:14.572Z", "startedDateTime": "2020-07-07T14:41:14.572Z",
"request": {
"url": "http://example.com/inexistent.html",
"method": "GET", "method": "GET",
"headersSize": 385 "url": "http://example.com/inexistent.html",
},
"isXHR": false, "isXHR": false,
"cause": { "cause": {
"type": "img", "type": "img",
"loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
"stacktraceAvailable": true, "stacktraceAvailable": true,
"lastFrame": { "lastFrame": {
"filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
@ -97,34 +75,20 @@ rawPackets.set(`GET request update`, {
"asyncCause": null "asyncCause": null
} }
}, },
"response": {
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"status": "404", "status": "404",
"statusText": "Not Found", "statusText": "Not Found",
"headersSize": 160,
"remoteAddress": "127.0.0.1", "remoteAddress": "127.0.0.1",
"remotePort": 8888, "remotePort": 8888,
"content": { "mimeType": "text/html; charset=utf-8",
"mimeType": "text/html; charset=utf-8"
},
"waitingTime": 1, "waitingTime": 1,
"bodySize": 418, "contentSize": 418,
"transferredSize": 578 "transferredSize": 578,
},
"timings": {}, "timings": {},
"private": false, "private": false,
"isThirdPartyTrackingResource": false, "isThirdPartyTrackingResource": false,
"referrerPolicy": "no-referrer-when-downgrade", "referrerPolicy": "no-referrer-when-downgrade",
"updates": [ "blockedReason": 0,
"eventTimings",
"requestCookies",
"requestHeaders",
"responseContent",
"responseCookies",
"responseHeaders",
"responseStart",
"securityInfo"
],
"totalTime": 3, "totalTime": 3,
"securityState": "insecure", "securityState": "insecure",
"isRacing": false "isRacing": false
@ -135,15 +99,11 @@ rawPackets.set(`XHR GET request`, {
"timeStamp": 1572867483805, "timeStamp": 1572867483805,
"actor": "server0.conn0.netEvent21", "actor": "server0.conn0.netEvent21",
"startedDateTime": "2020-07-07T14:41:14.612Z", "startedDateTime": "2020-07-07T14:41:14.612Z",
"request": {
"url": "http://example.com/inexistent.html",
"method": "GET", "method": "GET",
"headersSize": 385 "url": "http://example.com/inexistent.html",
},
"isXHR": true, "isXHR": true,
"cause": { "cause": {
"type": "xhr", "type": "xhr",
"loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
"stacktraceAvailable": true, "stacktraceAvailable": true,
"lastFrame": { "lastFrame": {
"filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
@ -153,34 +113,20 @@ rawPackets.set(`XHR GET request`, {
"asyncCause": null "asyncCause": null
} }
}, },
"response": {
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"status": "404", "status": "404",
"statusText": "Not Found", "statusText": "Not Found",
"headersSize": 160,
"remoteAddress": "127.0.0.1", "remoteAddress": "127.0.0.1",
"remotePort": 8888, "remotePort": 8888,
"content": { "mimeType": "text/html; charset=utf-8",
"mimeType": "text/html; charset=utf-8" "waitingTime": 1,
}, "contentSize": 418,
"waitingTime": 2, "transferredSize": 578,
"bodySize": 418,
"transferredSize": 578
},
"timings": {}, "timings": {},
"private": false, "private": false,
"isThirdPartyTrackingResource": false, "isThirdPartyTrackingResource": false,
"referrerPolicy": "no-referrer-when-downgrade", "referrerPolicy": "no-referrer-when-downgrade",
"updates": [ "blockedReason": 0,
"eventTimings",
"requestCookies",
"requestHeaders",
"responseContent",
"responseCookies",
"responseHeaders",
"responseStart",
"securityInfo"
],
"totalTime": 1, "totalTime": 1,
"securityState": "insecure", "securityState": "insecure",
"isRacing": false "isRacing": false
@ -190,15 +136,11 @@ rawPackets.set(`XHR GET request update`, {
"resourceType": "network-event", "resourceType": "network-event",
"timeStamp": 1572867483805, "timeStamp": 1572867483805,
"actor": "server0.conn0.netEvent20", "actor": "server0.conn0.netEvent20",
"request": {
"url": "http://example.com/inexistent.html",
"method": "GET", "method": "GET",
"headersSize": 385 "url": "http://example.com/inexistent.html",
},
"isXHR": true, "isXHR": true,
"cause": { "cause": {
"type": "xhr", "type": "xhr",
"loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
"stacktraceAvailable": true, "stacktraceAvailable": true,
"lastFrame": { "lastFrame": {
"filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
@ -208,34 +150,20 @@ rawPackets.set(`XHR GET request update`, {
"asyncCause": null "asyncCause": null
} }
}, },
"response": {
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"status": "404", "status": "404",
"statusText": "Not Found", "statusText": "Not Found",
"headersSize": 160,
"remoteAddress": "127.0.0.1", "remoteAddress": "127.0.0.1",
"remotePort": 8888, "remotePort": 8888,
"content": { "mimeType": "text/html; charset=utf-8",
"mimeType": "text/html; charset=utf-8" "waitingTime": 1,
}, "contentSize": 418,
"waitingTime": 2, "transferredSize": 578,
"bodySize": 418,
"transferredSize": 578
},
"timings": {}, "timings": {},
"private": false, "private": false,
"isThirdPartyTrackingResource": false, "isThirdPartyTrackingResource": false,
"referrerPolicy": "no-referrer-when-downgrade", "referrerPolicy": "no-referrer-when-downgrade",
"updates": [ "blockedReason": 0,
"eventTimings",
"requestCookies",
"requestHeaders",
"responseContent",
"responseCookies",
"responseHeaders",
"responseStart",
"securityInfo"
],
"totalTime": 1, "totalTime": 1,
"securityState": "insecure", "securityState": "insecure",
"isRacing": false "isRacing": false
@ -246,15 +174,11 @@ rawPackets.set(`XHR POST request`, {
"timeStamp": 1572867483805, "timeStamp": 1572867483805,
"actor": "server0.conn0.netEvent36", "actor": "server0.conn0.netEvent36",
"startedDateTime": "2019-11-04T11:06:35.007Z", "startedDateTime": "2019-11-04T11:06:35.007Z",
"request": {
"url": "http://example.com/inexistent.html",
"method": "POST", "method": "POST",
"headersSize": 385 "url": "http://example.com/inexistent.html",
},
"isXHR": true, "isXHR": true,
"cause": { "cause": {
"type": "xhr", "type": "xhr",
"loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
"stacktraceAvailable": true, "stacktraceAvailable": true,
"lastFrame": { "lastFrame": {
"filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
@ -264,34 +188,20 @@ rawPackets.set(`XHR POST request`, {
"asyncCause": null "asyncCause": null
} }
}, },
"response": {
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"status": "404", "status": "404",
"statusText": "Not Found", "statusText": "Not Found",
"headersSize": 160,
"remoteAddress": "127.0.0.1", "remoteAddress": "127.0.0.1",
"remotePort": 8888, "remotePort": 8888,
"content": { "mimeType": "text/html; charset=utf-8",
"mimeType": "text/html; charset=utf-8" "waitingTime": 1,
}, "contentSize": 418,
"waitingTime": 2, "transferredSize": 578,
"bodySize": 418,
"transferredSize": 578
},
"timings": {}, "timings": {},
"private": false, "private": false,
"isThirdPartyTrackingResource": false, "isThirdPartyTrackingResource": false,
"referrerPolicy": "no-referrer-when-downgrade", "referrerPolicy": "no-referrer-when-downgrade",
"updates": [ "blockedReason": 0,
"eventTimings",
"requestCookies",
"requestHeaders",
"responseContent",
"responseCookies",
"responseHeaders",
"responseStart",
"securityInfo"
],
"totalTime": 1, "totalTime": 1,
"securityState": "insecure", "securityState": "insecure",
"isRacing": false "isRacing": false
@ -301,15 +211,11 @@ rawPackets.set(`XHR POST request update`, {
"resourceType": "network-event", "resourceType": "network-event",
"timeStamp": 1572867483805, "timeStamp": 1572867483805,
"actor": "server0.conn0.netEvent36", "actor": "server0.conn0.netEvent36",
"request": {
"url": "http://example.com/inexistent.html",
"method": "POST", "method": "POST",
"headersSize": 385 "url": "http://example.com/inexistent.html",
},
"isXHR": true, "isXHR": true,
"cause": { "cause": {
"type": "xhr", "type": "xhr",
"loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
"stacktraceAvailable": true, "stacktraceAvailable": true,
"lastFrame": { "lastFrame": {
"filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
@ -319,34 +225,20 @@ rawPackets.set(`XHR POST request update`, {
"asyncCause": null "asyncCause": null
} }
}, },
"response": {
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"status": "404", "status": "404",
"statusText": "Not Found", "statusText": "Not Found",
"headersSize": 160,
"remoteAddress": "127.0.0.1", "remoteAddress": "127.0.0.1",
"remotePort": 8888, "remotePort": 8888,
"content": { "mimeType": "text/html; charset=utf-8",
"mimeType": "text/html; charset=utf-8" "waitingTime": 1,
}, "contentSize": 418,
"waitingTime": 2, "transferredSize": 578,
"bodySize": 418,
"transferredSize": 578
},
"timings": {}, "timings": {},
"private": false, "private": false,
"isThirdPartyTrackingResource": false, "isThirdPartyTrackingResource": false,
"referrerPolicy": "no-referrer-when-downgrade", "referrerPolicy": "no-referrer-when-downgrade",
"updates": [ "blockedReason": 0,
"eventTimings",
"requestCookies",
"requestHeaders",
"responseContent",
"responseCookies",
"responseHeaders",
"responseStart",
"securityInfo"
],
"totalTime": 2, "totalTime": 2,
"securityState": "insecure", "securityState": "insecure",
"isRacing": false "isRacing": false

Просмотреть файл

@ -6,9 +6,6 @@
const Services = require("Services"); const Services = require("Services");
const l10n = require("devtools/client/webconsole/utils/l10n"); const l10n = require("devtools/client/webconsole/utils/l10n");
const {
getUrlDetails,
} = require("devtools/client/netmonitor/src/utils/request-utils");
const { const {
ResourceWatcher, ResourceWatcher,
} = require("devtools/shared/resources/resource-watcher"); } = require("devtools/shared/resources/resource-watcher");
@ -362,24 +359,7 @@ function transformCSSMessageResource(cssMessageResource) {
} }
function transformNetworkEventResource(networkEventResource) { function transformNetworkEventResource(networkEventResource) {
return new NetworkEventMessage({ return new NetworkEventMessage(networkEventResource);
targetFront: networkEventResource.targetFront,
actor: networkEventResource.actor,
isXHR: networkEventResource.isXHR,
request: networkEventResource.request,
response: networkEventResource.response,
timeStamp: networkEventResource.timeStamp,
totalTime: networkEventResource.totalTime,
url: networkEventResource.request.url,
urlDetails: getUrlDetails(networkEventResource.request.url),
method: networkEventResource.request.method,
updates: networkEventResource.updates,
cause: networkEventResource.cause,
private: networkEventResource.private,
securityState: networkEventResource.securityState,
chromeContext: networkEventResource.chromeContext,
blockedReason: networkEventResource.blockedReason,
});
} }
function transformEvaluationResultPacket(packet) { function transformEvaluationResultPacket(packet) {
@ -790,8 +770,8 @@ function getNaturalOrder(messageA, messageB) {
function isMessageNetworkError(message) { function isMessageNetworkError(message) {
return ( return (
message.source === MESSAGE_SOURCE.NETWORK && message.source === MESSAGE_SOURCE.NETWORK &&
message?.response?.status && message?.status &&
message.response.status.toString().match(/^[4,5]\d\d$/) message?.status.toString().match(/^[4,5]\d\d$/)
); );
} }

Просмотреть файл

@ -423,32 +423,13 @@ class WebConsoleUI {
} }
_onResourceUpdated(updates) { _onResourceUpdated(updates) {
const messages = []; const messageUpdates = updates
for (const { resource } of updates) { .filter(
if ( ({ resource }) =>
resource.resourceType == this.hud.resourceWatcher.TYPES.NETWORK_EVENT resource.resourceType == this.hud.resourceWatcher.TYPES.NETWORK_EVENT
) { )
// network-message-updated will emit when all the update message arrives. .map(({ resource }) => resource);
// Since we can't ensure the order of the network update, we check this.wrapper.dispatchMessagesUpdate(messageUpdates);
// that message.updates has all we need.
// Note that 'requestPostData' is sent only for POST requests, so we need
// to count with that.
const NUMBER_OF_NETWORK_UPDATE = 8;
let expectedLength = NUMBER_OF_NETWORK_UPDATE;
if (resource.updates.includes("responseCache")) {
expectedLength++;
}
if (resource.updates.includes("requestPostData")) {
expectedLength++;
}
if (resource.updates.length === expectedLength) {
messages.push(resource);
}
}
}
this.wrapper.dispatchMessagesUpdate(messages);
} }
/** /**

Просмотреть файл

@ -30,23 +30,30 @@ module.exports = async function({
} }
const webConsoleFront = await targetFront.getFront("console"); const webConsoleFront = await targetFront.getFront("console");
const _resources = new Map(); const resources = new Map();
function onNetworkEvent(packet) { function onNetworkEvent(packet) {
const actor = packet.eventActor; const actor = packet.eventActor;
const resource = {
resources.set(actor.actor, {
resourceId: actor.channelId,
resourceType: ResourceWatcher.TYPES.NETWORK_EVENT,
isBlocked: !!actor.blockedReason,
types: [],
resourceUpdates: {},
});
onAvailable([
{
resourceId: actor.channelId, resourceId: actor.channelId,
resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, resourceType: ResourceWatcher.TYPES.NETWORK_EVENT,
timeStamp: actor.timeStamp, timeStamp: actor.timeStamp,
actor: actor.actor, actor: actor.actor,
startedDateTime: actor.startedDateTime, startedDateTime: actor.startedDateTime,
request: {
url: actor.url, url: actor.url,
method: actor.method, method: actor.method,
},
isXHR: actor.isXHR, isXHR: actor.isXHR,
cause: actor.cause, cause: { type: actor.cause.type },
response: {},
timings: {}, timings: {},
private: actor.private, private: actor.private,
fromCache: actor.fromCache, fromCache: actor.fromCache,
@ -59,61 +66,44 @@ module.exports = async function({
actor.cause.type == "websocket" actor.cause.type == "websocket"
? actor.url.replace(/^http/, "ws") ? actor.url.replace(/^http/, "ws")
: actor.channelId, : actor.channelId,
updates: [], },
}; ]);
// Lets remove the stacktrace info here as
// it is passed from the the server by the NETWORK_EVENT_STACKTRACE
// resource type.
delete resource.cause.stacktraceAvailable;
delete resource.cause.lastFrame;
_resources.set(actor.actor, resource);
onAvailable([resource]);
} }
function onNetworkEventUpdate(packet) { function onNetworkEventUpdate(packet) {
const resource = _resources.get(packet.from); const resource = resources.get(packet.from);
if (!resource) { if (!resource) {
return; return;
} }
const updateType = packet.updateType; const {
const resourceUpdates = {}; types,
resourceUpdates.updates = [...resource.updates, updateType]; resourceUpdates,
resourceId,
resourceType,
isBlocked,
} = resource;
switch (updateType) { switch (packet.updateType) {
case "requestHeaders":
resourceUpdates.request = Object.assign({}, resource.request, {
headersSize: packet.headersSize,
});
break;
case "requestPostData": case "requestPostData":
resourceUpdates.request = Object.assign({}, resource.request, { resourceUpdates.contentSize = packet.dataSize;
bodySize: packet.dataSize,
});
break; break;
case "responseStart": case "responseStart":
resourceUpdates.response = Object.assign({}, resource.response, { resourceUpdates.httpVersion = packet.response.httpVersion;
httpVersion: packet.response.httpVersion, resourceUpdates.status = packet.response.status;
status: packet.response.status, resourceUpdates.statusText = packet.response.statusText;
statusText: packet.response.statusText, resourceUpdates.remoteAddress = packet.response.remoteAddress;
headersSize: packet.response.headersSize, resourceUpdates.remotePort = packet.response.remotePort;
remoteAddress: packet.response.remoteAddress, resourceUpdates.mimeType = packet.response.mimeType;
remotePort: packet.response.remotePort, resourceUpdates.waitingTime = packet.response.waitingTime;
content: {
mimeType: packet.response.mimeType,
},
waitingTime: packet.response.waitingTime,
});
break; break;
case "responseContent": case "responseContent":
resourceUpdates.response = Object.assign({}, resource.response, { resourceUpdates.contentSize = packet.contentSize;
bodySize: packet.contentSize, resourceUpdates.transferredSize = packet.transferredSize;
transferredSize: packet.transferredSize, resourceUpdates.mimeType = packet.mimeType;
content: { mimeType: packet.mimeType }, resourceUpdates.blockingExtension = packet.blockingExtension;
}); resourceUpdates.blockedReason = packet.blockedReason;
break; break;
case "eventTimings": case "eventTimings":
resourceUpdates.totalTime = packet.totalTime; resourceUpdates.totalTime = packet.totalTime;
@ -123,40 +113,40 @@ module.exports = async function({
resourceUpdates.isRacing = packet.isRacing; resourceUpdates.isRacing = packet.isRacing;
break; break;
case "responseCache": case "responseCache":
resourceUpdates.response = Object.assign({}, resource.response, { resourceUpdates.responseCache = packet.responseCache;
responseCache: packet.responseCache,
});
break; break;
} }
// Update local resource. resourceUpdates[`${packet.updateType}Available`] = true;
Object.assign(resource, resourceUpdates); types.push(packet.updateType);
if (isBlocked) {
// Blocked requests
if (
!types.includes("requestHeaders") ||
!types.includes("requestCookies")
) {
return;
}
} else if (
// Un-blocked requests
!types.includes("requestHeaders") ||
!types.includes("requestCookies") ||
!types.includes("eventTimings") ||
!types.includes("responseContent")
) {
return;
}
onUpdated([ onUpdated([
{ {
resourceType: resource.resourceType, resourceType,
resourceId: resource.resourceId, resourceId,
resourceUpdates, resourceUpdates,
updateType,
}, },
]); ]);
if (resource.blockedReason) { resources.delete(resource.actor);
// Blocked requests
if (
resource.updates.includes("requestHeaders") &&
resource.updates.includes("requestCookies")
) {
_resources.delete(resource.actor);
}
} else if (
resource.updates.includes("requestHeaders") &&
resource.updates.includes("requestCookies") &&
resource.updates.includes("eventTimings") &&
resource.updates.includes("responseContent")
) {
_resources.delete(resource.actor);
}
} }
webConsoleFront.on("serverNetworkEvent", onNetworkEvent); webConsoleFront.on("serverNetworkEvent", onNetworkEvent);

Просмотреть файл

@ -847,4 +847,6 @@ const ResourceTransformers = {
.ROOT_NODE]: require("devtools/shared/resources/transformers/root-node"), .ROOT_NODE]: require("devtools/shared/resources/transformers/root-node"),
[ResourceWatcher.TYPES [ResourceWatcher.TYPES
.SESSION_STORAGE]: require("devtools/shared/resources/transformers/storage-session-storage.js"), .SESSION_STORAGE]: require("devtools/shared/resources/transformers/storage-session-storage.js"),
[ResourceWatcher.TYPES
.NETWORK_EVENT]: require("devtools/shared/resources/transformers/network-events"),
}; };

Просмотреть файл

@ -35,7 +35,6 @@ support-files =
[browser_resources_getAllResources.js] [browser_resources_getAllResources.js]
[browser_resources_network_event_stacktraces.js] [browser_resources_network_event_stacktraces.js]
[browser_resources_network_events.js] [browser_resources_network_events.js]
skip-if = os == "linux" #Bug 1655183
[browser_resources_platform_messages.js] [browser_resources_platform_messages.js]
[browser_resources_root_node.js] [browser_resources_root_node.js]
[browser_resources_several_resources.js] [browser_resources_several_resources.js]

Просмотреть файл

@ -18,63 +18,36 @@ add_task(async function() {
await testNetworkEventResourcesWithExistingResources(); await testNetworkEventResourcesWithExistingResources();
await testNetworkEventResourcesWithoutExistingResources(); await testNetworkEventResourcesWithoutExistingResources();
// These tests would be enabled when the server-side work is done. See Bug 1644191
// info("Test network events server listener"); // info("Test network events server listener");
// await pushPref("devtools.testing.enableServerWatcherSupport", true); // await pushPref("devtools.testing.enableServerWatcherSupport", true);
// await testNetworkEventResources(); // await testNetworkEventResourcesWithExistingResources();
// await testNetworkEventResourcesWithIgnoreExistingResources(); // await testNetworkEventResourcesWithoutExistingResources();
}); });
const UPDATES = [
"requestHeaders",
"requestCookies",
"responseStart",
"securityInfo",
"responseHeaders",
"responseCookies",
"eventTimings",
"responseContent",
];
async function testNetworkEventResourcesWithExistingResources() { async function testNetworkEventResourcesWithExistingResources() {
info(`Tests for network event resources with the existing resources`); info(`Tests for network event resources with the existing resources`);
await testNetworkEventResources({ await testNetworkEventResources({
ignoreExistingResources: false, ignoreExistingResources: false,
// 1 available event fired, for the existing resource in the cache. // 1 available event fired, for the existing resource in the cache.
// 1 available event fired, when live request is created. // 1 available event fired, when live request is created.
expectedOnAvailableCounts: 2, totalExpectedOnAvailableCounts: 2,
// 8 update events fired, when live request is updated. // 1 update events fired, when live request is updated.
expectedOnUpdatedCounts: 8, totalExpectedOnUpdatedCounts: 1,
expectedResourcesOnAvailable: { expectedResourcesOnAvailable: {
[`${EXAMPLE_DOMAIN}existing_post.html`]: { [`${EXAMPLE_DOMAIN}cached_post.html`]: {
resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, resourceType: ResourceWatcher.TYPES.NETWORK_EVENT,
request: {
url: `${EXAMPLE_DOMAIN}existing_post.html`,
method: "POST", method: "POST",
}, },
// gets reset based on the type of request
updates: [],
},
[`${EXAMPLE_DOMAIN}live_get.html`]: { [`${EXAMPLE_DOMAIN}live_get.html`]: {
resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, resourceType: ResourceWatcher.TYPES.NETWORK_EVENT,
request: {
url: `${EXAMPLE_DOMAIN}live_get.html`,
method: "GET", method: "GET",
}, },
// Throttling makes us receive the available event
// after processing all the updates events
updates: UPDATES,
},
}, },
expectedResourcesOnUpdated: { expectedResourcesOnUpdated: {
[`${EXAMPLE_DOMAIN}live_get.html`]: { [`${EXAMPLE_DOMAIN}live_get.html`]: {
resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, resourceType: ResourceWatcher.TYPES.NETWORK_EVENT,
request: {
url: `${EXAMPLE_DOMAIN}live_get.html`,
method: "GET", method: "GET",
}, },
updates: UPDATES,
},
}, },
}); });
} }
@ -84,30 +57,20 @@ async function testNetworkEventResourcesWithoutExistingResources() {
await testNetworkEventResources({ await testNetworkEventResources({
ignoreExistingResources: true, ignoreExistingResources: true,
// 1 available event fired, when live request is created. // 1 available event fired, when live request is created.
expectedOnAvailableCounts: 1, totalExpectedOnAvailableCounts: 1,
// 8 update events fired, when live request is updated. // 1 update events fired, when live request is updated.
expectedOnUpdatedCounts: 8, totalExpectedOnUpdatedCounts: 1,
expectedResourcesOnAvailable: { expectedResourcesOnAvailable: {
[`${EXAMPLE_DOMAIN}live_get.html`]: { [`${EXAMPLE_DOMAIN}live_get.html`]: {
resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, resourceType: ResourceWatcher.TYPES.NETWORK_EVENT,
request: {
url: `${EXAMPLE_DOMAIN}live_get.html`,
method: "GET", method: "GET",
}, },
// Throttling makes us receive the available event
// after processing all the updates events
updates: UPDATES,
},
}, },
expectedResourcesOnUpdated: { expectedResourcesOnUpdated: {
[`${EXAMPLE_DOMAIN}live_get.html`]: { [`${EXAMPLE_DOMAIN}live_get.html`]: {
resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, resourceType: ResourceWatcher.TYPES.NETWORK_EVENT,
request: {
url: `${EXAMPLE_DOMAIN}live_get.html`,
method: "GET", method: "GET",
}, },
updates: UPDATES,
},
}, },
}); });
} }
@ -118,55 +81,35 @@ async function testNetworkEventResources(options) {
tab tab
); );
const actualResourcesOnAvailable = {};
const actualResourcesOnUpdated = {};
info( info(
`Trigger some network requests *before* calling ResourceWatcher.watchResources `Trigger some network requests *before* calling ResourceWatcher.watchResources
in order to assert the behavior of already existing network events.` in order to assert the behavior of already existing network events.`
); );
let onResourceAvailable = () => {}; let onResourceAvailable = () => {};
let onResourceUpdated = () => {}; let onResourceUpdated = () => {};
const waitOnAllExpectedUpdatesForExistingRequests = new Promise(resolve => {
const existingRequestUrl = `${EXAMPLE_DOMAIN}existing_post.html`;
// Lets make sure there is already a network event resource in the cache.
const waitOnRequestForResourceWatcherCache = new Promise(resolve => {
onResourceAvailable = resources => { onResourceAvailable = resources => {
for (const resource of resources) { for (const resource of resources) {
// A blocked request would only have two updates so lets also resolve here is(
if ( resource.resourceType,
resource.request.url == existingRequestUrl && ResourceWatcher.TYPES.NETWORK_EVENT,
resource.blockedReason && "Received a network event resource"
resource.updates.length == 2 );
) {
// Reset the updates expectation as the request is blocked
if (options.expectedResourcesOnAvailable[resource.request.url]) {
options.expectedResourcesOnAvailable[
resource.request.url
].updates = [...resource.updates];
}
resolve();
}
} }
}; };
onResourceUpdated = updates => { onResourceUpdated = updates => {
for (const { resource } of updates) { for (const { resource } of updates) {
// Wait until all the update events have fired for the existing request. is(
// Handle both blocked and unblocked requests resource.resourceType,
if ( ResourceWatcher.TYPES.NETWORK_EVENT,
resource.request.url == existingRequestUrl && "Received a network update event resource"
(resource.updates.length == 8 || );
(resource.blockedReason && resource.updates.length == 2))
) {
// Makes sure the expectation always correct (for either blocked or unblocked requests)
if (options.expectedResourcesOnAvailable[resource.request.url]) {
options.expectedResourcesOnAvailable[
resource.request.url
].updates = [...resource.updates];
}
resolve(); resolve();
} }
}
}; };
resourceWatcher resourceWatcher
@ -177,23 +120,29 @@ async function testNetworkEventResources(options) {
.then(() => { .then(() => {
// We can only trigger the requests once `watchResources` settles, otherwise the // We can only trigger the requests once `watchResources` settles, otherwise the
// thread might be paused. // thread might be paused.
triggerNetworkRequests(tab.linkedBrowser, EXISTING_REQUESTS_COMMANDS); triggerNetworkRequests(tab.linkedBrowser, [cachedRequest]);
}); });
}); });
await waitOnAllExpectedUpdatesForExistingRequests; await waitOnRequestForResourceWatcherCache;
const actualResourcesOnAvailable = {};
const actualResourcesOnUpdated = {};
let { let {
expectedOnAvailableCounts, totalExpectedOnAvailableCounts,
expectedOnUpdatedCounts, totalExpectedOnUpdatedCounts,
expectedResourcesOnAvailable,
expectedResourcesOnUpdated,
ignoreExistingResources, ignoreExistingResources,
} = options; } = options;
const waitForAllOnAvailableEvents = waitUntil( const waitForAllExpectedOnAvailableEvents = waitUntil(
() => expectedOnAvailableCounts == 0 () => totalExpectedOnAvailableCounts == 0
); );
const waitForAllOnUpdatedEvents = waitUntil( const waitForAllExpectedOnUpdatedEvents = waitUntil(
() => expectedOnUpdatedCounts == 0 () => totalExpectedOnUpdatedCounts == 0
); );
const onAvailable = resources => { const onAvailable = resources => {
@ -203,13 +152,12 @@ async function testNetworkEventResources(options) {
ResourceWatcher.TYPES.NETWORK_EVENT, ResourceWatcher.TYPES.NETWORK_EVENT,
"Received a network event resource" "Received a network event resource"
); );
actualResourcesOnAvailable[resource.request.url] = { actualResourcesOnAvailable[resource.url] = {
resourceId: resource.resourceId, resourceId: resource.resourceId,
resourceType: resource.resourceType, resourceType: resource.resourceType,
request: resource.request, method: resource.method,
updates: [...resource.updates],
}; };
expectedOnAvailableCounts--; totalExpectedOnAvailableCounts--;
} }
}; };
@ -220,13 +168,12 @@ async function testNetworkEventResources(options) {
ResourceWatcher.TYPES.NETWORK_EVENT, ResourceWatcher.TYPES.NETWORK_EVENT,
"Received a network update event resource" "Received a network update event resource"
); );
actualResourcesOnUpdated[resource.request.url] = { actualResourcesOnUpdated[resource.url] = {
resourceId: resource.resourceId, resourceId: resource.resourceId,
resourceType: resource.resourceType, resourceType: resource.resourceType,
request: resource.request, method: resource.method,
updates: [...resource.updates],
}; };
expectedOnUpdatedCounts--; totalExpectedOnUpdatedCounts--;
} }
}; };
@ -240,14 +187,17 @@ async function testNetworkEventResources(options) {
`Trigger the rest of the requests *after* calling ResourceWatcher.watchResources `Trigger the rest of the requests *after* calling ResourceWatcher.watchResources
in order to assert the behavior of live network events.` in order to assert the behavior of live network events.`
); );
await triggerNetworkRequests(tab.linkedBrowser, LIVE_REQUESTS_COMMANDS); await triggerNetworkRequests(tab.linkedBrowser, [liveRequest]);
await Promise.all([waitForAllOnAvailableEvents, waitForAllOnUpdatedEvents]); await Promise.all([
waitForAllExpectedOnAvailableEvents,
waitForAllExpectedOnUpdatedEvents,
]);
info("Check the resources on available"); info("Check the resources on available");
is( is(
Object.keys(actualResourcesOnAvailable).length, Object.keys(actualResourcesOnAvailable).length,
Object.keys(options.expectedResourcesOnAvailable).length, Object.keys(expectedResourcesOnAvailable).length,
"Got the expected number of network events fired onAvailable" "Got the expected number of network events fired onAvailable"
); );
@ -259,8 +209,8 @@ async function testNetworkEventResources(options) {
); );
// assert the resources emitted when the network event is created // assert the resources emitted when the network event is created
for (const key in options.expectedResourcesOnAvailable) { for (const key in expectedResourcesOnAvailable) {
const expected = options.expectedResourcesOnAvailable[key]; const expected = expectedResourcesOnAvailable[key];
const actual = actualResourcesOnAvailable[key]; const actual = actualResourcesOnAvailable[key];
assertResources(actual, expected); assertResources(actual, expected);
} }
@ -269,20 +219,15 @@ async function testNetworkEventResources(options) {
is( is(
Object.keys(actualResourcesOnUpdated).length, Object.keys(actualResourcesOnUpdated).length,
Object.keys(options.expectedResourcesOnUpdated).length, Object.keys(expectedResourcesOnUpdated).length,
"Got the expected number of network events fired onUpdated" "Got the expected number of network events fired onUpdated"
); );
// assert the resources emitted when the network event is updated // assert the resources emitted when the network event is updated
for (const key in options.expectedResourcesOnUpdated) { for (const key in expectedResourcesOnUpdated) {
const expected = options.expectedResourcesOnUpdated[key]; const expected = expectedResourcesOnUpdated[key];
const actual = actualResourcesOnUpdated[key]; const actual = actualResourcesOnUpdated[key];
assertResources(actual, expected); assertResources(actual, expected);
is(
actual.updates.length,
expected.updates.length,
"The number of updates is correct"
);
} }
await resourceWatcher.unwatchResources( await resourceWatcher.unwatchResources(
@ -312,14 +257,8 @@ function assertResources(actual, expected) {
expected.resourceType, expected.resourceType,
"The resource type is correct" "The resource type is correct"
); );
is(actual.request.url, expected.request.url, "The url is correct"); is(actual.method, expected.method, "The method is correct");
is(actual.request.method, expected.request.method, "The method is correct");
} }
const EXISTING_REQUESTS_COMMANDS = [ const cachedRequest = `await fetch("/cached_post.html", { method: "POST" });`;
`await fetch("/existing_post.html", { method: "POST" });`, const liveRequest = `await fetch("/live_get.html", { method: "GET" });`;
];
const LIVE_REQUESTS_COMMANDS = [
`await fetch("/live_get.html", { method: "GET" });`,
];

Просмотреть файл

@ -5,6 +5,7 @@
DevToolsModules( DevToolsModules(
"console-messages.js", "console-messages.js",
"error-messages.js", "error-messages.js",
"network-events.js",
"root-node.js", "root-node.js",
"storage-local-storage.js", "storage-local-storage.js",
"storage-session-storage.js", "storage-session-storage.js",

Просмотреть файл

@ -0,0 +1,16 @@
/* 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 {
getUrlDetails,
// eslint-disable-next-line mozilla/reject-some-requires
} = require("devtools/client/netmonitor/src/utils/request-utils");
module.exports = function({ resource }) {
resource.urlDetails = getUrlDetails(resource.url);
resource.startedMs = Date.parse(resource.startedDateTime);
return resource;
};