author | Phil Ringnalda <philringnalda@gmail.com> |
Thu, 10 Sep 2015 21:14:27 -0700 | |
changeset 261880 | d77d81aaff9922e759ef94409b151abb09a19f9d |
parent 261879 | 28f1e57d47573b0c78e67221df93148111c24e2a |
child 261881 | 0f88a6861417daaf397c2b4714a8ebdfe6379e2c |
push id | 64866 |
push user | philringnalda@gmail.com |
push date | Fri, 11 Sep 2015 04:14:38 +0000 |
treeherder | mozilla-inbound@d77d81aaff99 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 861335 |
milestone | 43.0a1 |
backs out | e142ec232fe1ea5a26815ba2e60c8d79e8672e09 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -11,16 +11,17 @@ browser.jar: content/browser/devtools/readdir.js (projecteditor/lib/helpers/readdir.js) content/browser/devtools/projecteditor-loader.xul (projecteditor/chrome/content/projecteditor-loader.xul) content/browser/devtools/projecteditor-test.xul (projecteditor/chrome/content/projecteditor-test.xul) content/browser/devtools/projecteditor-loader.js (projecteditor/chrome/content/projecteditor-loader.js) content/browser/devtools/netmonitor.xul (netmonitor/netmonitor.xul) content/browser/devtools/netmonitor.css (netmonitor/netmonitor.css) content/browser/devtools/netmonitor-controller.js (netmonitor/netmonitor-controller.js) content/browser/devtools/netmonitor-view.js (netmonitor/netmonitor-view.js) + content/browser/devtools/NetworkPanel.xhtml (webconsole/NetworkPanel.xhtml) content/browser/devtools/webconsole.xul (webconsole/webconsole.xul) * content/browser/devtools/scratchpad.xul (scratchpad/scratchpad.xul) content/browser/devtools/scratchpad.js (scratchpad/scratchpad.js) content/browser/devtools/splitview.css (shared/splitview.css) content/browser/devtools/theme-switching.js (shared/theme-switching.js) content/browser/devtools/frame-script-utils.js (shared/frame-script-utils.js) content/browser/devtools/styleeditor.xul (styleeditor/styleeditor.xul) content/browser/devtools/styleeditor.css (styleeditor/styleeditor.css)
--- a/browser/devtools/netmonitor/netmonitor-controller.js +++ b/browser/devtools/netmonitor/netmonitor-controller.js @@ -318,47 +318,16 @@ let NetMonitorController = { this._currentActivity = aType; return reconfigureTab({ cacheDisabled: true, performReload: false }).then(standBy); } this._currentActivity = ACTIVITY_TYPE.NONE; return promise.reject(new Error("Invalid activity type")); }, /** - * Selects the specified request in the waterfall and opens the details view. - * - * @param string requestId - * The actor ID of the request to inspect. - * @return object - * A promise resolved once the task finishes. - */ - inspectRequest: function(requestId) { - // Look for the request in the existing ones or wait for it to appear, if - // the network monitor is still loading. - let deferred = promise.defer(); - let request = null; - let inspector = function() { - let predicate = i => i.value === requestId; - request = NetMonitorView.RequestsMenu.getItemForPredicate(predicate); - if (request) { - window.off(EVENTS.REQUEST_ADDED, inspector); - NetMonitorView.RequestsMenu.filterOn("all"); - NetMonitorView.RequestsMenu.selectedItem = request; - deferred.resolve(); - } - } - - inspector(); - if (!request) { - window.on(EVENTS.REQUEST_ADDED, inspector); - } - return deferred.promise; - }, - - /** * Getter that tells if the server supports sending custom network requests. * @type boolean */ get supportsCustomRequest() { return this.webConsoleClient && (this.webConsoleClient.traits.customNetworkRequest || !this._target.isApp); },
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/NetworkPanel.xhtml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" + "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [ +<!ENTITY % webConsoleDTD SYSTEM "chrome://browser/locale/devtools/webConsole.dtd" > +%webConsoleDTD; +]> + +<!-- 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/. --> + + +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" href="chrome://browser/skin/devtools/webconsole_networkpanel.css" type="text/css"/> +</head> +<body role="application"> +<table id="header"> + <tr> + <th class="property-name" + scope="row">&networkPanel.requestURLColon;</th> + <td class="property-value" + id="headUrl"></td> + </tr> + <tr> + <th class="property-name" + scope="row">&networkPanel.requestMethodColon;</th> + <td class="property-value" + id="headMethod"></td> + </tr> + <tr> + <th class="property-name" + scope="row">&networkPanel.statusCodeColon;</th> + <td class="property-value" + id="headStatus"></td> + </tr> +</table> + +<div class="group"> + <h1> + &networkPanel.requestHeaders; + <span id="requestHeadersInfo" class="info"></span> + </h1> + <table class="property-table" id="requestHeadersContent"></table> + + <div id="requestCookie" style="display:none"> + <h1>&networkPanel.requestCookie;</h1> + <table class="property-table" id="requestCookieContent"></table> + </div> + + <div id="requestBody" style="display:none"> + <h1>&networkPanel.requestBody;</h1> + <table class="property-table" id="requestBodyContent"></table> + </div> + <div id="requestFormData" style="display:none"> + <h1>&networkPanel.requestFormData;</h1> + <table class="property-table" id="requestFormDataContent"></table> + </div> + <p id="requestBodyFetchLink" style="display:none"></p> +</div> + +<div class="group" id="responseContainer" style="display:none"> + <h1> + &networkPanel.responseHeaders; + <span id="responseHeadersInfo" class="info">Δ</span> + </h1> + <table class="property-table" id="responseHeadersContent"></table> + + <div id="responseCookie" style="display:none"> + <h1>&networkPanel.responseCookie;</h1> + <table class="property-table" id="responseCookieContent"></table> + </div> + + <div id="responseBody" style="display:none"> + <h1> + &networkPanel.responseBody; + <span class="info" id="responseBodyInfo">Δ</span> + </h1> + <table class="property-table" id="responseBodyContent"></table> + </div> + <div id="responseBodyCached" style="display:none"> + <h1> + &networkPanel.responseBodyCached; + <span class="info" id="responseBodyCachedInfo">Δ</span> + </h1> + <table class="property-table" id="responseBodyCachedContent"></table> + </div> + <div id="responseNoBody" style="display:none"> + <h1> + &networkPanel.responseNoBody; + <span id="responseNoBodyInfo" class="info">Δ</span> + </h1> + </div> + <div id="responseBodyUnknownType" style="display:none"> + <h1> + &networkPanel.responseBodyUnknownType; + <span id="responseBodyUnknownTypeInfo" class="info">Δ</span> + </h1> + <table class="property-table" id="responseBodyUnknownTypeContent"></table> + </div> + <div id="responseImage" style="display:none"> + <h1> + &networkPanel.responseImage; + <span id="responseImageInfo" class="info"></span> + </h1> + <div id="responseImageNodeDiv"> + <img id="responseImageNode" /> + </div> + </div> + <div id="responseImageCached" style="display:none"> + <h1> + &networkPanel.responseImageCached; + <span id="responseImageCachedInfo" class="info"></span> + </h1> + <div id="responseImageNodeDiv"> + <img id="responseImageCachedNode" /> + </div> + </div> + <p id="responseBodyFetchLink" style="display:none"></p> +</div> +</body> +</html>
--- a/browser/devtools/webconsole/moz.build +++ b/browser/devtools/webconsole/moz.build @@ -5,11 +5,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] EXTRA_JS_MODULES.devtools.webconsole += [ 'console-commands.js', 'console-output.js', 'hudservice.js', + 'network-panel.js', 'panel.js', 'webconsole.js', ]
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/network-panel.js @@ -0,0 +1,835 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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 {Cc, Ci, Cu} = require("chrome"); + +loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper")); +loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); +loader.lazyServiceGetter(this, "mimeService", "@mozilla.org/mime;1", "nsIMIMEService"); + +let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils; + +const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties"; +let l10n = new WebConsoleUtils.l10n(STRINGS_URI); + + +/** + * Creates a new NetworkPanel. + * + * @constructor + * @param nsIDOMNode aParent + * Parent node to append the created panel to. + * @param object aHttpActivity + * HttpActivity to display in the panel. + * @param object aWebConsoleFrame + * The parent WebConsoleFrame object that owns this network panel + * instance. + */ +function NetworkPanel(aParent, aHttpActivity, aWebConsoleFrame) +{ + let doc = aParent.ownerDocument; + this.httpActivity = aHttpActivity; + this.webconsole = aWebConsoleFrame; + this._responseBodyFetch = this._responseBodyFetch.bind(this); + this._requestBodyFetch = this._requestBodyFetch.bind(this); + + // Create the underlaying panel + this.panel = createElement(doc, "panel", { + label: l10n.getStr("NetworkPanel.label"), + titlebar: "normal", + noautofocus: "true", + noautohide: "true", + close: "true" + }); + + // Create the iframe that displays the NetworkPanel XHTML. + this.iframe = createAndAppendElement(this.panel, "iframe", { + src: "chrome://browser/content/devtools/NetworkPanel.xhtml", + type: "content", + flex: "1" + }); + + let self = this; + + // Destroy the panel when it's closed. + this.panel.addEventListener("popuphidden", function onPopupHide() { + self.panel.removeEventListener("popuphidden", onPopupHide, false); + self.panel.parentNode.removeChild(self.panel); + self.panel = null; + self.iframe = null; + self.httpActivity = null; + self.webconsole = null; + + if (self.linkNode) { + self.linkNode._panelOpen = false; + self.linkNode = null; + } + }, false); + + // Set the document object and update the content once the panel is loaded. + this.iframe.addEventListener("load", function onLoad() { + if (!self.iframe) { + return; + } + + self.iframe.removeEventListener("load", onLoad, true); + self.update(); + }, true); + + this.panel.addEventListener("popupshown", function onPopupShown() { + self.panel.removeEventListener("popupshown", onPopupShown, true); + self.update(); + }, true); + + // Create the footer. + let footer = createElement(doc, "hbox", { align: "end" }); + createAndAppendElement(footer, "spacer", { flex: 1 }); + + createAndAppendElement(footer, "resizer", { dir: "bottomend" }); + this.panel.appendChild(footer); + + aParent.appendChild(this.panel); +} +exports.NetworkPanel = NetworkPanel; + +NetworkPanel.prototype = +{ + /** + * The current state of the output. + */ + _state: 0, + + /** + * State variables. + */ + _INIT: 0, + _DISPLAYED_REQUEST_HEADER: 1, + _DISPLAYED_REQUEST_BODY: 2, + _DISPLAYED_RESPONSE_HEADER: 3, + _TRANSITION_CLOSED: 4, + + _fromDataRegExp: /Content-Type\:\s*application\/x-www-form-urlencoded/, + + _contentType: null, + + /** + * Function callback invoked whenever the panel content is updated. This is + * used only by tests. + * + * @private + * @type function + */ + _onUpdate: null, + + get document() { + return this.iframe && this.iframe.contentWindow ? + this.iframe.contentWindow.document : null; + }, + + /** + * Small helper function that is nearly equal to l10n.getFormatStr + * except that it prefixes aName with "NetworkPanel.". + * + * @param string aName + * The name of an i10n string to format. This string is prefixed with + * "NetworkPanel." before calling the HUDService.getFormatStr function. + * @param array aArray + * Values used as placeholder for the i10n string. + * @returns string + * The i10n formated string. + */ + _format: function NP_format(aName, aArray) + { + return l10n.getFormatStr("NetworkPanel." + aName, aArray); + }, + + /** + * Returns the content type of the response body. This is based on the + * response.content.mimeType property. If this value is not available, then + * the content type is guessed by the file extension of the request URL. + * + * @return string + * Content type or empty string if no content type could be figured + * out. + */ + get contentType() + { + if (this._contentType) { + return this._contentType; + } + + let request = this.httpActivity.request; + let response = this.httpActivity.response; + + let contentType = ""; + let types = response.content ? + (response.content.mimeType || "").split(/,|;/) : []; + for (let i = 0; i < types.length; i++) { + if (types[i] in NetworkHelper.mimeCategoryMap) { + contentType = types[i]; + break; + } + } + + if (contentType) { + this._contentType = contentType; + return contentType; + } + + // Try to get the content type from the request file extension. + let uri = NetUtil.newURI(request.url); + if ((uri instanceof Ci.nsIURL) && uri.fileExtension) { + try { + contentType = mimeService.getTypeFromExtension(uri.fileExtension); + } + catch(ex) { + // Added to prevent failures on OS X 64. No Flash? + Cu.reportError(ex); + } + } + + this._contentType = contentType; + return contentType; + }, + + /** + * + * @returns boolean + * True if the response is an image, false otherwise. + */ + get _responseIsImage() + { + return this.contentType && + NetworkHelper.mimeCategoryMap[this.contentType] == "image"; + }, + + /** + * + * @returns boolean + * True if the response body contains text, false otherwise. + */ + get _isResponseBodyTextData() + { + return this.contentType ? + NetworkHelper.isTextMimeType(this.contentType) : false; + }, + + /** + * Tells if the server response is cached. + * + * @returns boolean + * Returns true if the server responded that the request is already + * in the browser's cache, false otherwise. + */ + get _isResponseCached() + { + return this.httpActivity.response.status == 304; + }, + + /** + * Tells if the request body includes form data. + * + * @returns boolean + * Returns true if the posted body contains form data. + */ + get _isRequestBodyFormData() + { + let requestBody = this.httpActivity.request.postData.text; + if (typeof requestBody == "object" && requestBody.type == "longString") { + requestBody = requestBody.initial; + } + return this._fromDataRegExp.test(requestBody); + }, + + /** + * Appends the node with id=aId by the text aValue. + * + * @private + * @param string aId + * @param string aValue + * @return nsIDOMElement + * The DOM element with id=aId. + */ + _appendTextNode: function NP__appendTextNode(aId, aValue) + { + let textNode = this.document.createTextNode(aValue); + let elem = this.document.getElementById(aId); + elem.appendChild(textNode); + return elem; + }, + + /** + * Generates some HTML to display the key-value pair of the aList data. The + * generated HTML is added to node with id=aParentId. + * + * @param string aParentId + * Id of the parent node to append the list to. + * @oaram array aList + * Array that holds the objects you want to display. Each object must + * have two properties: name and value. + * @param boolean aIgnoreCookie + * If true, the key-value named "Cookie" is not added to the list. + * @returns void + */ + _appendList: function NP_appendList(aParentId, aList, aIgnoreCookie) + { + let parent = this.document.getElementById(aParentId); + let doc = this.document; + + aList.sort(function(a, b) { + return a.name.toLowerCase() < b.name.toLowerCase(); + }); + + aList.forEach((aItem) => { + let name = aItem.name; + if (aIgnoreCookie && (name == "Cookie" || name == "Set-Cookie")) { + return; + } + + let value = aItem.value; + let longString = null; + if (typeof value == "object" && value.type == "longString") { + value = value.initial; + longString = true; + } + + /** + * The following code creates the HTML: + * <tr> + * <th scope="row" class="property-name">${line}:</th> + * <td class="property-value">${aList[line]}</td> + * </tr> + * and adds it to parent. + */ + let row = doc.createElement("tr"); + let textNode = doc.createTextNode(name + ":"); + let th = doc.createElement("th"); + th.setAttribute("scope", "row"); + th.setAttribute("class", "property-name"); + th.appendChild(textNode); + row.appendChild(th); + + textNode = doc.createTextNode(value); + let td = doc.createElement("td"); + td.setAttribute("class", "property-value"); + td.appendChild(textNode); + + if (longString) { + let a = doc.createElement("a"); + a.href = "#"; + a.className = "longStringEllipsis"; + a.addEventListener("mousedown", this._longStringClick.bind(this, aItem)); + a.textContent = l10n.getStr("longStringEllipsis"); + td.appendChild(a); + } + + row.appendChild(td); + + parent.appendChild(row); + }); + }, + + /** + * The click event handler for the ellipsis which allows the user to retrieve + * the full header value. + * + * @private + * @param object aHeader + * The header object with the |name| and |value| properties. + * @param nsIDOMEvent aEvent + * The DOM click event object. + */ + _longStringClick: function NP__longStringClick(aHeader, aEvent) + { + aEvent.preventDefault(); + + let longString = this.webconsole.webConsoleClient.longString(aHeader.value); + + longString.substring(longString.initial.length, longString.length, + function NP__onLongStringSubstring(aResponse) + { + if (aResponse.error) { + Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error); + return; + } + + aHeader.value = aHeader.value.initial + aResponse.substring; + + let textNode = aEvent.target.previousSibling; + textNode.textContent += aResponse.substring; + textNode.parentNode.removeChild(aEvent.target); + }); + }, + + /** + * Displays the node with id=aId. + * + * @private + * @param string aId + * @return nsIDOMElement + * The element with id=aId. + */ + _displayNode: function NP__displayNode(aId) + { + let elem = this.document.getElementById(aId); + elem.style.display = "block"; + }, + + /** + * Sets the request URL, request method, the timing information when the + * request started and the request header content on the NetworkPanel. + * If the request header contains cookie data, a list of sent cookies is + * generated and a special sent cookie section is displayed + the cookie list + * added to it. + * + * @returns void + */ + _displayRequestHeader: function NP__displayRequestHeader() + { + let request = this.httpActivity.request; + let requestTime = new Date(this.httpActivity.startedDateTime); + + this._appendTextNode("headUrl", request.url); + this._appendTextNode("headMethod", request.method); + this._appendTextNode("requestHeadersInfo", + l10n.timestampString(requestTime)); + + this._appendList("requestHeadersContent", request.headers, true); + + if (request.cookies.length > 0) { + this._displayNode("requestCookie"); + this._appendList("requestCookieContent", request.cookies); + } + }, + + /** + * Displays the request body section of the NetworkPanel and set the request + * body content on the NetworkPanel. + * + * @returns void + */ + _displayRequestBody: function NP__displayRequestBody() + { + let postData = this.httpActivity.request.postData; + this._displayNode("requestBody"); + this._appendTextNode("requestBodyContent", postData.text); + }, + + /* + * Displays the `sent form data` section. Parses the request header for the + * submitted form data displays it inside of the `sent form data` section. + * + * @returns void + */ + _displayRequestForm: function NP__processRequestForm() + { + let postData = this.httpActivity.request.postData.text; + let requestBodyLines = postData.split("\n"); + let formData = requestBodyLines[requestBodyLines.length - 1]. + replace(/\+/g, " ").split("&"); + + function unescapeText(aText) + { + try { + return decodeURIComponent(aText); + } + catch (ex) { + return decodeURIComponent(unescape(aText)); + } + } + + let formDataArray = []; + for (let i = 0; i < formData.length; i++) { + let data = formData[i]; + let idx = data.indexOf("="); + let key = data.substring(0, idx); + let value = data.substring(idx + 1); + formDataArray.push({ + name: unescapeText(key), + value: unescapeText(value) + }); + } + + this._appendList("requestFormDataContent", formDataArray); + this._displayNode("requestFormData"); + }, + + /** + * Displays the response section of the NetworkPanel, sets the response status, + * the duration between the start of the request and the receiving of the + * response header as well as the response header content on the the NetworkPanel. + * + * @returns void + */ + _displayResponseHeader: function NP__displayResponseHeader() + { + let timing = this.httpActivity.timings; + let response = this.httpActivity.response; + + this._appendTextNode("headStatus", + [response.httpVersion, response.status, + response.statusText].join(" ")); + + // Calculate how much time it took from the request start, until the + // response started to be received. + let deltaDuration = 0; + ["dns", "connect", "send", "wait"].forEach(function (aValue) { + let ms = timing[aValue]; + if (ms > -1) { + deltaDuration += ms; + } + }); + + this._appendTextNode("responseHeadersInfo", + this._format("durationMS", [deltaDuration])); + + this._displayNode("responseContainer"); + this._appendList("responseHeadersContent", response.headers, true); + + if (response.cookies.length > 0) { + this._displayNode("responseCookie"); + this._appendList("responseCookieContent", response.cookies); + } + }, + + /** + * Displays the respones image section, sets the source of the image displayed + * in the image response section to the request URL and the duration between + * the receiving of the response header and the end of the request. Once the + * image is loaded, the size of the requested image is set. + * + * @returns void + */ + _displayResponseImage: function NP__displayResponseImage() + { + let self = this; + let timing = this.httpActivity.timings; + let request = this.httpActivity.request; + let response = this.httpActivity.response; + let cached = ""; + + if (this._isResponseCached) { + cached = "Cached"; + } + + let imageNode = this.document.getElementById("responseImage" + + cached + "Node"); + + let text = response.content.text; + if (typeof text == "object" && text.type == "longString") { + this._showResponseBodyFetchLink(); + } + else { + imageNode.setAttribute("src", + "data:" + this.contentType + ";base64," + text); + } + + // This function is called to set the imageInfo. + function setImageInfo() { + self._appendTextNode("responseImage" + cached + "Info", + self._format("imageSizeDeltaDurationMS", + [ imageNode.width, imageNode.height, timing.receive ] + ) + ); + } + + // Check if the image is already loaded. + if (imageNode.width != 0) { + setImageInfo(); + } + else { + // Image is not loaded yet therefore add a load event. + imageNode.addEventListener("load", function imageNodeLoad() { + imageNode.removeEventListener("load", imageNodeLoad, false); + setImageInfo(); + }, false); + } + + this._displayNode("responseImage" + cached); + }, + + /** + * Displays the response body section, sets the the duration between + * the receiving of the response header and the end of the request as well as + * the content of the response body on the NetworkPanel. + * + * @returns void + */ + _displayResponseBody: function NP__displayResponseBody() + { + let timing = this.httpActivity.timings; + let response = this.httpActivity.response; + let cached = this._isResponseCached ? "Cached" : ""; + + this._appendTextNode("responseBody" + cached + "Info", + this._format("durationMS", [timing.receive])); + + this._displayNode("responseBody" + cached); + + let text = response.content.text; + if (typeof text == "object") { + text = text.initial; + this._showResponseBodyFetchLink(); + } + + this._appendTextNode("responseBody" + cached + "Content", text); + }, + + /** + * Show the "fetch response body" link. + * @private + */ + _showResponseBodyFetchLink: function NP__showResponseBodyFetchLink() + { + let content = this.httpActivity.response.content; + + let elem = this._appendTextNode("responseBodyFetchLink", + this._format("fetchRemainingResponseContentLink", + [content.text.length - content.text.initial.length])); + + elem.style.display = "block"; + elem.addEventListener("mousedown", this._responseBodyFetch); + }, + + /** + * Click event handler for the link that allows users to fetch the remaining + * response body. + * + * @private + * @param nsIDOMEvent aEvent + */ + _responseBodyFetch: function NP__responseBodyFetch(aEvent) + { + aEvent.target.style.display = "none"; + aEvent.target.removeEventListener("mousedown", this._responseBodyFetch); + + let content = this.httpActivity.response.content; + let longString = this.webconsole.webConsoleClient.longString(content.text); + longString.substring(longString.initial.length, longString.length, + (aResponse) => + { + if (aResponse.error) { + Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error); + return; + } + + content.text = content.text.initial + aResponse.substring; + let cached = this._isResponseCached ? "Cached" : ""; + + if (this._responseIsImage) { + let imageNode = this.document.getElementById("responseImage" + + cached + "Node"); + imageNode.src = + "data:" + this.contentType + ";base64," + content.text; + } + else { + this._appendTextNode("responseBody" + cached + "Content", + aResponse.substring); + } + }); + }, + + /** + * Displays the `Unknown Content-Type hint` and sets the duration between the + * receiving of the response header on the NetworkPanel. + * + * @returns void + */ + _displayResponseBodyUnknownType: function NP__displayResponseBodyUnknownType() + { + let timing = this.httpActivity.timings; + + this._displayNode("responseBodyUnknownType"); + this._appendTextNode("responseBodyUnknownTypeInfo", + this._format("durationMS", [timing.receive])); + + this._appendTextNode("responseBodyUnknownTypeContent", + this._format("responseBodyUnableToDisplay.content", [this.contentType])); + }, + + /** + * Displays the `no response body` section and sets the the duration between + * the receiving of the response header and the end of the request. + * + * @returns void + */ + _displayNoResponseBody: function NP_displayNoResponseBody() + { + let timing = this.httpActivity.timings; + + this._displayNode("responseNoBody"); + this._appendTextNode("responseNoBodyInfo", + this._format("durationMS", [timing.receive])); + }, + + /** + * Updates the content of the NetworkPanel's iframe. + * + * @returns void + */ + update: function NP_update() + { + if (!this.document || this.document.readyState != "complete") { + return; + } + + let updates = this.httpActivity.updates; + let timing = this.httpActivity.timings; + let request = this.httpActivity.request; + let response = this.httpActivity.response; + + switch (this._state) { + case this._INIT: + this._displayRequestHeader(); + this._state = this._DISPLAYED_REQUEST_HEADER; + // FALL THROUGH + + case this._DISPLAYED_REQUEST_HEADER: + // Process the request body if there is one. + if (!this.httpActivity.discardRequestBody && request.postData.text) { + this._updateRequestBody(); + this._state = this._DISPLAYED_REQUEST_BODY; + } + // FALL THROUGH + + case this._DISPLAYED_REQUEST_BODY: + if (!response.headers.length || !Object.keys(timing).length) { + break; + } + this._displayResponseHeader(); + this._state = this._DISPLAYED_RESPONSE_HEADER; + // FALL THROUGH + + case this._DISPLAYED_RESPONSE_HEADER: + if (updates.indexOf("responseContent") == -1 || + updates.indexOf("eventTimings") == -1) { + break; + } + + this._state = this._TRANSITION_CLOSED; + if (this.httpActivity.discardResponseBody) { + break; + } + + if (!response.content || !response.content.text) { + this._displayNoResponseBody(); + } + else if (this._responseIsImage) { + this._displayResponseImage(); + } + else if (!this._isResponseBodyTextData) { + this._displayResponseBodyUnknownType(); + } + else if (response.content.text) { + this._displayResponseBody(); + } + break; + } + + if (this._onUpdate) { + this._onUpdate(); + } + }, + + /** + * Update the panel to hold the current information we have about the request + * body. + * @private + */ + _updateRequestBody: function NP__updateRequestBody() + { + let postData = this.httpActivity.request.postData; + if (typeof postData.text == "object" && postData.text.type == "longString") { + let elem = this._appendTextNode("requestBodyFetchLink", + this._format("fetchRemainingRequestContentLink", + [postData.text.length - postData.text.initial.length])); + + elem.style.display = "block"; + elem.addEventListener("mousedown", this._requestBodyFetch); + return; + } + + // Check if we send some form data. If so, display the form data special. + if (this._isRequestBodyFormData) { + this._displayRequestForm(); + } + else { + this._displayRequestBody(); + } + }, + + /** + * Click event handler for the link that allows users to fetch the remaining + * request body. + * + * @private + * @param nsIDOMEvent aEvent + */ + _requestBodyFetch: function NP__requestBodyFetch(aEvent) + { + aEvent.target.style.display = "none"; + aEvent.target.removeEventListener("mousedown", this._responseBodyFetch); + + let postData = this.httpActivity.request.postData; + let longString = this.webconsole.webConsoleClient.longString(postData.text); + longString.substring(longString.initial.length, longString.length, + (aResponse) => + { + if (aResponse.error) { + Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error); + return; + } + + postData.text = postData.text.initial + aResponse.substring; + this._updateRequestBody(); + }); + }, +}; + +/** + * Creates a DOMNode and sets all the attributes of aAttributes on the created + * element. + * + * @param nsIDOMDocument aDocument + * Document to create the new DOMNode. + * @param string aTag + * Name of the tag for the DOMNode. + * @param object aAttributes + * Attributes set on the created DOMNode. + * + * @returns nsIDOMNode + */ +function createElement(aDocument, aTag, aAttributes) +{ + let node = aDocument.createElement(aTag); + if (aAttributes) { + for (let attr in aAttributes) { + node.setAttribute(attr, aAttributes[attr]); + } + } + return node; +} + +/** + * Creates a new DOMNode and appends it to aParent. + * + * @param nsIDOMNode aParent + * A parent node to append the created element. + * @param string aTag + * Name of the tag for the DOMNode. + * @param object aAttributes + * Attributes set on the created DOMNode. + * + * @returns nsIDOMNode + */ +function createAndAppendElement(aParent, aTag, aAttributes) +{ + let node = createElement(aParent.ownerDocument, aTag, aAttributes); + aParent.appendChild(node); + return node; +}
--- a/browser/devtools/webconsole/test/browser.ini +++ b/browser/devtools/webconsole/test/browser.ini @@ -184,16 +184,18 @@ skip-if = buildapp == 'mulet' skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s [browser_console_variables_view_while_debugging_and_inspecting.js] skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s [browser_eval_in_debugger_stackframe.js] skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s [browser_eval_in_debugger_stackframe2.js] [browser_jsterm_inspect.js] [browser_longstring_hang.js] +[browser_netpanel_longstring_expand.js] +skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s [browser_output_breaks_after_console_dir_uninspectable.js] [browser_output_longstring_expand.js] [browser_repeated_messages_accuracy.js] skip-if = buildapp == 'mulet' [browser_result_format_as_string.js] [browser_warn_user_about_replaced_api.js] [browser_webconsole_abbreviate_source_url.js] [browser_webconsole_allow_mixedcontent_securityerrors.js] @@ -220,16 +222,17 @@ skip-if = e10s # Bug 1042253 - webconsol [browser_webconsole_bug_588342_document_focus.js] skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s [browser_webconsole_bug_588730_text_node_insertion.js] [browser_webconsole_bug_588967_input_expansion.js] [browser_webconsole_bug_589162_css_filter.js] [browser_webconsole_bug_592442_closing_brackets.js] [browser_webconsole_bug_593003_iframe_wrong_hud.js] skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s +[browser_webconsole_bug_594477_clickable_output.js] [browser_webconsole_bug_594497_history_arrow_keys.js] [browser_webconsole_bug_595223_file_uri.js] [browser_webconsole_bug_595350_multiple_windows_and_tabs.js] skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s [browser_webconsole_bug_595934_message_categories.js] skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s [browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js] skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s @@ -237,24 +240,27 @@ skip-if = e10s # Bug 1042253 - webconsol [browser_webconsole_bug_597136_network_requests_from_chrome.js] [browser_webconsole_bug_597460_filter_scroll.js] [browser_webconsole_bug_597756_reopen_closed_tab.js] [browser_webconsole_bug_599725_response_headers.js] [browser_webconsole_bug_600183_charset.js] [browser_webconsole_bug_601177_log_levels.js] [browser_webconsole_bug_601352_scroll.js] [browser_webconsole_bug_601667_filter_buttons.js] +[browser_webconsole_bug_602572_log_bodies_checkbox.js] +skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s [browser_webconsole_bug_603750_websocket.js] [browser_webconsole_bug_611795.js] [browser_webconsole_bug_613013_console_api_iframe.js] [browser_webconsole_bug_613280_jsterm_copy.js] [browser_webconsole_bug_613642_maintain_scroll.js] [browser_webconsole_bug_613642_prune_scroll.js] [browser_webconsole_bug_614793_jsterm_scroll.js] [browser_webconsole_bug_618078_network_exceptions.js] +[browser_webconsole_bug_618311_close_panels.js] [browser_webconsole_bug_621644_jsterm_dollar.js] [browser_webconsole_bug_622303_persistent_filters.js] [browser_webconsole_bug_623749_ctrl_a_select_all_winnt.js] skip-if = os != "win" [browser_webconsole_bug_630733_response_redirect_headers.js] [browser_webconsole_bug_632275_getters_document_width.js] [browser_webconsole_bug_632347_iterators_generators.js] skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s @@ -323,16 +329,17 @@ skip-if = buildapp == 'mulet' || e10s # [browser_webconsole_inspect-parsed-documents.js] [browser_webconsole_js_input_expansion.js] [browser_webconsole_jsterm.js] skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout) [browser_webconsole_live_filtering_of_message_types.js] [browser_webconsole_live_filtering_on_search_strings.js] [browser_webconsole_message_node_id.js] [browser_webconsole_netlogging.js] +[browser_webconsole_network_panel.js] [browser_webconsole_notifications.js] [browser_webconsole_open-links-without-callback.js] [browser_webconsole_promise.js] [browser_webconsole_output_copy_newlines.js] [browser_webconsole_output_order.js] [browser_webconsole_property_provider.js] skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s [browser_webconsole_scratchpad_panel_link.js]
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/test/browser_netpanel_longstring_expand.js @@ -0,0 +1,312 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +// Tests that the network panel works with LongStringActors. + +"use strict"; + +const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" + + "test/test-console.html"; +const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/" + + "test/test-image.png"; + +const TEST_IMG_BASE64 = + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAVRJ" + + "REFUOI2lk7FLw0AUxr+YpC1CBqcMWfsvCCLdXFzqEJCgDl1EQRGxg9AhSBEJONhFhG52UCuF" + + "Djq5dxD8FwoO0qGDOBQkl7vLOeWa2EQDffDBvTu+373Hu1OEEJgntGgxGD6J+7fLXKbt5VNU" + + "yhsKAChRBQcPFVFeWskFGH694mZroCQqCLlAwPxcgJBP254CmAD5B7C7dgHLMLF3uzoL4DQE" + + "od+Z5sP1FizDxGgyBqfhLID9AahX29J89bwPFgMsSEAQglAf9WobhPpScbPXr4FQHyzIADTs" + + "DizDRMPuIOC+zEeTMZo9BwH3EfAMACccbtfGaDKGZZg423yUZrdrg3EqxQlPr0BTdTR7joRE" + + "N2uqnlBmCwW1hIJagtev4f3zA16/JvfiigMSYyzqJXlw/XKUyOORMUaBor6YavgdjKa8xGOn" + + "idadmwtwsnMu18q83/kHSou+bFNDDr4AAAAASUVORK5CYII="; + +let testDriver; + +function test() { + loadTab(TEST_URI).then(() => { + openConsole().then(testNetworkPanel); + }); +} + +function testNetworkPanel() { + testDriver = testGen(); + testDriver.next(); +} + +function checkIsVisible(aPanel, aList) { + for (let id in aList) { + let node = aPanel.document.getElementById(id); + let isVisible = aList[id]; + is(node.style.display, (isVisible ? "block" : "none"), + id + " isVisible=" + isVisible); + } +} + +function checkNodeContent(aPanel, aId, aContent) { + let node = aPanel.document.getElementById(aId); + if (node == null) { + ok(false, "Tried to access node " + aId + " that doesn't exist!"); + } else if (node.textContent.indexOf(aContent) != -1) { + ok(true, "checking content of " + aId); + } else { + ok(false, "Got false value for " + aId + ": " + node.textContent + + " doesn't have " + aContent); + } +} + +function checkNodeKeyValue(aPanel, aId, aKey, aValue) { + let node = aPanel.document.getElementById(aId); + + let headers = node.querySelectorAll("th"); + for (let i = 0; i < headers.length; i++) { + if (headers[i].textContent == (aKey + ":")) { + is(headers[i].nextElementSibling.textContent, aValue, + "checking content of " + aId + " for key " + aKey); + return; + } + } + + ok(false, "content check failed for " + aId + ", key " + aKey); +} + +function* testGen() { + let hud = HUDService.getHudByWindow(content); + let filterBox = hud.ui.filterBox; + + let headerValue = (new Array(456)).join("fooz bar"); + let headerValueGrip = { + type: "longString", + initial: headerValue.substr(0, 123), + length: headerValue.length, + actor: "faktor", + _fullString: headerValue, + }; + + let imageContentGrip = { + type: "longString", + initial: TEST_IMG_BASE64.substr(0, 143), + length: TEST_IMG_BASE64.length, + actor: "faktor2", + _fullString: TEST_IMG_BASE64, + }; + + let postDataValue = (new Array(123)).join("post me"); + let postDataGrip = { + type: "longString", + initial: postDataValue.substr(0, 172), + length: postDataValue.length, + actor: "faktor3", + _fullString: postDataValue, + }; + + let httpActivity = { + updates: ["responseContent", "eventTimings"], + discardRequestBody: false, + discardResponseBody: false, + startedDateTime: (new Date()).toISOString(), + request: { + url: TEST_IMG, + method: "GET", + cookies: [], + headers: [ + { name: "foo", value: "bar" }, + { name: "loongstring", value: headerValueGrip }, + ], + postData: { text: postDataGrip }, + }, + response: { + httpVersion: "HTTP/3.14", + status: 2012, + statusText: "ddahl likes tacos :)", + headers: [ + { name: "Content-Type", value: "image/png" }, + ], + content: { mimeType: "image/png", text: imageContentGrip }, + cookies: [], + }, + timings: { wait: 15, receive: 23 }, + }; + + let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + + is(filterBox._netPanel, networkPanel, + "Network panel stored on the anchor object"); + + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + info("test 1: check if a header value is expandable"); + + checkIsVisible(networkPanel, { + requestCookie: false, + requestFormData: false, + requestBody: false, + requestBodyFetchLink: true, + responseContainer: true, + responseBody: false, + responseNoBody: false, + responseImage: true, + responseImageCached: false, + responseBodyFetchLink: true, + }); + + checkNodeKeyValue(networkPanel, "requestHeadersContent", "foo", "bar"); + checkNodeKeyValue(networkPanel, "requestHeadersContent", "loongstring", + headerValueGrip.initial + "[\u2026]"); + + let webConsoleClient = networkPanel.webconsole.webConsoleClient; + let longStringFn = webConsoleClient.longString; + + let expectedGrip = headerValueGrip; + + function longStringClientProvider(aLongString) { + is(aLongString, expectedGrip, + "longString grip is correct"); + + return { + initial: expectedGrip.initial, + length: expectedGrip.length, + substring: function(aStart, aEnd, aCallback) { + is(aStart, expectedGrip.initial.length, + "substring start is correct"); + is(aEnd, expectedGrip.length, + "substring end is correct"); + + executeSoon(function() { + aCallback({ + substring: expectedGrip._fullString.substring(aStart, aEnd), + }); + + executeSoon(function() { + testDriver.next(); + }); + }); + }, + }; + } + + webConsoleClient.longString = longStringClientProvider; + + let clickable = networkPanel.document + .querySelector("#requestHeadersContent .longStringEllipsis"); + ok(clickable, "long string ellipsis is shown"); + + EventUtils.sendMouseEvent({ type: "mousedown"}, clickable, + networkPanel.document.defaultView); + + yield undefined; + + clickable = networkPanel.document + .querySelector("#requestHeadersContent .longStringEllipsis"); + ok(!clickable, "long string ellipsis is not shown"); + + checkNodeKeyValue(networkPanel, "requestHeadersContent", "loongstring", + expectedGrip._fullString); + + info("test 2: check that response body image fetching works"); + expectedGrip = imageContentGrip; + + let imgNode = networkPanel.document.getElementById("responseImageNode"); + ok(!imgNode.getAttribute("src"), "no image is displayed"); + + clickable = networkPanel.document.querySelector("#responseBodyFetchLink"); + EventUtils.sendMouseEvent({ type: "mousedown"}, clickable, + networkPanel.document.defaultView); + + yield undefined; + + imgNode = networkPanel.document.getElementById("responseImageNode"); + is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64, + "displayed image is correct"); + is(clickable.style.display, "none", "#responseBodyFetchLink is not visible"); + + info("test 3: expand the request body"); + + expectedGrip = postDataGrip; + + clickable = networkPanel.document.querySelector("#requestBodyFetchLink"); + EventUtils.sendMouseEvent({ type: "mousedown"}, clickable, + networkPanel.document.defaultView); + yield undefined; + + is(clickable.style.display, "none", "#requestBodyFetchLink is not visible"); + + checkIsVisible(networkPanel, { + requestBody: true, + requestBodyFetchLink: false, + }); + + checkNodeContent(networkPanel, "requestBodyContent", + expectedGrip._fullString); + + webConsoleClient.longString = longStringFn; + + networkPanel.panel.hidePopup(); + + info("test 4: reponse body long text"); + + httpActivity.response.content.mimeType = "text/plain"; + httpActivity.response.headers[0].value = "text/plain"; + + expectedGrip = imageContentGrip; + + // Reset response.content.text to avoid caching of the full string. + httpActivity.response.content.text = expectedGrip; + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + is(filterBox._netPanel, networkPanel, + "Network panel stored on httpActivity object"); + + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + checkIsVisible(networkPanel, { + requestCookie: false, + requestFormData: false, + requestBody: true, + requestBodyFetchLink: false, + responseContainer: true, + responseBody: true, + responseNoBody: false, + responseImage: false, + responseImageCached: false, + responseBodyFetchLink: true, + }); + + checkNodeContent(networkPanel, "responseBodyContent", expectedGrip.initial); + + webConsoleClient.longString = longStringClientProvider; + + clickable = networkPanel.document.querySelector("#responseBodyFetchLink"); + EventUtils.sendMouseEvent({ type: "mousedown"}, clickable, + networkPanel.document.defaultView); + + yield undefined; + + webConsoleClient.longString = longStringFn; + is(clickable.style.display, "none", "#responseBodyFetchLink is not visible"); + checkNodeContent(networkPanel, "responseBodyContent", + expectedGrip._fullString); + + networkPanel.panel.hidePopup(); + + // All done! + testDriver = null; + executeSoon(finishTest); + + yield undefined; +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js @@ -0,0 +1,132 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Contributor(s): + * Mihai Șucan <mihai.sucan@gmail.com> + * + * ***** END LICENSE BLOCK ***** */ + +"use strict"; + +const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" + + "test/test-console.html"; +let HUD; +let outputItem; +let outputNode; + +let test = asyncTest(function* () { + yield loadTab(TEST_URI); + + HUD = yield openConsole(); + outputNode = HUD.outputNode; + + // reload the tab + BrowserReload(); + yield loadBrowser(gBrowser.selectedBrowser); + + let event = yield clickEvents(); + yield testClickAgain(event); + yield networkPanelHidden(); + + HUD = outputItem = outputNode = null; +}); + +function clickEvents() { + let deferred = promise.defer(); + + waitForMessages({ + webconsole: HUD, + messages: [{ + text: "test-console.html", + category: CATEGORY_NETWORK, + severity: SEVERITY_LOG, + }], + }).then(([result]) => { + let msg = [...result.matched][0]; + outputItem = msg.querySelector(".message-body .url"); + ok(outputItem, "found a network message"); + document.addEventListener("popupshown", function onPanelShown(event) { + document.removeEventListener("popupshown", onPanelShown, false); + deferred.resolve(event); + }, false); + + // Send the mousedown and click events such that the network panel opens. + EventUtils.sendMouseEvent({type: "mousedown"}, outputItem); + EventUtils.sendMouseEvent({type: "click"}, outputItem); + }); + + return deferred.promise; +} + +function testClickAgain(event) { + info("testClickAgain"); + + let deferred = promise.defer(); + + document.addEventListener("popupshown", networkPanelShowFailure, false); + + // The network panel should not open for the second time. + EventUtils.sendMouseEvent({type: "mousedown"}, outputItem); + EventUtils.sendMouseEvent({type: "click"}, outputItem); + + executeSoon(function() { + document.addEventListener("popuphidden", function onHidden() { + document.removeEventListener("popuphidden", onHidden, false); + deferred.resolve(); + }, false); + event.target.hidePopup(); + }); + + return deferred.promise; +} + +function networkPanelShowFailure() { + ok(false, "the network panel should not show"); +} + +function networkPanelHidden() { + let deferred = promise.defer(); + + info("networkPanelHidden"); + + // The network panel should not show because this is a mouse event that starts + // in a position and ends in another. + EventUtils.sendMouseEvent({type: "mousedown", clientX: 3, clientY: 4}, + outputItem); + EventUtils.sendMouseEvent({type: "click", clientX: 5, clientY: 6}, + outputItem); + + // The network panel should not show because this is a middle-click. + EventUtils.sendMouseEvent({type: "mousedown", button: 1}, + outputItem); + EventUtils.sendMouseEvent({type: "click", button: 1}, + outputItem); + + // The network panel should not show because this is a right-click. + EventUtils.sendMouseEvent({type: "mousedown", button: 2}, + outputItem); + EventUtils.sendMouseEvent({type: "click", button: 2}, + outputItem); + + executeSoon(function() { + document.removeEventListener("popupshown", networkPanelShowFailure, false); + + // Done with the network output. Now test the jsterm output and the property + // panel. + HUD.jsterm.execute("document").then((msg) => { + info("jsterm execute 'document' callback"); + + HUD.jsterm.once("variablesview-open", deferred.resolve); + outputItem = msg.querySelector(".message-body a"); + ok(outputItem, "jsterm output message found"); + + // Send the mousedown and click events such that the property panel opens. + EventUtils.sendMouseEvent({type: "mousedown"}, outputItem); + EventUtils.sendMouseEvent({type: "click"}, outputItem); + }); + }); + + return deferred.promise; +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js @@ -0,0 +1,186 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Contributor(s): + * Mihai Șucan <mihai.sucan@gmail.com> + * + * ***** END LICENSE BLOCK ***** */ + +"use strict"; + +let menuitems = []; +let menupopups = []; +let huds = []; +let tabs = []; +let runCount = 0; + +const TEST_URI1 = "data:text/html;charset=utf-8,Web Console test for " + + "bug 602572: log bodies checkbox. tab 1"; +const TEST_URI2 = "data:text/html;charset=utf-8,Web Console test for " + + "bug 602572: log bodies checkbox. tab 2"; + +function test() { + if (runCount == 0) { + requestLongerTimeout(2); + } + + // open tab 2 + function openTab() { + loadTab(TEST_URI2).then((tab) => { + tabs.push(tab.tab); + openConsole().then((hud) => { + hud.iframeWindow.requestAnimationFrame(startTest); + }); + }); + } + + // open tab 1 + loadTab(TEST_URI1).then((tab) => { + tabs.push(tab.tab); + openConsole().then((hud) => { + hud.iframeWindow.requestAnimationFrame(() => { + info("iframe1 root height " + hud.ui.rootElement.clientHeight); + + openTab(); + }); + }); + }); +} + +function startTest() { + // Find the relevant elements in the Web Console of tab 2. + let win2 = tabs[runCount * 2 + 1].linkedBrowser.contentWindow; + huds[1] = HUDService.getHudByWindow(win2); + info("startTest: iframe2 root height " + huds[1].ui.rootElement.clientHeight); + + if (runCount == 0) { + menuitems[1] = huds[1].ui.rootElement.querySelector("#saveBodies"); + } else { + menuitems[1] = huds[1].ui.rootElement + .querySelector("#saveBodiesContextMenu"); + } + menupopups[1] = menuitems[1].parentNode; + + // Open the context menu from tab 2. + menupopups[1].addEventListener("popupshown", onpopupshown2, false); + executeSoon(function() { + menupopups[1].openPopup(); + }); +} + +function onpopupshown2(evt) { + menupopups[1].removeEventListener(evt.type, onpopupshown2, false); + + // By default bodies are not logged. + isnot(menuitems[1].getAttribute("checked"), "true", + "menuitems[1] is not checked"); + + ok(!huds[1].ui._saveRequestAndResponseBodies, "bodies are not logged"); + + // Enable body logging. + huds[1].ui.setSaveRequestAndResponseBodies(true).then(() => { + menupopups[1].hidePopup(); + }); + + menupopups[1].addEventListener("popuphidden", function _onhidden(evtPopup) { + menupopups[1].removeEventListener(evtPopup.type, _onhidden, false); + + info("menupopups[1] hidden"); + + // Reopen the context menu. + huds[1].ui.once("save-bodies-ui-toggled", () => testpopup2b(evtPopup)); + menupopups[1].openPopup(); + }, false); +} + +function testpopup2b() { + is(menuitems[1].getAttribute("checked"), "true", "menuitems[1] is checked"); + + menupopups[1].addEventListener("popuphidden", function _onhidden(evtPopup) { + menupopups[1].removeEventListener(evtPopup.type, _onhidden, false); + + info("menupopups[1] hidden"); + + // Switch to tab 1 and open the Web Console context menu from there. + gBrowser.selectedTab = tabs[runCount * 2]; + waitForFocus(function() { + // Find the relevant elements in the Web Console of tab 1. + let win1 = tabs[runCount * 2].linkedBrowser.contentWindow; + huds[0] = HUDService.getHudByWindow(win1); + + info("iframe1 root height " + huds[0].ui.rootElement.clientHeight); + + menuitems[0] = huds[0].ui.rootElement.querySelector("#saveBodies"); + menupopups[0] = huds[0].ui.rootElement.querySelector("menupopup"); + + menupopups[0].addEventListener("popupshown", onpopupshown1, false); + executeSoon(() => menupopups[0].openPopup()); + }, tabs[runCount * 2].linkedBrowser.contentWindow); + }, false); + + executeSoon(function() { + menupopups[1].hidePopup(); + }); +} + +function onpopupshown1(evt) { + menupopups[0].removeEventListener(evt.type, onpopupshown1, false); + + // The menuitem checkbox must not be in sync with the other tabs. + isnot(menuitems[0].getAttribute("checked"), "true", + "menuitems[0] is not checked"); + + // Enable body logging for tab 1 as well. + huds[0].ui.setSaveRequestAndResponseBodies(true).then(() => { + menupopups[0].hidePopup(); + }); + + // Close the menu, and switch back to tab 2. + menupopups[0].addEventListener("popuphidden", function _onhidden(evtPopup) { + menupopups[0].removeEventListener(evtPopup.type, _onhidden, false); + + info("menupopups[0] hidden"); + + gBrowser.selectedTab = tabs[runCount * 2 + 1]; + waitForFocus(function() { + // Reopen the context menu from tab 2. + huds[1].ui.once("save-bodies-ui-toggled", () => testpopup2c(evtPopup)); + menupopups[1].openPopup(); + }, tabs[runCount * 2 + 1].linkedBrowser.contentWindow); + }, false); +} + +function testpopup2c() { + is(menuitems[1].getAttribute("checked"), "true", "menuitems[1] is checked"); + + menupopups[1].addEventListener("popuphidden", function _onhidden(evtPopup) { + menupopups[1].removeEventListener(evtPopup.type, _onhidden, false); + + info("menupopups[1] hidden"); + + // Done if on second run + closeConsole(gBrowser.selectedTab).then(function() { + if (runCount == 0) { + runCount++; + info("start second run"); + executeSoon(test); + } else { + gBrowser.removeCurrentTab(); + gBrowser.selectedTab = tabs[2]; + gBrowser.removeCurrentTab(); + gBrowser.selectedTab = tabs[1]; + gBrowser.removeCurrentTab(); + gBrowser.selectedTab = tabs[0]; + gBrowser.removeCurrentTab(); + huds = menuitems = menupopups = tabs = null; + executeSoon(finishTest); + } + }); + }, false); + + executeSoon(function() { + menupopups[1].hidePopup(); + }); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js @@ -0,0 +1,93 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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 TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" + + "test/test-console.html"; + +let test = asyncTest(function* () { + yield loadTab(TEST_URI); + + let hud = yield openConsole(); + + BrowserReload(); + + let results = yield waitForMessages({ + webconsole: hud, + messages: [{ + text: "test-console.html", + category: CATEGORY_NETWORK, + severity: SEVERITY_LOG, + }], + }); + + yield performTest(hud, results); +}); + +function performTest(HUD, results) { + let deferred = promise.defer(); + + let networkMessage = [...results[0].matched][0]; + ok(networkMessage, "network message element"); + + let networkLink = networkMessage.querySelector(".url"); + ok(networkLink, "found network message link"); + + let popupset = document.getElementById("mainPopupSet"); + ok(popupset, "found #mainPopupSet"); + + let popupsShown = 0; + let hiddenPopups = 0; + + let onpopupshown = function() { + document.removeEventListener("popupshown", onpopupshown, false); + popupsShown++; + + executeSoon(function() { + let popups = popupset.querySelectorAll("panel[hudId=" + HUD.hudId + "]"); + is(popups.length, 1, "found one popup"); + + document.addEventListener("popuphidden", onpopuphidden, false); + + registerCleanupFunction(function() { + is(hiddenPopups, 1, "correct number of popups hidden"); + if (hiddenPopups != 1) { + document.removeEventListener("popuphidden", onpopuphidden, false); + } + }); + + executeSoon(closeConsole); + }); + }; + + let onpopuphidden = function() { + document.removeEventListener("popuphidden", onpopuphidden, false); + hiddenPopups++; + + executeSoon(function() { + let popups = popupset.querySelectorAll("panel[hudId=" + HUD.hudId + "]"); + is(popups.length, 0, "no popups found"); + + executeSoon(deferred.resolve); + }); + }; + + document.addEventListener("popupshown", onpopupshown, false); + + registerCleanupFunction(function() { + is(popupsShown, 1, "correct number of popups shown"); + if (popupsShown != 1) { + document.removeEventListener("popupshown", onpopupshown, false); + } + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, networkLink, + HUD.iframeWindow); + EventUtils.sendMouseEvent({ type: "mouseup" }, networkLink, HUD.iframeWindow); + EventUtils.sendMouseEvent({ type: "click" }, networkLink, HUD.iframeWindow); + + return deferred.promise; +}
--- a/browser/devtools/webconsole/test/browser_webconsole_netlogging.js +++ b/browser/devtools/webconsole/test/browser_webconsole_netlogging.js @@ -186,27 +186,25 @@ function testFormSubmission() { let form = content.document.querySelector("form"); form.submit(); }`); } function testNetworkPanel() { // Open the NetworkPanel. The functionality of the NetworkPanel is tested // within separate test files. - hud.ui.openNetworkPanel(lastRequest.actor).then(() => { - let toolbox = gDevTools.getToolbox(hud.target); - is(toolbox.currentToolId, "netmonitor", "Network panel was opened"); - let panel = toolbox.getCurrentPanel(); - let selected = panel.panelWin.NetMonitorView.RequestsMenu.selectedItem; - is(selected.attachment.method, lastRequest.request.method, - "The correct request is selected"); - is(selected.attachment.url, lastRequest.request.url, - "The correct request is definitely selected"); + let networkPanel = hud.ui.openNetworkPanel(hud.ui.filterBox, lastRequest); + + networkPanel.panel.addEventListener("popupshown", function onPopupShown() { + networkPanel.panel.removeEventListener("popupshown", onPopupShown, true); + + is(hud.ui.filterBox._netPanel, networkPanel, + "Network panel stored on anchor node"); + ok(true, "NetworkPanel was opened"); // All tests are done. Shutdown. + networkPanel.panel.hidePopup(); lastRequest = null; HUDService.lastFinishedRequest.callback = null; browser = requestCallback = hud = null; executeSoon(finishTest); - }).then(null, error => { - ok(false, "Got an error: " + error.message + "\n" + error.stack); - }); + }, true); }
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/test/browser_webconsole_network_panel.js @@ -0,0 +1,551 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +// Tests that the network panel works. + +"use strict"; + +const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" + + "test/test-console.html"; +const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/" + + "test/test-image.png"; +const TEST_ENCODING_ISO_8859_1 = "http://example.com/browser/browser/" + + "devtools/webconsole/test/" + + "test-encoding-ISO-8859-1.html"; + +const TEST_IMG_BASE64 = + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAVRJ" + + "REFUOI2lk7FLw0AUxr+YpC1CBqcMWfsvCCLdXFzqEJCgDl1EQRGxg9AhSBEJONhFhG52UCuF" + + "Djq5dxD8FwoO0qGDOBQkl7vLOeWa2EQDffDBvTu+373Hu1OEEJgntGgxGD6J+7fLXKbt5VNU" + + "yhsKAChRBQcPFVFeWskFGH694mZroCQqCLlAwPxcgJBP254CmAD5B7C7dgHLMLF3uzoL4DQE" + + "od+Z5sP1FizDxGgyBqfhLID9AahX29J89bwPFgMsSEAQglAf9WobhPpScbPXr4FQHyzIADTs" + + "DizDRMPuIOC+zEeTMZo9BwH3EfAMACccbtfGaDKGZZg423yUZrdrg3EqxQlPr0BTdTR7joRE" + + "N2uqnlBmCwW1hIJagtev4f3zA16/JvfiigMSYyzqJXlw/XKUyOORMUaBor6YavgdjKa8xGOn" + + "idadmwtwsnMu18q83/kHSou+bFNDDr4AAAAASUVORK5CYII="; + +let testDriver, hud; + +function test() { + loadTab(TEST_URI).then(() => { + openConsole().then(testNetworkPanel); + }); +} + +function testNetworkPanel(aHud) { + hud = aHud; + testDriver = testGen(); + testDriver.next(); +} + +function checkIsVisible(aPanel, aList) { + for (let id in aList) { + let node = aPanel.document.getElementById(id); + let isVisible = aList[id]; + is(node.style.display, (isVisible ? "block" : "none"), + id + " isVisible=" + isVisible); + } +} + +function checkNodeContent(aPanel, aId, aContent) { + let node = aPanel.document.getElementById(aId); + if (node == null) { + ok(false, "Tried to access node " + aId + " that doesn't exist!"); + } else if (node.textContent.indexOf(aContent) != -1) { + ok(true, "checking content of " + aId); + } else { + ok(false, "Got false value for " + aId + ": " + node.textContent + + " doesn't have " + aContent); + } +} + +function checkNodeKeyValue(aPanel, aId, aKey, aValue) { + let node = aPanel.document.getElementById(aId); + + let headers = node.querySelectorAll("th"); + for (let i = 0; i < headers.length; i++) { + if (headers[i].textContent == (aKey + ":")) { + is(headers[i].nextElementSibling.textContent, aValue, + "checking content of " + aId + " for key " + aKey); + return; + } + } + + ok(false, "content check failed for " + aId + ", key " + aKey); +} + +function* testGen() { + let filterBox = hud.ui.filterBox; + + let httpActivity = { + updates: [], + discardRequestBody: true, + discardResponseBody: true, + startedDateTime: (new Date()).toISOString(), + request: { + url: "http://www.testpage.com", + method: "GET", + cookies: [], + headers: [ + { name: "foo", value: "bar" }, + ], + }, + response: { + headers: [], + content: {}, + cookies: [], + }, + timings: {}, + }; + + let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + + is(filterBox._netPanel, networkPanel, + "Network panel stored on the anchor object"); + + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + info("test 1"); + + checkIsVisible(networkPanel, { + requestCookie: false, + requestFormData: false, + requestBody: false, + responseContainer: false, + responseBody: false, + responseNoBody: false, + responseImage: false, + responseImageCached: false + }); + + checkNodeContent(networkPanel, "header", "http://www.testpage.com"); + checkNodeContent(networkPanel, "header", "GET"); + checkNodeKeyValue(networkPanel, "requestHeadersContent", "foo", "bar"); + + // Test request body. + info("test 2: request body"); + httpActivity.discardRequestBody = false; + httpActivity.request.postData = { text: "hello world" }; + networkPanel.update(); + + checkIsVisible(networkPanel, { + requestBody: true, + requestFormData: false, + requestCookie: false, + responseContainer: false, + responseBody: false, + responseNoBody: false, + responseImage: false, + responseImageCached: false + }); + checkNodeContent(networkPanel, "requestBodyContent", "hello world"); + + // Test response header. + info("test 3: response header"); + httpActivity.timings.wait = 10; + httpActivity.response.httpVersion = "HTTP/3.14"; + httpActivity.response.status = 999; + httpActivity.response.statusText = "earthquake win"; + httpActivity.response.content.mimeType = "text/html"; + httpActivity.response.headers.push( + { name: "Content-Type", value: "text/html" }, + { name: "leaveHouses", value: "true" } + ); + + networkPanel.update(); + + checkIsVisible(networkPanel, { + requestBody: true, + requestFormData: false, + requestCookie: false, + responseContainer: true, + responseBody: false, + responseNoBody: false, + responseImage: false, + responseImageCached: false + }); + + checkNodeContent(networkPanel, "header", "HTTP/3.14 999 earthquake win"); + checkNodeKeyValue(networkPanel, "responseHeadersContent", "leaveHouses", + "true"); + checkNodeContent(networkPanel, "responseHeadersInfo", "10ms"); + + info("test 4"); + + httpActivity.discardResponseBody = false; + httpActivity.timings.receive = 2; + networkPanel.update(); + + checkIsVisible(networkPanel, { + requestBody: true, + requestCookie: false, + requestFormData: false, + responseContainer: true, + responseBody: false, + responseNoBody: false, + responseImage: false, + responseImageCached: false + }); + + info("test 5"); + + httpActivity.updates.push("responseContent", "eventTimings"); + networkPanel.update(); + + checkNodeContent(networkPanel, "responseNoBodyInfo", "2ms"); + checkIsVisible(networkPanel, { + requestBody: true, + requestCookie: false, + responseContainer: true, + responseBody: false, + responseNoBody: true, + responseImage: false, + responseImageCached: false + }); + + networkPanel.panel.hidePopup(); + + // Second run: Test for cookies and response body. + info("test 6: cookies and response body"); + httpActivity.request.cookies.push( + { name: "foo", value: "bar" }, + { name: "hello", value: "world" } + ); + httpActivity.response.content.text = "get out here"; + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + is(filterBox._netPanel, networkPanel, + "Network panel stored on httpActivity object"); + + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + checkIsVisible(networkPanel, { + requestBody: true, + requestFormData: false, + requestCookie: true, + responseContainer: true, + responseCookie: false, + responseBody: true, + responseNoBody: false, + responseImage: false, + responseImageCached: false + }); + + checkNodeKeyValue(networkPanel, "requestCookieContent", "foo", "bar"); + checkNodeKeyValue(networkPanel, "requestCookieContent", "hello", "world"); + checkNodeContent(networkPanel, "responseBodyContent", "get out here"); + checkNodeContent(networkPanel, "responseBodyInfo", "2ms"); + + networkPanel.panel.hidePopup(); + + // Third run: Test for response cookies. + info("test 6b: response cookies"); + httpActivity.response.cookies.push( + { name: "foobar", value: "boom" }, + { name: "foobaz", value: "omg" } + ); + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + is(filterBox._netPanel, networkPanel, + "Network panel stored on httpActivity object"); + + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + checkIsVisible(networkPanel, { + requestBody: true, + requestFormData: false, + requestCookie: true, + responseContainer: true, + responseCookie: true, + responseBody: true, + responseNoBody: false, + responseImage: false, + responseImageCached: false, + responseBodyFetchLink: false, + }); + + checkNodeKeyValue(networkPanel, "responseCookieContent", "foobar", "boom"); + checkNodeKeyValue(networkPanel, "responseCookieContent", "foobaz", "omg"); + + networkPanel.panel.hidePopup(); + + // Check image request. + info("test 7: image request"); + httpActivity.response.headers[1].value = "image/png"; + httpActivity.response.content.mimeType = "image/png"; + httpActivity.response.content.text = TEST_IMG_BASE64; + httpActivity.request.url = TEST_IMG; + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + checkIsVisible(networkPanel, { + requestBody: true, + requestFormData: false, + requestCookie: true, + responseContainer: true, + responseBody: false, + responseNoBody: false, + responseImage: true, + responseImageCached: false, + responseBodyFetchLink: false, + }); + + let imgNode = networkPanel.document.getElementById("responseImageNode"); + is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64, + "Displayed image is correct"); + + function checkImageResponseInfo() { + checkNodeContent(networkPanel, "responseImageInfo", "2ms"); + checkNodeContent(networkPanel, "responseImageInfo", "16x16px"); + } + + // Check if the image is loaded already. + imgNode.addEventListener("load", function onLoad() { + imgNode.removeEventListener("load", onLoad, false); + checkImageResponseInfo(); + networkPanel.panel.hidePopup(); + testDriver.next(); + }, false); + yield undefined; + + // Check cached image request. + info("test 8: cached image request"); + httpActivity.response.httpVersion = "HTTP/1.1"; + httpActivity.response.status = 304; + httpActivity.response.statusText = "Not Modified"; + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + checkIsVisible(networkPanel, { + requestBody: true, + requestFormData: false, + requestCookie: true, + responseContainer: true, + responseBody: false, + responseNoBody: false, + responseImage: false, + responseImageCached: true + }); + + imgNode = networkPanel.document.getElementById("responseImageCachedNode"); + is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64, + "Displayed image is correct"); + + networkPanel.panel.hidePopup(); + + // Test sent form data. + info("test 9: sent form data"); + httpActivity.request.postData.text = [ + "Content-Type: application/x-www-form-urlencoded", + "Content-Length: 59", + "name=rob&age=20" + ].join("\n"); + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + checkIsVisible(networkPanel, { + requestBody: false, + requestFormData: true, + requestCookie: true, + responseContainer: true, + responseBody: false, + responseNoBody: false, + responseImage: false, + responseImageCached: true + }); + + checkNodeKeyValue(networkPanel, "requestFormDataContent", "name", "rob"); + checkNodeKeyValue(networkPanel, "requestFormDataContent", "age", "20"); + networkPanel.panel.hidePopup(); + + // Test no space after Content-Type: + info("test 10: no space after Content-Type header in post data"); + httpActivity.request.postData.text = "Content-Type:application/x-www-" + + "form-urlencoded\n"; + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + checkIsVisible(networkPanel, { + requestBody: false, + requestFormData: true, + requestCookie: true, + responseContainer: true, + responseBody: false, + responseNoBody: false, + responseImage: false, + responseImageCached: true + }); + + networkPanel.panel.hidePopup(); + + // Test cached data. + + info("test 11: cached data"); + + httpActivity.request.url = TEST_ENCODING_ISO_8859_1; + httpActivity.response.headers[1].value = "application/json"; + httpActivity.response.content.mimeType = "application/json"; + httpActivity.response.content.text = "my cached data is here!"; + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + checkIsVisible(networkPanel, { + requestBody: false, + requestFormData: true, + requestCookie: true, + responseContainer: true, + responseBody: false, + responseBodyCached: true, + responseNoBody: false, + responseImage: false, + responseImageCached: false + }); + + checkNodeContent(networkPanel, "responseBodyCachedContent", + "my cached data is here!"); + + networkPanel.panel.hidePopup(); + + // Test a response with a content type that can't be displayed in the + // NetworkPanel. + info("test 12: unknown content type"); + httpActivity.response.headers[1].value = "application/x-shockwave-flash"; + httpActivity.response.content.mimeType = "application/x-shockwave-flash"; + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + networkPanel._onUpdate = function() { + networkPanel._onUpdate = null; + executeSoon(function() { + testDriver.next(); + }); + }; + + yield undefined; + + checkIsVisible(networkPanel, { + requestBody: false, + requestFormData: true, + requestCookie: true, + responseContainer: true, + responseBody: false, + responseBodyCached: false, + responseBodyUnknownType: true, + responseNoBody: false, + responseImage: false, + responseImageCached: false + }); + + let responseString = + WCUL10n.getFormatStr("NetworkPanel.responseBodyUnableToDisplay.content", + ["application/x-shockwave-flash"]); + checkNodeContent(networkPanel, + "responseBodyUnknownTypeContent", + responseString); + networkPanel.panel.hidePopup(); + + /* + + // This test disabled. See bug 603620. + + // Test if the NetworkPanel figures out the content type based on an URL as + // well. + delete httpActivity.response.header["Content-Type"]; + httpActivity.url = "http://www.test.com/someCrazyFile.swf?done=right&ending=txt"; + + networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); + networkPanel.isDoneCallback = function NP_doneCallback() { + networkPanel.isDoneCallback = null; + testDriver.next(); + } + + yield undefined; + + checkIsVisible(networkPanel, { + requestBody: false, + requestFormData: true, + requestCookie: true, + responseContainer: true, + responseBody: false, + responseBodyCached: false, + responseBodyUnknownType: true, + responseNoBody: false, + responseImage: false, + responseImageCached: false + }); + + // Systems without Flash installed will return an empty string here. Ignore. + if (networkPanel.document.getElementById("responseBodyUnknownTypeContent").textContent !== "") + checkNodeContent(networkPanel, "responseBodyUnknownTypeContent", responseString); + else + ok(true, "Flash not installed"); + + networkPanel.panel.hidePopup(); */ + + // All done! + testDriver = hud = null; + executeSoon(finishTest); + + yield undefined; +}
--- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -15,16 +15,18 @@ loader.lazyServiceGetter(this, "clipboar "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper"); loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm"); loader.lazyGetter(this, "EventEmitter", () => require("devtools/toolkit/event-emitter")); loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/shared/autocomplete-popup").AutocompletePopup); loader.lazyGetter(this, "ToolSidebar", () => require("devtools/framework/sidebar").ToolSidebar); +loader.lazyGetter(this, "NetworkPanel", + () => require("devtools/webconsole/network-panel").NetworkPanel); loader.lazyGetter(this, "ConsoleOutput", () => require("devtools/webconsole/console-output").ConsoleOutput); loader.lazyGetter(this, "Messages", () => require("devtools/webconsole/console-output").Messages); loader.lazyGetter(this, "asyncStorage", () => require("devtools/toolkit/shared/async-storage")); loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/toolkit/client/main", true); loader.lazyRequireGetter(this, "ObjectClient", "devtools/toolkit/client/main", true); @@ -1666,17 +1668,21 @@ WebConsoleFrame.prototype = { messageNode.classList.add("mixed-content"); this.makeMixedContentNode(body); } let statusNode = this.document.createElementNS(XHTML_NS, "a"); statusNode.className = "status"; body.appendChild(statusNode); - let onClick = () => this.openNetworkPanel(networkInfo.actor); + let onClick = () => { + if (!messageNode._panelOpen) { + this.openNetworkPanel(messageNode, networkInfo); + } + }; this._addMessageLinkCallback(urlNode, onClick); this._addMessageLinkCallback(statusNode, onClick); networkInfo.node = messageNode; this._updateNetMessage(actorId); @@ -1958,27 +1964,144 @@ WebConsoleFrame.prototype = { if (messageNode._netPanel) { messageNode._netPanel.update(); } return updated; }, /** - * Opens the network monitor and highlights the specified request. + * Opens a NetworkPanel. * - * @param string requestId - * The actor ID of the network request. + * @param nsIDOMNode aNode + * The message node you want the panel to be anchored to. + * @param object aHttpActivity + * The HTTP activity object that holds network request and response + * information. This object is given to the NetworkPanel constructor. + * @return object + * The new NetworkPanel instance. */ - openNetworkPanel: function WCF_openNetworkPanel(requestId) + openNetworkPanel: function WCF_openNetworkPanel(aNode, aHttpActivity) { - let toolbox = gDevTools.getToolbox(this.owner.target); - return toolbox.selectTool("netmonitor").then(panel => { - return panel.panelWin.NetMonitorController.inspectRequest(requestId); - }); + let actor = aHttpActivity.actor; + + if (actor) { + this.webConsoleClient.getRequestHeaders(actor, (aResponse) => { + if (aResponse.error) { + Cu.reportError("WCF_openNetworkPanel getRequestHeaders:" + + aResponse.error); + return; + } + + aHttpActivity.request.headers = aResponse.headers; + + this.webConsoleClient.getRequestCookies(actor, onRequestCookies); + }); + } + + let onRequestCookies = (aResponse) => { + if (aResponse.error) { + Cu.reportError("WCF_openNetworkPanel getRequestCookies:" + + aResponse.error); + return; + } + + aHttpActivity.request.cookies = aResponse.cookies; + + this.webConsoleClient.getResponseHeaders(actor, onResponseHeaders); + }; + + let onResponseHeaders = (aResponse) => { + if (aResponse.error) { + Cu.reportError("WCF_openNetworkPanel getResponseHeaders:" + + aResponse.error); + return; + } + + aHttpActivity.response.headers = aResponse.headers; + + this.webConsoleClient.getResponseCookies(actor, onResponseCookies); + }; + + let onResponseCookies = (aResponse) => { + if (aResponse.error) { + Cu.reportError("WCF_openNetworkPanel getResponseCookies:" + + aResponse.error); + return; + } + + aHttpActivity.response.cookies = aResponse.cookies; + + this.webConsoleClient.getRequestPostData(actor, onRequestPostData); + }; + + let onRequestPostData = (aResponse) => { + if (aResponse.error) { + Cu.reportError("WCF_openNetworkPanel getRequestPostData:" + + aResponse.error); + return; + } + + aHttpActivity.request.postData = aResponse.postData; + aHttpActivity.discardRequestBody = aResponse.postDataDiscarded; + + this.webConsoleClient.getResponseContent(actor, onResponseContent); + }; + + let onResponseContent = (aResponse) => { + if (aResponse.error) { + Cu.reportError("WCF_openNetworkPanel getResponseContent:" + + aResponse.error); + return; + } + + aHttpActivity.response.content = aResponse.content; + aHttpActivity.discardResponseBody = aResponse.contentDiscarded; + + this.webConsoleClient.getEventTimings(actor, onEventTimings); + }; + + let onEventTimings = (aResponse) => { + if (aResponse.error) { + Cu.reportError("WCF_openNetworkPanel getEventTimings:" + + aResponse.error); + return; + } + + aHttpActivity.timings = aResponse.timings; + + openPanel(); + }; + + let openPanel = () => { + aNode._netPanel = netPanel; + + let panel = netPanel.panel; + panel.openPopup(aNode, "after_pointer", 0, 0, false, false); + panel.sizeTo(450, 500); + panel.setAttribute("hudId", this.hudId); + + panel.addEventListener("popuphiding", function WCF_netPanel_onHide() { + panel.removeEventListener("popuphiding", WCF_netPanel_onHide); + + aNode._panelOpen = false; + aNode._netPanel = null; + }); + + aNode._panelOpen = true; + }; + + let netPanel = new NetworkPanel(this.popupset, aHttpActivity, this); + netPanel.linkNode = aNode; + + if (!actor) { + openPanel(); + } + + return netPanel; }, /** * Handler for page location changes. * * @param string aURI * New page location. * @param string aTitle
--- a/browser/locales/en-US/chrome/browser/devtools/webConsole.dtd +++ b/browser/locales/en-US/chrome/browser/devtools/webConsole.dtd @@ -6,16 +6,34 @@ - keep it in English, or another language commonly spoken among web developers. - You want to make that choice consistent across the developer tools. - A good criteria is the language in which you'd find the best - documentation on web development on the web. --> <!ENTITY window.title "Web Console"> <!ENTITY browserConsole.title "Browser Console"> +<!ENTITY networkPanel.requestURLColon "Request URL:"> +<!ENTITY networkPanel.requestMethodColon "Request Method:"> +<!ENTITY networkPanel.statusCodeColon "Status Code:"> + +<!ENTITY networkPanel.requestHeaders "Request Headers"> +<!ENTITY networkPanel.requestCookie "Sent Cookie"> +<!ENTITY networkPanel.requestBody "Request Body"> +<!ENTITY networkPanel.requestFormData "Sent Form Data"> + +<!ENTITY networkPanel.responseHeaders "Response Headers"> +<!ENTITY networkPanel.responseCookie "Received Cookie"> +<!ENTITY networkPanel.responseBody "Response Body"> +<!ENTITY networkPanel.responseBodyCached "Cached Data"> +<!ENTITY networkPanel.responseBodyUnknownType "Unknown Content Type"> +<!ENTITY networkPanel.responseNoBody "No Response Body"> +<!ENTITY networkPanel.responseImage "Received Image"> +<!ENTITY networkPanel.responseImageCached "Cached Image"> + <!-- LOCALIZATION NOTE (saveBodies.label): You can see this string in the Web - Console context menu. --> <!ENTITY saveBodies.label "Log Request and Response Bodies"> <!ENTITY saveBodies.accesskey "L"> <!-- LOCALIZATION NOTE (openURL.label): You can see this string in the Web - Console context menu. --> <!ENTITY openURL.label "Open URL in New Tab">
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties +++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties @@ -35,22 +35,39 @@ update.accesskey=U cmd.commandkey=K webConsoleCmd.accesskey=W # LOCALIZATION NOTE (timestampFormat): %1$02S = hours (24-hour clock), # %2$02S = minutes, %3$02S = seconds, %4$03S = milliseconds. timestampFormat=%02S:%02S:%02S.%03S helperFuncUnsupportedTypeError=Can't call pprint on this type of object. +NetworkPanel.label=Inspect Network Request # LOCALIZATION NOTE (NetworkPanel.deltaDurationMS): this string is used to # show the duration between two network events (e.g request and response # header or response header and response body). Parameters: %S is the duration. NetworkPanel.durationMS=%Sms +# LOCALIZATION NOTE (NetworkPanel.imageSizeDeltaDurationMS): this string is +# used to show the duration between the response header and the response body +# event. It also shows the size of the received or cached image. Parameters: +# %1$S is the width of the inspected image, %2$S is the height of the +# inspected image, %3$S is the duration between the response header and the +# response body event. Example: 150x100px, Δ50ms. +NetworkPanel.imageSizeDeltaDurationMS=%1$Sx%2$Spx, Δ%3$Sms + +# LOCALIZATION NOTE (NetworkPanel.responseBodyUnableToDisplay.content): this +# string is displayed within the response body section of the NetworkPanel if +# the content type of the network request can't be displayed. E.g. any kind of +# text is easy to display, but some audio or flash data received from the +# server can't be displayed. Parameters: %S is the content type that can't be +# displayed, examples are application/x-shockwave-flash, music/crescendo. +NetworkPanel.responseBodyUnableToDisplay.content=Unable to display responses of type "%S" + ConsoleAPIDisabled=The Web Console logging API (console.log, console.info, console.warn, console.error) has been disabled by a script on this page. # LOCALIZATION NOTE (webConsoleWindowTitleAndURL): the Web Console floating # panel title. For RTL languages you need to set the LRM in the string to give # the URL the correct direction. Parameters: %S is the web page URL. webConsoleWindowTitleAndURL=Web Console - %S # LOCALIZATION NOTE (webConsoleXhrIndicator): the indicator displayed before
--- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -325,16 +325,17 @@ browser.jar: skin/classic/browser/devtools/command-eyedropper.png (../shared/devtools/images/command-eyedropper.png) skin/classic/browser/devtools/command-eyedropper@2x.png (../shared/devtools/images/command-eyedropper@2x.png) skin/classic/browser/devtools/command-rulers.png (../shared/devtools/images/command-rulers.png) skin/classic/browser/devtools/command-rulers@2x.png (../shared/devtools/images/command-rulers@2x.png) skin/classic/browser/devtools/alerticon-warning.png (../shared/devtools/images/alerticon-warning.png) skin/classic/browser/devtools/alerticon-warning@2x.png (../shared/devtools/images/alerticon-warning@2x.png) * skin/classic/browser/devtools/ruleview.css (../shared/devtools/ruleview.css) * skin/classic/browser/devtools/webconsole.css (../shared/devtools/webconsole.css) + skin/classic/browser/devtools/webconsole_networkpanel.css (../shared/devtools/webconsole_networkpanel.css) skin/classic/browser/devtools/webconsole.svg (../shared/devtools/images/webconsole.svg) skin/classic/browser/devtools/commandline.css (../shared/devtools/commandline.css) skin/classic/browser/devtools/markup-view.css (../shared/devtools/markup-view.css) skin/classic/browser/devtools/editor-error.png (../shared/devtools/images/editor-error.png) skin/classic/browser/devtools/editor-breakpoint.png (../shared/devtools/images/editor-breakpoint.png) skin/classic/browser/devtools/editor-debug-location.png (../shared/devtools/images/editor-debug-location.png) skin/classic/browser/devtools/editor-debug-location@2x.png (../shared/devtools/images/editor-debug-location@2x.png) skin/classic/browser/devtools/breadcrumbs-divider@2x.png (../shared/devtools/images/breadcrumbs-divider@2x.png)
--- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -427,16 +427,17 @@ browser.jar: skin/classic/browser/devtools/commandline.css (../shared/devtools/commandline.css) skin/classic/browser/devtools/markup-view.css (../shared/devtools/markup-view.css) skin/classic/browser/devtools/editor-error.png (../shared/devtools/images/editor-error.png) skin/classic/browser/devtools/editor-breakpoint.png (../shared/devtools/images/editor-breakpoint.png) skin/classic/browser/devtools/editor-breakpoint@2x.png (../shared/devtools/images/editor-breakpoint@2x.png) skin/classic/browser/devtools/editor-debug-location.png (../shared/devtools/images/editor-debug-location.png) skin/classic/browser/devtools/editor-debug-location@2x.png (../shared/devtools/images/editor-debug-location@2x.png) * skin/classic/browser/devtools/webconsole.css (../shared/devtools/webconsole.css) + skin/classic/browser/devtools/webconsole_networkpanel.css (../shared/devtools/webconsole_networkpanel.css) skin/classic/browser/devtools/webconsole.svg (../shared/devtools/images/webconsole.svg) skin/classic/browser/devtools/breadcrumbs-divider@2x.png (../shared/devtools/images/breadcrumbs-divider@2x.png) skin/classic/browser/devtools/breadcrumbs-scrollbutton.png (../shared/devtools/images/breadcrumbs-scrollbutton.png) skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png) skin/classic/browser/devtools/animationinspector.css (../shared/devtools/animationinspector.css) * skin/classic/browser/devtools/canvasdebugger.css (../shared/devtools/canvasdebugger.css) skin/classic/browser/devtools/debugger.css (../shared/devtools/debugger.css) skin/classic/browser/devtools/eyedropper.css (../shared/devtools/eyedropper.css)
new file mode 100644 --- /dev/null +++ b/browser/themes/shared/devtools/webconsole_networkpanel.css @@ -0,0 +1,100 @@ +/* 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/. */ + +body { + font-family: sans-serif; + font-size: 11px; + background: #EEE; + color: #000; +} + +#header { + padding: 5px; + overflow-x:auto; + display: block; +} + +h1 { + font-size: 13px; + line-height: 15px; + padding: 3px 10px; + vertical-align: bottom; + margin: 0px; + background: linear-gradient(#BBB, #999); + border-radius: 2px; + text-shadow: #FFF 0px 1px 0px; +} + +h1 .info { + font-size: 11px; + line-height: 15px; + vertical-align: bottom; + float: right; + color: #333; + padding-right: 3px; +} + +.property-table { + padding: 2px 5px; + background: linear-gradient(#FFF, #F8F8F8); + color: #333; + width: 100%; + max-height: 330px; + overflow: auto; + display: block; +} + +.property-name { + font-size: 11px; + font-weight: bold; + padding-right: 4px; + color: #000; + white-space: nowrap; + text-align: end; + vertical-align: top; + width: 10%; +} + +.property-value { + padding-right: 5px; + font-size: 11px; + word-wrap: break-word; + width: 90%; +} + +div.group { + margin-top: 10px; +} + +div.group, +#header { + background: #FFF; + border-color: #E1E1E1; + border-style: solid; + border-width: 1px; + box-shadow: 0 1px 1.5px rgba(0, 0, 0, 0.2); + border-radius: 4px 4px 4px 4px; +} + +img#responseImageNode { + box-shadow: rgba(0,0,0,0.2) 0px 3px 3.5px; + max-width: 100%; +} + +#responseImageNodeDiv { + padding: 5px; +} + +#responseBodyFetchLink, #requestBodyFetchLink { + padding: 5px; + margin: 0; + cursor: pointer; + font-weight: bold; + font-size: 1.1em; + text-decoration: underline; +} + +.longStringEllipsis { + margin-left: 0.6em; +}
--- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -452,16 +452,17 @@ browser.jar: skin/classic/browser/devtools/command-rulers@2x.png (../shared/devtools/images/command-rulers@2x.png) skin/classic/browser/devtools/markup-view.css (../shared/devtools/markup-view.css) skin/classic/browser/devtools/editor-error.png (../shared/devtools/images/editor-error.png) skin/classic/browser/devtools/editor-breakpoint.png (../shared/devtools/images/editor-breakpoint.png) skin/classic/browser/devtools/editor-breakpoint@2x.png (../shared/devtools/images/editor-breakpoint@2x.png) skin/classic/browser/devtools/editor-debug-location.png (../shared/devtools/images/editor-debug-location.png) skin/classic/browser/devtools/editor-debug-location@2x.png (../shared/devtools/images/editor-debug-location@2x.png) * skin/classic/browser/devtools/webconsole.css (../shared/devtools/webconsole.css) + skin/classic/browser/devtools/webconsole_networkpanel.css (../shared/devtools/webconsole_networkpanel.css) skin/classic/browser/devtools/webconsole.svg (../shared/devtools/images/webconsole.svg) skin/classic/browser/devtools/breadcrumbs-divider@2x.png (../shared/devtools/images/breadcrumbs-divider@2x.png) skin/classic/browser/devtools/breadcrumbs-scrollbutton.png (../shared/devtools/images/breadcrumbs-scrollbutton.png) skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png) skin/classic/browser/devtools/animationinspector.css (../shared/devtools/animationinspector.css) skin/classic/browser/devtools/eyedropper.css (../shared/devtools/eyedropper.css) * skin/classic/browser/devtools/canvasdebugger.css (../shared/devtools/canvasdebugger.css) skin/classic/browser/devtools/debugger.css (../shared/devtools/debugger.css)