Bug 1592535 - Add a column displaying the last frame in the stack trace info if it exists r=Harald,Honza

Differential Revision: https://phabricator.services.mozilla.com/D61423

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Bryan Kok 2020-02-14 13:14:29 +00:00
Родитель cec9db85d8
Коммит b974090591
16 изменённых файлов: 374 добавлений и 4 удалений

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

@ -2080,10 +2080,10 @@ pref("devtools.netmonitor.panes-search-width", 550);
pref("devtools.netmonitor.panes-search-height", 450);
pref("devtools.netmonitor.filters", "[\"all\"]");
pref("devtools.netmonitor.visibleColumns",
"[\"status\",\"method\",\"domain\",\"file\",\"cause\",\"type\",\"transferred\",\"contentSize\",\"waterfall\"]"
"[\"status\",\"method\",\"domain\",\"file\",\"cause\",\"initiator\",\"type\",\"transferred\",\"contentSize\",\"waterfall\"]"
);
pref("devtools.netmonitor.columnsData",
'[{"name":"status","minWidth":30,"width":5}, {"name":"method","minWidth":30,"width":5}, {"name":"domain","minWidth":30,"width":10}, {"name":"file","minWidth":30,"width":25}, {"name":"url","minWidth":30,"width":25}, {"name":"cause","minWidth":30,"width":10},{"name":"type","minWidth":30,"width":5},{"name":"transferred","minWidth":30,"width":10},{"name":"contentSize","minWidth":30,"width":5},{"name":"waterfall","minWidth":150,"width":25}]');
'[{"name":"status","minWidth":30,"width":5}, {"name":"method","minWidth":30,"width":5}, {"name":"domain","minWidth":30,"width":10}, {"name":"file","minWidth":30,"width":25}, {"name":"url","minWidth":30,"width":25}, {"name":"cause","minWidth":30,"width":10},{"name":"initiator","minWidth":30,"width":25},{"name":"type","minWidth":30,"width":5},{"name":"transferred","minWidth":30,"width":10},{"name":"contentSize","minWidth":30,"width":5},{"name":"waterfall","minWidth":150,"width":25}]');
pref("devtools.netmonitor.ws.payload-preview-height", 128);
pref("devtools.netmonitor.ws.visibleColumns",
'["data", "time"]'

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

@ -576,6 +576,10 @@ netmonitor.toolbar.remoteip=Remote IP
# in the network table toolbar, above the "cause" column.
netmonitor.toolbar.cause=Cause
# LOCALIZATION NOTE (netmonitor.toolbar.initiator): This is the label displayed
# in the network table toolbar, above the "initiator" column.
netmonitor.toolbar.initiator=Initiator
# LOCALIZATION NOTE (netmonitor.toolbar.type): This is the label displayed
# in the network table toolbar, above the "type" column.
netmonitor.toolbar.type=Type

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

@ -510,6 +510,54 @@
outline: none;
}
.theme-light {
--network-initiator-line-color: var(--theme-highlight-blue);
--network-initiator-color: var(--theme-highlight-purple);
}
.theme-dark {
--network-initiator-line-color: hsl(210, 40%, 60%);
--network-initiator-color: var(--blue-40);
}
.requests-list-initiator {
text-decoration: none;
}
.requests-list-initiator:hover {
text-decoration: underline;
}
.requests-list-initiator-lastframe {
display: unset;
}
.request-list-item .requests-list-initiator-filename {
cursor: pointer;
text-decoration: inherit;
}
.request-list-item:not(.selected) .requests-list-initiator-filename {
color: var(--network-initiator-color);
}
.request-list-item.selected .requests-list-initiator-filename {
color: inherit;
}
.request-list-item .requests-list-initiator-line {
cursor: pointer;
text-decoration: inherit;
}
.request-list-item:not(selected) .requests-list-initiator-line {
color: var(--network-initiator-line-color);
}
.request-list-item.selected .requests-list-initiator-line {
color: inherit;
}
/* Responsive web design support */
@media (max-width: 700px) {

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

@ -0,0 +1,54 @@
/* 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 { Component } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const {
getUrlBaseName,
} = require("devtools/client/netmonitor/src/utils/request-utils");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
class RequestListColumnInitiator extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
onInitiatorBadgeMouseDown: PropTypes.func.isRequired,
};
}
shouldComponentUpdate(nextProps) {
return this.props.item.cause !== nextProps.item.cause;
}
render() {
const {
item: { cause },
onInitiatorBadgeMouseDown,
} = this.props;
let initiator = "";
let lineNumber = "";
if (cause && cause.lastFrame) {
const { filename, lineNumber: _lineNumber } = cause.lastFrame;
initiator = getUrlBaseName(filename);
lineNumber = ":" + _lineNumber;
}
return dom.td(
{
className: "requests-list-column requests-list-initiator",
onMouseDown: onInitiatorBadgeMouseDown,
},
dom.div(
{ className: "requests-list-initiator-lastframe" },
dom.span({ className: "requests-list-initiator-filename" }, initiator),
dom.span({ className: "requests-list-initiator-line" }, lineNumber)
)
);
}
}
module.exports = RequestListColumnInitiator;

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

@ -82,6 +82,7 @@ class RequestListContent extends Component {
firstRequestStartedMs: PropTypes.number.isRequired,
fromCache: PropTypes.bool,
onCauseBadgeMouseDown: PropTypes.func.isRequired,
onInitiatorBadgeMouseDown: PropTypes.func.isRequired,
onItemRightMouseButtonDown: PropTypes.func.isRequired,
onItemMouseDown: PropTypes.func.isRequired,
onSecurityIconMouseDown: PropTypes.func.isRequired,
@ -318,6 +319,7 @@ class RequestListContent extends Component {
displayedRequests,
firstRequestStartedMs,
onCauseBadgeMouseDown,
onInitiatorBadgeMouseDown,
onSecurityIconMouseDown,
onWaterfallMouseDown,
requestFilterTypes,
@ -367,6 +369,8 @@ class RequestListContent extends Component {
onMouseDown: evt =>
this.onMouseDown(evt, item.id, item.channelId),
onCauseBadgeMouseDown: () => onCauseBadgeMouseDown(item.cause),
onInitiatorBadgeMouseDown: () =>
onInitiatorBadgeMouseDown(item.cause),
onSecurityIconMouseDown: () =>
onSecurityIconMouseDown(item.securityState),
onWaterfallMouseDown: () => onWaterfallMouseDown(),
@ -424,6 +428,11 @@ module.exports = connect(
dispatch(Actions.selectDetailsPanelTab("stack-trace"));
}
},
onInitiatorBadgeMouseDown: cause => {
if (cause.lastFrame) {
dispatch(Actions.selectDetailsPanelTab("stack-trace"));
}
},
selectRequest: (id, channelId) =>
dispatch(Actions.selectRequest(id, channelId)),
onItemRightMouseButtonDown: id => dispatch(Actions.rightClickRequest(id)),

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

@ -20,6 +20,7 @@ const {
// Components
/* global
RequestListColumnInitiator,
RequestListColumnCause,
RequestListColumnContentSize,
RequestListColumnCookies,
@ -38,7 +39,11 @@ const {
RequestListColumnUrl,
RequestListColumnWaterfall
*/
loader.lazyGetter(this, "RequestListColumnInitiator", function() {
return createFactory(
require("devtools/client/netmonitor/src/components/request-list/RequestListColumnInitiator")
);
});
loader.lazyGetter(this, "RequestListColumnCause", function() {
return createFactory(
require("devtools/client/netmonitor/src/components/request-list/RequestListColumnCause")
@ -191,6 +196,11 @@ const COLUMN_COMPONENTS = [
ColumnComponent: RequestListColumnCause,
props: ["onCauseBadgeMouseDown"],
},
{
column: "initiator",
ColumnComponent: RequestListColumnInitiator,
props: ["onInitiatorBadgeMouseDown"],
},
{ column: "type", ColumnComponent: RequestListColumnType },
{
column: "cookies",

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

@ -9,6 +9,7 @@ DevToolsModules(
'RequestListColumnCookies.js',
'RequestListColumnDomain.js',
'RequestListColumnFile.js',
'RequestListColumnInitiator.js',
'RequestListColumnMethod.js',
'RequestListColumnProtocol.js',
'RequestListColumnRemoteIP.js',

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

@ -264,6 +264,10 @@ const HEADERS = [
name: "cause",
canFilter: true,
},
{
name: "initiator",
canFilter: true,
},
{
name: "type",
canFilter: false,

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

@ -37,6 +37,7 @@ const cols = {
scheme: false,
remoteip: false,
cause: true,
initiator: true,
type: true,
cookies: false,
setCookies: false,

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

@ -38,6 +38,9 @@ const {
getFormattedIPAndPort,
} = require("devtools/client/netmonitor/src/utils/format-utils");
const { getUnicodeUrl } = require("devtools/client/shared/unicode-url");
const {
getUrlBaseName,
} = require("devtools/client/netmonitor/src/utils/request-utils");
/*
The function `parseFilters` is from:
@ -166,6 +169,16 @@ function isFlagFilterMatch(item, { type, value, negative }) {
? causeType.toLowerCase().includes(value)
: false;
},
initiator: () => {
const initiator = item.cause.lastFrame
? getUrlBaseName(item.cause.lastFrame.filename) +
":" +
item.cause.lastFrame.lineNumber
: "";
return typeof initiator === "string"
? initiator.toLowerCase().includes(value)
: !value;
},
transferred: () => {
if (item.fromCache) {
return false;

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

@ -15,6 +15,9 @@ const {
const {
RESPONSE_HEADERS,
} = require("devtools/client/netmonitor/src/constants");
const {
getUrlBaseName,
} = require("devtools/client/netmonitor/src/utils/request-utils");
/**
* Predicates used when sorting items.
@ -167,6 +170,31 @@ function cause(first, second) {
return result || waterfall(first, second);
}
function initiator(first, second) {
let firstInitiator = "";
let firstInitiatorLineNumber = 0;
if (first.cause.lastFrame) {
firstInitiator = getUrlBaseName(first.cause.lastFrame.filename);
firstInitiatorLineNumber = first.cause.lastFrame.lineNumber;
}
let secondInitiator = "";
let secondInitiatorLineNumber = 0;
if (second.cause.lastFrame) {
secondInitiator = getUrlBaseName(second.cause.lastFrame.filename);
secondInitiatorLineNumber = second.cause.lastFrame.lineNumber;
}
let result = compareValues(firstInitiator, secondInitiator);
if (result === 0) {
result = compareValues(firstInitiatorLineNumber, secondInitiatorLineNumber);
}
return result || waterfall(first, second);
}
function setCookies(first, second) {
let { responseCookies: firstResponseCookies = { cookies: [] } } = first;
let { responseCookies: secondResponseCookies = { cookies: [] } } = second;
@ -255,6 +283,7 @@ const sorters = {
setCookies,
remoteip,
cause,
initiator,
type,
transferred,
contentSize,

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

@ -156,6 +156,7 @@ skip-if = verify # Bug 1607678
[browser_net_headers_sorted.js]
[browser_net_headers-resize.js]
[browser_net_image-tooltip.js]
[browser_net_initiator.js]
[browser_net_json-b64.js]
[browser_net_json-empty.js]
[browser_net_json-null.js]

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

@ -0,0 +1,182 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {
getUrlBaseName,
} = require("devtools/client/netmonitor/src/utils/request-utils");
/**
* Tests if request initiator is reported correctly.
*/
const INITIATOR_FILE_NAME = "html_cause-test-page.html";
const INITIATOR_URL = EXAMPLE_URL + INITIATOR_FILE_NAME;
const EXPECTED_REQUESTS = [
{
method: "GET",
url: INITIATOR_URL,
causeType: "document",
causeUri: null,
stack: false,
},
{
method: "GET",
url: EXAMPLE_URL + "stylesheet_request",
causeType: "stylesheet",
causeUri: INITIATOR_URL,
stack: false,
},
{
method: "GET",
url: EXAMPLE_URL + "img_request",
causeType: "img",
causeUri: INITIATOR_URL,
stack: false,
},
{
method: "GET",
url: EXAMPLE_URL + "xhr_request",
causeType: "xhr",
causeUri: INITIATOR_URL,
stack: [
{ fn: "performXhrRequestCallback", file: INITIATOR_FILE_NAME, line: 26 },
],
},
{
method: "GET",
url: EXAMPLE_URL + "fetch_request",
causeType: "fetch",
causeUri: INITIATOR_URL,
stack: [{ fn: "performFetchRequest", file: INITIATOR_FILE_NAME, line: 31 }],
},
{
method: "GET",
url: EXAMPLE_URL + "promise_fetch_request",
causeType: "fetch",
causeUri: INITIATOR_URL,
stack: [
{
fn: "performPromiseFetchRequestCallback",
file: INITIATOR_FILE_NAME,
line: 37,
},
{
fn: "performPromiseFetchRequest",
file: INITIATOR_FILE_NAME,
line: 36,
asyncCause: "promise callback",
},
],
},
{
method: "GET",
url: EXAMPLE_URL + "timeout_fetch_request",
causeType: "fetch",
causeUri: INITIATOR_URL,
stack: [
{
fn: "performTimeoutFetchRequestCallback2",
file: INITIATOR_FILE_NAME,
line: 44,
},
{
fn: "performTimeoutFetchRequestCallback1",
file: INITIATOR_FILE_NAME,
line: 43,
asyncCause: "setTimeout handler",
},
],
},
{
method: "POST",
url: EXAMPLE_URL + "beacon_request",
causeType: "beacon",
causeUri: INITIATOR_URL,
stack: [
{ fn: "performBeaconRequest", file: INITIATOR_FILE_NAME, line: 50 },
],
},
];
add_task(async function() {
// Async stacks aren't on by default in all builds
await SpecialPowers.pushPrefEnv({
set: [["javascript.options.asyncstack", true]],
});
// the initNetMonitor function clears the network request list after the
// page is loaded. That's why we first load a bogus page from SIMPLE_URL,
// and only then load the real thing from INITIATOR_URL - we want to catch
// all the requests the page is making, not only the XHRs.
// We can't use about:blank here, because initNetMonitor checks that the
// page has actually made at least one request.
const { tab, monitor } = await initNetMonitor(SIMPLE_URL);
const { document, store, windowRequire } = monitor.panelWin;
const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
const { getSortedRequests } = windowRequire(
"devtools/client/netmonitor/src/selectors/index"
);
store.dispatch(Actions.batchEnable(false));
const wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
BrowserTestUtils.loadURI(tab.linkedBrowser, INITIATOR_URL);
await wait;
// For all expected requests
for (const [index, { stack }] of EXPECTED_REQUESTS.entries()) {
if (!stack) {
continue;
}
EventUtils.sendMouseEvent(
{ type: "mousedown" },
document.querySelectorAll(".request-list-item .requests-list-initiator")[
index
]
);
// Clicking on the initiator column should open the Stack Trace panel
const onStackTraceRendered = waitUntil(() =>
document.querySelector("#stack-trace-panel .stack-trace .frame-link")
);
await onStackTraceRendered;
}
is(
store.getState().requests.requests.length,
EXPECTED_REQUESTS.length,
"All the page events should be recorded."
);
validateRequests(EXPECTED_REQUESTS, monitor);
// Sort the requests by initiator and check the order
EventUtils.sendMouseEvent(
{ type: "click" },
document.querySelector("#requests-list-initiator-button")
);
const expectedOrder = EXPECTED_REQUESTS.map(r => {
if (r.stack) {
const { file, line } = r.stack[0];
return getUrlBaseName(file) + ":" + line;
}
return "";
}).sort();
expectedOrder.forEach((expectedInitiator, i) => {
const request = getSortedRequests(store.getState())[i];
if (request.cause.stacktraceAvailable) {
const { fileName, lineNumber } = request.cause.lastFrame;
const initiator = getUrlBaseName(fileName) + ":" + lineNumber;
is(
initiator,
expectedInitiator,
`The request #${i} has the expected initiator after sorting`
);
}
});
await teardown(monitor);
});

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

@ -143,7 +143,7 @@ const gDefaultFilters = Services.prefs.getCharPref(
// Reveal many columns for test
Services.prefs.setCharPref(
"devtools.netmonitor.visibleColumns",
'["cause","contentSize","cookies","domain","duration",' +
'["cause","initiator","contentSize","cookies","domain","duration",' +
'"endTime","file","url","latency","method","protocol",' +
'"remoteip","responseTime","scheme","setCookies",' +
'"startTime","status","transferred","type","waterfall"]'
@ -157,6 +157,7 @@ Services.prefs.setCharPref(
'{"name":"file","minWidth":30,"width":25},' +
'{"name":"url","minWidth":30,"width":25},' +
'{"name":"cause","minWidth":30,"width":10},' +
'{"name":"initiator","minWidth":30,"width":25},' +
'{"name":"type","minWidth":30,"width":5},' +
'{"name":"transferred","minWidth":30,"width":10},' +
'{"name":"contentSize","minWidth":30,"width":5},' +

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

@ -58,6 +58,7 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
this.observer.init();
this.stackTraces = new Set();
this.lastFrames = new Map();
this.onStackTraceAvailable = this.onStackTraceAvailable.bind(this);
this.onRequestContent = this.onRequestContent.bind(this);
@ -156,6 +157,7 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
}
this.stackTraces.clear();
this.lastFrames.clear();
if (this.messageManager) {
this.stopListening();
this.messageManager = null;
@ -170,14 +172,19 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
this.stopListening();
this.messageManager = mm;
this.stackTraces = new Set();
this.lastFrames.clear();
this.startListening();
},
onStackTraceAvailable(msg) {
const { channelId } = msg.data;
if (!msg.data.stacktrace) {
this.lastFrames.delete(channelId);
this.stackTraces.delete(channelId);
} else {
if (msg.data.lastFrame) {
this.lastFrames.set(channelId, msg.data.lastFrame);
}
this.stackTraces.add(channelId);
}
},
@ -300,6 +307,10 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
if (event.cause.stacktrace) {
this.stackTraces.delete(id);
}
if (this.lastFrames.has(id)) {
event.cause.lastFrame = this.lastFrames.get(id);
this.lastFrames.delete(id);
}
actor.init(event);
this._networkEventActorsByURL.set(actor._request.url, actor);

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

@ -71,6 +71,8 @@ StackTraceCollector.prototype = {
messageManager.sendAsyncMessage("debug:request-stack-available", {
channelId: id,
stacktrace: stacktrace && stacktrace.length > 0,
lastFrame:
stacktrace && stacktrace.length > 0 ? stacktrace[0] : undefined,
});
}
this.stacktracesById.set(id, stacktrace);