mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 15:52:07 +00:00
Bug 1134073 - Part 2: Show network request cause and stacktrace in netmonitor UI. r=ochameau
--HG-- extra : rebase_source : 6efa4fdec35a7f9ca0acdafba3aba1886bac8602
This commit is contained in:
parent
8b53520b4b
commit
ec1e427b5d
@ -39,6 +39,10 @@
|
||||
- in the network table toolbar, above the "domain" column. -->
|
||||
<!ENTITY netmonitorUI.toolbar.domain "Domain">
|
||||
|
||||
<!-- LOCALIZATION NOTE (netmonitorUI.toolbar.cause): This is the label displayed
|
||||
- in the network table toolbar, above the "cause" column. -->
|
||||
<!ENTITY netmonitorUI.toolbar.cause "Cause">
|
||||
|
||||
<!-- LOCALIZATION NOTE (netmonitorUI.toolbar.type): This is the label displayed
|
||||
- in the network table toolbar, above the "type" column. -->
|
||||
<!ENTITY netmonitorUI.toolbar.type "Type">
|
||||
|
@ -433,6 +433,13 @@ var NetMonitorController = {
|
||||
get supportsPerfStats() {
|
||||
return this.tabClient &&
|
||||
(this.tabClient.traits.reconfigure || !this._target.isApp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Open a given source in Debugger
|
||||
*/
|
||||
viewSourceInDebugger(sourceURL, sourceLine) {
|
||||
return this._toolbox.viewSourceInDebugger(sourceURL, sourceLine);
|
||||
}
|
||||
};
|
||||
|
||||
@ -629,12 +636,14 @@ NetworkEventsHandler.prototype = {
|
||||
startedDateTime,
|
||||
request: { method, url },
|
||||
isXHR,
|
||||
cause,
|
||||
fromCache,
|
||||
fromServiceWorker
|
||||
} = networkInfo;
|
||||
|
||||
NetMonitorView.RequestsMenu.addRequest(
|
||||
actor, startedDateTime, method, url, isXHR, fromCache, fromServiceWorker
|
||||
actor, startedDateTime, method, url, isXHR, cause, fromCache,
|
||||
fromServiceWorker
|
||||
);
|
||||
window.emit(EVENTS.NETWORK_EVENT, actor);
|
||||
},
|
||||
|
@ -30,7 +30,9 @@ const {ViewHelpers, Heritage, WidgetMethods, setNamedTimeout} =
|
||||
* Localization convenience methods.
|
||||
*/
|
||||
const NET_STRINGS_URI = "chrome://devtools/locale/netmonitor.properties";
|
||||
const WEBCONSOLE_STRINGS_URI = "chrome://devtools/locale/webconsole.properties";
|
||||
var L10N = new LocalizationHelper(NET_STRINGS_URI);
|
||||
const WEBCONSOLE_L10N = new LocalizationHelper(WEBCONSOLE_STRINGS_URI);
|
||||
|
||||
// ms
|
||||
const WDA_DEFAULT_VERIFY_INTERVAL = 50;
|
||||
@ -61,6 +63,8 @@ const RESIZE_REFRESH_RATE = 50;
|
||||
// ms
|
||||
const REQUESTS_REFRESH_RATE = 50;
|
||||
const REQUESTS_TOOLTIP_POSITION = "topcenter bottomleft";
|
||||
// tooltip show/hide delay in ms
|
||||
const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500;
|
||||
// px
|
||||
const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
|
||||
// px
|
||||
@ -102,6 +106,31 @@ const CONTENT_MIME_TYPE_MAPPINGS = {
|
||||
"/rss": Editor.modes.css,
|
||||
"/css": Editor.modes.css
|
||||
};
|
||||
const LOAD_CAUSE_STRINGS = {
|
||||
[Ci.nsIContentPolicy.TYPE_INVALID]: "invalid",
|
||||
[Ci.nsIContentPolicy.TYPE_OTHER]: "other",
|
||||
[Ci.nsIContentPolicy.TYPE_SCRIPT]: "script",
|
||||
[Ci.nsIContentPolicy.TYPE_IMAGE]: "img",
|
||||
[Ci.nsIContentPolicy.TYPE_STYLESHEET]: "stylesheet",
|
||||
[Ci.nsIContentPolicy.TYPE_OBJECT]: "object",
|
||||
[Ci.nsIContentPolicy.TYPE_DOCUMENT]: "document",
|
||||
[Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "subdocument",
|
||||
[Ci.nsIContentPolicy.TYPE_REFRESH]: "refresh",
|
||||
[Ci.nsIContentPolicy.TYPE_XBL]: "xbl",
|
||||
[Ci.nsIContentPolicy.TYPE_PING]: "ping",
|
||||
[Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "xhr",
|
||||
[Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "objectSubdoc",
|
||||
[Ci.nsIContentPolicy.TYPE_DTD]: "dtd",
|
||||
[Ci.nsIContentPolicy.TYPE_FONT]: "font",
|
||||
[Ci.nsIContentPolicy.TYPE_MEDIA]: "media",
|
||||
[Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "websocket",
|
||||
[Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "csp",
|
||||
[Ci.nsIContentPolicy.TYPE_XSLT]: "xslt",
|
||||
[Ci.nsIContentPolicy.TYPE_BEACON]: "beacon",
|
||||
[Ci.nsIContentPolicy.TYPE_FETCH]: "fetch",
|
||||
[Ci.nsIContentPolicy.TYPE_IMAGESET]: "imageset",
|
||||
[Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest"
|
||||
};
|
||||
const DEFAULT_EDITOR_CONFIG = {
|
||||
mode: Editor.modes.text,
|
||||
readOnly: true,
|
||||
@ -431,6 +460,20 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this.userInputTimer = Cc["@mozilla.org/timer;1"]
|
||||
.createInstance(Ci.nsITimer);
|
||||
|
||||
// Create a tooltip for the newly appended network request item.
|
||||
this.tooltip = new Tooltip(document, {
|
||||
closeOnEvents: [{
|
||||
emitter: $("#requests-menu-contents"),
|
||||
event: "scroll",
|
||||
useCapture: true
|
||||
}]
|
||||
});
|
||||
this.tooltip.startTogglingOnHover(this.widget, this._onHover, {
|
||||
toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
|
||||
interactive: true
|
||||
});
|
||||
this.tooltip.defaultPosition = REQUESTS_TOOLTIP_POSITION;
|
||||
|
||||
Prefs.filters.forEach(type => this.filterOn(type));
|
||||
this.sortContents(this._byTiming);
|
||||
|
||||
@ -637,15 +680,20 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
* Specifies the request's url.
|
||||
* @param boolean isXHR
|
||||
* True if this request was initiated via XHR.
|
||||
* @param object cause
|
||||
* Specifies the request's cause. Has the following properties:
|
||||
* - type: nsContentPolicyType constant
|
||||
* - loadingDocumentUri: URI of the request origin
|
||||
* - stacktrace: JS stacktrace of the request
|
||||
* @param boolean fromCache
|
||||
* Indicates if the result came from the browser cache
|
||||
* @param boolean fromServiceWorker
|
||||
* Indicates if the request has been intercepted by a Service Worker
|
||||
*/
|
||||
addRequest: function (id, startedDateTime, method, url, isXHR, fromCache,
|
||||
fromServiceWorker) {
|
||||
this._addQueue.push([id, startedDateTime, method, url, isXHR, fromCache,
|
||||
fromServiceWorker]);
|
||||
addRequest: function (id, startedDateTime, method, url, isXHR, cause,
|
||||
fromCache, fromServiceWorker) {
|
||||
this._addQueue.push([id, startedDateTime, method, url, isXHR, cause,
|
||||
fromCache, fromServiceWorker]);
|
||||
|
||||
// Lazy updating is disabled in some tests.
|
||||
if (!this.lazyUpdate) {
|
||||
@ -885,7 +933,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
let selected = this.selectedItem.attachment;
|
||||
|
||||
// Create the element node for the network request item.
|
||||
let menuView = this._createMenuView(selected.method, selected.url);
|
||||
let menuView = this._createMenuView(selected.method, selected.url,
|
||||
selected.cause);
|
||||
|
||||
// Append a network request item to this container.
|
||||
let newItem = this.push([menuView], {
|
||||
@ -1451,19 +1500,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Refreshes the toggling anchor for the specified item's tooltip.
|
||||
*
|
||||
* @param object item
|
||||
* The network request item in this container.
|
||||
*/
|
||||
refreshTooltip: function (item) {
|
||||
let tooltip = item.attachment.tooltip;
|
||||
tooltip.hide();
|
||||
tooltip.startTogglingOnHover(item.target, this._onHover);
|
||||
tooltip.defaultPosition = REQUESTS_TOOLTIP_POSITION;
|
||||
},
|
||||
|
||||
/**
|
||||
* Attaches security icon click listener for the given request menu item.
|
||||
*
|
||||
@ -1510,13 +1546,13 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
let widget = NetMonitorView.RequestsMenu.widget;
|
||||
let isScrolledToBottom = widget.isScrolledToBottom();
|
||||
|
||||
for (let [id, startedDateTime, method, url, isXHR, fromCache,
|
||||
for (let [id, startedDateTime, method, url, isXHR, cause, fromCache,
|
||||
fromServiceWorker] of this._addQueue) {
|
||||
// Convert the received date/time string to a unix timestamp.
|
||||
let unixTime = Date.parse(startedDateTime);
|
||||
|
||||
// Create the element node for the network request item.
|
||||
let menuView = this._createMenuView(method, url);
|
||||
let menuView = this._createMenuView(method, url, cause);
|
||||
|
||||
// Remember the first and last event boundaries.
|
||||
this._registerFirstRequestStart(unixTime);
|
||||
@ -1530,22 +1566,12 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
method: method,
|
||||
url: url,
|
||||
isXHR: isXHR,
|
||||
cause: cause,
|
||||
fromCache: fromCache,
|
||||
fromServiceWorker: fromServiceWorker
|
||||
}
|
||||
});
|
||||
|
||||
// Create a tooltip for the newly appended network request item.
|
||||
requestItem.attachment.tooltip = new Tooltip(document, {
|
||||
closeOnEvents: [{
|
||||
emitter: $("#requests-menu-contents"),
|
||||
event: "scroll",
|
||||
useCapture: true
|
||||
}]
|
||||
});
|
||||
|
||||
this.refreshTooltip(requestItem);
|
||||
|
||||
if (id == this._preferredItemId) {
|
||||
this.selectedItem = requestItem;
|
||||
}
|
||||
@ -1754,21 +1780,26 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
* Specifies the request method (e.g. "GET", "POST", etc.)
|
||||
* @param string url
|
||||
* Specifies the request's url.
|
||||
* @param object cause
|
||||
* Specifies the request's cause. Has two properties:
|
||||
* - type: nsContentPolicyType constant
|
||||
* - uri: URI of the request origin
|
||||
* @return nsIDOMNode
|
||||
* The network request view.
|
||||
*/
|
||||
_createMenuView: function (method, url) {
|
||||
_createMenuView: function (method, url, cause) {
|
||||
let template = $("#requests-menu-item-template");
|
||||
let fragment = document.createDocumentFragment();
|
||||
|
||||
this.updateMenuView(template, "method", method);
|
||||
this.updateMenuView(template, "url", url);
|
||||
|
||||
// Flatten the DOM by removing one redundant box (the template container).
|
||||
for (let node of template.childNodes) {
|
||||
fragment.appendChild(node.cloneNode(true));
|
||||
}
|
||||
|
||||
this.updateMenuView(fragment, "method", method);
|
||||
this.updateMenuView(fragment, "url", url);
|
||||
this.updateMenuView(fragment, "cause", cause);
|
||||
|
||||
return fragment;
|
||||
},
|
||||
|
||||
@ -1900,6 +1931,20 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
node.setAttribute("tooltiptext", value);
|
||||
break;
|
||||
}
|
||||
case "cause": {
|
||||
let labelNode = $(".requests-menu-cause-label", target);
|
||||
let text = LOAD_CAUSE_STRINGS[value.type] || "unknown";
|
||||
labelNode.setAttribute("value", text);
|
||||
if (value.loadingDocumentUri) {
|
||||
labelNode.setAttribute("tooltiptext", value.loadingDocumentUri);
|
||||
}
|
||||
|
||||
let stackNode = $(".requests-menu-cause-stack", target);
|
||||
if (value.stacktrace && value.stacktrace.length > 0) {
|
||||
stackNode.removeAttribute("hidden");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "contentSize": {
|
||||
let node = $(".requests-menu-size", target);
|
||||
|
||||
@ -2230,11 +2275,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
* Called when two items switch places, when the contents are sorted.
|
||||
*/
|
||||
_onSwap: function ({ detail: [firstItem, secondItem] }) {
|
||||
// Sorting will create new anchor nodes for all the swapped request items
|
||||
// in this container, so it's necessary to refresh the Tooltip instances.
|
||||
this.refreshTooltip(firstItem);
|
||||
this.refreshTooltip(secondItem);
|
||||
|
||||
// Reattach click listener to the security icons
|
||||
this.attachSecurityIconClickListener(firstItem);
|
||||
this.attachSecurityIconClickListener(secondItem);
|
||||
@ -2252,28 +2292,92 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
*/
|
||||
_onHover: Task.async(function* (target, tooltip) {
|
||||
let requestItem = this.getItemForElement(target);
|
||||
if (!requestItem || !requestItem.attachment.responseContent) {
|
||||
if (!requestItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let hovered = requestItem.attachment;
|
||||
let { mimeType, text, encoding } = hovered.responseContent.content;
|
||||
|
||||
if (mimeType && mimeType.includes("image/") && (
|
||||
target.classList.contains("requests-menu-icon") ||
|
||||
target.classList.contains("requests-menu-file"))) {
|
||||
let string = yield gNetwork.getString(text);
|
||||
let anchor = $(".requests-menu-icon", requestItem.target);
|
||||
let src = formDataURI(mimeType, encoding, string);
|
||||
|
||||
tooltip.setImageContent(src, {
|
||||
maxDim: REQUESTS_TOOLTIP_IMAGE_MAX_DIM
|
||||
});
|
||||
return anchor;
|
||||
if (hovered.responseContent && target.closest(".requests-menu-icon-and-file")) {
|
||||
return this._setTooltipImageContent(tooltip, requestItem);
|
||||
} else if (hovered.cause && target.closest(".requests-menu-cause-stack")) {
|
||||
return this._setTooltipStackTraceContent(tooltip, requestItem);
|
||||
}
|
||||
|
||||
return false;
|
||||
}),
|
||||
|
||||
_setTooltipImageContent: Task.async(function* (tooltip, requestItem) {
|
||||
let { mimeType, text, encoding } = requestItem.attachment.responseContent.content;
|
||||
|
||||
if (!mimeType || !mimeType.includes("image/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let string = yield gNetwork.getString(text);
|
||||
let anchor = $(".requests-menu-icon", requestItem.target);
|
||||
let src = formDataURI(mimeType, encoding, string);
|
||||
|
||||
tooltip.setImageContent(src, {
|
||||
maxDim: REQUESTS_TOOLTIP_IMAGE_MAX_DIM
|
||||
});
|
||||
|
||||
return anchor;
|
||||
}),
|
||||
|
||||
_setTooltipStackTraceContent: Task.async(function* (tooltip, requestItem) {
|
||||
let {stacktrace} = requestItem.attachment.cause;
|
||||
|
||||
if (!stacktrace || stacktrace.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let doc = tooltip.doc;
|
||||
let el = doc.createElement("vbox");
|
||||
el.className = "requests-menu-stack-trace";
|
||||
|
||||
for (let f of stacktrace) {
|
||||
let { functionName, filename, lineNumber, columnNumber } = f;
|
||||
|
||||
let frameEl = doc.createElement("hbox");
|
||||
frameEl.className = "requests-menu-stack-frame devtools-monospace";
|
||||
|
||||
let funcEl = doc.createElement("label");
|
||||
funcEl.className = "requests-menu-stack-frame-function-name";
|
||||
funcEl.setAttribute("value",
|
||||
functionName || WEBCONSOLE_L10N.getStr("stacktrace.anonymousFunction"));
|
||||
frameEl.appendChild(funcEl);
|
||||
|
||||
let fileEl = doc.createElement("label");
|
||||
fileEl.className = "requests-menu-stack-frame-file-name";
|
||||
// Parse a stack frame in format "url -> url"
|
||||
let sourceUrl = filename.split(" -> ").pop();
|
||||
fileEl.setAttribute("value", sourceUrl);
|
||||
fileEl.setAttribute("tooltiptext", sourceUrl);
|
||||
fileEl.setAttribute("crop", "start");
|
||||
frameEl.appendChild(fileEl);
|
||||
|
||||
let lineEl = doc.createElement("label");
|
||||
lineEl.className = "requests-menu-stack-frame-line";
|
||||
lineEl.setAttribute("value", `:${lineNumber}:${columnNumber}`);
|
||||
frameEl.appendChild(lineEl);
|
||||
|
||||
frameEl.addEventListener("click", () => {
|
||||
// avoid an ugly visual artefact when the view is switched to debugger and the
|
||||
// tooltip is hidden only after a delay - the tooltip is moved outside the browser
|
||||
// window.
|
||||
tooltip.hide();
|
||||
NetMonitorController.viewSourceInDebugger(filename, lineNumber);
|
||||
}, false);
|
||||
|
||||
el.appendChild(frameEl);
|
||||
}
|
||||
|
||||
tooltip.content = el;
|
||||
tooltip.panel.setAttribute("wide", "");
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
/**
|
||||
* A handler that opens the security tab in the details view if secure or
|
||||
* broken security indicator is clicked.
|
||||
|
@ -221,6 +221,17 @@
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-cause-header-box"
|
||||
class="requests-menu-header requests-menu-cause"
|
||||
align="center">
|
||||
<button id="requests-menu-cause-button"
|
||||
class="requests-menu-header-button requests-menu-cause"
|
||||
data-key="cause"
|
||||
label="&netmonitorUI.toolbar.cause;"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-type-header-box"
|
||||
class="requests-menu-header requests-menu-type"
|
||||
align="center">
|
||||
@ -323,6 +334,10 @@
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox class="requests-menu-subitem requests-menu-cause" align="center">
|
||||
<label class="requests-menu-cause-stack" value="JS" hidden="true"/>
|
||||
<label class="plain requests-menu-cause-label" flex="1" crop="end"/>
|
||||
</hbox>
|
||||
<label class="plain requests-menu-subitem requests-menu-type"
|
||||
crop="end"/>
|
||||
<label class="plain requests-menu-subitem requests-menu-transferred"
|
||||
|
@ -16,6 +16,7 @@ function NetMonitorPanel(iframeWindow, toolbox) {
|
||||
this._view = this.panelWin.NetMonitorView;
|
||||
this._controller = this.panelWin.NetMonitorController;
|
||||
this._controller._target = this.target;
|
||||
this._controller._toolbox = this._toolbox;
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
@ -246,6 +246,25 @@
|
||||
width: 8vw;
|
||||
}
|
||||
|
||||
.requests-menu-cause {
|
||||
max-width: 8em;
|
||||
width: 8vw;
|
||||
}
|
||||
|
||||
.requests-menu-cause-stack {
|
||||
background-color: var(--theme-body-color-alt);
|
||||
color: var(--theme-body-background);
|
||||
font-size: 8px;
|
||||
font-weight: bold;
|
||||
line-height: 10px;
|
||||
border-radius: 3px;
|
||||
padding: 0 2px;
|
||||
margin: 0;
|
||||
margin-inline-end: 3px;
|
||||
-moz-user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.requests-menu-transferred {
|
||||
max-width: 8em;
|
||||
text-align: center;
|
||||
@ -676,6 +695,40 @@
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
/* Requests menu stacktrace tooltip */
|
||||
.requests-menu-stack-trace {
|
||||
max-height: 400px;
|
||||
width: 586px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.requests-menu-stack-frame {
|
||||
color: var(--theme-body-color-alt);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.requests-menu-stack-frame:hover {
|
||||
background-color: var(--theme-selection-background-semitransparent);
|
||||
}
|
||||
|
||||
.requests-menu-stack-frame-function-name {
|
||||
color: var(--theme-highlight-blue);
|
||||
cursor: inherit;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.requests-menu-stack-frame-file-name {
|
||||
cursor: inherit;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
|
||||
.requests-menu-stack-frame-line {
|
||||
color: var(--theme-highlight-orange);
|
||||
cursor: inherit;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
/* Performance analysis buttons */
|
||||
|
||||
#requests-menu-network-summary-button {
|
||||
|
Loading…
Reference in New Issue
Block a user