diff options
Diffstat (limited to 'devtools')
75 files changed, 1313 insertions, 456 deletions
diff --git a/devtools/bootstrap.js b/devtools/bootstrap.js index bedca0c78..3c154fef4 100644 --- a/devtools/bootstrap.js +++ b/devtools/bootstrap.js @@ -187,7 +187,7 @@ function reload(event) { // HUDService is going to close it on unload. // Instead we have to manually toggle it. if (reopenBrowserConsole) { - let HUDService = devtools.require("devtools/client/webconsole/hudservice"); + let {HUDService} = devtools.require("devtools/client/webconsole/hudservice"); HUDService.toggleBrowserConsole(); } diff --git a/devtools/client/commandline/test/browser_cmd_commands.js b/devtools/client/commandline/test/browser_cmd_commands.js index 6c69034ec..78db77bfd 100644 --- a/devtools/client/commandline/test/browser_cmd_commands.js +++ b/devtools/client/commandline/test/browser_cmd_commands.js @@ -4,7 +4,7 @@ // Test various GCLI commands const TEST_URI = "data:text/html;charset=utf-8,gcli-commands"; -const HUDService = require("devtools/client/webconsole/hudservice"); +const {HUDService} = require("devtools/client/webconsole/hudservice"); // Use the old webconsole since pprint isn't working on new one (Bug 1304794) Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false); diff --git a/devtools/client/devtools-startup.js b/devtools/client/devtools-startup.js index 2271dd790..4a385a9d5 100644 --- a/devtools/client/devtools-startup.js +++ b/devtools/client/devtools-startup.js @@ -25,7 +25,7 @@ function DevToolsStartup() {} DevToolsStartup.prototype = { handle: function (cmdLine) { - let consoleFlag = cmdLine.handleFlag("jsconsole", false); + let consoleFlag = cmdLine.handleFlag("browserconsole", false); let debuggerFlag = cmdLine.handleFlag("jsdebugger", false); let devtoolsFlag = cmdLine.handleFlag("devtools", false); @@ -75,9 +75,9 @@ DevToolsStartup.prototype = { this.initDevTools(); let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); - let hudservice = require("devtools/client/webconsole/hudservice"); + let { HUDService } = require("devtools/client/webconsole/hudservice"); let { console } = Cu.import("resource://gre/modules/Console.jsm", {}); - hudservice.toggleBrowserConsole().then(null, console.error); + HUDService.toggleBrowserConsole().then(null, console.error); } else { // the Browser Console was already open window.focus(); @@ -104,12 +104,14 @@ DevToolsStartup.prototype = { return Services.prefs.getBoolPref(pref); }); } catch (ex) { + let { console } = Cu.import("resource://gre/modules/Console.jsm", {}); console.error(ex); return false; } if (!remoteDebuggingEnabled) { let errorMsg = "Could not run chrome debugger! You need the following " + "prefs to be set to true: " + kDebuggerPrefs.join(", "); + let { console } = Cu.import("resource://gre/modules/Console.jsm", {}); console.error(new Error(errorMsg)); // Dump as well, as we're doing this from a commandline, make sure people // don't miss it: @@ -190,7 +192,10 @@ DevToolsStartup.prototype = { listener.open(); dump("Started debugger server on " + portOrPath + "\n"); } catch (e) { - dump("Unable to start debugger server on " + portOrPath + ": " + e); + let _error = "Unable to start debugger server on " + portOrPath + ": " + + e; + Cu.reportError(_error); + dump(_error + "\n"); } if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) { @@ -199,12 +204,14 @@ DevToolsStartup.prototype = { }, /* eslint-disable max-len */ - helpInfo: " --jsconsole Open the Browser Console.\n" + - " --jsdebugger Open the Browser Toolbox.\n" + - " --devtools Open DevTools on initial load.\n" + - " --start-debugger-server [ws:][ <port> | <path> ] Start the debugger server on\n" + - " a TCP port or Unix domain socket path. Defaults to TCP port\n" + - " 6000. Use WebSocket protocol if ws: prefix is specified.\n", + helpInfo: " --browserconsole Open the Browser Console.\n" + + " --jsdebugger Open the Browser Toolbox.\n" + + " --devtools Open DevTools on initial load.\n" + + " --start-debugger-server [ws:][<port>|<path>] Start the debugger server on\n" + + " a TCP port or Unix domain socket path.\n" + + " Defaults to TCP port 6000.\n" + + " Use WebSocket protocol if ws: prefix\n" + + " is specified.\n", /* eslint-disable max-len */ classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"), diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js index 82d5d2915..926e30647 100644 --- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -22,7 +22,7 @@ var {Task} = require("devtools/shared/task"); var {gDevTools} = require("devtools/client/framework/devtools"); var EventEmitter = require("devtools/shared/event-emitter"); var Telemetry = require("devtools/client/shared/telemetry"); -var HUDService = require("devtools/client/webconsole/hudservice"); +var { HUDService } = require("devtools/client/webconsole/hudservice"); var viewSource = require("devtools/client/shared/view-source"); var { attachThread, detachThread } = require("./attach-thread"); var Menu = require("devtools/client/framework/menu"); diff --git a/devtools/client/inspector/rules/models/rule.js b/devtools/client/inspector/rules/models/rule.js index 1a3fa057a..4c978cb58 100644 --- a/devtools/client/inspector/rules/models/rule.js +++ b/devtools/client/inspector/rules/models/rule.js @@ -140,11 +140,18 @@ Rule.prototype = { line, mediaText}) => { let mediaString = mediaText ? " @" + mediaText : ""; let linePart = line > 0 ? (":" + line) : ""; + let decodedHref = href; + + if (decodedHref) { + try { + decodedHref = decodeURIComponent(href); + } catch (e) {} + } let sourceStrings = { - full: (href || CssLogic.l10n("rule.sourceInline")) + linePart + + full: (decodedHref || CssLogic.l10n("rule.sourceInline")) + linePart + mediaString, - short: CssLogic.shortSource({href: href}) + linePart + mediaString + short: CssLogic.shortSource({href: decodedHref}) + linePart + mediaString }; return sourceStrings; diff --git a/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js b/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js index 927deb8ce..6a4fd1d66 100644 --- a/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js +++ b/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js @@ -10,10 +10,12 @@ thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source"); // Test the links from the rule-view to the styleeditor -const STYLESHEET_URL = "data:text/css," + encodeURIComponent( - ["#first {", - "color: blue", - "}"].join("\n")); +const STYLESHEET_DATA_URL_CONTENTS = ["#first {", + "color: blue", + "}"].join("\n"); +const STYLESHEET_DATA_URL = + `data:text/css,${encodeURIComponent(STYLESHEET_DATA_URL_CONTENTS)}`; +const STYLESHEET_DECODED_DATA_URL = `data:text/css,${STYLESHEET_DATA_URL_CONTENTS}`; const EXTERNAL_STYLESHEET_FILE_NAME = "doc_style_editor_link.css"; const EXTERNAL_STYLESHEET_URL = URL_ROOT + EXTERNAL_STYLESHEET_FILE_NAME; @@ -31,7 +33,7 @@ const DOCUMENT_URL = "data:text/html;charset=utf-8," + encodeURIComponent(` <style> div { font-weight: bold; } </style> - <link rel="stylesheet" type="text/css" href="${STYLESHEET_URL}"> + <link rel="stylesheet" type="text/css" href="${STYLESHEET_DATA_URL}"> <link rel="stylesheet" type="text/css" href="${EXTERNAL_STYLESHEET_URL}"> </head> <body> @@ -178,15 +180,28 @@ function* testDisabledStyleEditor(view, toolbox) { } function testRuleViewLinkLabel(view) { - let link = getRuleViewLinkByIndex(view, 2); + info("Checking the data URL link label"); + + let link = getRuleViewLinkByIndex(view, 1); let labelElem = link.querySelector(".ruleview-rule-source-label"); let value = labelElem.textContent; let tooltipText = labelElem.getAttribute("title"); - is(value, EXTERNAL_STYLESHEET_FILE_NAME + ":1", - "rule view stylesheet display value matches filename and line number"); - is(tooltipText, EXTERNAL_STYLESHEET_URL + ":1", - "rule view stylesheet tooltip text matches the full URI path"); + is(value, `${STYLESHEET_DATA_URL_CONTENTS}:1`, + "Rule view data URL stylesheet display value matches contents"); + is(tooltipText, `${STYLESHEET_DECODED_DATA_URL}:1`, + "Rule view data URL stylesheet tooltip text matches the full URI path"); + + info("Checking the external link label"); + link = getRuleViewLinkByIndex(view, 2); + labelElem = link.querySelector(".ruleview-rule-source-label"); + value = labelElem.textContent; + tooltipText = labelElem.getAttribute("title"); + + is(value, `${EXTERNAL_STYLESHEET_FILE_NAME}:1`, + "Rule view external stylesheet display value matches filename and line number"); + is(tooltipText, `${EXTERNAL_STYLESHEET_URL}:1`, + "Rule view external stylesheet tooltip text matches the full URI path"); } function testUnselectableRuleViewLink(view, index) { diff --git a/devtools/client/inspector/test/browser.ini b/devtools/client/inspector/test/browser.ini index 65ad71c0c..499df25f0 100644 --- a/devtools/client/inspector/test/browser.ini +++ b/devtools/client/inspector/test/browser.ini @@ -113,6 +113,7 @@ subsuite = clipboard [browser_inspector_infobar_01.js] [browser_inspector_infobar_02.js] [browser_inspector_infobar_03.js] +[browser_inspector_infobar_04.js] [browser_inspector_infobar_textnode.js] [browser_inspector_initialization.js] skip-if = (e10s && debug) # Bug 1250058 - Docshell leak on debug e10s diff --git a/devtools/client/inspector/test/browser_inspector_infobar_04.js b/devtools/client/inspector/test/browser_inspector_infobar_04.js new file mode 100644 index 000000000..f1b9eca49 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_infobar_04.js @@ -0,0 +1,38 @@ +/* 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"; + +// Check the position and text content of the highlighter nodeinfo bar under page zoom. + +const TEST_URI = URL_ROOT + "doc_inspector_infobar_01.html"; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL(TEST_URI); + let testData = { + selector: "#top", + dims: "500" + " \u00D7 " + "100" + }; + + yield testInfobar(testData, inspector, testActor); + info("Change zoom page to level 2."); + yield testActor.zoomPageTo(2); + info("Testing again the infobar after zoom."); + yield testInfobar(testData, inspector, testActor); +}); + +function* testInfobar(test, inspector, testActor) { + info(`Testing ${test.selector}`); + + yield selectAndHighlightNode(test.selector, inspector); + + // Ensure the node is the correct one. + let id = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-id"); + is(id, test.selector, `Node ${test.selector} selected.`); + + let dims = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-dimensions"); + is(dims, test.dims, "Node's infobar displays the right dimensions."); +} diff --git a/devtools/client/locales/en-US/netmonitor.properties b/devtools/client/locales/en-US/netmonitor.properties index e6118ca9f..021b56a2b 100644 --- a/devtools/client/locales/en-US/netmonitor.properties +++ b/devtools/client/locales/en-US/netmonitor.properties @@ -143,12 +143,12 @@ networkMenu.sortedDesc=Sorted descending # in the network table footer when there are no requests available. networkMenu.empty=No requests -# LOCALIZATION NOTE (networkMenu.summary): Semi-colon list of plural forms. +# LOCALIZATION NOTE (networkMenu.summary2): Semi-colon list of plural forms. # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals # This label is displayed in the network table footer providing concise # information about all requests. Parameters: #1 is the number of requests, -# #2 is the size, #3 is the number of seconds. -networkMenu.summary=One request, #2 KB, #3 s;#1 requests, #2 KB, #3 s +# #2 is the size, #3 is the transferred size, #4 is the number of seconds. +networkMenu.summary2=One request, #2 KB (transferred: #3 KB), #4 s;#1 requests, #2 KB (transferred: #3 KB), #4 s # LOCALIZATION NOTE (networkMenu.sizeB): This is the label displayed # in the network menu specifying the size of a request (in bytes). @@ -221,10 +221,22 @@ tableChart.unavailable=No data available # in pie or table charts specifying the size of a request (in kilobytes). charts.sizeKB=%S KB +# LOCALIZATION NOTE (charts.transferredSizeKB): This is the label displayed +# in pie or table charts specifying the size of a transferred request (in kilobytes). +charts.transferredSizeKB=%S KB + # LOCALIZATION NOTE (charts.totalS): This is the label displayed # in pie or table charts specifying the time for a request to finish (in seconds). charts.totalS=%S s +# LOCALIZATION NOTE (charts.totalSize): This is the label displayed +# in the performance analysis view for total requests size, in kilobytes. +charts.totalSize=Size: %S KB + +# LOCALIZATION NOTE (charts.totalTranferredSize): This is the label displayed +# in the performance analysis view for total transferred size, in kilobytes. +charts.totalTransferredSize=Transferred Size: %S KB + # LOCALIZATION NOTE (charts.cacheEnabled): This is the label displayed # in the performance analysis view for "cache enabled" charts. charts.cacheEnabled=Primed cache @@ -233,10 +245,6 @@ charts.cacheEnabled=Primed cache # in the performance analysis view for "cache disabled" charts. charts.cacheDisabled=Empty cache -# LOCALIZATION NOTE (charts.totalSize): This is the label displayed -# in the performance analysis view for total requests size, in kilobytes. -charts.totalSize=Size: %S KB - # LOCALIZATION NOTE (charts.totalSeconds): Semi-colon list of plural forms. # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals # This is the label displayed in the performance analysis view for the @@ -251,6 +259,23 @@ charts.totalCached=Cached responses: %S # in the performance analysis view for total requests. charts.totalCount=Total requests: %S +# LOCALIZATION NOTE (charts.size): This is the label displayed +# in the header column in the performance analysis view for size of the request. +charts.size=Size + +# LOCALIZATION NOTE (charts.type): This is the label displayed +# in the header column in the performance analysis view for type of request. +charts.type=Type + +# LOCALIZATION NOTE (charts.transferred): This is the label displayed +# in the header column in the performance analysis view for transferred +# size of the request. +charts.transferred=Transferred + +# LOCALIZATION NOTE (charts.time): This is the label displayed +# in the header column in the performance analysis view for time of request. +charts.time=Time + # LOCALIZATION NOTE (netRequest.headers): A label used for Headers tab # This tab displays list of HTTP headers netRequest.headers=Headers @@ -556,6 +581,11 @@ netmonitor.timings.blocked=Blocked: # in a "dns" state. netmonitor.timings.dns=DNS resolution: +# LOCALIZATION NOTE (netmonitor.timings.ssl): This is the label displayed +# in the network details timings tab identifying the amount of time spent +# in a "tls" handshake state. +netmonitor.timings.ssl=TLS setup: + # LOCALIZATION NOTE (netmonitor.timings.connect): This is the label displayed # in the network details timings tab identifying the amount of time spent # in a "connect" state. @@ -592,6 +622,37 @@ netmonitor.security.protocolVersion=Protocol version: # in the security tab describing the cipher suite used to secure this connection. netmonitor.security.cipherSuite=Cipher suite: +# LOCALIZATION NOTE (netmonitor.security.keaGroup): This is the label displayed +# in the security tab describing the key exchange group suite used to secure +# this connection. +netmonitor.security.keaGroup=Key Exchange Group: + +# LOCALIZATION NOTE (netmonitor.security.keaGroup.none): This is the label +# displayed in the security tab describing the case when no group was used. +netmonitor.security.keaGroup.none=none + +# LOCALIZATION NOTE (netmonitor.security.keaGroup.custom): This is the label +# displayed in the security tab describing the case when a custom group was used. +netmonitor.security.keaGroup.custom=custom + +# LOCALIZATION NOTE (netmonitor.security.keaGroup.unknown): This is the value +# displayed in the security tab describing an unknown group. +netmonitor.security.keaGroup.unknown=unknown group + +# LOCALIZATION NOTE (netmonitor.security.signatureScheme): This is the label +# displayed in the security tab describing the signature scheme used by for +# the server certificate in this connection. +netmonitor.security.signatureScheme=Signature Scheme: + +# LOCALIZATION NOTE (netmonitor.security.signatureScheme.none): This is the +# label displayed in the security tab describing the case when no signature +# was used. +netmonitor.security.signatureScheme.none=none + +# LOCALIZATION NOTE (netmonitor.security.signatureScheme.unknown): This is the +# value displayed in the security tab describing an unknown signature scheme. +netmonitor.security.signatureScheme.unknown=unknown signature scheme + # LOCALIZATION NOTE (netmonitor.security.hsts): This is the label displayed # in the security tab describing the usage of HTTP Strict Transport Security. netmonitor.security.hsts=HTTP Strict Transport Security: diff --git a/devtools/client/menus.js b/devtools/client/menus.js index 1aee85095..7e36839da 100644 --- a/devtools/client/menus.js +++ b/devtools/client/menus.js @@ -120,7 +120,7 @@ exports.menuitems = [ { id: "menu_browserConsole", l10nKey: "browserConsoleCmd", oncommand() { - let HUDService = require("devtools/client/webconsole/hudservice"); + let {HUDService} = require("devtools/client/webconsole/hudservice"); HUDService.openBrowserConsoleOrFocus(); }, key: { diff --git a/devtools/client/netmonitor/actions/index.js b/devtools/client/netmonitor/actions/index.js index 3f7b0bd2f..2ce23448e 100644 --- a/devtools/client/netmonitor/actions/index.js +++ b/devtools/client/netmonitor/actions/index.js @@ -4,6 +4,7 @@ "use strict"; const filters = require("./filters"); -const sidebar = require("./sidebar"); +const requests = require("./requests"); +const ui = require("./ui"); -module.exports = Object.assign({}, filters, sidebar); +module.exports = Object.assign({}, filters, requests, ui); diff --git a/devtools/client/netmonitor/actions/moz.build b/devtools/client/netmonitor/actions/moz.build index 477cafb41..d0ac61944 100644 --- a/devtools/client/netmonitor/actions/moz.build +++ b/devtools/client/netmonitor/actions/moz.build @@ -6,5 +6,6 @@ DevToolsModules( 'filters.js', 'index.js', - 'sidebar.js', + 'requests.js', + 'ui.js', ) diff --git a/devtools/client/netmonitor/actions/requests.js b/devtools/client/netmonitor/actions/requests.js new file mode 100644 index 000000000..ae794a437 --- /dev/null +++ b/devtools/client/netmonitor/actions/requests.js @@ -0,0 +1,25 @@ +/* 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 { + UPDATE_REQUESTS, +} = require("../constants"); + +/** + * Update request items + * + * @param {array} requests - visible request items + */ +function updateRequests(items) { + return { + type: UPDATE_REQUESTS, + items, + }; +} + +module.exports = { + updateRequests, +}; diff --git a/devtools/client/netmonitor/actions/sidebar.js b/devtools/client/netmonitor/actions/sidebar.js deleted file mode 100644 index 7e8dca5c1..000000000 --- a/devtools/client/netmonitor/actions/sidebar.js +++ /dev/null @@ -1,49 +0,0 @@ -/* 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 { - DISABLE_TOGGLE_BUTTON, - SHOW_SIDEBAR, - TOGGLE_SIDEBAR, -} = require("../constants"); - -/** - * Change ToggleButton disabled state. - * - * @param {boolean} disabled - expected button disabled state - */ -function disableToggleButton(disabled) { - return { - type: DISABLE_TOGGLE_BUTTON, - disabled: disabled, - }; -} - -/** - * Change sidebar visible state. - * - * @param {boolean} visible - expected sidebar visible state - */ -function showSidebar(visible) { - return { - type: SHOW_SIDEBAR, - visible: visible, - }; -} - -/** - * Toggle to show/hide sidebar. - */ -function toggleSidebar() { - return { - type: TOGGLE_SIDEBAR, - }; -} - -module.exports = { - disableToggleButton, - showSidebar, - toggleSidebar, -}; diff --git a/devtools/client/netmonitor/actions/ui.js b/devtools/client/netmonitor/actions/ui.js new file mode 100644 index 000000000..c6df307ed --- /dev/null +++ b/devtools/client/netmonitor/actions/ui.js @@ -0,0 +1,36 @@ ++/* 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 {
+ OPEN_SIDEBAR,
+ TOGGLE_SIDEBAR,
+} = require("../constants");
+
+/**
+ * Change sidebar open state.
+ *
+ * @param {boolean} open - open state
+ */
+function openSidebar(open) {
+ return {
+ type: OPEN_SIDEBAR,
+ open,
+ };
+}
+
+/**
+ * Toggle sidebar open state.
+ */
+function toggleSidebar() {
+ return {
+ type: TOGGLE_SIDEBAR,
+ };
+}
+
+module.exports = {
+ openSidebar,
+ toggleSidebar,
+};
diff --git a/devtools/client/netmonitor/components/moz.build b/devtools/client/netmonitor/components/moz.build index 47ef7f026..42459584d 100644 --- a/devtools/client/netmonitor/components/moz.build +++ b/devtools/client/netmonitor/components/moz.build @@ -6,5 +6,6 @@ DevToolsModules( 'filter-buttons.js', 'search-box.js', + 'summary-button.js', 'toggle-button.js', ) diff --git a/devtools/client/netmonitor/components/summary-button.js b/devtools/client/netmonitor/components/summary-button.js new file mode 100644 index 000000000..223552fbf --- /dev/null +++ b/devtools/client/netmonitor/components/summary-button.js @@ -0,0 +1,61 @@ +/* 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/. */ + +/* globals NetMonitorView */ + +"use strict"; + +const { + CONTENT_SIZE_DECIMALS, + REQUEST_TIME_DECIMALS, +} = require("../constants"); +const { DOM, PropTypes } = require("devtools/client/shared/vendor/react"); +const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { PluralForm } = require("devtools/shared/plural-form"); +const { L10N } = require("../l10n"); +const { + getDisplayedRequestsSummary +} = require("../selectors/index"); + +const { button, span } = DOM; + +function SummaryButton({ + summary, + triggerSummary +}) { + let { count, contentSize, transferredSize, millis } = summary; + const text = (count === 0) ? L10N.getStr("networkMenu.empty") : + PluralForm.get(count, L10N.getStr("networkMenu.summary2")) + .replace("#1", count) + .replace("#2", L10N.numberWithDecimals(contentSize / 1024, + CONTENT_SIZE_DECIMALS)) + .replace("#3", L10N.numberWithDecimals(transferredSize / 1024, + CONTENT_SIZE_DECIMALS)) + .replace("#4", L10N.numberWithDecimals(millis / 1000, + REQUEST_TIME_DECIMALS)); + + return button({ + id: "requests-menu-network-summary-button", + className: "devtools-button", + title: count ? text : L10N.getStr("netmonitor.toolbar.perf"), + onClick: triggerSummary, + }, + span({ className: "summary-info-icon" }), + span({ className: "summary-info-text" }, text)); +} + +SummaryButton.propTypes = { + summary: PropTypes.object.isRequired, +}; + +module.exports = connect( + (state) => ({ + summary: getDisplayedRequestsSummary(state), + }), + (dispatch) => ({ + triggerSummary: () => { + NetMonitorView.toggleFrontendMode(); + }, + }) +)(SummaryButton); diff --git a/devtools/client/netmonitor/components/toggle-button.js b/devtools/client/netmonitor/components/toggle-button.js index db546c55d..c9a59a76b 100644 --- a/devtools/client/netmonitor/components/toggle-button.js +++ b/devtools/client/netmonitor/components/toggle-button.js @@ -1,5 +1,3 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ @@ -11,47 +9,43 @@ const { connect } = require("devtools/client/shared/vendor/react-redux"); const { L10N } = require("../l10n"); const Actions = require("../actions/index"); -// Shortcuts const { button } = DOM; -/** - * Button used to toggle sidebar - */ function ToggleButton({ disabled, - onToggle, - visible, + open, + triggerSidebar, }) { let className = ["devtools-button"]; - if (!visible) { + if (!open) { className.push("pane-collapsed"); } - let titleMsg = visible ? L10N.getStr("collapseDetailsPane") : - L10N.getStr("expandDetailsPane"); + + const title = open ? L10N.getStr("collapseDetailsPane") : + L10N.getStr("expandDetailsPane"); return button({ id: "details-pane-toggle", className: className.join(" "), - title: titleMsg, - disabled: disabled, + title, + disabled, tabIndex: "0", - onMouseDown: onToggle, + onMouseDown: triggerSidebar, }); } ToggleButton.propTypes = { disabled: PropTypes.bool.isRequired, - onToggle: PropTypes.func.isRequired, - visible: PropTypes.bool.isRequired, + triggerSidebar: PropTypes.func.isRequired, }; module.exports = connect( (state) => ({ - disabled: state.sidebar.toggleButtonDisabled, - visible: state.sidebar.visible, + disabled: state.requests.items.length === 0, + open: state.ui.sidebar.open, }), (dispatch) => ({ - onToggle: () => { + triggerSidebar: () => { dispatch(Actions.toggleSidebar()); let requestsMenu = NetMonitorView.RequestsMenu; diff --git a/devtools/client/netmonitor/constants.js b/devtools/client/netmonitor/constants.js index a540d74b2..33bc71ea7 100644 --- a/devtools/client/netmonitor/constants.js +++ b/devtools/client/netmonitor/constants.js @@ -5,15 +5,17 @@ const general = { FREETEXT_FILTER_SEARCH_DELAY: 200, + CONTENT_SIZE_DECIMALS: 2, + REQUEST_TIME_DECIMALS: 2, }; const actionTypes = { TOGGLE_FILTER_TYPE: "TOGGLE_FILTER_TYPE", ENABLE_FILTER_TYPE_ONLY: "ENABLE_FILTER_TYPE_ONLY", - TOGGLE_SIDEBAR: "TOGGLE_SIDEBAR", - SHOW_SIDEBAR: "SHOW_SIDEBAR", - DISABLE_TOGGLE_BUTTON: "DISABLE_TOGGLE_BUTTON", SET_FILTER_TEXT: "SET_FILTER_TEXT", + OPEN_SIDEBAR: "OPEN_SIDEBAR", + TOGGLE_SIDEBAR: "TOGGLE_SIDEBAR", + UPDATE_REQUESTS: "UPDATE_REQUESTS", }; module.exports = Object.assign({}, general, actionTypes); diff --git a/devtools/client/netmonitor/moz.build b/devtools/client/netmonitor/moz.build index 4b34b093b..2c9b32d3f 100644 --- a/devtools/client/netmonitor/moz.build +++ b/devtools/client/netmonitor/moz.build @@ -17,6 +17,7 @@ DevToolsModules( 'events.js', 'filter-predicates.js', 'l10n.js', + 'open-request-in-tab.js', 'panel.js', 'performance-statistics-view.js', 'prefs.js', diff --git a/devtools/client/netmonitor/netmonitor-view.js b/devtools/client/netmonitor/netmonitor-view.js index 68470f7a9..414b9ab8f 100644 --- a/devtools/client/netmonitor/netmonitor-view.js +++ b/devtools/client/netmonitor/netmonitor-view.js @@ -125,7 +125,6 @@ var NetMonitorView = { if (!Prefs.statistics) { $("#request-menu-context-perf").hidden = true; $("#notice-perf-message").hidden = true; - $("#requests-menu-network-summary-button").hidden = true; } }, @@ -171,10 +170,10 @@ var NetMonitorView = { if (flags.visible) { this._body.classList.remove("pane-collapsed"); - gStore.dispatch(Actions.showSidebar(true)); + gStore.dispatch(Actions.openSidebar(true)); } else { this._body.classList.add("pane-collapsed"); - gStore.dispatch(Actions.showSidebar(false)); + gStore.dispatch(Actions.openSidebar(false)); } if (tabIndex !== undefined) { @@ -234,7 +233,7 @@ var NetMonitorView = { // populating the statistics view. // • The response mime type is used for categorization. yield whenDataAvailable(requestsView, [ - "responseHeaders", "status", "contentSize", "mimeType", "totalTime" + "responseHeaders", "status", "contentSize", "transferredSize", "mimeType", "totalTime" ]); } catch (ex) { // Timed out while waiting for data. Continue with what we have. @@ -964,7 +963,7 @@ NetworkDetailsView.prototype = { if (!response) { return; } - let { blocked, dns, connect, send, wait, receive } = response.timings; + let { blocked, dns, connect, ssl, send, wait, receive } = response.timings; let tabboxWidth = $("#details-pane").getAttribute("width"); @@ -989,6 +988,11 @@ NetworkDetailsView.prototype = { $("#timings-summary-connect .requests-menu-timings-total") .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", connect)); + $("#timings-summary-ssl .requests-menu-timings-box") + .setAttribute("width", ssl * scale); + $("#timings-summary-ssl .requests-menu-timings-total") + .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", ssl)); + $("#timings-summary-send .requests-menu-timings-box") .setAttribute("width", send * scale); $("#timings-summary-send .requests-menu-timings-total") @@ -1008,6 +1012,8 @@ NetworkDetailsView.prototype = { .style.transform = "translateX(" + (scale * blocked) + "px)"; $("#timings-summary-connect .requests-menu-timings-box") .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)"; + $("#timings-summary-ssl .requests-menu-timings-box") + .style.transform = "translateX(" + (scale * blocked) + "px)"; $("#timings-summary-send .requests-menu-timings-box") .style.transform = "translateX(" + (scale * (blocked + dns + connect)) + "px)"; @@ -1023,6 +1029,8 @@ NetworkDetailsView.prototype = { .style.transform = "translateX(" + (scale * blocked) + "px)"; $("#timings-summary-connect .requests-menu-timings-total") .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)"; + $("#timings-summary-ssl .requests-menu-timings-total") + .style.transform = "translateX(" + (scale * blocked) + "px)"; $("#timings-summary-send .requests-menu-timings-total") .style.transform = "translateX(" + (scale * (blocked + dns + connect)) + "px)"; @@ -1125,6 +1133,8 @@ NetworkDetailsView.prototype = { setValue("#security-protocol-version-value", securityInfo.protocolVersion); setValue("#security-ciphersuite-value", securityInfo.cipherSuite); + setValue("#security-keagroup-value", securityInfo.keaGroupName); + setValue("#security-signaturescheme-value", securityInfo.signatureSchemeName); // Host header let domain = getUriHostPort(url); diff --git a/devtools/client/netmonitor/netmonitor.xul b/devtools/client/netmonitor/netmonitor.xul index bb580f7ad..aa5c4d848 100644 --- a/devtools/client/netmonitor/netmonitor.xul +++ b/devtools/client/netmonitor/netmonitor.xul @@ -28,9 +28,8 @@ id="react-filter-buttons-hook"/> <spacer id="requests-menu-spacer" flex="1"/> - <toolbarbutton id="requests-menu-network-summary-button" - class="devtools-toolbarbutton icon-and-text" - data-localization="tooltiptext=netmonitor.toolbar.perf"/> + <html:div xmlns="http://www.w3.org/1999/xhtml" + id="react-summary-button-hook"/> <html:div xmlns="http://www.w3.org/1999/xhtml" id="react-search-box-hook"/> <html:div xmlns="http://www.w3.org/1999/xhtml" @@ -479,6 +478,14 @@ <hbox class="requests-menu-timings-box connect"/> <label class="plain requests-menu-timings-total"/> </hbox> + <hbox id="timings-summary-ssl" + class="tabpanel-summary-container" + align="center"> + <label class="plain tabpanel-summary-label" + data-localization="content=netmonitor.timings.ssl"/> + <hbox class="requests-menu-timings-box ssl"/> + <label class="plain requests-menu-timings-total"/> + </hbox> <hbox id="timings-summary-send" class="tabpanel-summary-container" align="center"> @@ -551,6 +558,26 @@ id="security-warning-cipher" data-localization="tooltiptext=netmonitor.security.warning.cipher" /> </hbox> + <hbox id="security-keagroup" + class="tabpanel-summary-container" + align="baseline"> + <label class="plain tabpanel-summary-label" + data-localization="content=netmonitor.security.keaGroup"/> + <textbox id="security-keagroup-value" + class="plain tabpanel-summary-value devtools-monospace cropped-textbox" + flex="1" + readonly="true"/> + </hbox> + <hbox id="security-signaturescheme" + class="tabpanel-summary-container" + align="baseline"> + <label class="plain tabpanel-summary-label" + data-localization="content=netmonitor.security.signatureScheme"/> + <textbox id="security-signaturescheme-value" + class="plain tabpanel-summary-value devtools-monospace cropped-textbox" + flex="1" + readonly="true"/> + </hbox> </vbox> </vbox> <vbox id="security-info-domain" diff --git a/devtools/client/netmonitor/open-request-in-tab.js b/devtools/client/netmonitor/open-request-in-tab.js new file mode 100644 index 000000000..aeb35dad0 --- /dev/null +++ b/devtools/client/netmonitor/open-request-in-tab.js @@ -0,0 +1,40 @@ +/* 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/. */
+/* eslint-disable mozilla/reject-some-requires */
+
+"use strict";
+
+let { Cc, Ci } = require("chrome");
+const Services = require("Services");
+const { gDevTools } = require("devtools/client/framework/devtools");
+
+/**
+ * Opens given request in a new tab.
+ */
+function openRequestInTab(request) {
+ let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+ let rawData = request.requestPostData ? request.requestPostData.postData : null;
+ let postData;
+
+ if (rawData && rawData.text) {
+ let stringStream = getInputStreamFromString(rawData.text);
+ postData = Cc["@mozilla.org/network/mime-input-stream;1"]
+ .createInstance(Ci.nsIMIMEInputStream);
+ postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
+ postData.setData(stringStream);
+ }
+
+ win.gBrowser.selectedTab = win.gBrowser.addTab(request.url, null, null, postData);
+}
+
+function getInputStreamFromString(data) {
+ let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stringStream.data = data;
+ return stringStream;
+}
+
+module.exports = {
+ openRequestInTab,
+};
diff --git a/devtools/client/netmonitor/performance-statistics-view.js b/devtools/client/netmonitor/performance-statistics-view.js index c712c083d..38b98fb68 100644 --- a/devtools/client/netmonitor/performance-statistics-view.js +++ b/devtools/client/netmonitor/performance-statistics-view.js @@ -92,27 +92,35 @@ PerformanceStatisticsView.prototype = { let string = L10N.numberWithDecimals(value / 1024, CONTENT_SIZE_DECIMALS); return L10N.getFormatStr("charts.sizeKB", string); }, + transferredSize: value => { + let string = L10N.numberWithDecimals(value / 1024, CONTENT_SIZE_DECIMALS); + return L10N.getFormatStr("charts.transferredSizeKB", string); + }, time: value => { let string = L10N.numberWithDecimals(value / 1000, REQUEST_TIME_DECIMALS); return L10N.getFormatStr("charts.totalS", string); } }, _commonChartTotals: { + cached: total => { + return L10N.getFormatStr("charts.totalCached", total); + }, + count: total => { + return L10N.getFormatStr("charts.totalCount", total); + }, size: total => { let string = L10N.numberWithDecimals(total / 1024, CONTENT_SIZE_DECIMALS); return L10N.getFormatStr("charts.totalSize", string); }, + transferredSize: total => { + let string = L10N.numberWithDecimals(total / 1024, CONTENT_SIZE_DECIMALS); + return L10N.getFormatStr("charts.totalTransferredSize", string); + }, time: total => { let seconds = total / 1000; let string = L10N.numberWithDecimals(seconds, REQUEST_TIME_DECIMALS); return PluralForm.get(seconds, L10N.getStr("charts.totalSeconds")).replace("#1", string); - }, - cached: total => { - return L10N.getFormatStr("charts.totalCached", total); - }, - count: total => { - return L10N.getFormatStr("charts.totalCount", total); } }, @@ -136,6 +144,14 @@ PerformanceStatisticsView.prototype = { let chart = Chart.PieTable(document, { diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER, title: L10N.getStr(title), + header: { + cached: "", + count: "", + label: L10N.getStr("charts.type"), + size: L10N.getStr("charts.size"), + transferredSize: L10N.getStr("charts.transferred"), + time: L10N.getStr("charts.time") + }, data: data, strings: strings, totals: totals, @@ -161,13 +177,14 @@ PerformanceStatisticsView.prototype = { * True if the cache is considered enabled, false for disabled. */ _sanitizeChartDataSource: function (items, emptyCache) { - let data = [ + const data = [ "html", "css", "js", "xhr", "fonts", "images", "media", "flash", "ws", "other" - ].map(e => ({ + ].map((type) => ({ cached: 0, count: 0, - label: e, + label: type, size: 0, + transferredSize: 0, time: 0 })); @@ -211,6 +228,7 @@ PerformanceStatisticsView.prototype = { if (emptyCache || !responseIsFresh(details)) { data[type].time += details.totalTime || 0; data[type].size += details.contentSize || 0; + data[type].transferredSize += details.transferredSize || 0; } else { data[type].cached++; } diff --git a/devtools/client/netmonitor/reducers/index.js b/devtools/client/netmonitor/reducers/index.js index 58638a030..f36b1d91f 100644 --- a/devtools/client/netmonitor/reducers/index.js +++ b/devtools/client/netmonitor/reducers/index.js @@ -5,9 +5,11 @@ const { combineReducers } = require("devtools/client/shared/vendor/redux"); const filters = require("./filters"); -const sidebar = require("./sidebar"); +const requests = require("./requests"); +const ui = require("./ui"); module.exports = combineReducers({ filters, - sidebar, + requests, + ui, }); diff --git a/devtools/client/netmonitor/reducers/moz.build b/devtools/client/netmonitor/reducers/moz.build index 477cafb41..d0ac61944 100644 --- a/devtools/client/netmonitor/reducers/moz.build +++ b/devtools/client/netmonitor/reducers/moz.build @@ -6,5 +6,6 @@ DevToolsModules( 'filters.js', 'index.js', - 'sidebar.js', + 'requests.js', + 'ui.js', ) diff --git a/devtools/client/netmonitor/reducers/requests.js b/devtools/client/netmonitor/reducers/requests.js new file mode 100644 index 000000000..9ba888cad --- /dev/null +++ b/devtools/client/netmonitor/reducers/requests.js @@ -0,0 +1,28 @@ +/* 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 I = require("devtools/client/shared/vendor/immutable"); +const { + UPDATE_REQUESTS, +} = require("../constants"); + +const Requests = I.Record({ + items: [], +}); + +function updateRequests(state, action) { + return state.set("items", action.items || state.items); +} + +function requests(state = new Requests(), action) { + switch (action.type) { + case UPDATE_REQUESTS: + return updateRequests(state, action); + default: + return state; + } +} + +module.exports = requests; diff --git a/devtools/client/netmonitor/reducers/sidebar.js b/devtools/client/netmonitor/reducers/sidebar.js deleted file mode 100644 index eaa8b63df..000000000 --- a/devtools/client/netmonitor/reducers/sidebar.js +++ /dev/null @@ -1,43 +0,0 @@ -/* 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 I = require("devtools/client/shared/vendor/immutable"); -const { - DISABLE_TOGGLE_BUTTON, - SHOW_SIDEBAR, - TOGGLE_SIDEBAR, -} = require("../constants"); - -const SidebarState = I.Record({ - toggleButtonDisabled: true, - visible: false, -}); - -function disableToggleButton(state, action) { - return state.set("toggleButtonDisabled", action.disabled); -} - -function showSidebar(state, action) { - return state.set("visible", action.visible); -} - -function toggleSidebar(state, action) { - return state.set("visible", !state.visible); -} - -function sidebar(state = new SidebarState(), action) { - switch (action.type) { - case DISABLE_TOGGLE_BUTTON: - return disableToggleButton(state, action); - case SHOW_SIDEBAR: - return showSidebar(state, action); - case TOGGLE_SIDEBAR: - return toggleSidebar(state, action); - default: - return state; - } -} - -module.exports = sidebar; diff --git a/devtools/client/netmonitor/reducers/ui.js b/devtools/client/netmonitor/reducers/ui.js new file mode 100644 index 000000000..033b944f3 --- /dev/null +++ b/devtools/client/netmonitor/reducers/ui.js @@ -0,0 +1,40 @@ +/* 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 I = require("devtools/client/shared/vendor/immutable"); +const { + OPEN_SIDEBAR, + TOGGLE_SIDEBAR, +} = require("../constants"); + +const Sidebar = I.Record({ + open: false, +}); + +const UI = I.Record({ + sidebar: new Sidebar(), +}); + +function openSidebar(state, action) { + return state.setIn(["sidebar", "open"], action.open); +} + +function toggleSidebar(state, action) { + return state.setIn(["sidebar", "open"], !state.sidebar.open); +} + +function ui(state = new UI(), action) { + switch (action.type) { + case OPEN_SIDEBAR: + return openSidebar(state, action); + case TOGGLE_SIDEBAR: + return toggleSidebar(state, action); + default: + return state; + } +} + +module.exports = ui; diff --git a/devtools/client/netmonitor/request-list-context-menu.js b/devtools/client/netmonitor/request-list-context-menu.js index 215296265..331a7bde3 100644 --- a/devtools/client/netmonitor/request-list-context-menu.js +++ b/devtools/client/netmonitor/request-list-context-menu.js @@ -10,6 +10,7 @@ const Services = require("Services"); const { Task } = require("devtools/shared/task"); const { Curl } = require("devtools/client/shared/curl"); const { gDevTools } = require("devtools/client/framework/devtools"); +const { openRequestInTab } = require("devtools/client/netmonitor/open-request-in-tab"); const Menu = require("devtools/client/framework/menu"); const MenuItem = require("devtools/client/framework/menu-item"); const { L10N } = require("./l10n"); @@ -186,8 +187,7 @@ RequestListContextMenu.prototype = { */ openRequestInTab() { let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType); - let { url } = this.selectedItem.attachment; - win.openUILinkIn(url, "tab", { relatedToCurrent: true }); + openRequestInTab(this.selectedItem.attachment); }, /** diff --git a/devtools/client/netmonitor/request-utils.js b/devtools/client/netmonitor/request-utils.js index ba54efb4f..647d71e7c 100644 --- a/devtools/client/netmonitor/request-utils.js +++ b/devtools/client/netmonitor/request-utils.js @@ -92,7 +92,7 @@ exports.getFormDataSections = Task.async(function* (headers, uploadHeaders, post exports.formDataURI = function (mimeType, encoding, text) { if (!encoding) { encoding = "base64"; - text = btoa(text); + text = btoa(unescape(encodeURIComponent(text))); } return "data:" + mimeType + ";" + encoding + "," + text; }; diff --git a/devtools/client/netmonitor/requests-menu-view.js b/devtools/client/netmonitor/requests-menu-view.js index 6ea6381ec..4ee307145 100644 --- a/devtools/client/netmonitor/requests-menu-view.js +++ b/devtools/client/netmonitor/requests-menu-view.js @@ -19,7 +19,6 @@ const {setImageTooltip, getImageDimensions} = const {Heritage, WidgetMethods, setNamedTimeout} = require("devtools/client/shared/widgets/view-helpers"); const {CurlUtils} = require("devtools/client/shared/curl"); -const {PluralForm} = require("devtools/shared/plural-form"); const {Filters, isFreetextMatch} = require("./filter-predicates"); const {Sorters} = require("./sort-predicates"); const {L10N, WEBCONSOLE_L10N} = require("./l10n"); @@ -90,10 +89,11 @@ function storeWatcher(initialValue, reduceValue, onChange) { let currentValue = initialValue; return () => { + const oldValue = currentValue; const newValue = reduceValue(currentValue); - if (newValue !== currentValue) { - onChange(newValue, currentValue); + if (newValue !== oldValue) { currentValue = newValue; + onChange(newValue, oldValue); } }; } @@ -129,8 +129,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { let widgetParentEl = $("#requests-menu-contents"); this.widget = new SideMenuWidget(widgetParentEl); this._splitter = $("#network-inspector-view-splitter"); - this._summary = $("#requests-menu-network-summary-button"); - this._summary.setAttribute("label", L10N.getStr("networkMenu.empty")); // Create a tooltip for the newly appended network request item. this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" }); @@ -211,13 +209,10 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { if (NetMonitorController.supportsPerfStats) { $("#requests-menu-perf-notice-button").addEventListener("command", this._onContextPerfCommand, false); - $("#requests-menu-network-summary-button").addEventListener("command", - this._onContextPerfCommand, false); $("#network-statistics-back-button").addEventListener("command", this._onContextPerfCommand, false); } else { $("#notice-perf-message").hidden = true; - $("#requests-menu-network-summary-button").hidden = true; } if (!NetMonitorController.supportsTransferredResponseSize) { @@ -257,8 +252,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { this._onReloadCommand, false); $("#requests-menu-perf-notice-button").removeEventListener("command", this._onContextPerfCommand, false); - $("#requests-menu-network-summary-button").removeEventListener("command", - this._onContextPerfCommand, false); $("#network-statistics-back-button").removeEventListener("command", this._onContextPerfCommand, false); @@ -406,9 +399,20 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { if (rawHeadersHidden) { let selected = this.selectedItem.attachment; let selectedRequestHeaders = selected.requestHeaders.headers; - let selectedResponseHeaders = selected.responseHeaders.headers; + // display Status-Line above other response headers + let selectedStatusLine = selected.httpVersion + + " " + selected.status + + " " + selected.statusText + + "\n"; requestTextarea.value = writeHeaderText(selectedRequestHeaders); - responseTextare.value = writeHeaderText(selectedResponseHeaders); + // sometimes it's empty + if (selected.responseHeaders) { + let selectedResponseHeaders = selected.responseHeaders.headers; + responseTextare.value = selectedStatusLine + + writeHeaderText(selectedResponseHeaders); + } else { + responseTextare.value = selectedStatusLine; + } $("#raw-headers").hidden = false; } else { requestTextarea.value = null; @@ -422,7 +426,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { */ reFilterRequests: function () { this.filterContents(this._filterPredicate); - this.refreshSummary(); + this.updateRequests(); this.refreshZebra(); }, @@ -541,7 +545,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { break; } - this.refreshSummary(); + this.updateRequests(); this.refreshZebra(); }, @@ -552,41 +556,17 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { NetMonitorController.NetworkEventsHandler.clearMarkers(); NetMonitorView.Sidebar.toggle(false); - this.store.dispatch(Actions.disableToggleButton(true)); $("#requests-menu-empty-notice").hidden = false; this.empty(); - this.refreshSummary(); + this.updateRequests(); }, /** - * Refreshes the status displayed in this container's footer, providing - * concise information about all requests. + * Update store request itmes and trigger related UI update */ - refreshSummary: function () { - let visibleItems = this.visibleItems; - let visibleRequestsCount = visibleItems.length; - if (!visibleRequestsCount) { - this._summary.setAttribute("label", L10N.getStr("networkMenu.empty")); - return; - } - - let totalBytes = this._getTotalBytesOfRequests(visibleItems); - let totalMillis = - this._getNewestRequest(visibleItems).attachment.endedMillis - - this._getOldestRequest(visibleItems).attachment.startedMillis; - - // https://developer.mozilla.org/en-US/docs/Localization_and_Plurals - let str = PluralForm.get(visibleRequestsCount, - L10N.getStr("networkMenu.summary")); - - this._summary.setAttribute("label", str - .replace("#1", visibleRequestsCount) - .replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024, - CONTENT_SIZE_DECIMALS)) - .replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000, - REQUEST_TIME_DECIMALS)) - ); + updateRequests: function () { + this.store.dispatch(Actions.updateRequests(this.visibleItems)); }, /** @@ -865,7 +845,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { this._updateQueue = []; this._addQueue = []; - this.store.dispatch(Actions.disableToggleButton(!this.itemCount)); $("#requests-menu-empty-notice").hidden = !!this.itemCount; // Make sure all the requests are sorted and filtered. @@ -875,7 +854,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { // so this doesn't happen once per network event update). this.sortContents(); this.filterContents(); - this.refreshSummary(); + this.updateRequests(); this.refreshZebra(); // Rescale all the waterfalls so that everything is visible at once. @@ -1131,7 +1110,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { */ _createWaterfallView: function (item, timings, fromCache) { let { target } = item; - let sections = ["blocked", "dns", "connect", "send", "wait", "receive"]; + let sections = ["blocked", "dns", "connect", "ssl", "send", "wait", "receive"]; // Skipping "blocked" because it doesn't work yet. let timingsNode = $(".requests-menu-timings", target); @@ -1559,59 +1538,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { }, /** - * Gets the total number of bytes representing the cumulated content size of - * a set of requests. Returns 0 for an empty set. - * - * @param array itemsArray - * @return number - */ - _getTotalBytesOfRequests: function (itemsArray) { - if (!itemsArray.length) { - return 0; - } - - let result = 0; - itemsArray.forEach(item => { - let size = item.attachment.contentSize; - result += (typeof size == "number") ? size : 0; - }); - - return result; - }, - - /** - * Gets the oldest (first performed) request in a set. Returns null for an - * empty set. - * - * @param array itemsArray - * @return object - */ - _getOldestRequest: function (itemsArray) { - if (!itemsArray.length) { - return null; - } - return itemsArray.reduce((prev, curr) => - prev.attachment.startedMillis < curr.attachment.startedMillis ? - prev : curr); - }, - - /** - * Gets the newest (latest performed) request in a set. Returns null for an - * empty set. - * - * @param array itemsArray - * @return object - */ - _getNewestRequest: function (itemsArray) { - if (!itemsArray.length) { - return null; - } - return itemsArray.reduce((prev, curr) => - prev.attachment.startedMillis > curr.attachment.startedMillis ? - prev : curr); - }, - - /** * Gets the available waterfall width in this container. * @return number */ diff --git a/devtools/client/netmonitor/selectors/index.js b/devtools/client/netmonitor/selectors/index.js index f473149b5..60d6007cd 100644 --- a/devtools/client/netmonitor/selectors/index.js +++ b/devtools/client/netmonitor/selectors/index.js @@ -1,8 +1,78 @@ /* 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 { createSelector } = require("devtools/client/shared/vendor/reselect"); + +/** + * Gets the total number of bytes representing the cumulated content size of + * a set of requests. Returns 0 for an empty set. + * + * @param {array} items - an array of request items + * @return {number} total bytes of requests + */ +function getContentSizeOfRequests(items) { + if (!items.length) { + return 0; + } + + let result = 0; + items.forEach((item) => { + let size = item.attachment.contentSize; + result += (typeof size == "number") ? size : 0; + }); + + return result; +} + +function getTransferredSizeOfRequests(items) { + if (!items.length) { + return 0; + } + + let result = 0; + items.forEach((item) => { + let size = item.attachment.transferredSize; + result += (typeof size == "number") ? size : 0; + }); + + return result; +} + +/** + * Gets the total milliseconds for all requests. Returns null for an + * empty set. + * + * @param {array} items - an array of request items + * @return {object} total milliseconds for all requests + */ +function getMillisOfRequests(items) { + if (!items.length) { + return null; + } + + const oldest = items.reduce((prev, curr) => + prev.attachment.startedMillis < curr.attachment.startedMillis ? + prev : curr); + const newest = items.reduce((prev, curr) => + prev.attachment.startedMillis > curr.attachment.startedMillis ? + prev : curr); + + return newest.attachment.endedMillis - oldest.attachment.startedMillis; +} + +const getDisplayedRequestsSummary = createSelector( + (state) => state.requests.items, + (requests) => ({ + count: requests.length, + contentSize: getContentSizeOfRequests(requests), + transferredSize: getTransferredSizeOfRequests(requests), + millis: getMillisOfRequests(requests), + }) +); + module.exports = { - // selectors... + getDisplayedRequestsSummary, }; diff --git a/devtools/client/netmonitor/test/browser_net_charts-03.js b/devtools/client/netmonitor/test/browser_net_charts-03.js index c7d9b0c1a..4a655a8ca 100644 --- a/devtools/client/netmonitor/test/browser_net_charts-03.js +++ b/devtools/client/netmonitor/test/browser_net_charts-03.js @@ -33,6 +33,10 @@ add_task(function* () { totals: { label1: value => "Hello " + L10N.numberWithDecimals(value, 2), label2: value => "World " + L10N.numberWithDecimals(value, 2) + }, + header: { + label1: "label1header", + label2: "label2header", } }); @@ -51,39 +55,48 @@ add_task(function* () { is(title.getAttribute("value"), "Table title", "The title node displays the correct text."); - is(rows.length, 3, "There should be 3 table chart rows created."); + is(rows.length, 4, "There should be 3 table chart rows and a header created."); - ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"), - "A colored blob exists for the firt row."); is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "label1", - "The first column of the first row exists."); + "The first column of the header exists."); is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label2", + "The second column of the header exists."); + is(rows[0].querySelectorAll("span")[0].textContent, "label1header", + "The first column of the header displays the correct text."); + is(rows[0].querySelectorAll("span")[1].textContent, "label2header", + "The second column of the header displays the correct text."); + + ok(rows[1].querySelector(".table-chart-row-box.chart-colored-blob"), + "A colored blob exists for the firt row."); + is(rows[1].querySelectorAll("span")[0].getAttribute("name"), "label1", + "The first column of the first row exists."); + is(rows[1].querySelectorAll("span")[1].getAttribute("name"), "label2", "The second column of the first row exists."); - is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "1", + is(rows[1].querySelectorAll("label")[0].getAttribute("value"), "1", "The first column of the first row displays the correct text."); - is(rows[0].querySelectorAll("label")[1].getAttribute("value"), "11.1foo", + is(rows[1].querySelectorAll("label")[1].getAttribute("value"), "11.1foo", "The second column of the first row displays the correct text."); - ok(rows[1].querySelector(".table-chart-row-box.chart-colored-blob"), + ok(rows[2].querySelector(".table-chart-row-box.chart-colored-blob"), "A colored blob exists for the second row."); - is(rows[1].querySelectorAll("label")[0].getAttribute("name"), "label1", + is(rows[2].querySelectorAll("label")[0].getAttribute("name"), "label1", "The first column of the second row exists."); - is(rows[1].querySelectorAll("label")[1].getAttribute("name"), "label2", + is(rows[2].querySelectorAll("label")[1].getAttribute("name"), "label2", "The second column of the second row exists."); - is(rows[1].querySelectorAll("label")[0].getAttribute("value"), "2", + is(rows[2].querySelectorAll("label")[0].getAttribute("value"), "2", "The first column of the second row displays the correct text."); - is(rows[1].querySelectorAll("label")[1].getAttribute("value"), "12.2bar", + is(rows[2].querySelectorAll("label")[1].getAttribute("value"), "12.2bar", "The second column of the first row displays the correct text."); - ok(rows[2].querySelector(".table-chart-row-box.chart-colored-blob"), + ok(rows[3].querySelector(".table-chart-row-box.chart-colored-blob"), "A colored blob exists for the third row."); - is(rows[2].querySelectorAll("label")[0].getAttribute("name"), "label1", + is(rows[3].querySelectorAll("label")[0].getAttribute("name"), "label1", "The first column of the third row exists."); - is(rows[2].querySelectorAll("label")[1].getAttribute("name"), "label2", + is(rows[3].querySelectorAll("label")[1].getAttribute("name"), "label2", "The second column of the third row exists."); - is(rows[2].querySelectorAll("label")[0].getAttribute("value"), "3", + is(rows[3].querySelectorAll("label")[0].getAttribute("value"), "3", "The first column of the third row displays the correct text."); - is(rows[2].querySelectorAll("label")[1].getAttribute("value"), "13.3baz", + is(rows[3].querySelectorAll("label")[1].getAttribute("value"), "13.3baz", "The second column of the third row displays the correct text."); is(sums.length, 2, "There should be 2 total summaries created."); diff --git a/devtools/client/netmonitor/test/browser_net_charts-04.js b/devtools/client/netmonitor/test/browser_net_charts-04.js index 0d150c409..921701ae5 100644 --- a/devtools/client/netmonitor/test/browser_net_charts-04.js +++ b/devtools/client/netmonitor/test/browser_net_charts-04.js @@ -22,6 +22,10 @@ add_task(function* () { totals: { label1: value => "Hello " + L10N.numberWithDecimals(value, 2), label2: value => "World " + L10N.numberWithDecimals(value, 2) + }, + header: { + label1: "", + label2: "" } }); @@ -40,17 +44,17 @@ add_task(function* () { is(title.getAttribute("value"), "Table title", "The title node displays the correct text."); - is(rows.length, 1, "There should be 1 table chart row created."); + is(rows.length, 2, "There should be 1 table chart row and a 1 header created."); - ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"), + ok(rows[1].querySelector(".table-chart-row-box.chart-colored-blob"), "A colored blob exists for the firt row."); - is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "size", + is(rows[1].querySelectorAll("label")[0].getAttribute("name"), "size", "The first column of the first row exists."); - is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label", + is(rows[1].querySelectorAll("label")[1].getAttribute("name"), "label", "The second column of the first row exists."); - is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "", + is(rows[1].querySelectorAll("label")[0].getAttribute("value"), "", "The first column of the first row displays the correct text."); - is(rows[0].querySelectorAll("label")[1].getAttribute("value"), + is(rows[1].querySelectorAll("label")[1].getAttribute("value"), L10N.getStr("tableChart.loading"), "The second column of the first row displays the correct text."); diff --git a/devtools/client/netmonitor/test/browser_net_charts-05.js b/devtools/client/netmonitor/test/browser_net_charts-05.js index 00445b132..c444d2c65 100644 --- a/devtools/client/netmonitor/test/browser_net_charts-05.js +++ b/devtools/client/netmonitor/test/browser_net_charts-05.js @@ -33,6 +33,10 @@ add_task(function* () { totals: { size: value => "Hello " + L10N.numberWithDecimals(value, 2), label: value => "World " + L10N.numberWithDecimals(value, 2) + }, + header: { + label1: "", + label2: "" } }); @@ -53,9 +57,9 @@ add_task(function* () { ok(node.querySelector(".table-chart-container"), "A table chart was created successfully."); - is(rows.length, 3, "There should be 3 pie chart slices created."); - is(rows.length, 3, "There should be 3 table chart rows created."); - is(sums.length, 2, "There should be 2 total summaries created."); + is(rows.length, 4, "There should be 3 pie chart slices and 1 header created."); + is(rows.length, 4, "There should be 3 table chart rows and 1 header created."); + is(sums.length, 2, "There should be 2 total summaries and 1 header created."); yield teardown(monitor); }); diff --git a/devtools/client/netmonitor/test/browser_net_charts-07.js b/devtools/client/netmonitor/test/browser_net_charts-07.js index bb992e4eb..a655f258c 100644 --- a/devtools/client/netmonitor/test/browser_net_charts-07.js +++ b/devtools/client/netmonitor/test/browser_net_charts-07.js @@ -20,6 +20,10 @@ add_task(function* () { totals: { label1: value => "Hello " + L10N.numberWithDecimals(value, 2), label2: value => "World " + L10N.numberWithDecimals(value, 2) + }, + header: { + label1: "", + label2: "" } }); @@ -29,17 +33,17 @@ add_task(function* () { let rows = grid.querySelectorAll(".table-chart-row"); let sums = node.querySelectorAll(".table-chart-summary-label"); - is(rows.length, 1, "There should be 1 table chart row created."); + is(rows.length, 2, "There should be 1 table chart row and 1 header created."); - ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"), + ok(rows[1].querySelector(".table-chart-row-box.chart-colored-blob"), "A colored blob exists for the firt row."); - is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "size", + is(rows[1].querySelectorAll("label")[0].getAttribute("name"), "size", "The first column of the first row exists."); - is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label", + is(rows[1].querySelectorAll("label")[1].getAttribute("name"), "label", "The second column of the first row exists."); - is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "", + is(rows[1].querySelectorAll("label")[0].getAttribute("value"), "", "The first column of the first row displays the correct text."); - is(rows[0].querySelectorAll("label")[1].getAttribute("value"), + is(rows[1].querySelectorAll("label")[1].getAttribute("value"), L10N.getStr("tableChart.unavailable"), "The second column of the first row displays the correct text."); diff --git a/devtools/client/netmonitor/test/browser_net_curl-utils.js b/devtools/client/netmonitor/test/browser_net_curl-utils.js index 7a5fc7926..0d35c6141 100644 --- a/devtools/client/netmonitor/test/browser_net_curl-utils.js +++ b/devtools/client/netmonitor/test/browser_net_curl-utils.js @@ -7,7 +7,7 @@ * Tests Curl Utils functionality. */ -const { CurlUtils } = require("devtools/client/shared/curl"); +const { Curl, CurlUtils } = require("devtools/client/shared/curl"); add_task(function* () { let { tab, monitor } = yield initNetMonitor(CURL_UTILS_URL); @@ -37,6 +37,8 @@ add_task(function* () { data = yield createCurlData(requests.post.attachment, gNetwork); testIsUrlEncodedRequest(data); testWritePostDataTextParams(data); + testWriteEmptyPostDataTextParams(data); + testDataArgumentOnGeneratedCommand(data); data = yield createCurlData(requests.multipart.attachment, gNetwork); testIsMultipartRequest(data); @@ -85,6 +87,18 @@ function testWritePostDataTextParams(data) { "Should return a serialized representation of the request parameters"); } +function testWriteEmptyPostDataTextParams(data) { + let params = CurlUtils.writePostDataTextParams(null); + is(params, "", + "Should return a empty string when no parameters provided"); +} + +function testDataArgumentOnGeneratedCommand(data) { + let curlCommand = Curl.generateCommand(data); + ok(curlCommand.includes("--data"), + "Should return a curl command with --data"); +} + function testGetMultipartBoundary(data) { let boundary = CurlUtils.getMultipartBoundary(data); ok(/-{3,}\w+/.test(boundary), diff --git a/devtools/client/netmonitor/test/browser_net_footer-summary.js b/devtools/client/netmonitor/test/browser_net_footer-summary.js index e484b2097..8faa8470b 100644 --- a/devtools/client/netmonitor/test/browser_net_footer-summary.js +++ b/devtools/client/netmonitor/test/browser_net_footer-summary.js @@ -10,13 +10,14 @@ add_task(function* () { requestLongerTimeout(2); + let { getSummary } = require("devtools/client/netmonitor/selectors/index"); let { L10N } = require("devtools/client/netmonitor/l10n"); let { PluralForm } = require("devtools/shared/plural-form"); let { tab, monitor } = yield initNetMonitor(FILTERING_URL); info("Starting test... "); - let { $, NetMonitorView } = monitor.panelWin; + let { $, NetMonitorView, gStore } = monitor.panelWin; let { RequestsMenu } = NetMonitorView; RequestsMenu.lazyUpdate = false; @@ -43,33 +44,27 @@ add_task(function* () { yield teardown(monitor); function testStatus() { - let summary = $("#requests-menu-network-summary-button"); - let value = summary.getAttribute("label"); + const { count, contentSize, transferredSize, millis } = getSummary(gStore.getState()); + let value = $("#requests-menu-network-summary-button").textContent; info("Current summary: " + value); - let visibleItems = RequestsMenu.visibleItems; - let visibleRequestsCount = visibleItems.length; let totalRequestsCount = RequestsMenu.itemCount; - info("Current requests: " + visibleRequestsCount + " of " + totalRequestsCount + "."); + info("Current requests: " + count + " of " + totalRequestsCount + "."); - if (!totalRequestsCount || !visibleRequestsCount) { + if (!totalRequestsCount || !count) { is(value, L10N.getStr("networkMenu.empty"), "The current summary text is incorrect, expected an 'empty' label."); return; } - let totalBytes = RequestsMenu._getTotalBytesOfRequests(visibleItems); - let totalMillis = - RequestsMenu._getNewestRequest(visibleItems).attachment.endedMillis - - RequestsMenu._getOldestRequest(visibleItems).attachment.startedMillis; + info("Computed total bytes: " + contentSize); + info("Computed total millis: " + millis); - info("Computed total bytes: " + totalBytes); - info("Computed total millis: " + totalMillis); - - is(value, PluralForm.get(visibleRequestsCount, L10N.getStr("networkMenu.summary")) - .replace("#1", visibleRequestsCount) - .replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024, 2)) - .replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000, 2)) + is(value, PluralForm.get(count, L10N.getStr("networkMenu.summary2")) + .replace("#1", count) + .replace("#2", L10N.numberWithDecimals((contentSize || 0) / 1024, 2)) + .replace("#3", L10N.numberWithDecimals((transferredSize || 0) / 1024, 2)) + .replace("#4", L10N.numberWithDecimals((millis || 0) / 1000, 2)) , "The current summary text is incorrect."); } }); diff --git a/devtools/client/netmonitor/test/browser_net_security-details.js b/devtools/client/netmonitor/test/browser_net_security-details.js index 0a83b3ed9..61f39a414 100644 --- a/devtools/client/netmonitor/test/browser_net_security-details.js +++ b/devtools/client/netmonitor/test/browser_net_security-details.js @@ -66,6 +66,10 @@ add_task(function* () { checkLabel("#security-cert-issuer-o", "Mozilla Testing"); checkLabel("#security-cert-issuer-ou", "<Not Available>"); + // These two values can change. So only check they're not empty. + checkLabelNotEmpty("#security-keagroup-value"); + checkLabelNotEmpty("#security-signaturescheme-value"); + // Locale sensitive and varies between timezones. Cant't compare equality or // the test fails depending on which part of the world the test is executed. checkLabelNotEmpty("#security-cert-validity-begins"); diff --git a/devtools/client/netmonitor/test/browser_net_simple-request-data.js b/devtools/client/netmonitor/test/browser_net_simple-request-data.js index 1b952bd71..f21318d7a 100644 --- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js +++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js @@ -226,6 +226,8 @@ function test() { "The eventTimings attachment has an incorrect |timings.blocked| property."); is(typeof requestItem.attachment.eventTimings.timings.dns, "number", "The eventTimings attachment has an incorrect |timings.dns| property."); + is(typeof requestItem.attachment.eventTimings.timings.ssl, "number", + "The eventTimings attachment has an incorrect |timings.ssl| property."); is(typeof requestItem.attachment.eventTimings.timings.connect, "number", "The eventTimings attachment has an incorrect |timings.connect| property."); is(typeof requestItem.attachment.eventTimings.timings.send, "number", diff --git a/devtools/client/netmonitor/toolbar-view.js b/devtools/client/netmonitor/toolbar-view.js index 28c3cf99b..1834d3ee9 100644 --- a/devtools/client/netmonitor/toolbar-view.js +++ b/devtools/client/netmonitor/toolbar-view.js @@ -7,6 +7,7 @@ const Provider = createFactory(require("devtools/client/shared/vendor/react-redu const FilterButtons = createFactory(require("./components/filter-buttons")); const ToggleButton = createFactory(require("./components/toggle-button")); const SearchBox = createFactory(require("./components/search-box")); +const SummaryButton = createFactory(require("./components/summary-button")); const { L10N } = require("./l10n"); // Shortcuts @@ -28,8 +29,9 @@ ToolbarView.prototype = { this._clearContainerNode = $("#react-clear-button-hook"); this._filterContainerNode = $("#react-filter-buttons-hook"); - this._toggleContainerNode = $("#react-details-pane-toggle-hook"); + this._summaryContainerNode = $("#react-summary-button-hook"); this._searchContainerNode = $("#react-search-box-hook"); + this._toggleContainerNode = $("#react-details-pane-toggle-hook"); // clear button ReactDOM.render(button({ @@ -47,6 +49,12 @@ ToolbarView.prototype = { FilterButtons() ), this._filterContainerNode); + // summary button + ReactDOM.render(Provider( + { store }, + SummaryButton() + ), this._summaryContainerNode); + // search box ReactDOM.render(Provider( { store }, @@ -68,8 +76,9 @@ ToolbarView.prototype = { ReactDOM.unmountComponentAtNode(this._clearContainerNode); ReactDOM.unmountComponentAtNode(this._filterContainerNode); - ReactDOM.unmountComponentAtNode(this._toggleContainerNode); + ReactDOM.unmountComponentAtNode(this._summaryContainerNode); ReactDOM.unmountComponentAtNode(this._searchContainerNode); + ReactDOM.unmountComponentAtNode(this._toggleContainerNode); } }; diff --git a/devtools/client/scratchpad/scratchpad.js b/devtools/client/scratchpad/scratchpad.js index 306b635df..60221f39d 100644 --- a/devtools/client/scratchpad/scratchpad.js +++ b/devtools/client/scratchpad/scratchpad.js @@ -81,7 +81,7 @@ loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true); loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true); loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/shared/client/main", true); loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/main", true); -loader.lazyRequireGetter(this, "HUDService", "devtools/client/webconsole/hudservice"); +loader.lazyRequireGetter(this, "HUDService", "devtools/client/webconsole/hudservice", true); XPCOMUtils.defineLazyGetter(this, "REMOTE_TIMEOUT", () => Services.prefs.getIntPref("devtools.debugger.remote-timeout")); @@ -1744,6 +1744,9 @@ var Scratchpad = { this.editor.focus(); this.editor.setCursor({ line: lines.length, ch: lines.pop().length }); + // Add the commands controller for the source-editor. + this.editor.insertCommandsController(); + if (state) this.dirty = !state.saved; diff --git a/devtools/client/scratchpad/scratchpad.xul b/devtools/client/scratchpad/scratchpad.xul index 3712f163d..8694c1bb5 100644 --- a/devtools/client/scratchpad/scratchpad.xul +++ b/devtools/client/scratchpad/scratchpad.xul @@ -121,7 +121,7 @@ <key id="sp-key-reloadAndRun" key="&reloadAndRun.key;" command="sp-cmd-reloadAndRun" - modifiers="accel,alt"/> + modifiers="shift,alt"/> <key id="sp-key-evalFunction" key="&evalFunction.key;" command="sp-cmd-evalFunction" diff --git a/devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js b/devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js index 4da2a2daf..418bdfb56 100644 --- a/devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js +++ b/devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js @@ -2,7 +2,7 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -const HUDService = require("devtools/client/webconsole/hudservice"); +const {HUDService} = require("devtools/client/webconsole/hudservice"); function test() { diff --git a/devtools/client/shared/curl.js b/devtools/client/shared/curl.js index 44465193f..45122be37 100644 --- a/devtools/client/shared/curl.js +++ b/devtools/client/shared/curl.js @@ -76,7 +76,8 @@ const Curl = { // Create post data. let postData = []; - if (utils.isUrlEncodedRequest(data) || data.method == "PUT") { + if (utils.isUrlEncodedRequest(data) || + ["PUT", "POST"].includes(data.method)) { postDataText = data.postDataText; postData.push("--data"); postData.push(escapeString(utils.writePostDataTextParams(postDataText))); @@ -207,6 +208,9 @@ const CurlUtils = { * Post data parameters. */ writePostDataTextParams: function (postDataText) { + if (!postDataText) { + return ""; + } let lines = postDataText.split("\r\n"); return lines[lines.length - 1]; }, diff --git a/devtools/client/shared/widgets/Chart.jsm b/devtools/client/shared/widgets/Chart.jsm index 0894a62ca..0b7cb71fb 100644 --- a/devtools/client/shared/widgets/Chart.jsm +++ b/devtools/client/shared/widgets/Chart.jsm @@ -105,7 +105,7 @@ function PieTableChart(node, pie, table) { * - "mouseout", when the mouse leaves a slice or a row * - "click", when the mouse enters a slice or a row */ -function createPieTableChart(document, { title, diameter, data, strings, totals, sorted }) { +function createPieTableChart(document, { title, diameter, data, strings, totals, sorted, header }) { if (data && sorted) { data = data.slice().sort((a, b) => +(a.size < b.size)); } @@ -119,7 +119,8 @@ function createPieTableChart(document, { title, diameter, data, strings, totals, title: title, data: data, strings: strings, - totals: totals + totals: totals, + header: header, }); let container = document.createElement("hbox"); @@ -338,7 +339,7 @@ function createPieChart(document, { data, width, height, centerX, centerY, radiu * - "mouseout", when the mouse leaves a row * - "click", when the mouse clicks a row */ -function createTableChart(document, { title, data, strings, totals }) { +function createTableChart(document, { title, data, strings, totals, header }) { strings = strings || {}; totals = totals || {}; let isPlaceholder = false; @@ -371,6 +372,24 @@ function createTableChart(document, { title, data, strings, totals }) { tableNode.className = "plain table-chart-grid"; container.appendChild(tableNode); + const headerNode = document.createElement("div"); + headerNode.className = "table-chart-row"; + + const headerBoxNode = document.createElement("div"); + headerBoxNode.className = "table-chart-row-box"; + headerNode.appendChild(headerBoxNode); + + for (let [key, value] of Object.entries(header)) { + let headerLabelNode = document.createElement("span"); + headerLabelNode.className = "plain table-chart-row-label"; + headerLabelNode.setAttribute("name", key); + headerLabelNode.textContent = value; + + headerNode.appendChild(headerLabelNode); + } + + tableNode.appendChild(headerNode); + for (let rowInfo of data) { let rowNode = document.createElement("hbox"); rowNode.className = "table-chart-row"; diff --git a/devtools/client/sourceeditor/editor-commands-controller.js b/devtools/client/sourceeditor/editor-commands-controller.js new file mode 100644 index 000000000..2587f9a1f --- /dev/null +++ b/devtools/client/sourceeditor/editor-commands-controller.js @@ -0,0 +1,97 @@ +/* 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"; + +/** + * The source editor exposes XUL commands that can be used when embedded in a XUL + * document. This controller drives the availability and behavior of the commands. When + * the editor input field is focused, this controller will update the matching menu item + * entries found in application menus or context menus. + */ + +/** + * Returns a controller object that can be used for editor-specific commands: + * - find + * - find again + * - go to line + * - undo + * - redo + * - delete + * - select all + */ +function createController(ed) { + return { + supportsCommand: function (cmd) { + switch (cmd) { + case "cmd_find": + case "cmd_findAgain": + case "cmd_gotoLine": + case "cmd_undo": + case "cmd_redo": + case "cmd_delete": + case "cmd_selectAll": + return true; + } + + return false; + }, + + isCommandEnabled: function (cmd) { + let cm = ed.codeMirror; + + switch (cmd) { + case "cmd_find": + case "cmd_gotoLine": + case "cmd_selectAll": + return true; + case "cmd_findAgain": + return cm.state.search != null && cm.state.search.query != null; + case "cmd_undo": + return ed.canUndo(); + case "cmd_redo": + return ed.canRedo(); + case "cmd_delete": + return ed.somethingSelected(); + } + + return false; + }, + + doCommand: function (cmd) { + let cm = ed.codeMirror; + + let map = { + "cmd_selectAll": "selectAll", + "cmd_find": "find", + "cmd_undo": "undo", + "cmd_redo": "redo", + "cmd_delete": "delCharAfter", + "cmd_findAgain": "findNext" + }; + + if (map[cmd]) { + cm.execCommand(map[cmd]); + return; + } + + if (cmd == "cmd_gotoLine") { + ed.jumpToLine(); + } + }, + + onEvent: function () {} + }; +} + +/** + * Create and insert a commands controller for the provided SourceEditor instance. + */ +function insertCommandsController(sourceEditor) { + let input = sourceEditor.codeMirror.getInputField(); + let controller = createController(sourceEditor); + input.controllers.insertControllerAt(0, controller); +} + +module.exports = { insertCommandsController }; diff --git a/devtools/client/sourceeditor/editor.js b/devtools/client/sourceeditor/editor.js index ce2136afc..1b3c1d31a 100644 --- a/devtools/client/sourceeditor/editor.js +++ b/devtools/client/sourceeditor/editor.js @@ -489,6 +489,16 @@ Editor.prototype = { }, /** + * The source editor can expose several commands linked from system and context menus. + * Kept for backward compatibility with scratchpad and styleeditor. + */ + insertCommandsController: function () { + const { insertCommandsController } = + require("devtools/client/sourceeditor/editor-commands-controller"); + insertCommandsController(this); + }, + + /** * Returns text from the text area. If line argument is provided * the method returns only that line. */ diff --git a/devtools/client/sourceeditor/moz.build b/devtools/client/sourceeditor/moz.build index 5081325c5..765accb14 100644 --- a/devtools/client/sourceeditor/moz.build +++ b/devtools/client/sourceeditor/moz.build @@ -12,6 +12,7 @@ DevToolsModules( 'autocomplete.js', 'css-autocompleter.js', 'debugger.js', + 'editor-commands-controller.js', 'editor.js' ) diff --git a/devtools/client/styleeditor/StyleEditorUI.jsm b/devtools/client/styleeditor/StyleEditorUI.jsm index cdb267669..b2735b3fc 100644 --- a/devtools/client/styleeditor/StyleEditorUI.jsm +++ b/devtools/client/styleeditor/StyleEditorUI.jsm @@ -72,10 +72,18 @@ function StyleEditorUI(debuggee, target, panelDoc, cssProperties) { this.editors = []; this.selectedEditor = null; this.savedLocations = {}; + this._seenSheets = new Map(); + + // Don't add any style sheets that might arrive via events, until + // the call to initialize. Style sheets can arrive from the server + // at any time, for example if a new style sheet was added, or if + // the style sheet actor was just created and is walking the style + // sheets for the first time. In any case, in |initialize| we're + // going to fetch the list of sheets anyway. + this._suppressAdd = true; this._onOptionsPopupShowing = this._onOptionsPopupShowing.bind(this); this._onOptionsPopupHiding = this._onOptionsPopupHiding.bind(this); - this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this); this._onNewDocument = this._onNewDocument.bind(this); this._onMediaPrefChanged = this._onMediaPrefChanged.bind(this); this._updateMediaList = this._updateMediaList.bind(this); @@ -83,10 +91,13 @@ function StyleEditorUI(debuggee, target, panelDoc, cssProperties) { this._onError = this._onError.bind(this); this._updateOpenLinkItem = this._updateOpenLinkItem.bind(this); this._openLinkNewTab = this._openLinkNewTab.bind(this); + this._addStyleSheet = this._addStyleSheet.bind(this); this._prefObserver = new PrefObserver("devtools.styleeditor."); this._prefObserver.on(PREF_ORIG_SOURCES, this._onNewDocument); this._prefObserver.on(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged); + + this._debuggee.on("stylesheet-added", this._addStyleSheet); } this.StyleEditorUI = StyleEditorUI; @@ -165,7 +176,7 @@ StyleEditorUI.prototype = { this._view = new SplitView(viewRoot); wire(this._view.rootElement, ".style-editor-newButton", () =>{ - this._debuggee.addStyleSheet(null).then(this._onStyleSheetCreated); + this._debuggee.addStyleSheet(null); }); wire(this._view.rootElement, ".style-editor-importButton", ()=> { @@ -233,6 +244,7 @@ StyleEditorUI.prototype = { * StyleSheet object for new sheet */ _onNewDocument: function () { + this._suppressAdd = true; this._debuggee.getStyleSheets().then((styleSheets) => { return this._resetStyleSheetList(styleSheets); }).then(null, e => console.error(e)); @@ -246,6 +258,7 @@ StyleEditorUI.prototype = { */ _resetStyleSheetList: Task.async(function* (styleSheets) { this._clear(); + this._suppressAdd = false; for (let sheet of styleSheets) { try { @@ -288,6 +301,10 @@ StyleEditorUI.prototype = { this._view.removeAll(); this.selectedEditor = null; + // Here the keys are style sheet actors, and the values are + // promises that resolve to the sheet's editor. See |_addStyleSheet|. + this._seenSheets = new Map(); + this._suppressAdd = true; this._root.classList.add("loading"); }, @@ -298,46 +315,67 @@ StyleEditorUI.prototype = { * * @param {StyleSheetFront} styleSheet * Style sheet to add to style editor + * @param {Boolean} isNew + * True if this style sheet was created by a call to the + * style sheets actor's @see addStyleSheet method. + * @return {Promise} + * A promise that resolves to the style sheet's editor when the style sheet has + * been fully loaded. If the style sheet has a source map, and source mapping + * is enabled, then the promise resolves to null. */ - _addStyleSheet: Task.async(function* (styleSheet) { - let editor = yield this._addStyleSheetEditor(styleSheet); - - if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) { - return; + _addStyleSheet: function (styleSheet, isNew) { + if (this._suppressAdd) { + return null; } - let sources = yield styleSheet.getOriginalSources(); - if (sources && sources.length) { - let parentEditorName = editor.friendlyName; - this._removeStyleSheetEditor(editor); - - for (let source of sources) { - // set so the first sheet will be selected, even if it's a source - source.styleSheetIndex = styleSheet.styleSheetIndex; - source.relatedStyleSheet = styleSheet; - source.relatedEditorName = parentEditorName; - yield this._addStyleSheetEditor(source); - } + if (!this._seenSheets.has(styleSheet)) { + let promise = (async () => { + let editor = await this._addStyleSheetEditor(styleSheet, isNew); + + if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) { + return editor; + } + + let sources = await styleSheet.getOriginalSources(); + // A single generated sheet might map to multiple original + // sheets, so make editors for each of them. + if (sources && sources.length) { + let parentEditorName = editor.friendlyName; + this._removeStyleSheetEditor(editor); + editor = null; + + for (let source of sources) { + // set so the first sheet will be selected, even if it's a source + source.styleSheetIndex = styleSheet.styleSheetIndex; + source.relatedStyleSheet = styleSheet; + source.relatedEditorName = parentEditorName; + await this._addStyleSheetEditor(source); + } + } + + return editor; + })(); + this._seenSheets.set(styleSheet, promise); } - }), + return this._seenSheets.get(styleSheet); + }, /** * Add a new editor to the UI for a source. * * @param {StyleSheet} styleSheet * Object representing stylesheet - * @param {nsIfile} file - * Optional file object that sheet was imported from * @param {Boolean} isNew * Optional if stylesheet is a new sheet created by user * @return {Promise} that is resolved with the created StyleSheetEditor when * the editor is fully initialized or rejected on error. */ - _addStyleSheetEditor: Task.async(function* (styleSheet, file, isNew) { + _addStyleSheetEditor: Task.async(function* (styleSheet, isNew) { // recall location of saved file for this sheet after page reload + let file = null; let identifier = this.getStyleSheetIdentifier(styleSheet); let savedFile = this.savedLocations[identifier]; - if (savedFile && !file) { + if (savedFile) { file = savedFile; } @@ -388,8 +426,16 @@ StyleEditorUI.prototype = { NetUtil.readInputStreamToString(stream, stream.available()); stream.close(); + this._suppressAdd = true; this._debuggee.addStyleSheet(source).then((styleSheet) => { - this._onStyleSheetCreated(styleSheet, selectedFile); + this._suppressAdd = false; + this._addStyleSheet(styleSheet, true).then(editor => { + if (editor) { + editor.savedFile = selectedFile; + } + // Just for testing purposes. + this.emit("test:editor-updated", editor); + }); }); }); }; @@ -398,14 +444,6 @@ StyleEditorUI.prototype = { }, /** - * When a new or imported stylesheet has been added to the document. - * Add an editor for it. - */ - _onStyleSheetCreated: function (styleSheet, file) { - this._addStyleSheetEditor(styleSheet, file, true); - }, - - /** * Forward any error from a stylesheet. * * @param {string} event @@ -1013,6 +1051,9 @@ StyleEditorUI.prototype = { this._clearStyleSheetEditors(); + this._seenSheets = null; + this._suppressAdd = false; + let sidebar = this._panelDoc.querySelector(".splitview-controller"); let sidebarWidth = sidebar.getAttribute("width"); Services.prefs.setIntPref(PREF_NAV_WIDTH, sidebarWidth); @@ -1025,5 +1066,7 @@ StyleEditorUI.prototype = { this._prefObserver.off(PREF_ORIG_SOURCES, this._onNewDocument); this._prefObserver.off(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged); this._prefObserver.destroy(); + + this._debuggee.off("stylesheet-added", this._addStyleSheet); } }; diff --git a/devtools/client/styleeditor/StyleSheetEditor.jsm b/devtools/client/styleeditor/StyleSheetEditor.jsm index 980e51974..832fcacde 100644 --- a/devtools/client/styleeditor/StyleSheetEditor.jsm +++ b/devtools/client/styleeditor/StyleSheetEditor.jsm @@ -468,6 +468,9 @@ StyleSheetEditor.prototype = { sourceEditor.container.addEventListener("mousemove", this._onMouseMove); } + // Add the commands controller for the source-editor. + sourceEditor.insertCommandsController(); + this.emit("source-editor-load"); }); }, diff --git a/devtools/client/styleeditor/test/browser.ini b/devtools/client/styleeditor/test/browser.ini index 1a85546af..4a84d45e6 100644 --- a/devtools/client/styleeditor/test/browser.ini +++ b/devtools/client/styleeditor/test/browser.ini @@ -60,6 +60,7 @@ support-files = !/devtools/client/shared/test/test-actor-registry.js !/devtools/client/shared/test/test-actor.js +[browser_styleeditor_add_stylesheet.js] [browser_styleeditor_autocomplete.js] [browser_styleeditor_autocomplete-disabled.js] [browser_styleeditor_bom.js] diff --git a/devtools/client/styleeditor/test/browser_styleeditor_add_stylesheet.js b/devtools/client/styleeditor/test/browser_styleeditor_add_stylesheet.js new file mode 100644 index 000000000..d8315d212 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_add_stylesheet.js @@ -0,0 +1,37 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that a newly-added style sheet shows up in the style editor. + +const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html"; + +add_task(function* () { + let { ui } = yield openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 2, "Two sheets present after load."); + + // We have to wait for the length to change, because we might still + // be seeing events from the initial open. + let added = new Promise(resolve => { + let handler = () => { + if (ui.editors.length === 3) { + ui.off("editor-added", handler); + resolve(); + } + }; + ui.on("editor-added", handler); + }); + + info("Adding a style sheet"); + yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => { + let document = content.document; + const style = document.createElement("style"); + style.appendChild(document.createTextNode("div { background: #f06; }")); + document.head.appendChild(style); + }); + yield added; + + is(ui.editors.length, 3, "Three sheets present after new style sheet"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_import.js b/devtools/client/styleeditor/test/browser_styleeditor_import.js index f31f72ce7..2f42317b9 100644 --- a/devtools/client/styleeditor/test/browser_styleeditor_import.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_import.js @@ -18,7 +18,7 @@ const SOURCE = "body{background:red;}"; add_task(function* () { let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI); - let added = ui.once("editor-added"); + let added = ui.once("test:editor-updated"); importSheet(ui, panel.panelWindow); info("Waiting for editor to be added for the imported sheet."); diff --git a/devtools/client/themes/common.css b/devtools/client/themes/common.css index 3d713ada7..133770150 100644 --- a/devtools/client/themes/common.css +++ b/devtools/client/themes/common.css @@ -2,7 +2,9 @@ * 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/. */ + @import url("resource://devtools/client/themes/splitters.css"); +@namespace html url("http://www.w3.org/1999/xhtml"); :root { font: message-box; @@ -33,6 +35,10 @@ font-size: 80%; } +/* Override wrong system font from forms.css */ +html|button, html|select { + font: message-box; +} /* Autocomplete Popup */ diff --git a/devtools/client/themes/netmonitor.css b/devtools/client/themes/netmonitor.css index fea634a0e..ccffb2acc 100644 --- a/devtools/client/themes/netmonitor.css +++ b/devtools/client/themes/netmonitor.css @@ -8,6 +8,7 @@ } #react-clear-button-hook, +#react-summary-button-hook, #react-details-pane-toggle-hook { display: flex; } @@ -47,7 +48,7 @@ #details-pane-toggle, #details-pane.pane-collapsed, .requests-menu-waterfall, - #requests-menu-network-summary-button > .toolbarbutton-text { + #requests-menu-network-summary-button > .summary-info-text { display: none; } } @@ -58,6 +59,7 @@ --timing-blocked-color: rgba(235, 83, 104, 0.8); --timing-dns-color: rgba(223, 128, 255, 0.8); /* pink */ + --timing-ssl-color: rgba(217, 102, 41, 0.8); /* orange */ --timing-connect-color: rgba(217, 102, 41, 0.8); /* orange */ --timing-send-color: rgba(70, 175, 227, 0.8); /* light blue */ --timing-wait-color: rgba(94, 136, 176, 0.8); /* blue grey */ @@ -73,6 +75,7 @@ --timing-blocked-color: rgba(235, 83, 104, 0.8); --timing-dns-color: rgba(223, 128, 255, 0.8); /* pink */ + --timing-ssl-color: rgba(217, 102, 41, 0.8); /* orange */ --timing-connect-color: rgba(217, 102, 41, 0.8); /* orange */ --timing-send-color: rgba(0, 136, 204, 0.8); /* blue */ --timing-wait-color: rgba(95, 136, 176, 0.8); /* blue grey */ @@ -504,6 +507,10 @@ background-color: var(--timing-connect-color); } +.requests-menu-timings-box.ssl { + background-color: var(--timing-ssl-color); +} + .requests-menu-timings-box.send { background-color: var(--timing-send-color); } @@ -744,16 +751,35 @@ /* Performance analysis buttons */ #requests-menu-network-summary-button { + display: flex; + align-items: center; background: none; box-shadow: none; border-color: transparent; - list-style-image: url(images/profiler-stopwatch.svg); padding-inline-end: 0; cursor: pointer; margin-inline-end: 1em; min-width: 0; } +#requests-menu-network-summary-button > .summary-info-icon { + background-image: url(images/profiler-stopwatch.svg); + filter: var(--icon-filter); + width: 16px; + height: 16px; + opacity: 0.8; +} + +#requests-menu-network-summary-button > .summary-info-text { + opacity: 0.8; + margin-inline-start: 0.5em; +} + +#requests-menu-network-summary-button:hover > .summary-info-icon, +#requests-menu-network-summary-button:hover > .summary-info-text { + opacity: 1; +} + /* Performance analysis view */ #network-statistics-toolbar { diff --git a/devtools/client/webconsole/hudservice.js b/devtools/client/webconsole/hudservice.js index 46b4f2a13..3023b7bb3 100644 --- a/devtools/client/webconsole/hudservice.js +++ b/devtools/client/webconsole/hudservice.js @@ -51,6 +51,23 @@ HUD_SERVICE.prototype = */ consoles: null, + _browerConsoleSessionState: false, + storeBrowserConsoleSessionState() { + this._browerConsoleSessionState = !!this.getBrowserConsole(); + }, + getBrowserConsoleSessionState() { + return this._browerConsoleSessionState; + }, + + /** + * Restore the Browser Console as provided by SessionStore. + */ + restoreBrowserConsoleSession: function HS_restoreBrowserConsoleSession() { + if (!HUDService.getBrowserConsole()) { + HUDService.toggleBrowserConsole(); + } + }, + /** * Assign a function to this property to listen for every request that * completes. Used by unit tests. The callback takes one argument: the HTTP @@ -647,6 +664,9 @@ BrowserConsole.prototype = extend(WebConsole.prototype, { return this._bc_init; } + // Only add the shutdown observer if we've opened a Browser Console window. + ShutdownObserver.init(); + this.ui._filterPrefsPrefix = BROWSER_CONSOLE_FILTER_PREFS_PREFIX; let window = this.iframeWindow; @@ -703,16 +723,32 @@ BrowserConsole.prototype = extend(WebConsole.prototype, { }); const HUDService = new HUD_SERVICE(); +exports.HUDService = HUDService; -(() => { - let methods = ["openWebConsole", "openBrowserConsole", - "toggleBrowserConsole", "getOpenWebConsole", - "getBrowserConsole", "getHudByWindow", - "openBrowserConsoleOrFocus", "getHudReferenceById"]; - for (let method of methods) { - exports[method] = HUDService[method].bind(HUDService); - } +/** + * The ShutdownObserver listens for app shutdown and saves the current state + * of the Browser Console for session restore. + */ +var ShutdownObserver = { + _initialized: false, + init() { + if (this._initialized) { + return; + } + + Services.obs.addObserver(this, "quit-application-granted", false); + + this._initialized = true; + }, - exports.consoles = HUDService.consoles; - exports.lastFinishedRequest = HUDService.lastFinishedRequest; -})(); + observe(message, topic) { + if (topic == "quit-application-granted") { + HUDService.storeBrowserConsoleSessionState(); + this.uninit(); + } + }, + + uninit() { + Services.obs.removeObserver(this, "quit-application-granted"); + } +}; diff --git a/devtools/client/webconsole/panel.js b/devtools/client/webconsole/panel.js index 3e3a4f4b9..b692de681 100644 --- a/devtools/client/webconsole/panel.js +++ b/devtools/client/webconsole/panel.js @@ -8,7 +8,7 @@ const promise = require("promise"); -loader.lazyGetter(this, "HUDService", () => require("devtools/client/webconsole/hudservice")); +loader.lazyRequireGetter(this, "HUDService", "devtools/client/webconsole/hudservice", true); loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter")); /** diff --git a/devtools/client/webconsole/test/browser.ini b/devtools/client/webconsole/test/browser.ini index 918411182..1c7913835 100644 --- a/devtools/client/webconsole/test/browser.ini +++ b/devtools/client/webconsole/test/browser.ini @@ -182,6 +182,7 @@ subsuite = clipboard [browser_console_optimized_out_vars.js] [browser_console_private_browsing.js] skip-if = e10s # Bug 1042253 - webconsole e10s tests +[browser_console_restore.js] [browser_console_server_logging.js] [browser_console_variables_view.js] [browser_console_variables_view_filter.js] diff --git a/devtools/client/webconsole/test/browser_console.js b/devtools/client/webconsole/test/browser_console.js index 7bd1ffdc2..4358ac0f1 100644 --- a/devtools/client/webconsole/test/browser_console.js +++ b/devtools/client/webconsole/test/browser_console.js @@ -22,7 +22,7 @@ const TEST_IMAGE = "http://example.com/browser/devtools/client/webconsole/" + add_task(function* () { yield loadTab(TEST_URI); - let opened = waitForConsole(); + let opened = waitForBrowserConsole(); let hud = HUDService.getBrowserConsole(); ok(!hud, "browser console is not open"); @@ -141,20 +141,3 @@ function consoleOpened(hud) { ], }); } - -function waitForConsole() { - let deferred = promise.defer(); - - Services.obs.addObserver(function observer(aSubject) { - Services.obs.removeObserver(observer, "web-console-created"); - aSubject.QueryInterface(Ci.nsISupportsString); - - let hud = HUDService.getBrowserConsole(); - ok(hud, "browser console is open"); - is(aSubject.data, hud.hudId, "notification hudId is correct"); - - executeSoon(() => deferred.resolve(hud)); - }, "web-console-created", false); - - return deferred.promise; -} diff --git a/devtools/client/webconsole/test/browser_console_restore.js b/devtools/client/webconsole/test/browser_console_restore.js new file mode 100644 index 000000000..fb08d9c70 --- /dev/null +++ b/devtools/client/webconsole/test/browser_console_restore.js @@ -0,0 +1,30 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Check that the browser console gets session state is set correctly, and that +// it re-opens when restore is requested. + +"use strict"; + +add_task(function* () { + is(HUDService.getBrowserConsoleSessionState(), false, "Session state false by default"); + HUDService.storeBrowserConsoleSessionState(); + is(HUDService.getBrowserConsoleSessionState(), false, + "Session state still not true even after setting (since Browser Console is closed)"); + + yield HUDService.toggleBrowserConsole(); + HUDService.storeBrowserConsoleSessionState(); + is(HUDService.getBrowserConsoleSessionState(), true, + "Session state true (since Browser Console is opened)"); + + info("Closing the browser console and waiting for the session restore to reopen it") + yield HUDService.toggleBrowserConsole(); + + let opened = waitForBrowserConsole(); + HUDService.restoreBrowserConsoleSession(); + + info("Waiting for the console to open after session restore") + yield opened; +}); diff --git a/devtools/client/webconsole/test/head.js b/devtools/client/webconsole/test/head.js index 519cb78b0..2787933c4 100644 --- a/devtools/client/webconsole/test/head.js +++ b/devtools/client/webconsole/test/head.js @@ -12,7 +12,7 @@ Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtool var {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils"); var {Messages} = require("devtools/client/webconsole/console-output"); const asyncStorage = require("devtools/shared/async-storage"); -const HUDService = require("devtools/client/webconsole/hudservice"); +const {HUDService} = require("devtools/client/webconsole/hudservice"); // Services.prefs.setBoolPref("devtools.debugger.log", true); @@ -1842,3 +1842,18 @@ function getRenderedSource(root) { column: location.getAttribute("data-column"), } : null; } + +function waitForBrowserConsole() { + return new Promise(resolve => { + Services.obs.addObserver(function observer(subject) { + Services.obs.removeObserver(observer, "web-console-created"); + subject.QueryInterface(Ci.nsISupportsString); + + let hud = HUDService.getBrowserConsole(); + ok(hud, "browser console is open"); + is(subject.data, hud.hudId, "notification hudId is correct"); + + executeSoon(() => resolve(hud)); + }, "web-console-created"); + }); +} diff --git a/devtools/server/actors/highlighters/box-model.js b/devtools/server/actors/highlighters/box-model.js index 35f201a04..ae4284424 100644 --- a/devtools/server/actors/highlighters/box-model.js +++ b/devtools/server/actors/highlighters/box-model.js @@ -15,7 +15,10 @@ const { isNodeValid, moveInfobar, } = require("./utils/markup"); -const { setIgnoreLayoutChanges } = require("devtools/shared/layout/utils"); +const { + setIgnoreLayoutChanges, + getCurrentZoom, + } = require("devtools/shared/layout/utils"); const inspector = require("devtools/server/actors/inspector"); const nodeConstants = require("devtools/shared/dom-node-constants"); @@ -670,10 +673,14 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, { pseudos += ":" + pseudo; } - let rect = this._getOuterQuad("border").bounds; - let dim = parseFloat(rect.width.toPrecision(6)) + + // We want to display the original `width` and `height`, instead of the ones affected + // by any zoom. Since the infobar can be displayed also for text nodes, we can't + // access the computed style for that, and this is why we recalculate them here. + let zoom = getCurrentZoom(this.win); + let { width, height } = this._getOuterQuad("border").bounds; + let dim = parseFloat((width / zoom).toPrecision(6)) + " \u00D7 " + - parseFloat(rect.height.toPrecision(6)); + parseFloat((height / zoom).toPrecision(6)); this.getElement("infobar-tagname").setTextContent(displayName); this.getElement("infobar-id").setTextContent(id); diff --git a/devtools/server/actors/stylesheets.js b/devtools/server/actors/stylesheets.js index f20634e6c..7fcbca8c4 100644 --- a/devtools/server/actors/stylesheets.js +++ b/devtools/server/actors/stylesheets.js @@ -13,7 +13,6 @@ const events = require("sdk/event/core"); const protocol = require("devtools/shared/protocol"); const {LongStringActor} = require("devtools/server/actors/string"); const {fetch} = require("devtools/shared/DevToolsUtils"); -const {listenOnce} = require("devtools/shared/async-utils"); const {originalSourceSpec, mediaRuleSpec, styleSheetSpec, styleSheetsSpec} = require("devtools/shared/specs/stylesheets"); const {SourceMapConsumer} = require("source-map"); @@ -251,7 +250,7 @@ var StyleSheetActor = protocol.ActorClassWithSpec(styleSheetSpec, { }, destroy: function () { - if (this._transitionTimeout) { + if (this._transitionTimeout && this.window) { this.window.clearTimeout(this._transitionTimeout); removePseudoClassLock( this.document.documentElement, TRANSITION_PSEUDO_CLASS); @@ -801,6 +800,64 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { protocol.Actor.prototype.initialize.call(this, null); this.parentActor = tabActor; + + this._onNewStyleSheetActor = this._onNewStyleSheetActor.bind(this); + this._onSheetAdded = this._onSheetAdded.bind(this); + this._onWindowReady = this._onWindowReady.bind(this); + + events.on(this.parentActor, "stylesheet-added", this._onNewStyleSheetActor); + events.on(this.parentActor, "window-ready", this._onWindowReady); + + // We listen for StyleSheetApplicableStateChanged rather than + // StyleSheetAdded, because the latter will be sent before the + // rules are ready. Using the former (with a check to ensure that + // the sheet is enabled) ensures that the sheet is ready before we + // try to make an actor for it. + this.parentActor.chromeEventHandler + .addEventListener("StyleSheetApplicableStateChanged", this._onSheetAdded, true); + + // This is used when creating a new style sheet, so that we can + // pass the correct flag when emitting our stylesheet-added event. + // See addStyleSheet and _onNewStyleSheetActor for more details. + this._nextStyleSheetIsNew = false; + }, + + destroy: function () { + for (let win of this.parentActor.windows) { + // This flag only exists for devtools, so we are free to clear + // it when we're done. + win.document.styleSheetChangeEventsEnabled = false; + } + + events.off(this.parentActor, "stylesheet-added", this._onNewStyleSheetActor); + events.off(this.parentActor, "window-ready", this._onWindowReady); + + this.parentActor.chromeEventHandler.removeEventListener("StyleSheetAdded", + this._onSheetAdded, true); + + protocol.Actor.prototype.destroy.call(this); + }, + + /** + * Event handler that is called when a the tab actor emits window-ready. + * + * @param {Event} evt + * The triggering event. + */ + _onWindowReady: function (evt) { + this._addStyleSheets(evt.window); + }, + + /** + * Event handler that is called when a the tab actor emits stylesheet-added. + * + * @param {StyleSheetActor} actor + * The new style sheet actor. + */ + _onNewStyleSheetActor: function (actor) { + // Forward it to the client side. + events.emit(this, "stylesheet-added", actor, this._nextStyleSheetIsNew); + this._nextStyleSheetIsNew = false; }, /** @@ -808,23 +865,11 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { * all the style sheets in this document. */ getStyleSheets: Task.async(function* () { - // Iframe document can change during load (bug 1171919). Track their windows - // instead. - let windows = [this.window]; let actors = []; - for (let win of windows) { + for (let win of this.parentActor.windows) { let sheets = yield this._addStyleSheets(win); actors = actors.concat(sheets); - - // Recursively handle style sheets of the documents in iframes. - for (let iframe of win.document.querySelectorAll("iframe, browser, frame")) { - if (iframe.contentDocument && iframe.contentWindow) { - // Sometimes, iframes don't have any document, like the - // one that are over deeply nested (bug 285395) - windows.push(iframe.contentWindow); - } - } } return actors; }), @@ -832,15 +877,13 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { /** * Check if we should be showing this stylesheet. * - * @param {Document} doc - * Document for which we're checking * @param {DOMCSSStyleSheet} sheet * Stylesheet we're interested in * * @return boolean * Whether the stylesheet should be listed. */ - _shouldListSheet: function (doc, sheet) { + _shouldListSheet: function (sheet) { // Special case about:PreferenceStyleSheet, as it is generated on the // fly and the URI is not registered with the about: handler. // https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37 @@ -852,6 +895,22 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { }, /** + * Event handler that is called when a new style sheet is added to + * a document. In particular, StyleSheetApplicableStateChanged is + * listened for, because StyleSheetAdded is sent too early, before + * the rules are ready. + * + * @param {Event} evt + * The triggering event. + */ + _onSheetAdded: function (evt) { + let sheet = evt.stylesheet; + if (this._shouldListSheet(sheet)) { + this.parentActor.createStyleSheetActor(sheet); + } + }, + + /** * Add all the stylesheets for the document in this window to the map and * create an actor for each one if not already created. * @@ -865,24 +924,16 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { { return Task.spawn(function* () { let doc = win.document; - // readyState can be uninitialized if an iframe has just been created but - // it has not started to load yet. - if (doc.readyState === "loading" || doc.readyState === "uninitialized") { - // Wait for the document to load first. - yield listenOnce(win, "DOMContentLoaded", true); - - // Make sure we have the actual document for this window. If the - // readyState was initially uninitialized, the initial dummy document - // was replaced with the actual document (bug 1171919). - doc = win.document; - } + // We have to set this flag in order to get the + // StyleSheetApplicableStateChanged events. See Document.webidl. + doc.styleSheetChangeEventsEnabled = true; let isChrome = Services.scriptSecurityManager.isSystemPrincipal(doc.nodePrincipal); let styleSheets = isChrome ? DOMUtils.getAllStyleSheets(doc) : doc.styleSheets; let actors = []; for (let i = 0; i < styleSheets.length; i++) { let sheet = styleSheets[i]; - if (!this._shouldListSheet(doc, sheet)) { + if (!this._shouldListSheet(sheet)) { continue; } @@ -917,7 +968,7 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) { // Associated styleSheet may be null if it has already been seen due // to duplicate @imports for the same URL. - if (!rule.styleSheet || !this._shouldListSheet(doc, rule.styleSheet)) { + if (!rule.styleSheet || !this._shouldListSheet(rule.styleSheet)) { continue; } let actor = this.parentActor.createStyleSheetActor(rule.styleSheet); @@ -948,6 +999,13 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { * Object with 'styelSheet' property for form on new actor. */ addStyleSheet: function (text) { + // This is a bit convoluted. The style sheet actor may be created + // by a notification from platform. In this case, we can't easily + // pass the "new" flag through to createStyleSheetActor, so we set + // a flag locally and check it before sending an event to the + // client. See |_onNewStyleSheetActor|. + this._nextStyleSheetIsNew = true; + let parent = this.document.documentElement; let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style"); style.setAttribute("type", "text/css"); diff --git a/devtools/server/actors/webbrowser.js b/devtools/server/actors/webbrowser.js index 1808895b1..dffe49b91 100644 --- a/devtools/server/actors/webbrowser.js +++ b/devtools/server/actors/webbrowser.js @@ -2501,7 +2501,11 @@ DebuggerProgressListener.prototype = { if (isWindow && isStop) { // Don't dispatch "navigate" event just yet when there is a redirect to // about:neterror page. - if (request.status != Cr.NS_OK) { + // Navigating to about:neterror will make `status` be something else than NS_OK. + // But for some error like NS_BINDING_ABORTED we don't want to emit any `navigate` + // event as the page load has been cancelled and the related page document is going + // to be a dead wrapper. + if (request.status != Cr.NS_OK && request.status != Cr.NS_BINDING_ABORTED) { // Instead, listen for DOMContentLoaded as about:neterror is loaded // with LOAD_BACKGROUND flags and never dispatches load event. // That may be the same reason why there is no onStateChange event diff --git a/devtools/shared/gcli/commands/cmd.js b/devtools/shared/gcli/commands/cmd.js index 1777ed960..1b3cb2ecb 100644 --- a/devtools/shared/gcli/commands/cmd.js +++ b/devtools/shared/gcli/commands/cmd.js @@ -137,8 +137,6 @@ exports.items = [ return !prefBranch.prefHasUserValue(PREF_DIR); }, exec: function(args, context) { - gcli.load(); - let dirName = prefBranch.getComplexValue(PREF_DIR, Ci.nsISupportsString).data.trim(); return l10n.lookupFormat("cmdStatus3", [ dirName ]); @@ -170,8 +168,6 @@ exports.items = [ supportsString.data = args.directory; prefBranch.setComplexValue(PREF_DIR, Ci.nsISupportsString, supportsString); - gcli.load(); - return l10n.lookupFormat("cmdStatus3", [ args.directory ]); } } diff --git a/devtools/shared/gcli/commands/cookie.js b/devtools/shared/gcli/commands/cookie.js index f1680042f..203ac738c 100644 --- a/devtools/shared/gcli/commands/cookie.js +++ b/devtools/shared/gcli/commands/cookie.js @@ -100,7 +100,7 @@ exports.items = [ let cookies = []; while (enm.hasMoreElements()) { - let cookie = enm.getNext().QueryInterface(Ci.nsICookie); + let cookie = enm.getNext().QueryInterface(Ci.nsICookie2); if (isCookieAtHost(cookie, host)) { cookies.push({ host: cookie.host, @@ -108,9 +108,10 @@ exports.items = [ value: cookie.value, path: cookie.path, expires: cookie.expires, - secure: cookie.secure, - httpOnly: cookie.httpOnly, - sameDomain: cookie.sameDomain + isDomain: cookie.isDomain, + isHttpOnly: cookie.isHttpOnly, + isSecure: cookie.isSecure, + isSession: cookie.isSession, }); } } @@ -169,11 +170,14 @@ exports.items = [ for (let cookie of cookies) { cookie.expires = translateExpires(cookie.expires); - let noAttrs = !cookie.secure && !cookie.httpOnly && !cookie.sameDomain; - cookie.attrs = (cookie.secure ? "secure" : " ") + - (cookie.httpOnly ? "httpOnly" : " ") + - (cookie.sameDomain ? "sameDomain" : " ") + - (noAttrs ? l10n.lookup("cookieListOutNone") : " "); + let noAttrs = !cookie.isDomain && !cookie.isHttpOnly && + !cookie.isSecure && !cookie.isSession; + cookie.attrs = ((cookie.isDomain ? "isDomain " : "") + + (cookie.isHttpOnly ? "isHttpOnly " : "") + + (cookie.isSecure ? "isSecure " : "") + + (cookie.isSession ? "isSession " : "") + + (noAttrs ? l10n.lookup("cookieListOutNone") : "")) + .trim(); } return context.createView({ diff --git a/devtools/shared/gcli/commands/screenshot.js b/devtools/shared/gcli/commands/screenshot.js index e2f38b6d9..11bafcab9 100644 --- a/devtools/shared/gcli/commands/screenshot.js +++ b/devtools/shared/gcli/commands/screenshot.js @@ -277,6 +277,12 @@ function createScreenshotData(document, args) { window.scrollTo(0,0); width = window.innerWidth + window.scrollMaxX - window.scrollMinX; height = window.innerHeight + window.scrollMaxY - window.scrollMinY; + let writingMode = "horizontal-tb"; + if (window.getComputedStyle(document.documentElement)) { + writingMode = window.getComputedStyle(document.documentElement).writingMode; + } + let orientation = writingMode.substring(0, writingMode.indexOf("-")).toLowerCase(); + left = ((orientation != "vertical") ? left : (-width + window.innerWidth)); filename = filename.replace(".png", "-fullpage.png"); } else if (args.selector) { diff --git a/devtools/shared/gcli/source/lib/gcli/commands/help.js b/devtools/shared/gcli/source/lib/gcli/commands/help.js index 7d1cc9087..365c53380 100644 --- a/devtools/shared/gcli/source/lib/gcli/commands/help.js +++ b/devtools/shared/gcli/source/lib/gcli/commands/help.js @@ -275,7 +275,7 @@ exports.items = [ ' <div if="${command.isParent}">\n' + ' <p class="gcli-help-header">${l10n.subCommands}:</p>\n' + ' <ul class="gcli-help-${subcommands}">\n' + - ' <li if="${subcommands.length === 0}">${l10n.subcommandsNone}</li>\n' + + ' <li if="${subcommands.length === 0}">${l10n.subCommandsNone}</li>\n' + ' <li foreach="subcommand in ${subcommands}">\n' + ' ${subcommand.name}: ${subcommand.description}\n' + ' <span class="gcli-out-shortcut" data-command="help ${subcommand.name}"\n' + @@ -321,7 +321,7 @@ exports.items = [ '\n' + '<span if="${command.isParent}"># ${l10n.subCommands}:</span>\n' + '\n' + - '<span if="${subcommands.length === 0}">${l10n.subcommandsNone}</span>\n' + + '<span if="${subcommands.length === 0}">${l10n.subCommandsNone}</span>\n' + '<loop foreach="subcommand in ${subcommands}">* ${subcommand.name}: ${subcommand.description}\n' + '</loop>\n' + '</div>\n', diff --git a/devtools/shared/inspector/css-logic.js b/devtools/shared/inspector/css-logic.js index c8cdd2fdb..901b7a189 100644 --- a/devtools/shared/inspector/css-logic.js +++ b/devtools/shared/inspector/css-logic.js @@ -30,6 +30,8 @@ "use strict"; +const MAX_DATA_URL_LENGTH = 40; + /** * Provide access to the style information in a page. * CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access @@ -103,6 +105,13 @@ exports.shortSource = function (sheet) { return exports.l10n("rule.sourceInline"); } + // If the sheet is a data URL, return a trimmed version of it. + let dataUrl = sheet.href.trim().match(/^data:.*?,((?:.|\r|\n)*)$/); + if (dataUrl) { + return dataUrl[1].length > MAX_DATA_URL_LENGTH ? + `${dataUrl[1].substr(0, MAX_DATA_URL_LENGTH - 1)}…` : dataUrl[1]; + } + // We try, in turn, the filename, filePath, query string, whole thing let url = {}; try { @@ -123,8 +132,7 @@ exports.shortSource = function (sheet) { return url.query; } - let dataUrl = sheet.href.match(/^(data:[^,]*),/); - return dataUrl ? dataUrl[1] : sheet.href; + return sheet.href; }; const TAB_CHARS = "\t"; diff --git a/devtools/shared/locales/en-US/gcli.properties b/devtools/shared/locales/en-US/gcli.properties index e5231e44a..59c344832 100644 --- a/devtools/shared/locales/en-US/gcli.properties +++ b/devtools/shared/locales/en-US/gcli.properties @@ -141,9 +141,10 @@ helpManual=Provide help either on a specific command (if a search string is prov helpSearchDesc=Search string helpSearchManual3=search string to use in narrowing down the displayed commands. Regular expressions not supported. -# LOCALIZATION NOTE: These strings are displayed in the help page for a -# command in the console. +# LOCALIZATION NOTE (helpManSynopsis, helpManDescription): These strings are +# displayed in the help page for a command in the console. helpManSynopsis=Synopsis +helpManDescription=Description # LOCALIZATION NOTE: This message is displayed in the help page if the command # has no parameters. @@ -177,6 +178,10 @@ helpIntro=GCLI is an experiment to create a highly usable command line for web d # sub-commands. subCommands=Sub-Commands +# LOCALIZATION NOTE: Text shown as part of the output of the 'help' command +# when the command in question should have sub-commands but in fact has none. +subCommandsNone=None + # LOCALIZATION NOTE: This error message is displayed when the command line is # cannot find a match for the parse types. commandParseError=Command line parsing error diff --git a/devtools/shared/specs/stylesheets.js b/devtools/shared/specs/stylesheets.js index c89a7c088..fc75281f8 100644 --- a/devtools/shared/specs/stylesheets.js +++ b/devtools/shared/specs/stylesheets.js @@ -105,6 +105,14 @@ exports.styleSheetSpec = styleSheetSpec; const styleSheetsSpec = generateActorSpec({ typeName: "stylesheets", + events: { + "stylesheet-added": { + type: "stylesheetAdded", + sheet: Arg(0, "stylesheet"), + isNew: Arg(1, "boolean") + }, + }, + methods: { getStyleSheets: { request: {}, diff --git a/devtools/shared/webconsole/network-helper.js b/devtools/shared/webconsole/network-helper.js index af6a2e55b..4e25fac26 100644 --- a/devtools/shared/webconsole/network-helper.js +++ b/devtools/shared/webconsole/network-helper.js @@ -63,6 +63,8 @@ const {components, Cc, Ci} = require("chrome"); loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const Services = require("Services"); +const { LocalizationHelper } = require("devtools/shared/l10n"); +const L10N = new LocalizationHelper("devtools/client/locales/netmonitor.properties"); // The cache used in the `nsIURL` function. const gNSURLStore = new Map(); @@ -620,6 +622,31 @@ var NetworkHelper = { // Cipher suite. info.cipherSuite = SSLStatus.cipherName; + // Key exchange group name. + info.keaGroupName = SSLStatus.keaGroupName; + // Localise two special values. + if (info.keaGroupName == "none") { + info.keaGroupName = L10N.getStr("netmonitor.security.keaGroup.none"); + } + if (info.keaGroupName == "custom") { + info.keaGroupName = L10N.getStr("netmonitor.security.keaGroup.custom"); + } + if (info.keaGroupName == "unknown group") { + info.keaGroupName = L10N.getStr("netmonitor.security.keaGroup.unknown"); + } + + // Certificate signature scheme. + info.signatureSchemeName = SSLStatus.signatureSchemeName; + // Localise two special values. + if (info.signatureSchemeName == "none") { + info.signatureSchemeName = + L10N.getStr("netmonitor.security.signatureScheme.none"); + } + if (info.signatureSchemeName == "unknown signature") { + info.signatureSchemeName = + L10N.getStr("netmonitor.security.signatureScheme.unknown"); + } + // Protocol version. info.protocolVersion = this.formatSecurityProtocol(SSLStatus.protocolVersion); diff --git a/devtools/shared/webconsole/network-monitor.js b/devtools/shared/webconsole/network-monitor.js index 084493432..a55162f52 100644 --- a/devtools/shared/webconsole/network-monitor.js +++ b/devtools/shared/webconsole/network-monitor.js @@ -732,7 +732,9 @@ NetworkMonitor.prototype = { 0x804b0004: "STATUS_CONNECTED_TO", 0x804b0005: "STATUS_SENDING_TO", 0x804b000a: "STATUS_WAITING_FOR", - 0x804b0006: "STATUS_RECEIVING_FROM" + 0x804b0006: "STATUS_RECEIVING_FROM", + 0x804b000c: "STATUS_TLS_STARTING", + 0x804b000d: "STATUS_TLS_ENDING" }, httpDownloadActivities: [ @@ -1327,8 +1329,24 @@ NetworkMonitor.prototype = { let response = {}; response.httpVersion = statusLineArray.shift(); - response.remoteAddress = httpActivity.channel.remoteAddress; - response.remotePort = httpActivity.channel.remotePort; + // XXX: + // Sometimes, when using a proxy server (manual proxy configuration), + // throws an error: + // 0x80040111 (NS_ERROR_NOT_AVAILABLE) + // [nsIHttpChannelInternal.remoteAddress] + // Bug 1337791 is the suspect. + response.remoteAddress = null; + try { + response.remoteAddress = httpActivity.channel.remoteAddress; + } catch (e) { + Cu.reportError(e); + } + response.remotePort = null; + try { + response.remotePort = httpActivity.channel.remotePort; + } catch (e) { + Cu.reportError(e); + } response.status = statusLineArray.shift(); response.statusText = statusLineArray.join(" "); response.headersSize = extraStringData.length; @@ -1390,6 +1408,7 @@ NetworkMonitor.prototype = { timings: { blocked: 0, dns: 0, + ssl: 0, connect: 0, send: 0, wait: 0, @@ -1424,6 +1443,36 @@ NetworkMonitor.prototype = { harTimings.connect = -1; } + if (timings.STATUS_TLS_STARTING && timings.STATUS_TLS_ENDING) { + harTimings.ssl = timings.STATUS_TLS_ENDING.last - + timings.STATUS_TLS_STARTING.first; + } else { + harTimings.ssl = -1; + } + + // sometimes the connection information events are attached to a speculative + // channel instead of this one, but necko might glue them back together in the + // nsITimedChannel interface used by Resource and Navigation Timing + let timedChannel = httpActivity.channel.QueryInterface(Ci.nsITimedChannel); + + if ((harTimings.connect <= 0) && timedChannel) { + if (timedChannel.secureConnectionStartTime > timedChannel.connectStartTime) { + harTimings.connect = + timedChannel.secureConnectionStartTime - timedChannel.connectStartTime; + harTimings.ssl = + timedChannel.connectEndTime - timedChannel.secureConnectionStartTime; + } else { + harTimings.connect = + timedChannel.connectEndTime - timedChannel.connectStartTime; + harTimings.ssl = -1; + } + } + + if ((harTimings.dns <= 0) && timedChannel) { + harTimings.dns = + timedChannel.domainLookupEndTime - timedChannel.domainLookupStartTime; + } + if (timings.STATUS_SENDING_TO) { harTimings.send = timings.STATUS_SENDING_TO.last - timings.STATUS_SENDING_TO.first; } else if (timings.REQUEST_HEADER && timings.REQUEST_BODY_SENT) { |