summaryrefslogtreecommitdiffstats
path: root/devtools
diff options
context:
space:
mode:
Diffstat (limited to 'devtools')
-rw-r--r--devtools/bootstrap.js2
-rwxr-xr-x[-rw-r--r--]devtools/client/canvasdebugger/test/browser_profiling-canvas.js2
-rw-r--r--devtools/client/commandline/test/browser_cmd_commands.js2
-rw-r--r--devtools/client/commandline/test/browser_cmd_csscoverage_startstop.js1
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js4
-rw-r--r--devtools/client/devtools-startup.js27
-rw-r--r--devtools/client/framework/browser-menus.js98
-rw-r--r--devtools/client/framework/devtools-browser.js109
-rw-r--r--devtools/client/framework/moz.build5
-rw-r--r--devtools/client/framework/test/browser_keybindings_01.js3
-rw-r--r--devtools/client/framework/toolbox.js2
-rw-r--r--devtools/client/inspector/markup/test/browser_markup_events3.js4
-rw-r--r--devtools/client/inspector/rules/models/rule.js11
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_style-editor-link.js35
-rw-r--r--devtools/client/inspector/test/browser.ini1
-rw-r--r--devtools/client/inspector/test/browser_inspector_infobar_04.js38
-rw-r--r--devtools/client/locales/en-US/inspector.properties2
-rw-r--r--devtools/client/locales/en-US/netmonitor.properties80
-rw-r--r--devtools/client/locales/en-US/storage.dtd3
-rw-r--r--devtools/client/locales/en-US/storage.properties11
-rw-r--r--devtools/client/menus.js6
-rw-r--r--devtools/client/netmonitor/actions/index.js6
-rw-r--r--devtools/client/netmonitor/actions/moz.build4
-rw-r--r--devtools/client/netmonitor/actions/requests.js25
-rw-r--r--devtools/client/netmonitor/actions/sidebar.js49
-rw-r--r--devtools/client/netmonitor/actions/timing-markers.js19
-rw-r--r--devtools/client/netmonitor/actions/ui.js36
-rw-r--r--devtools/client/netmonitor/components/moz.build1
-rw-r--r--devtools/client/netmonitor/components/summary-button.js79
-rw-r--r--devtools/client/netmonitor/components/toggle-button.js32
-rw-r--r--devtools/client/netmonitor/constants.js10
-rw-r--r--devtools/client/netmonitor/moz.build1
-rw-r--r--devtools/client/netmonitor/netmonitor-controller.js5
-rw-r--r--devtools/client/netmonitor/netmonitor-view.js20
-rw-r--r--devtools/client/netmonitor/netmonitor.xul33
-rw-r--r--devtools/client/netmonitor/open-request-in-tab.js40
-rw-r--r--devtools/client/netmonitor/performance-statistics-view.js36
-rw-r--r--devtools/client/netmonitor/reducers/index.js8
-rw-r--r--devtools/client/netmonitor/reducers/moz.build4
-rw-r--r--devtools/client/netmonitor/reducers/requests.js28
-rw-r--r--devtools/client/netmonitor/reducers/sidebar.js43
-rw-r--r--devtools/client/netmonitor/reducers/timing-markers.js52
-rw-r--r--devtools/client/netmonitor/reducers/ui.js40
-rw-r--r--devtools/client/netmonitor/request-list-context-menu.js5
-rw-r--r--devtools/client/netmonitor/request-utils.js2
-rw-r--r--devtools/client/netmonitor/requests-menu-view.js135
-rw-r--r--devtools/client/netmonitor/selectors/index.js92
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-03.js45
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-04.js16
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-05.js10
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-07.js16
-rw-r--r--devtools/client/netmonitor/test/browser_net_curl-utils.js27
-rw-r--r--devtools/client/netmonitor/test/browser_net_footer-summary.js31
-rw-r--r--devtools/client/netmonitor/test/browser_net_security-details.js4
-rw-r--r--devtools/client/netmonitor/test/browser_net_simple-request-data.js2
-rw-r--r--devtools/client/netmonitor/test/html_curl-utils.html17
-rw-r--r--devtools/client/netmonitor/toolbar-view.js13
-rw-r--r--devtools/client/performance/performance-controller.js3
-rw-r--r--devtools/client/performance/test/browser_perf-recording-notices-05.js4
-rw-r--r--devtools/client/scratchpad/scratchpad.js5
-rw-r--r--devtools/client/scratchpad/scratchpad.xul2
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js2
-rw-r--r--devtools/client/scratchpad/test/head.js1
-rw-r--r--devtools/client/shared/curl.js6
-rw-r--r--devtools/client/shared/developer-toolbar.js20
-rw-r--r--devtools/client/shared/moz.build1
-rw-r--r--devtools/client/shared/natural-sort.js106
-rw-r--r--devtools/client/shared/widgets/Chart.jsm25
-rw-r--r--devtools/client/shared/widgets/TableWidget.js133
-rw-r--r--devtools/client/sourceeditor/editor-commands-controller.js97
-rw-r--r--devtools/client/sourceeditor/editor.js10
-rw-r--r--devtools/client/sourceeditor/moz.build1
-rw-r--r--devtools/client/storage/storage.xul10
-rw-r--r--devtools/client/storage/test/browser.ini13
-rw-r--r--devtools/client/storage/test/browser_storage_basic.js64
-rw-r--r--devtools/client/storage/test/browser_storage_basic_with_fragment.js139
-rw-r--r--devtools/client/storage/test/browser_storage_cookies_add.js20
-rw-r--r--devtools/client/storage/test/browser_storage_cookies_delete_all.js110
-rw-r--r--devtools/client/storage/test/browser_storage_cookies_domain.js12
-rw-r--r--devtools/client/storage/test/browser_storage_cookies_domain_port.js29
-rw-r--r--devtools/client/storage/test/browser_storage_cookies_edit.js21
-rw-r--r--devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js5
-rw-r--r--devtools/client/storage/test/browser_storage_cookies_tab_navigation.js3
-rw-r--r--devtools/client/storage/test/browser_storage_delete.js10
-rw-r--r--devtools/client/storage/test/browser_storage_delete_all.js6
-rw-r--r--devtools/client/storage/test/browser_storage_delete_tree.js22
-rw-r--r--devtools/client/storage/test/browser_storage_dom_cache_disabled.js37
-rw-r--r--devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js188
-rw-r--r--devtools/client/storage/test/browser_storage_dynamic_updates_localStorage.js70
-rw-r--r--devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js83
-rw-r--r--devtools/client/storage/test/browser_storage_empty_objectstores.js10
-rw-r--r--devtools/client/storage/test/browser_storage_indexeddb_delete.js6
-rw-r--r--devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js10
-rw-r--r--devtools/client/storage/test/browser_storage_indexeddb_duplicate_names.js31
-rw-r--r--devtools/client/storage/test/browser_storage_localstorage_add.js20
-rw-r--r--devtools/client/storage/test/browser_storage_overflow.js64
-rw-r--r--devtools/client/storage/test/browser_storage_sessionstorage_add.js20
-rw-r--r--devtools/client/storage/test/browser_storage_sidebar.js16
-rw-r--r--devtools/client/storage/test/browser_storage_sidebar_update.js2
-rw-r--r--devtools/client/storage/test/browser_storage_values.js18
-rw-r--r--devtools/client/storage/test/head.js124
-rw-r--r--devtools/client/storage/test/storage-indexeddb-duplicate-names.html50
-rw-r--r--devtools/client/storage/test/storage-listings-with-fragment.html0
-rw-r--r--devtools/client/storage/test/storage-listings.html18
-rw-r--r--devtools/client/storage/test/storage-overflow.html2
-rw-r--r--devtools/client/storage/test/storage-secured-iframe.html5
-rw-r--r--devtools/client/storage/test/storage-unsecured-iframe.html3
-rw-r--r--devtools/client/storage/test/storage-updates.html4
-rw-r--r--devtools/client/storage/ui.js312
-rw-r--r--devtools/client/styleeditor/StyleEditorUI.jsm109
-rw-r--r--devtools/client/styleeditor/StyleSheetEditor.jsm3
-rw-r--r--devtools/client/styleeditor/test/browser.ini1
-rw-r--r--devtools/client/styleeditor/test/browser_styleeditor_add_stylesheet.js37
-rw-r--r--devtools/client/styleeditor/test/browser_styleeditor_import.js2
-rw-r--r--devtools/client/themes/common.css6
-rw-r--r--devtools/client/themes/images/reload.svg5
-rw-r--r--devtools/client/themes/netmonitor.css30
-rw-r--r--devtools/client/themes/storage.css20
-rw-r--r--devtools/client/webconsole/hudservice.js58
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/head.js1
-rw-r--r--devtools/client/webconsole/panel.js2
-rw-r--r--devtools/client/webconsole/test/browser.ini1
-rw-r--r--devtools/client/webconsole/test/browser_console.js19
-rw-r--r--devtools/client/webconsole/test/browser_console_restore.js30
-rw-r--r--devtools/client/webconsole/test/browser_webconsole_closure_inspection.js2
-rw-r--r--devtools/client/webconsole/test/browser_webconsole_jsterm.js1
-rw-r--r--devtools/client/webconsole/test/browser_webconsole_output_02.js4
-rw-r--r--devtools/client/webconsole/test/browser_webconsole_strict_mode_errors.js2
-rw-r--r--devtools/client/webconsole/test/head.js17
-rw-r--r--devtools/moz.build8
-rw-r--r--devtools/server/actors/errordocs.js1
-rw-r--r--devtools/server/actors/highlighters/box-model.js15
-rw-r--r--devtools/server/actors/object.js5
-rw-r--r--devtools/server/actors/storage.js548
-rw-r--r--devtools/server/actors/stylesheets.js120
-rw-r--r--devtools/server/actors/webbrowser.js6
-rw-r--r--devtools/server/event-parsers.js76
-rw-r--r--devtools/server/tests/browser/browser.ini2
-rw-r--r--devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js105
-rw-r--r--devtools/server/tests/browser/browser_storage_dynamic_windows.js61
-rw-r--r--devtools/server/tests/browser/browser_storage_listings.js84
-rw-r--r--devtools/server/tests/browser/browser_storage_updates.js34
-rw-r--r--devtools/server/tests/browser/head.js36
-rw-r--r--devtools/server/tests/browser/storage-cookies-same-name.html28
-rwxr-xr-x[-rw-r--r--]devtools/server/tests/mochitest/test_memory_allocations_05.html5
-rw-r--r--devtools/server/tests/unit/test_functiongrips-01.js2
-rw-r--r--devtools/shared/gcli/commands/cmd.js4
-rw-r--r--devtools/shared/gcli/commands/cookie.js22
-rw-r--r--devtools/shared/gcli/commands/screenshot.js6
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/help.js4
-rw-r--r--devtools/shared/inspector/css-logic.js12
-rw-r--r--devtools/shared/locales/en-US/gcli.properties9
-rw-r--r--devtools/shared/qrcode/moz.build2
-rw-r--r--devtools/shared/specs/storage.js28
-rw-r--r--devtools/shared/specs/stylesheets.js8
-rw-r--r--devtools/shared/webconsole/network-helper.js27
-rw-r--r--devtools/shared/webconsole/network-monitor.js55
-rw-r--r--devtools/shared/webconsole/test/test_page_errors.html10
158 files changed, 4029 insertions, 1049 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/canvasdebugger/test/browser_profiling-canvas.js b/devtools/client/canvasdebugger/test/browser_profiling-canvas.js
index ede8a4dbf..75f8da4f3 100644..100755
--- a/devtools/client/canvasdebugger/test/browser_profiling-canvas.js
+++ b/devtools/client/canvasdebugger/test/browser_profiling-canvas.js
@@ -37,7 +37,7 @@ function* ifTestingSupported() {
for (let i = 0; i < functionCalls.length - 1; i += 2) {
ok(functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0.");
ok(functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time.");
- ok(functionCalls[i + 1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct.");
+ ok(functionCalls[i + 1].timestamp >= functionCalls[i].timestamp, "The timestamp of the called function is correct.");
}
yield removeTab(target.tab);
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/commandline/test/browser_cmd_csscoverage_startstop.js b/devtools/client/commandline/test/browser_cmd_csscoverage_startstop.js
index 2bdb86d86..c48136989 100644
--- a/devtools/client/commandline/test/browser_cmd_csscoverage_startstop.js
+++ b/devtools/client/commandline/test/browser_cmd_csscoverage_startstop.js
@@ -4,6 +4,7 @@
// Tests that the addon commands works as they should
const csscoverage = require("devtools/shared/fronts/csscoverage");
+const {gDevTools} = require("devtools/client/framework/devtools");
const PAGE_1 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page1.html";
const PAGE_2 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page2.html";
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js b/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js
index 739d3b2a7..b2fa66872 100644
--- a/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js
@@ -53,13 +53,13 @@ function test() {
.getAttribute("value"), "getName",
"Should have the right property name for 'getName' in person.");
is(personNode.get("getName").target.querySelector(".value")
- .getAttribute("value"), "_pfactory/<.getName()",
+ .getAttribute("value"), "getName()",
"'getName' in person should have the right value.");
is(personNode.get("getFoo").target.querySelector(".name")
.getAttribute("value"), "getFoo",
"Should have the right property name for 'getFoo' in person.");
is(personNode.get("getFoo").target.querySelector(".value")
- .getAttribute("value"), "_pfactory/<.getFoo()",
+ .getAttribute("value"), "getFoo()",
"'getFoo' in person should have the right value.");
// Expand the function nodes. This causes their properties to be
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/browser-menus.js b/devtools/client/framework/browser-menus.js
index 3d6c4def6..e62afddac 100644
--- a/devtools/client/framework/browser-menus.js
+++ b/devtools/client/framework/browser-menus.js
@@ -133,10 +133,11 @@ function attachKeybindingsToBrowser(doc, keys) {
*/
function createToolMenuElements(toolDefinition, doc) {
let id = toolDefinition.id;
+ let appmenuId = "appmenuitem_" + id;
let menuId = "menuitem_" + id;
// Prevent multiple entries for the same tool.
- if (doc.getElementById(menuId)) {
+ if (doc.getElementById(appmenuId) || doc.getElementById(menuId)) {
return;
}
@@ -156,6 +157,13 @@ function createToolMenuElements(toolDefinition, doc) {
});
}
+ let appmenuitem = createMenuItem({
+ doc,
+ id: "appmenuitem_" + id,
+ label: toolDefinition.menuLabel || toolDefinition.label,
+ accesskey: null
+ });
+
let menuitem = createMenuItem({
doc,
id: "menuitem_" + id,
@@ -166,10 +174,12 @@ function createToolMenuElements(toolDefinition, doc) {
// Refer to the key in order to display the key shortcut at menu ends
menuitem.setAttribute("key", key.id);
}
+ appmenuitem.addEventListener("command", oncommand);
menuitem.addEventListener("command", oncommand);
return {
key,
+ appmenuitem,
menuitem
};
}
@@ -186,22 +196,34 @@ function createToolMenuElements(toolDefinition, doc) {
* The tool definition after which the tool menu item is to be added.
*/
function insertToolMenuElements(doc, toolDefinition, prevDef) {
- let { key, menuitem } = createToolMenuElements(toolDefinition, doc);
+ let { key, appmenuitem, menuitem } = createToolMenuElements(toolDefinition, doc);
if (key) {
attachKeybindingsToBrowser(doc, key);
}
- let ref;
+ let amp;
+ if (prevDef) {
+ let appmenuitem = doc.getElementById("appmenuitem_" + prevDef.id);
+ amp = appmenuitem && appmenuitem.nextSibling ? appmenuitem.nextSibling : null;
+ } else {
+ amp = doc.getElementById("appmenu_devtools_separator");
+ }
+
+ if (amp) {
+ amp.parentNode.insertBefore(appmenuitem, amp);
+ }
+
+ let mp;
if (prevDef) {
let menuitem = doc.getElementById("menuitem_" + prevDef.id);
- ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
+ mp = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
} else {
- ref = doc.getElementById("menu_devtools_separator");
+ mp = doc.getElementById("menu_devtools_separator");
}
- if (ref) {
- ref.parentNode.insertBefore(menuitem, ref);
+ if (mp) {
+ mp.parentNode.insertBefore(menuitem, mp);
}
}
exports.insertToolMenuElements = insertToolMenuElements;
@@ -220,6 +242,11 @@ function removeToolFromMenu(toolId, doc) {
key.remove();
}
+ let appmenuitem = doc.getElementById("appmenuitem_" + toolId);
+ if (appmenuitem) {
+ appmenuitem.remove();
+ }
+
let menuitem = doc.getElementById("menuitem_" + toolId);
if (menuitem) {
menuitem.remove();
@@ -235,6 +262,7 @@ exports.removeToolFromMenu = removeToolFromMenu;
*/
function addAllToolsToMenu(doc) {
let fragKeys = doc.createDocumentFragment();
+ let fragAppMenuItems = doc.createDocumentFragment();
let fragMenuItems = doc.createDocumentFragment();
for (let toolDefinition of gDevTools.getToolDefinitionArray()) {
@@ -251,11 +279,17 @@ function addAllToolsToMenu(doc) {
if (elements.key) {
fragKeys.appendChild(elements.key);
}
+ fragAppMenuItems.appendChild(elements.appmenuitem);
fragMenuItems.appendChild(elements.menuitem);
}
attachKeybindingsToBrowser(doc, fragKeys);
+ let amps = doc.getElementById("appmenu_devtools_separator");
+ if (amps) {
+ amps.parentNode.insertBefore(fragAppMenuItems, amps);
+ }
+
let mps = doc.getElementById("menu_devtools_separator");
if (mps) {
mps.parentNode.insertBefore(fragMenuItems, mps);
@@ -270,18 +304,29 @@ function addAllToolsToMenu(doc) {
*/
function addTopLevelItems(doc) {
let keys = doc.createDocumentFragment();
+ let appmenuItems = doc.createDocumentFragment();
let menuItems = doc.createDocumentFragment();
let { menuitems } = require("../menus");
for (let item of menuitems) {
if (item.separator) {
+ let appseparator = doc.createElement("menuseparator");
+ appseparator.id = "app" + item.id;
let separator = doc.createElement("menuseparator");
separator.id = item.id;
+ appmenuItems.appendChild(appseparator);
menuItems.appendChild(separator);
} else {
let { id, l10nKey } = item;
// Create a <menuitem>
+ let appmenuitem = createMenuItem({
+ doc,
+ id: "app" + id,
+ label: l10n(l10nKey + ".label"),
+ accesskey: null,
+ isCheckbox: item.checkbox
+ });
let menuitem = createMenuItem({
doc,
id,
@@ -289,7 +334,9 @@ function addTopLevelItems(doc) {
accesskey: l10n(l10nKey + ".accesskey"),
isCheckbox: item.checkbox
});
+ appmenuitem.addEventListener("command", item.oncommand);
menuitem.addEventListener("command", item.oncommand);
+ appmenuItems.appendChild(appmenuitem);
menuItems.appendChild(menuitem);
if (item.key && l10nKey) {
@@ -330,6 +377,9 @@ function addTopLevelItems(doc) {
for (let node of keys.children) {
nodes.push(node);
}
+ for (let node of appmenuItems.children) {
+ nodes.push(node);
+ }
for (let node of menuItems.children) {
nodes.push(node);
}
@@ -337,15 +387,33 @@ function addTopLevelItems(doc) {
attachKeybindingsToBrowser(doc, keys);
+ // There are hardcoded menu items in the Web Developer menus plus it is a
+ // location of menu items via overlays from extensions so we want to make
+ // sure the last seperator and the "Get More Tools..." items are last.
+ // This will emulate the behavior when devtools menu items were actually
+ // physically present in browser.xul
+
+ // Tools > Web Developer
let menu = doc.getElementById("menuWebDeveloperPopup");
- menu.appendChild(menuItems);
-
- // There is still "Page Source" menuitem hardcoded into browser.xul. Instead
- // of manually inserting everything around it, move it to the expected
- // position.
- let pageSource = doc.getElementById("menu_pageSource");
- let endSeparator = doc.getElementById("devToolsEndSeparator");
- menu.insertBefore(pageSource, endSeparator);
+ // Insert the Devtools Menu Items before everything else
+ menu.insertBefore(menuItems, menu.firstChild);
+ // Move the devtools last seperator and Get More Tools menu items to the bottom
+ let menu_endSeparator = doc.getElementById("menu_devToolsEndSeparator");
+ let menu_getMoreDevtools = doc.getElementById("menu_getMoreDevtools");
+ menu.insertBefore(menu_getMoreDevtools, null);
+ menu.insertBefore(menu_endSeparator, menu_getMoreDevtools);
+
+ // Application Menu > Web Developer (If existant)
+ let appmenu = doc.getElementById("appmenu_webDeveloper_popup");
+ if (appmenu) {
+ // Insert the Devtools Menu Items after the hardcoded idless seperator
+ appmenu.insertBefore(appmenuItems, appmenu.childNodes[2].nextSibling);
+ // Move the devtools last seperator and Get More Tools menu items to the bottom
+ let appmenu_endSeparator = doc.getElementById("appmenu_devToolsEndSeparator");
+ let appmenu_getMoreDevtools = doc.getElementById("appmenu_getMoreDevtools");
+ appmenu.insertBefore(appmenu_getMoreDevtools, null);
+ appmenu.insertBefore(appmenu_endSeparator, appmenu_getMoreDevtools);
+ }
}
/**
diff --git a/devtools/client/framework/devtools-browser.js b/devtools/client/framework/devtools-browser.js
index b9f4d92ba..f032f82aa 100644
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -8,7 +8,7 @@
* This is the main module loaded in Firefox desktop that handles browser
* windows and coordinates devtools around each window.
*
- * This module is loaded lazily by devtools-clhandler.js, once the first
+ * This module is loaded lazily by devtools-startup.js, once the first
* browser window is ready (i.e. fired browser-delayed-startup-finished event)
**/
@@ -27,8 +27,10 @@ loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
loader.lazyRequireGetter(this, "BrowserMenus", "devtools/client/framework/browser-menus");
-loader.lazyImporter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
loader.lazyImporter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
+#ifdef MC_BASILISK
+loader.lazyImporter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
+#endif
const {LocalizationHelper} = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
@@ -85,6 +87,9 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
function toggleMenuItem(id, isEnabled) {
let cmd = doc.getElementById(id);
+ if (!cmd) {
+ return;
+ }
if (isEnabled) {
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
@@ -94,22 +99,39 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
}
}
+ let idEls = [];
+
// Enable developer toolbar?
let devToolbarEnabled = Services.prefs.getBoolPref("devtools.toolbar.enabled");
- toggleMenuItem("menu_devToolbar", devToolbarEnabled);
- let focusEl = doc.getElementById("menu_devToolbar");
- if (devToolbarEnabled) {
- focusEl.removeAttribute("disabled");
- } else {
- focusEl.setAttribute("disabled", "true");
- }
+ idEls = [
+ "appmenu_devToolbar",
+ "menu_devToolbar"
+ ];
+ idEls.forEach(function (idEl) {
+ toggleMenuItem(idEl, devToolbarEnabled);
+ let focusEl = doc.getElementById(idEl);
+ if (!focusEl) {
+ return;
+ }
+ if (devToolbarEnabled) {
+ focusEl.removeAttribute("disabled");
+ } else {
+ focusEl.setAttribute("disabled", "true");
+ }
+ });
if (devToolbarEnabled && Services.prefs.getBoolPref("devtools.toolbar.visible")) {
win.DeveloperToolbar.show(false).catch(console.error);
}
// Enable WebIDE?
let webIDEEnabled = Services.prefs.getBoolPref("devtools.webide.enabled");
- toggleMenuItem("menu_webide", webIDEEnabled);
+ idEls = [
+ "appmenu_webide",
+ "menu_webide"
+ ];
+ idEls.forEach(function (idEl) {
+ toggleMenuItem(idEl, webIDEEnabled);
+ });
let showWebIDEWidget = Services.prefs.getBoolPref("devtools.webide.widget.enabled");
if (webIDEEnabled && showWebIDEWidget) {
@@ -122,11 +144,29 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled");
let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
let remoteEnabled = chromeEnabled && devtoolsRemoteEnabled;
- toggleMenuItem("menu_browserToolbox", remoteEnabled);
- toggleMenuItem("menu_browserContentToolbox", remoteEnabled && win.gMultiProcessBrowser);
+ idEls = [
+ "appmenu_browserToolbox",
+ "menu_browserToolbox"
+ ];
+ idEls.forEach(function (idEl) {
+ toggleMenuItem(idEl, remoteEnabled);
+ });
+ idEls = [
+ "appmenu_browserContentToolbox",
+ "menu_browserContentToolbox"
+ ];
+ idEls.forEach(function (idEl) {
+ toggleMenuItem(idEl, remoteEnabled && win.gMultiProcessBrowser);
+ });
// Enable DevTools connection screen, if the preference allows this.
- toggleMenuItem("menu_devtools_connect", devtoolsRemoteEnabled);
+ idEls = [
+ "appmenu_devtools_connect",
+ "menu_devtools_connect"
+ ];
+ idEls.forEach(function (idEl) {
+ toggleMenuItem(idEl, devtoolsRemoteEnabled);
+ });
},
observe: function (subject, topic, prefName) {
@@ -295,6 +335,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
* Install Developer widget
*/
installDeveloperWidget: function () {
+#ifdef MC_BASILISK
let id = "developer-button";
let widget = CustomizableUI.getWidget(id);
if (widget && widget.provider == CustomizableUI.PROVIDER_API) {
@@ -343,6 +384,9 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
doc.getElementById("PanelUI-multiView").appendChild(view);
}
});
+#else
+ return;
+#endif
},
/**
@@ -350,6 +394,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
*/
// Used by itself
installWebIDEWidget: function () {
+#ifdef MC_BASILISK
if (this.isWebIDEWidgetInstalled()) {
return;
}
@@ -371,11 +416,18 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
gDevToolsBrowser.openWebIDE();
}
});
+#else
+ return;
+#endif
},
isWebIDEWidgetInstalled: function () {
+#ifdef MC_BASILISK
let widgetWrapper = CustomizableUI.getWidget("webide-button");
return !!(widgetWrapper && widgetWrapper.provider == CustomizableUI.PROVIDER_API);
+#else
+ return false;
+#endif
},
/**
@@ -387,10 +439,14 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
* Uninstall WebIDE widget
*/
uninstallWebIDEWidget: function () {
+#ifdef MC_BASILISK
if (this.isWebIDEWidgetInstalled()) {
CustomizableUI.removeWidgetFromArea("webide-button");
}
CustomizableUI.destroyWidget("webide-button");
+#else
+ return;
+#endif
},
/**
@@ -398,7 +454,11 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
*/
// Used by webide.js
moveWebIDEWidgetInNavbar: function () {
+#ifdef MC_BASILISK
CustomizableUI.addWidgetToArea("webide-button", CustomizableUI.AREA_NAVBAR);
+#else
+ return;
+#endif
},
/**
@@ -591,12 +651,23 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
let hasToolbox = gDevToolsBrowser.hasToolboxOpened(win);
- let menu = win.document.getElementById("menu_devToolbox");
- if (hasToolbox) {
- menu.setAttribute("checked", "true");
- } else {
- menu.removeAttribute("checked");
- }
+ let idEls = [];
+
+ idEls = [
+ "appmenu_devToolbox",
+ "menu_devToolbox"
+ ];
+ idEls.forEach(function (idEl) {
+ let menu = win.document.getElementById(idEl);
+ if (!menu) {
+ return;
+ }
+ if (hasToolbox) {
+ menu.setAttribute("checked", "true");
+ } else {
+ menu.removeAttribute("checked");
+ }
+ });
}
},
diff --git a/devtools/client/framework/moz.build b/devtools/client/framework/moz.build
index 7b28b4b9e..407e21f8b 100644
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -13,7 +13,6 @@ DevToolsModules(
'about-devtools-toolbox.js',
'attach-thread.js',
'browser-menus.js',
- 'devtools-browser.js',
'devtools.js',
'gDevTools.jsm',
'location-store.js',
@@ -31,3 +30,7 @@ DevToolsModules(
'toolbox.js',
'ToolboxProcess.jsm',
)
+
+FINAL_TARGET_PP_FILES.chrome.devtools.modules.devtools.client.framework += [
+ 'devtools-browser.js',
+]
diff --git a/devtools/client/framework/test/browser_keybindings_01.js b/devtools/client/framework/test/browser_keybindings_01.js
index 4e4effb07..134fb127c 100644
--- a/devtools/client/framework/test/browser_keybindings_01.js
+++ b/devtools/client/framework/test/browser_keybindings_01.js
@@ -8,6 +8,9 @@
const TEST_URL = "data:text/html,<html><head><title>Test for the " +
"highlighter keybindings</title></head><body>" +
"<h1>Keybindings!</h1></body></html>"
+
+const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
+
function test()
{
waitForExplicitFinish();
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/markup/test/browser_markup_events3.js b/devtools/client/inspector/markup/test/browser_markup_events3.js
index a9dc2a499..a38e9a011 100644
--- a/devtools/client/inspector/markup/test/browser_markup_events3.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events3.js
@@ -115,7 +115,7 @@ const TEST_DATA = [ // eslint-disable-line
expected: [
{
type: "click",
- filename: TEST_URL + ":1",
+ filename: TEST_URL + ":0",
attributes: [
"Bubbling",
"DOM2"
@@ -129,7 +129,7 @@ const TEST_DATA = [ // eslint-disable-line
expected: [
{
type: "click",
- filename: TEST_URL + ":1",
+ filename: TEST_URL + ":0",
attributes: [
"Bubbling",
"DOM2"
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/inspector.properties b/devtools/client/locales/en-US/inspector.properties
index b6f3e072b..252d72bfa 100644
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -66,7 +66,7 @@ inspector.collapsePane=Collapse pane
# inspector UI.
inspector.expandPane=Expand pane
-# LOCALIZATION NOTE (inspector.searchResultsCount): This is the label that
+# LOCALIZATION NOTE (inspector.searchResultsCount2): This is the label that
# will show up next to the inspector search box. %1$S is the current result
# index and %2$S is the total number of search results. For example: "3 of 9".
# This won't be visible until the search box is updated in Bug 835896.
diff --git a/devtools/client/locales/en-US/netmonitor.properties b/devtools/client/locales/en-US/netmonitor.properties
index e6118ca9f..4926c234b 100644
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -143,12 +143,17 @@ 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.timeS): This is the label displayed
+# in the network table footer providing concise information about all requests.
+# Events DOMContentLoaded and load - specifying the number of seconds.
+networkMenu.timeS=%S s
# LOCALIZATION NOTE (networkMenu.sizeB): This is the label displayed
# in the network menu specifying the size of a request (in bytes).
@@ -221,10 +226,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 +250,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 +264,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 +586,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 +627,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/locales/en-US/storage.dtd b/devtools/client/locales/en-US/storage.dtd
index 211c79436..adfc03183 100644
--- a/devtools/client/locales/en-US/storage.dtd
+++ b/devtools/client/locales/en-US/storage.dtd
@@ -9,3 +9,6 @@
<!-- LOCALIZATION NOTE : Label of popup menu action to delete all storage items. -->
<!ENTITY storage.popupMenu.deleteAllLabel "Delete All">
+
+<!-- LOCALIZATION NOTE : Label of popup menu action to delete all session cookies. -->
+<!ENTITY storage.popupMenu.deleteAllSessionCookiesLabel "Delete All Session Cookies">
diff --git a/devtools/client/locales/en-US/storage.properties b/devtools/client/locales/en-US/storage.properties
index 1eeb88ff9..4719520bd 100644
--- a/devtools/client/locales/en-US/storage.properties
+++ b/devtools/client/locales/en-US/storage.properties
@@ -35,6 +35,7 @@ tree.labels.Cache=Cache Storage
# LOCALIZATION NOTE (table.headers.*.*):
# These strings are the header names of the columns in the Storage Table for
# each type of storage available through the Storage Tree to the side.
+table.headers.cookies.uniqueKey=Unique key
table.headers.cookies.name=Name
table.headers.cookies.path=Path
table.headers.cookies.host=Domain
@@ -52,14 +53,16 @@ table.headers.sessionStorage.value=Value
table.headers.Cache.url=URL
table.headers.Cache.status=Status
+table.headers.indexedDB.uniqueKey=Unique key
table.headers.indexedDB.name=Key
table.headers.indexedDB.db=Database Name
+table.headers.indexedDB.storage=Storage
table.headers.indexedDB.objectStore=Object Store Name
table.headers.indexedDB.value=Value
table.headers.indexedDB.origin=Origin
table.headers.indexedDB.version=Version
table.headers.indexedDB.objectStores=Object Stores
-table.headers.indexedDB.keyPath=Key
+table.headers.indexedDB.keyPath2=Key Path
table.headers.indexedDB.autoIncrement=Auto Increment
table.headers.indexedDB.indexes=Indexes
@@ -84,7 +87,11 @@ storage.parsedValue.label=Parsed Value
# Label of popup menu action to delete storage item.
storage.popupMenu.deleteLabel=Delete “%S”
-# LOCALIZATION NOTE (storage.popupMenu.deleteAllLabel):
+# LOCALIZATION NOTE (storage.popupMenu.addItemLabel):
+# Label of popup menu action to add an item.
+storage.popupMenu.addItemLabel=Add Item
+
+# LOCALIZATION NOTE (storage.popupMenu.deleteAllFromLabel):
# Label of popup menu action to delete all storage items.
storage.popupMenu.deleteAllFromLabel=Delete All From “%S”
diff --git a/devtools/client/menus.js b/devtools/client/menus.js
index 1aee85095..1d2168967 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: {
@@ -183,9 +183,9 @@ exports.menuitems = [
}
},
{ separator: true,
- id: "devToolsEndSeparator"
+ id: "menu_devToolsEndSeparator"
},
- { id: "getMoreDevtools",
+ { id: "menu_getMoreDevtools",
l10nKey: "getMoreDevtoolsCmd",
oncommand(event) {
let window = event.target.ownerDocument.defaultView;
diff --git a/devtools/client/netmonitor/actions/index.js b/devtools/client/netmonitor/actions/index.js
index 3f7b0bd2f..de8f4ae1d 100644
--- a/devtools/client/netmonitor/actions/index.js
+++ b/devtools/client/netmonitor/actions/index.js
@@ -4,6 +4,8 @@
"use strict";
const filters = require("./filters");
-const sidebar = require("./sidebar");
+const requests = require("./requests");
+const timingMarkers = require("./timing-markers");
+const ui = require("./ui");
-module.exports = Object.assign({}, filters, sidebar);
+module.exports = Object.assign({}, filters, requests, timingMarkers, ui);
diff --git a/devtools/client/netmonitor/actions/moz.build b/devtools/client/netmonitor/actions/moz.build
index 477cafb41..ce904cae8 100644
--- a/devtools/client/netmonitor/actions/moz.build
+++ b/devtools/client/netmonitor/actions/moz.build
@@ -6,5 +6,7 @@
DevToolsModules(
'filters.js',
'index.js',
- 'sidebar.js',
+ 'requests.js',
+ 'timing-markers.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/timing-markers.js b/devtools/client/netmonitor/actions/timing-markers.js
new file mode 100644
index 000000000..4f1363a70
--- /dev/null
+++ b/devtools/client/netmonitor/actions/timing-markers.js
@@ -0,0 +1,19 @@
+/* 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 { ADD_TIMING_MARKER, CLEAR_TIMING_MARKERS } = require("../constants");
+
+exports.addTimingMarker = (marker) => {
+ return {
+ type: ADD_TIMING_MARKER,
+ marker
+ };
+};
+
+exports.clearTimingMarkers = () => {
+ return {
+ type: CLEAR_TIMING_MARKERS
+ };
+};
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..00595e5e6
--- /dev/null
+++ b/devtools/client/netmonitor/components/summary-button.js
@@ -0,0 +1,79 @@
+/* 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,
+ getDisplayedTimingMarker
+} = require("../selectors/index");
+
+const { button, span } = DOM;
+
+function SummaryButton({
+ summary,
+ triggerSummary,
+ timingMarkers
+}) {
+ let { count, contentSize, transferredSize, millis } = summary;
+ let {
+ DOMContentLoaded,
+ load,
+ } = timingMarkers;
+ 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))
+ + ((DOMContentLoaded > -1)
+ ? ", " + "DOMContentLoaded: " + L10N.getFormatStrWithNumbers("networkMenu.timeS", L10N.numberWithDecimals(DOMContentLoaded / 1000, REQUEST_TIME_DECIMALS))
+ : "")
+ + ((load > -1)
+ ? ", " + "load: " + L10N.getFormatStrWithNumbers("networkMenu.timeS", L10N.numberWithDecimals(load / 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,
+ timingMarkers: PropTypes.object.isRequired,
+};
+
+module.exports = connect(
+ (state) => ({
+ summary: getDisplayedRequestsSummary(state),
+ timingMarkers: {
+ DOMContentLoaded:
+ getDisplayedTimingMarker(state, "firstDocumentDOMContentLoadedTimestamp"),
+ load: getDisplayedTimingMarker(state, "firstDocumentLoadTimestamp"),
+ },
+ }),
+ (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..1605496a5 100644
--- a/devtools/client/netmonitor/constants.js
+++ b/devtools/client/netmonitor/constants.js
@@ -5,15 +5,19 @@
const general = {
FREETEXT_FILTER_SEARCH_DELAY: 200,
+ CONTENT_SIZE_DECIMALS: 2,
+ REQUEST_TIME_DECIMALS: 2,
};
const actionTypes = {
+ ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
+ CLEAR_TIMING_MARKERS: "CLEAR_TIMING_MARKERS",
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-controller.js b/devtools/client/netmonitor/netmonitor-controller.js
index 739e174fb..39bee8570 100644
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -407,6 +407,9 @@ TargetEventsHandler.prototype = {
if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
NetMonitorView.RequestsMenu.reset();
NetMonitorView.Sidebar.toggle(false);
+ } else {
+ // If the log is persistent, just clear some informations.
+ NetMonitorView.RequestsMenu.resetNotPersistent();
}
// Switch to the default network traffic inspector view.
if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) {
@@ -414,6 +417,7 @@ TargetEventsHandler.prototype = {
}
// Clear any accumulated markers.
NetMonitorController.NetworkEventsHandler.clearMarkers();
+ gStore.dispatch(Actions.clearTimingMarkers());
window.emit(EVENTS.TARGET_WILL_NAVIGATE);
break;
@@ -534,6 +538,7 @@ NetworkEventsHandler.prototype = {
_onDocLoadingMarker: function (marker) {
window.emit(EVENTS.TIMELINE_EVENT, marker);
this._markers.push(marker);
+ gStore.dispatch(Actions.addTimingMarker(marker));
},
/**
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..f003c7805 100644
--- a/devtools/client/netmonitor/reducers/index.js
+++ b/devtools/client/netmonitor/reducers/index.js
@@ -5,9 +5,13 @@
const { combineReducers } = require("devtools/client/shared/vendor/redux");
const filters = require("./filters");
-const sidebar = require("./sidebar");
+const requests = require("./requests");
+const timingMarkers = require("./timing-markers");
+const ui = require("./ui");
module.exports = combineReducers({
filters,
- sidebar,
+ requests,
+ timingMarkers,
+ ui,
});
diff --git a/devtools/client/netmonitor/reducers/moz.build b/devtools/client/netmonitor/reducers/moz.build
index 477cafb41..ce904cae8 100644
--- a/devtools/client/netmonitor/reducers/moz.build
+++ b/devtools/client/netmonitor/reducers/moz.build
@@ -6,5 +6,7 @@
DevToolsModules(
'filters.js',
'index.js',
- 'sidebar.js',
+ 'requests.js',
+ 'timing-markers.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/timing-markers.js b/devtools/client/netmonitor/reducers/timing-markers.js
new file mode 100644
index 000000000..cb500b2c4
--- /dev/null
+++ b/devtools/client/netmonitor/reducers/timing-markers.js
@@ -0,0 +1,52 @@
+/* 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 { ADD_TIMING_MARKER,
+ CLEAR_TIMING_MARKERS } = require("../constants");
+
+const TimingMarkers = I.Record({
+ firstDocumentDOMContentLoadedTimestamp: -1,
+ firstDocumentLoadTimestamp: -1,
+});
+
+function addTimingMarker(state, action) {
+ if (action.marker.name == "document::DOMContentLoaded" &&
+ state.firstDocumentDOMContentLoadedTimestamp == -1) {
+ return state.set("firstDocumentDOMContentLoadedTimestamp",
+ action.marker.unixTime / 1000);
+ }
+
+ if (action.marker.name == "document::Load" &&
+ state.firstDocumentLoadTimestamp == -1) {
+ return state.set("firstDocumentLoadTimestamp",
+ action.marker.unixTime / 1000);
+ }
+
+ return state;
+}
+
+function clearTimingMarkers(state) {
+ return state.withMutations(st => {
+ st.remove("firstDocumentDOMContentLoadedTimestamp");
+ st.remove("firstDocumentLoadTimestamp");
+ });
+}
+
+function timingMarkers(state = new TimingMarkers(), action) {
+ switch (action.type) {
+ case ADD_TIMING_MARKER:
+ return addTimingMarker(state, action);
+
+ case CLEAR_TIMING_MARKERS:
+ return clearTimingMarkers(state);
+
+ default:
+ return state;
+ }
+}
+
+module.exports = timingMarkers;
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..bacb7dc33 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");
@@ -185,9 +186,7 @@ RequestListContextMenu.prototype = {
* Opens selected item in a new tab.
*/
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..0c854d264 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);
@@ -283,6 +276,14 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
this._updateQueue = [];
this._firstRequestStartedMillis = -1;
this._lastRequestEndedMillis = -1;
+ this.resetNotPersistent();
+ },
+
+ /**
+ * Reset informations that "devtools.webconsole.persistlog == true".
+ */
+ resetNotPersistent: function () {
+ this._firstRequestStartedMillisNotPersistent = -1;
},
/**
@@ -406,9 +407,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 +434,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
*/
reFilterRequests: function () {
this.filterContents(this._filterPredicate);
- this.refreshSummary();
+ this.updateRequests();
this.refreshZebra();
},
@@ -541,7 +553,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
break;
}
- this.refreshSummary();
+ this.updateRequests();
this.refreshZebra();
},
@@ -552,41 +564,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));
},
/**
@@ -670,6 +658,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
// Append a network request item to this container.
let requestItem = this.push([menuView, id], {
attachment: {
+ firstRequestStartedMillisNotPersistent: this._firstRequestStartedMillisNotPersistent,
startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
startedMillis: unixTime,
method: method,
@@ -865,7 +854,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 +863,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 +1119,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);
@@ -1543,6 +1531,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
if (this._firstRequestStartedMillis == -1) {
this._firstRequestStartedMillis = unixTime;
}
+ if (this._firstRequestStartedMillisNotPersistent == -1) {
+ this._firstRequestStartedMillisNotPersistent = unixTime;
+ }
},
/**
@@ -1559,59 +1550,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
*/
@@ -1637,6 +1575,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
_ctx: null,
_cachedWaterfallWidth: 0,
_firstRequestStartedMillis: -1,
+ _firstRequestStartedMillisNotPersistent: -1,
_lastRequestEndedMillis: -1,
_updateQueue: [],
_addQueue: [],
diff --git a/devtools/client/netmonitor/selectors/index.js b/devtools/client/netmonitor/selectors/index.js
index f473149b5..ba5c19094 100644
--- a/devtools/client/netmonitor/selectors/index.js
+++ b/devtools/client/netmonitor/selectors/index.js
@@ -1,8 +1,98 @@
/* 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),
+ })
+);
+
+function getDisplayedTimingMarker(state, marker) {
+ let timingMarker = null;
+ if (state.timingMarkers) {
+ timingMarker = state.timingMarkers.get(marker);
+ }
+ let firstRequestStartedMillis = null;
+ if (state.requests.items.length) {
+ firstRequestStartedMillis = state.requests
+ .items[state.requests.items.length - 1]
+ .attachment
+ .firstRequestStartedMillisNotPersistent;
+ }
+ if (timingMarker && firstRequestStartedMillis) {
+ return timingMarker - firstRequestStartedMillis;
+ } else {
+ return -1;
+ }
+}
+
module.exports = {
- // selectors...
+ getDisplayedRequestsSummary,
+ getDisplayedTimingMarker,
};
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..afac78cfc 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);
@@ -18,7 +18,7 @@ add_task(function* () {
RequestsMenu.lazyUpdate = false;
- let wait = waitForNetworkEvents(monitor, 1, 3);
+ let wait = waitForNetworkEvents(monitor, 1, 4);
yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
content.wrappedJSObject.performRequests(url);
});
@@ -27,8 +27,9 @@ add_task(function* () {
let requests = {
get: RequestsMenu.getItemAtIndex(0),
post: RequestsMenu.getItemAtIndex(1),
- multipart: RequestsMenu.getItemAtIndex(2),
- multipartForm: RequestsMenu.getItemAtIndex(3)
+ patch: RequestsMenu.getItemAtIndex(2),
+ multipart: RequestsMenu.getItemAtIndex(3),
+ multipartForm: RequestsMenu.getItemAtIndex(4)
};
let data = yield createCurlData(requests.get.attachment, gNetwork);
@@ -37,6 +38,12 @@ add_task(function* () {
data = yield createCurlData(requests.post.attachment, gNetwork);
testIsUrlEncodedRequest(data);
testWritePostDataTextParams(data);
+ testWriteEmptyPostDataTextParams(data);
+ testDataArgumentOnGeneratedCommand(data);
+
+ data = yield createCurlData(requests.patch.attachment, gNetwork);
+ testWritePostDataTextParams(data);
+ testDataArgumentOnGeneratedCommand(data);
data = yield createCurlData(requests.multipart.attachment, gNetwork);
testIsMultipartRequest(data);
@@ -85,6 +92,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/test/html_curl-utils.html b/devtools/client/netmonitor/test/html_curl-utils.html
index 8ff7ecdf0..eb5c0c5b6 100644
--- a/devtools/client/netmonitor/test/html_curl-utils.html
+++ b/devtools/client/netmonitor/test/html_curl-utils.html
@@ -52,6 +52,17 @@
xhr.send(params);
}
+ function ajaxPatch(aUrl, aCallback) {
+ let xhr = new XMLHttpRequest();
+ xhr.open("PATCH", aUrl, true);
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.onload = function() {
+ aCallback();
+ };
+ var params = "param1=value1&param2=value2&param3=value3";
+ xhr.send(params);
+ }
+
function ajaxMultipart(aUrl, aCallback) {
var xhr = new XMLHttpRequest();
xhr.open("POST", aUrl, true);
@@ -88,8 +99,10 @@
function performRequests(aUrl) {
ajaxGet(aUrl, () => {
ajaxPost(aUrl, () => {
- ajaxMultipart(aUrl, () => {
- submitForm();
+ ajaxPatch(aUrl, () => {
+ ajaxMultipart(aUrl, () => {
+ submitForm();
+ });
});
});
});
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/performance/performance-controller.js b/devtools/client/performance/performance-controller.js
index e47a0c401..4bd272f41 100644
--- a/devtools/client/performance/performance-controller.js
+++ b/devtools/client/performance/performance-controller.js
@@ -527,11 +527,10 @@ var PerformanceController = {
if (flags.testing) {
return { supported: true, enabled: true };
}
- let supported = system.constants.E10S_TESTING_ONLY;
// This is only checked on tool startup -- requires a restart if
// e10s subsequently enabled.
let enabled = this._e10s;
- return { supported, enabled };
+ return { supported: false, enabled };
},
/**
diff --git a/devtools/client/performance/test/browser_perf-recording-notices-05.js b/devtools/client/performance/test/browser_perf-recording-notices-05.js
index b6267470d..7a6b6b6b9 100644
--- a/devtools/client/performance/test/browser_perf-recording-notices-05.js
+++ b/devtools/client/performance/test/browser_perf-recording-notices-05.js
@@ -41,8 +41,8 @@ add_task(function* () {
enabled = true;
PerformanceController._setMultiprocessAttributes();
ok($("#performance-view").getAttribute("e10s"), "",
- "When e10s is enabled, but not supported, this probably means we no longer have " +
- "E10S_TESTING_ONLY, and we have no e10s attribute.");
+ "When e10s is enabled, but not supported, this probably means we " +
+ "have no e10s attribute.");
supported = true;
enabled = true;
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 0603fa95e..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,shift"/>
+ 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/scratchpad/test/head.js b/devtools/client/scratchpad/test/head.js
index 15619a169..955c037d7 100644
--- a/devtools/client/scratchpad/test/head.js
+++ b/devtools/client/scratchpad/test/head.js
@@ -9,6 +9,7 @@ const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
const {ScratchpadManager} = Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm", {});
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {gDevTools} = require("devtools/client/framework/devtools");
const Services = require("Services");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const flags = require("devtools/shared/flags");
diff --git a/devtools/client/shared/curl.js b/devtools/client/shared/curl.js
index 44465193f..420fe6aa5 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", "PATCH"].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/developer-toolbar.js b/devtools/client/shared/developer-toolbar.js
index 2528591a6..d84402418 100644
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -449,7 +449,15 @@ DeveloperToolbar.prototype.show = function (focus) {
[ this.tooltipPanel, this.outputPanel ] = panels;
- this._doc.getElementById("menu_devToolbar").setAttribute("checked", "true");
+ let checkboxValue = "true";
+ let appmenuEl = this._doc.getElementById("appmenu_devToolbar");
+ let menuEl = this._doc.getElementById("menu_devToolbar");
+ if (appmenuEl) {
+ appmenuEl.setAttribute("checked", checkboxValue);
+ }
+ if (menuEl) {
+ menuEl.setAttribute("checked", checkboxValue);
+ }
this.target = TargetFactory.forTab(this._chromeWindow.gBrowser.selectedTab);
const options = {
@@ -569,7 +577,15 @@ DeveloperToolbar.prototype.hide = function () {
Services.prefs.setBoolPref("devtools.toolbar.visible", false);
- this._doc.getElementById("menu_devToolbar").setAttribute("checked", "false");
+ let checkboxValue = "false";
+ let appmenuEl = this._doc.getElementById("appmenu_devToolbar");
+ let menuEl = this._doc.getElementById("menu_devToolbar");
+ if (appmenuEl) {
+ appmenuEl.setAttribute("checked", checkboxValue);
+ }
+ if (menuEl) {
+ menuEl.setAttribute("checked", checkboxValue);
+ }
this.destroy();
this._telemetry.toolClosed("developertoolbar");
diff --git a/devtools/client/shared/moz.build b/devtools/client/shared/moz.build
index 1c61970c0..7be4a0088 100644
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -35,6 +35,7 @@ DevToolsModules(
'Jsbeautify.jsm',
'key-shortcuts.js',
'keycodes.js',
+ 'natural-sort.js',
'network-throttling-profiles.js',
'node-attribute-parser.js',
'options-view.js',
diff --git a/devtools/client/shared/natural-sort.js b/devtools/client/shared/natural-sort.js
new file mode 100644
index 000000000..904d76431
--- /dev/null
+++ b/devtools/client/shared/natural-sort.js
@@ -0,0 +1,106 @@
+/*
+ * Natural Sort algorithm for Javascript - Version 0.8.1 - Released under MIT license
+ * Author: Jim Palmer (based on chunking idea from Dave Koelle)
+ *
+ * Includes pull request to move regexes out of main function for performance
+ * increases.
+ *
+ * Repository:
+ * https://github.com/overset/javascript-natural-sort/
+ */
+
+"use strict";
+
+var re = /(^([+\-]?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?(?=\D|\s|$))|^0x[\da-fA-F]+$|\d+)/g;
+var sre = /^\s+|\s+$/g; // trim pre-post whitespace
+var snre = /\s+/g; // normalize all whitespace to single ' ' character
+
+// eslint-disable-next-line
+var dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/;
+var hre = /^0x[0-9a-f]+$/i;
+var ore = /^0/;
+var b0re = /^\0/;
+var e0re = /\0$/;
+
+exports.naturalSortCaseSensitive =
+function naturalSortCaseSensitive(a, b) {
+ return naturalSort(a, b, false);
+};
+
+exports.naturalSortCaseInsensitive =
+function naturalSortCaseInsensitive(a, b) {
+ return naturalSort(a, b, true);
+};
+
+/**
+ * Sort numbers, strings, IP Addresses, Dates, Filenames, version numbers etc.
+ * "the way humans do."
+ *
+ * This function should only be called via naturalSortCaseSensitive and
+ * naturalSortCaseInsensitive.
+ *
+ * e.g. [3, 2, 1, 10].sort(naturalSort)
+ *
+ * @param {Object} a
+ * Passed in by Array.sort(a, b)
+ * @param {Object} b
+ * Passed in by Array.sort(a, b)
+ * @param {Boolean} insensitive
+ * Should the search be case insensitive?
+ */
+function naturalSort(a, b, insensitive) {
+ // convert all to strings strip whitespace
+ let i = function (s) {
+ return (insensitive && ("" + s).toLowerCase() || "" + s)
+ .replace(sre, "");
+ };
+ let x = i(a) || "";
+ let y = i(b) || "";
+ // chunk/tokenize
+ let xN = x.replace(re, "\0$1\0").replace(e0re, "").replace(b0re, "").split("\0");
+ let yN = y.replace(re, "\0$1\0").replace(e0re, "").replace(b0re, "").split("\0");
+ // numeric, hex or date detection
+ let xD = parseInt(x.match(hre), 16) || (xN.length !== 1 && Date.parse(x));
+ let yD = parseInt(y.match(hre), 16) || xD && y.match(dre) && Date.parse(y) || null;
+ let normChunk = function (s, l) {
+ // normalize spaces; find floats not starting with '0', string or 0 if
+ // not defined (Clint Priest)
+ return (!s.match(ore) || l == 1) &&
+ parseFloat(s) || s.replace(snre, " ").replace(sre, "") || 0;
+ };
+ let oFxNcL;
+ let oFyNcL;
+
+ // first try and sort Hex codes or Dates
+ if (yD) {
+ if (xD < yD) {
+ return -1;
+ } else if (xD > yD) {
+ return 1;
+ }
+ }
+
+ // natural sorting through split numeric strings and default strings
+ // eslint-disable-next-line
+ for (let cLoc = 0, xNl = xN.length, yNl = yN.length, numS = Math.max(xNl, yNl); cLoc < numS; cLoc++) {
+ oFxNcL = normChunk(xN[cLoc] || "", xNl);
+ oFyNcL = normChunk(yN[cLoc] || "", yNl);
+
+ // handle numeric vs string comparison - number < string - (Kyle Adams)
+ if (isNaN(oFxNcL) !== isNaN(oFyNcL)) {
+ return isNaN(oFxNcL) ? 1 : -1;
+ }
+ // if unicode use locale comparison
+ // eslint-disable-next-line
+ if (/[^\x00-\x80]/.test(oFxNcL + oFyNcL) && oFxNcL.localeCompare) {
+ let comp = oFxNcL.localeCompare(oFyNcL);
+ return comp / Math.abs(comp);
+ }
+ if (oFxNcL < oFyNcL) {
+ return -1;
+ } else if (oFxNcL > oFyNcL) {
+ return 1;
+ }
+ }
+ return null;
+}
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/shared/widgets/TableWidget.js b/devtools/client/shared/widgets/TableWidget.js
index 5dacd1b67..a0f0dfc11 100644
--- a/devtools/client/shared/widgets/TableWidget.js
+++ b/devtools/client/shared/widgets/TableWidget.js
@@ -8,6 +8,8 @@ loader.lazyRequireGetter(this, "setNamedTimeout",
"devtools/client/shared/widgets/view-helpers", true);
loader.lazyRequireGetter(this, "clearNamedTimeout",
"devtools/client/shared/widgets/view-helpers", true);
+loader.lazyRequireGetter(this, "naturalSortCaseInsensitive",
+ "devtools/client/shared/natural-sort", true);
const {KeyCodes} = require("devtools/client/shared/keycodes");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@@ -123,6 +125,8 @@ function TableWidget(node, options = {}) {
TableWidget.prototype = {
items: null,
+ editBookmark: null,
+ scrollIntoViewOnUpdate: null,
/**
* Getter for the headers context menu popup id.
@@ -139,7 +143,12 @@ TableWidget.prototype = {
*/
set selectedRow(id) {
for (let column of this.columns.values()) {
- column.selectRow(id[this.uniqueId] || id);
+ if (id) {
+ column.selectRow(id[this.uniqueId] || id);
+ } else {
+ column.selectedRow = null;
+ column.selectRow(null);
+ }
}
},
@@ -615,8 +624,13 @@ TableWidget.prototype = {
/**
* Populates the header context menu with the names of the columns along with
* displaying which columns are hidden or visible.
+ *
+ * @param {Array} privateColumns=[]
+ * An array of column names that should never appear in the table. This
+ * allows us to e.g. have an invisible compound primary key for a
+ * table's rows.
*/
- populateMenuPopup: function () {
+ populateMenuPopup: function (privateColumns = []) {
if (!this.menupopup) {
return;
}
@@ -626,6 +640,10 @@ TableWidget.prototype = {
}
for (let column of this.columns.values()) {
+ if (privateColumns.includes(column.id)) {
+ continue;
+ }
+
let menuitem = this.document.createElementNS(XUL_NS, "menuitem");
menuitem.setAttribute("label", column.header.getAttribute("value"));
menuitem.setAttribute("data-id", column.id);
@@ -663,16 +681,21 @@ TableWidget.prototype = {
* Creates the columns in the table. Without calling this method, data cannot
* be inserted into the table unless `initialColumns` was supplied.
*
- * @param {object} columns
+ * @param {Object} columns
* A key value pair representing the columns of the table. Where the
* key represents the id of the column and the value is the displayed
* label in the header of the column.
- * @param {string} sortOn
+ * @param {String} sortOn
* The id of the column on which the table will be initially sorted on.
- * @param {array} hiddenColumns
+ * @param {Array} hiddenColumns
* Ids of all the columns that are hidden by default.
+ * @param {Array} privateColumns=[]
+ * An array of column names that should never appear in the table. This
+ * allows us to e.g. have an invisible compound primary key for a
+ * table's rows.
*/
- setColumns: function (columns, sortOn = this.sortedOn, hiddenColumns = []) {
+ setColumns: function (columns, sortOn = this.sortedOn, hiddenColumns = [],
+ privateColumns = []) {
for (let column of this.columns.values()) {
column.destroy();
}
@@ -702,13 +725,18 @@ TableWidget.prototype = {
}
this.columns.set(id, new Column(this, id, columns[id]));
- if (hiddenColumns.indexOf(id) > -1) {
+ if (hiddenColumns.includes(id) || privateColumns.includes(id)) {
+ // Hide the column.
this.columns.get(id).toggleColumn();
+
+ if (privateColumns.includes(id)) {
+ this.columns.get(id).private = true;
+ }
}
}
this.sortedOn = sortOn;
this.sortBy(this.sortedOn);
- this.populateMenuPopup();
+ this.populateMenuPopup(privateColumns);
},
/**
@@ -778,6 +806,11 @@ TableWidget.prototype = {
return;
}
+ if (this.editBookmark && !this.items.has(this.editBookmark)) {
+ // Key has been updated... update bookmark.
+ this.editBookmark = item[this.uniqueId];
+ }
+
let index = this.columns.get(this.sortedOn).push(item);
for (let [key, column] of this.columns) {
if (key != this.sortedOn) {
@@ -814,7 +847,8 @@ TableWidget.prototype = {
column.remove(item);
column.updateZebra();
}
- if (this.items.size == 0) {
+ if (this.items.size === 0) {
+ this.selectedRow = null;
this.tbody.setAttribute("empty", "empty");
}
@@ -857,6 +891,8 @@ TableWidget.prototype = {
this.tbody.setAttribute("empty", "empty");
this.setPlaceholderText(this.emptyText);
+ this.selectedRow = null;
+
this.emit(EVENTS.TABLE_CLEARED, this);
},
@@ -958,6 +994,9 @@ module.exports.TableWidget = TableWidget;
* The displayed string on the column's header.
*/
function Column(table, id, header) {
+ // By default cells are visible in the UI.
+ this._private = false;
+
this.tbody = table.tbody;
this.document = table.document;
this.window = table.window;
@@ -1041,6 +1080,23 @@ Column.prototype = {
},
/**
+ * Get the private state of the column (visibility in the UI).
+ */
+ get private() {
+ return this._private;
+ },
+
+ /**
+ * Set the private state of the column (visibility in the UI).
+ *
+ * @param {Boolean} state
+ * Private (true or false)
+ */
+ set private(state) {
+ this._private = state;
+ },
+
+ /**
* Sets the sorted value
*/
set sorted(value) {
@@ -1115,7 +1171,9 @@ Column.prototype = {
},
/**
- * Called when a row is updated.
+ * Called when a row is updated e.g. a cell is changed. This means that
+ * for a new row this method will be called once for each column. If a single
+ * cell is changed this method will be called just once.
*
* @param {string} event
* The event name of the event. i.e. EVENTS.ROW_UPDATED
@@ -1124,7 +1182,23 @@ Column.prototype = {
*/
onRowUpdated: function (event, id) {
this._updateItems();
+
if (this.highlightUpdated && this.items[id] != null) {
+ if (this.table.scrollIntoViewOnUpdate) {
+ let cell = this.cells[this.items[id]];
+
+ // When a new row is created this method is called once for each column
+ // as each cell is updated. We can only scroll to cells if they are
+ // visible. We check for visibility and once we find the first visible
+ // cell in a row we scroll it into view and reset the
+ // scrollIntoViewOnUpdate flag.
+ if (cell.label.clientHeight > 0) {
+ cell.scrollIntoView();
+
+ this.table.scrollIntoViewOnUpdate = null;
+ }
+ }
+
if (this.table.editBookmark) {
// A rows position in the table can change as the result of an edit. In
// order to ensure that the correct row is highlighted after an edit we
@@ -1136,6 +1210,7 @@ Column.prototype = {
this.cells[this.items[id]].flash();
}
+
this.updateZebra();
},
@@ -1160,15 +1235,16 @@ Column.prototype = {
*/
selectRowAt: function (index) {
if (this.selectedRow != null) {
- this.cells[this.items[this.selectedRow]].toggleClass("theme-selected");
+ this.cells[this.items[this.selectedRow]].classList.remove("theme-selected");
}
- if (index < 0) {
+
+ let cell = this.cells[index];
+ if (cell) {
+ cell.classList.add("theme-selected");
+ this.selectedRow = cell.id;
+ } else {
this.selectedRow = null;
- return;
}
- let cell = this.cells[index];
- cell.toggleClass("theme-selected");
- this.selectedRow = cell.id;
},
/**
@@ -1218,11 +1294,11 @@ Column.prototype = {
let index;
if (this.sorted == 1) {
index = this.cells.findIndex(element => {
- return value < element.value;
+ return naturalSortCaseInsensitive(value, element.value) === -1;
});
} else {
index = this.cells.findIndex(element => {
- return value > element.value;
+ return naturalSortCaseInsensitive(value, element.value) === 1;
});
}
index = index >= 0 ? index : this.cells.length;
@@ -1332,7 +1408,6 @@ Column.prototype = {
this.cells = [];
this.items = {};
this._itemsDirty = false;
- this.selectedRow = null;
while (this.header.nextSibling) {
this.header.nextSibling.remove();
}
@@ -1350,7 +1425,7 @@ Column.prototype = {
a[this.id].textContent : a[this.id];
let val2 = (b[this.id] instanceof Node) ?
b[this.id].textContent : b[this.id];
- return val1 > val2;
+ return naturalSortCaseInsensitive(val1, val2);
});
} else if (this.sorted > 1) {
items.sort((a, b) => {
@@ -1358,12 +1433,12 @@ Column.prototype = {
a[this.id].textContent : a[this.id];
let val2 = (b[this.id] instanceof Node) ?
b[this.id].textContent : b[this.id];
- return val2 > val1;
+ return naturalSortCaseInsensitive(val2, val1);
});
}
if (this.selectedRow) {
- this.cells[this.items[this.selectedRow]].toggleClass("theme-selected");
+ this.cells[this.items[this.selectedRow]].classList.remove("theme-selected");
}
this.items = {};
// Otherwise, just use the sorted array passed to update the cells value.
@@ -1373,7 +1448,7 @@ Column.prototype = {
this.cells[i].id = item[this.uniqueId];
});
if (this.selectedRow) {
- this.cells[this.items[this.selectedRow]].toggleClass("theme-selected");
+ this.cells[this.items[this.selectedRow]].classList.add("theme-selected");
}
this._itemsDirty = false;
this.updateZebra();
@@ -1387,7 +1462,9 @@ Column.prototype = {
if (!cell.hidden) {
i++;
}
- cell.toggleClass("even", !(i % 2));
+
+ let even = !(i % 2);
+ cell.classList.toggle("even", even);
}
},
@@ -1523,8 +1600,8 @@ Cell.prototype = {
return this._value;
},
- toggleClass: function (className, condition) {
- this.label.classList.toggle(className, condition);
+ get classList() {
+ return this.label.classList;
},
/**
@@ -1550,6 +1627,10 @@ Cell.prototype = {
this.label.focus();
},
+ scrollIntoView: function () {
+ this.label.scrollIntoView(false);
+ },
+
destroy: function () {
this.label.remove();
this.label = null;
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/storage/storage.xul b/devtools/client/storage/storage.xul
index 9fbef5199..fa006ac7c 100644
--- a/devtools/client/storage/storage.xul
+++ b/devtools/client/storage/storage.xul
@@ -26,13 +26,18 @@
<menupopup id="storage-tree-popup">
<menuitem id="storage-tree-popup-delete-all"
label="&storage.popupMenu.deleteAllLabel;"/>
+ <menuitem id="storage-tree-popup-delete-all-session-cookies"
+ label="&storage.popupMenu.deleteAllSessionCookiesLabel;"/>
<menuitem id="storage-tree-popup-delete"/>
</menupopup>
<menupopup id="storage-table-popup">
+ <menuitem id="storage-table-popup-add"/>
<menuitem id="storage-table-popup-delete"/>
<menuitem id="storage-table-popup-delete-all-from"/>
<menuitem id="storage-table-popup-delete-all"
label="&storage.popupMenu.deleteAllLabel;"/>
+ <menuitem id="storage-table-popup-delete-all-session-cookies"
+ label="&storage.popupMenu.deleteAllSessionCookiesLabel;"/>
</menupopup>
</popupset>
@@ -41,6 +46,11 @@
<splitter class="devtools-side-splitter"/>
<vbox flex="1">
<hbox id="storage-toolbar" class="devtools-toolbar">
+ <button id="add-button"
+ class="devtools-button add-button"></button>
+ <button id="refresh-button"
+ class="devtools-button refresh-button"></button>
+ <spacer flex="1"/>
<textbox id="storage-searchbox"
class="devtools-filterinput"
type="search"
diff --git a/devtools/client/storage/test/browser.ini b/devtools/client/storage/test/browser.ini
index dd7f48bd7..0290aaa5e 100644
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -7,7 +7,9 @@ support-files =
storage-cookies.html
storage-empty-objectstores.html
storage-idb-delete-blocked.html
+ storage-indexeddb-duplicate-names.html
storage-listings.html
+ storage-listings-with-fragment.html
storage-localstorage.html
storage-overflow.html
storage-search.html
@@ -19,25 +21,34 @@ support-files =
!/devtools/client/framework/test/shared-head.js
[browser_storage_basic.js]
+[browser_storage_basic_with_fragment.js]
[browser_storage_cache_delete.js]
[browser_storage_cache_error.js]
+[browser_storage_cookies_add.js]
[browser_storage_cookies_delete_all.js]
[browser_storage_cookies_domain.js]
+[browser_storage_cookies_domain_port.js]
[browser_storage_cookies_edit.js]
[browser_storage_cookies_edit_keyboard.js]
[browser_storage_cookies_tab_navigation.js]
[browser_storage_delete.js]
[browser_storage_delete_all.js]
[browser_storage_delete_tree.js]
-[browser_storage_dynamic_updates.js]
+[browser_storage_dom_cache_disabled.js]
+[browser_storage_dynamic_updates_cookies.js]
+[browser_storage_dynamic_updates_localStorage.js]
+[browser_storage_dynamic_updates_sessionStorage.js]
[browser_storage_empty_objectstores.js]
[browser_storage_indexeddb_delete.js]
[browser_storage_indexeddb_delete_blocked.js]
+[browser_storage_indexeddb_duplicate_names.js]
+[browser_storage_localstorage_add.js]
[browser_storage_localstorage_edit.js]
[browser_storage_localstorage_error.js]
[browser_storage_overflow.js]
[browser_storage_search.js]
[browser_storage_search_keyboard_trap.js]
+[browser_storage_sessionstorage_add.js]
[browser_storage_sessionstorage_edit.js]
[browser_storage_sidebar.js]
[browser_storage_sidebar_update.js]
diff --git a/devtools/client/storage/test/browser_storage_basic.js b/devtools/client/storage/test/browser_storage_basic.js
index 343d46170..35d08afce 100644
--- a/devtools/client/storage/test/browser_storage_basic.js
+++ b/devtools/client/storage/test/browser_storage_basic.js
@@ -2,6 +2,8 @@
* 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-globals-from head.js */
+
// Basic test to assert that the storage tree and table corresponding to each
// item in the storage tree is correctly displayed
@@ -21,10 +23,30 @@
"use strict";
const testCases = [
- [["cookies", "test1.example.org"],
- ["c1", "cs2", "c3", "uc1"]],
- [["cookies", "sectest1.example.org"],
- ["uc1", "cs2", "sc1"]],
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c3", "test1.example.org", "/"),
+ getCookieId("c4", ".example.org", "/"),
+ getCookieId("uc1", ".example.org", "/"),
+ getCookieId("uc2", ".example.org", "/")
+ ]
+ ],
+ [
+ ["cookies", "https://sectest1.example.org"],
+ [
+ getCookieId("uc1", ".example.org", "/"),
+ getCookieId("uc2", ".example.org", "/"),
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c4", ".example.org", "/"),
+ getCookieId("sc1", "sectest1.example.org",
+ "/browser/devtools/client/storage/test/"),
+ getCookieId("sc2", "sectest1.example.org",
+ "/browser/devtools/client/storage/test/")
+ ]
+ ],
[["localStorage", "http://test1.example.org"],
["ls1", "ls2"]],
[["localStorage", "http://sectest1.example.org"],
@@ -38,28 +60,28 @@ const testCases = [
[["sessionStorage", "https://sectest1.example.org"],
["iframe-s-ss1"]],
[["indexedDB", "http://test1.example.org"],
- ["idb1", "idb2"]],
- [["indexedDB", "http://test1.example.org", "idb1"],
+ ["idb1 (default)", "idb2 (default)"]],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)"],
["obj1", "obj2"]],
- [["indexedDB", "http://test1.example.org", "idb2"],
+ [["indexedDB", "http://test1.example.org", "idb2 (default)"],
["obj3"]],
- [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
[1, 2, 3]],
- [["indexedDB", "http://test1.example.org", "idb1", "obj2"],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
[1]],
- [["indexedDB", "http://test1.example.org", "idb2", "obj3"],
+ [["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
[]],
[["indexedDB", "http://sectest1.example.org"],
[]],
[["indexedDB", "https://sectest1.example.org"],
- ["idb-s1", "idb-s2"]],
- [["indexedDB", "https://sectest1.example.org", "idb-s1"],
+ ["idb-s1 (default)", "idb-s2 (default)"]],
+ [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)"],
["obj-s1"]],
- [["indexedDB", "https://sectest1.example.org", "idb-s2"],
+ [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
["obj-s2"]],
- [["indexedDB", "https://sectest1.example.org", "idb-s1", "obj-s1"],
+ [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)", "obj-s1"],
[6, 7]],
- [["indexedDB", "https://sectest1.example.org", "idb-s2", "obj-s2"],
+ [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)", "obj-s2"],
[16]],
[["Cache", "http://test1.example.org", "plop"],
[MAIN_DOMAIN + "404_cached_file.js",
@@ -71,8 +93,8 @@ const testCases = [
*/
function testTree() {
let doc = gPanelWindow.document;
- for (let item of testCases) {
- ok(doc.querySelector("[data-id='" + JSON.stringify(item[0]) + "']"),
+ for (let [item] of testCases) {
+ ok(doc.querySelector("[data-id='" + JSON.stringify(item) + "']"),
"Tree item " + item[0] + " should be present in the storage tree");
}
}
@@ -86,8 +108,8 @@ function* testTables() {
gUI.tree.expandAll();
// First tree item is already selected so no clicking and waiting for update
- for (let id of testCases[0][1]) {
- ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
+ for (let [treeItem, items] of testCases.slice(1)) {
+ yield selectTreeItem(treeItem);
"Table item " + id + " should be present");
}
@@ -98,10 +120,10 @@ function* testTables() {
// Check whether correct number of items are present in the table
is(doc.querySelectorAll(
".table-widget-wrapper:first-of-type .table-widget-cell"
- ).length, item[1].length, "Number of items in table is correct");
+ ).length, items.length, "Number of items in table is correct");
// Check if all the desired items are present in the table
- for (let id of item[1]) {
+ for (let id of items) {
ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
"Table item " + id + " should be present");
}
diff --git a/devtools/client/storage/test/browser_storage_basic_with_fragment.js b/devtools/client/storage/test/browser_storage_basic_with_fragment.js
new file mode 100644
index 000000000..7769781c0
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_basic_with_fragment.js
@@ -0,0 +1,139 @@
+/* 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/. */
+
+/* import-globals-from head.js */
+
+// A second basic test to assert that the storage tree and table corresponding
+// to each item in the storage tree is correctly displayed.
+
+// This test differs from browser_storage_basic.js because the URLs we load
+// include fragments e.g. http://example.com/test.js#abcdefg
+// ^^^^^^^^
+// fragment
+
+// Entries that should be present in the tree for this test
+// Format for each entry in the array :
+// [
+// ["path", "to", "tree", "item"], - The path to the tree item to click formed
+// by id of each item
+// ["key_value1", "key_value2", ...] - The value of the first (unique) column
+// for each row in the table corresponding
+// to the tree item selected.
+// ]
+// These entries are formed by the cookies, local storage, session storage and
+// indexedDB entries created in storage-listings.html,
+// storage-secured-iframe.html and storage-unsecured-iframe.html
+
+"use strict";
+
+const testCases = [
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c3", "test1.example.org", "/"),
+ getCookieId("uc1", ".example.org", "/")
+ ]
+ ],
+ [
+ ["cookies", "https://sectest1.example.org"],
+ [
+ getCookieId("uc1", ".example.org", "/"),
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("sc1", "sectest1.example.org", "/browser/devtools/client/storage/test/")
+ ]
+ ],
+ [["localStorage", "http://test1.example.org"],
+ ["ls1", "ls2"]],
+ [["localStorage", "http://sectest1.example.org"],
+ ["iframe-u-ls1"]],
+ [["localStorage", "https://sectest1.example.org"],
+ ["iframe-s-ls1"]],
+ [["sessionStorage", "http://test1.example.org"],
+ ["ss1"]],
+ [["sessionStorage", "http://sectest1.example.org"],
+ ["iframe-u-ss1", "iframe-u-ss2"]],
+ [["sessionStorage", "https://sectest1.example.org"],
+ ["iframe-s-ss1"]],
+ [["indexedDB", "http://test1.example.org"],
+ ["idb1 (default)", "idb2 (default)"]],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)"],
+ ["obj1", "obj2"]],
+ [["indexedDB", "http://test1.example.org", "idb2 (default)"],
+ ["obj3"]],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
+ [1, 2, 3]],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
+ [1]],
+ [["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
+ []],
+ [["indexedDB", "http://sectest1.example.org"],
+ []],
+ [["indexedDB", "https://sectest1.example.org"],
+ ["idb-s1 (default)", "idb-s2 (default)"]],
+ [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)"],
+ ["obj-s1"]],
+ [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
+ ["obj-s2"]],
+ [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)", "obj-s1"],
+ [6, 7]],
+ [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)", "obj-s2"],
+ [16]],
+ [["Cache", "http://test1.example.org", "plop"],
+ [MAIN_DOMAIN + "404_cached_file.js",
+ MAIN_DOMAIN + "browser_storage_basic.js"]],
+];
+
+/**
+ * Test that the desired number of tree items are present
+ */
+function testTree() {
+ let doc = gPanelWindow.document;
+ for (let [item] of testCases) {
+ ok(doc.querySelector("[data-id='" + JSON.stringify(item) + "']"),
+ "Tree item " + item[0] + " should be present in the storage tree");
+ }
+}
+
+/**
+ * Test that correct table entries are shown for each of the tree item
+ */
+function* testTables() {
+ let doc = gPanelWindow.document;
+ // Expand all nodes so that the synthesized click event actually works
+ gUI.tree.expandAll();
+
+ // First tree item is already selected so no clicking and waiting for update
+ for (let id of testCases[0][1]) {
+ ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
+ "Table item " + id + " should be present");
+ }
+
+ // Click rest of the tree items and wait for the table to be updated
+ for (let [treeItem, items] of testCases.slice(1)) {
+ yield selectTreeItem(treeItem);
+
+ // Check whether correct number of items are present in the table
+ is(doc.querySelectorAll(
+ ".table-widget-wrapper:first-of-type .table-widget-cell"
+ ).length, items.length, "Number of items in table is correct");
+
+ // Check if all the desired items are present in the table
+ for (let id of items) {
+ ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
+ "Table item " + id + " should be present");
+ }
+ }
+}
+
+add_task(function* () {
+ yield openTabAndSetupStorage(
+ MAIN_DOMAIN + "storage-listings-with-fragment.html#abc");
+
+ testTree();
+ yield testTables();
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_cookies_add.js b/devtools/client/storage/test/browser_storage_cookies_add.js
new file mode 100644
index 000000000..ac66eb92c
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_cookies_add.js
@@ -0,0 +1,20 @@
+/* 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/. */
+
+// Basic test to check the adding of cookies.
+
+"use strict";
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
+ showAllColumns(true);
+
+ yield performAdd(["cookies", "http://test1.example.org"]);
+ yield performAdd(["cookies", "http://test1.example.org"]);
+ yield performAdd(["cookies", "http://test1.example.org"]);
+ yield performAdd(["cookies", "http://test1.example.org"]);
+ yield performAdd(["cookies", "http://test1.example.org"]);
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_cookies_delete_all.js b/devtools/client/storage/test/browser_storage_cookies_delete_all.js
index 6e6008e66..f8e9bb288 100644
--- a/devtools/client/storage/test/browser_storage_cookies_delete_all.js
+++ b/devtools/client/storage/test/browser_storage_cookies_delete_all.js
@@ -8,11 +8,13 @@
// Test deleting all cookies
-function* performDelete(store, rowName, deleteAll) {
+function* performDelete(store, rowName, action) {
let contextMenu = gPanelWindow.document.getElementById(
"storage-table-popup");
let menuDeleteAllItem = contextMenu.querySelector(
"#storage-table-popup-delete-all");
+ let menuDeleteAllSessionCookiesItem = contextMenu.querySelector(
+ "#storage-table-popup-delete-all-session-cookies");
let menuDeleteAllFromItem = contextMenu.querySelector(
"#storage-table-popup-delete-all-from");
@@ -21,17 +23,23 @@ function* performDelete(store, rowName, deleteAll) {
yield selectTreeItem(store);
let eventWait = gUI.once("store-objects-updated");
+ let cells = getRowCells(rowName, true);
- let cells = getRowCells(rowName);
yield waitForContextMenu(contextMenu, cells.name, () => {
info(`Opened context menu in ${storeName}, row '${rowName}'`);
- if (deleteAll) {
- menuDeleteAllItem.click();
- } else {
- menuDeleteAllFromItem.click();
- let hostName = cells.host.value;
- ok(menuDeleteAllFromItem.getAttribute("label").includes(hostName),
+ switch (action) {
+ case "deleteAll":
+ menuDeleteAllItem.click();
+ break;
+ case "deleteAllSessionCookies":
+ menuDeleteAllSessionCookiesItem.click();
+ break;
+ case "deleteAllFrom":
+ menuDeleteAllFromItem.click();
+ let hostName = cells.host.value;
+ ok(menuDeleteAllFromItem.getAttribute("label").includes(hostName),
`Context menu item label contains '${hostName}'`);
+ break;
}
});
@@ -43,31 +51,101 @@ add_task(function* () {
info("test state before delete");
yield checkState([
- [["cookies", "test1.example.org"], ["c1", "c3", "cs2", "uc1"]],
- [["cookies", "sectest1.example.org"], ["cs2", "sc1", "uc1"]],
+ [
+ ["cookies", "http://test1.example.org"], [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("c3", "test1.example.org", "/"),
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c4", ".example.org", "/"),
+ getCookieId("uc1", ".example.org", "/"),
+ getCookieId("uc2", ".example.org", "/")
+ ]
+ ],
+ [
+ ["cookies", "https://sectest1.example.org"], [
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c4", ".example.org", "/"),
+ getCookieId("sc1", "sectest1.example.org",
+ "/browser/devtools/client/storage/test/"),
+ getCookieId("sc2", "sectest1.example.org",
+ "/browser/devtools/client/storage/test/"),
+ getCookieId("uc1", ".example.org", "/"),
+ getCookieId("uc2", ".example.org", "/")
+ ]
+ ],
]);
info("delete all from domain");
// delete only cookies that match the host exactly
- yield performDelete(["cookies", "test1.example.org"], "c1", false);
+ let id = getCookieId("c1", "test1.example.org", "/browser");
+ yield performDelete(["cookies", "http://test1.example.org"], id, "deleteAllFrom");
info("test state after delete all from domain");
yield checkState([
// Domain cookies (.example.org) must not be deleted.
- [["cookies", "test1.example.org"], ["cs2", "uc1"]],
- [["cookies", "sectest1.example.org"], ["cs2", "sc1", "uc1"]],
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c4", ".example.org", "/"),
+ getCookieId("uc1", ".example.org", "/"),
+ getCookieId("uc2", ".example.org", "/")
+ ]
+ ],
+ [
+ ["cookies", "https://sectest1.example.org"],
+ [
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c4", ".example.org", "/"),
+ getCookieId("uc1", ".example.org", "/"),
+ getCookieId("uc2", ".example.org", "/"),
+ getCookieId("sc1", "sectest1.example.org",
+ "/browser/devtools/client/storage/test/"),
+ getCookieId("sc2", "sectest1.example.org",
+ "/browser/devtools/client/storage/test/")
+ ]
+ ],
+ ]);
+
+ info("delete all session cookies");
+ // delete only session cookies
+ id = getCookieId("cs2", ".example.org", "/");
+ yield performDelete(["cookies", "sectest1.example.org"], id,
+ "deleteAllSessionCookies");
+
+ info("test state after delete all session cookies");
+ yield checkState([
+ // Cookies with expiry date must not be deleted.
+ [
+ ["cookies", "test1.example.org"],
+ [
+ getCookieId("c4", ".example.org", "/"),
+ getCookieId("uc2", ".example.org", "/")
+ ]
+ ],
+ [
+ ["cookies", "sectest1.example.org"],
+ [
+ getCookieId("c4", ".example.org", "/"),
+ getCookieId("uc2", ".example.org", "/"),
+ getCookieId("sc2", "sectest1.example.org",
+ "/browser/devtools/client/storage/test/")
+ ]
+ ],
]);
info("delete all");
// delete all cookies for host, including domain cookies
- yield performDelete(["cookies", "sectest1.example.org"], "uc1", true);
+ id = getCookieId("uc2", ".example.org", "/");
+ yield performDelete(["cookies", "http://sectest1.example.org"], id,
+ "deleteAll");
info("test state after delete all");
yield checkState([
// Domain cookies (.example.org) are deleted too, so deleting in sectest1
// also removes stuff from test1.
- [["cookies", "test1.example.org"], []],
- [["cookies", "sectest1.example.org"], []],
+ [["cookies", "http://test1.example.org"], []],
+ [["cookies", "https://sectest1.example.org"], []],
]);
yield finishTests();
diff --git a/devtools/client/storage/test/browser_storage_cookies_domain.js b/devtools/client/storage/test/browser_storage_cookies_domain.js
index dc93d6e67..06f0a464d 100644
--- a/devtools/client/storage/test/browser_storage_cookies_domain.js
+++ b/devtools/client/storage/test/browser_storage_cookies_domain.js
@@ -13,8 +13,16 @@ add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
yield checkState([
- [["cookies", "test1.example.org"],
- ["test1", "test2", "test3", "test4", "test5"]],
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("test1", ".test1.example.org", "/browser"),
+ getCookieId("test2", "test1.example.org", "/browser"),
+ getCookieId("test3", ".test1.example.org", "/browser"),
+ getCookieId("test4", "test1.example.org", "/browser"),
+ getCookieId("test5", ".test1.example.org", "/browser")
+ ]
+ ],
]);
yield finishTests();
diff --git a/devtools/client/storage/test/browser_storage_cookies_domain_port.js b/devtools/client/storage/test/browser_storage_cookies_domain_port.js
new file mode 100644
index 000000000..a5bc2e5be
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_cookies_domain_port.js
@@ -0,0 +1,29 @@
+/* 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/. */
+
+/* import-globals-from ../../framework/test/shared-head.js */
+
+"use strict";
+
+// Test that cookies with domain equal to full host name and port are listed.
+// E.g., ".example.org:8000" vs. example.org:8000).
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN_WITH_PORT + "storage-cookies.html");
+
+ yield checkState([
+ [
+ ["cookies", "http://test1.example.org:8000"],
+ [
+ getCookieId("test1", ".test1.example.org", "/browser"),
+ getCookieId("test2", "test1.example.org", "/browser"),
+ getCookieId("test3", ".test1.example.org", "/browser"),
+ getCookieId("test4", "test1.example.org", "/browser"),
+ getCookieId("test5", ".test1.example.org", "/browser")
+ ]
+ ],
+ ]);
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_cookies_edit.js b/devtools/client/storage/test/browser_storage_cookies_edit.js
index 5818e4864..14944b398 100644
--- a/devtools/client/storage/test/browser_storage_cookies_edit.js
+++ b/devtools/client/storage/test/browser_storage_cookies_edit.js
@@ -10,13 +10,20 @@ add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
showAllColumns(true);
- yield editCell("test3", "name", "newTest3");
- yield editCell("newTest3", "path", "/");
- yield editCell("newTest3", "host", "test1.example.org");
- yield editCell("newTest3", "expires", "Tue, 14 Feb 2040 17:41:14 GMT");
- yield editCell("newTest3", "value", "newValue3");
- yield editCell("newTest3", "isSecure", "true");
- yield editCell("newTest3", "isHttpOnly", "true");
+ let id = getCookieId("test3", ".test1.example.org", "/browser");
+ yield editCell(id, "name", "newTest3");
+
+ id = getCookieId("newTest3", ".test1.example.org", "/browser");
+ yield editCell(id, "host", "test1.example.org");
+
+ id = getCookieId("newTest3", "test1.example.org", "/browser");
+ yield editCell(id, "path", "/");
+
+ id = getCookieId("newTest3", "test1.example.org", "/");
+ yield editCell(id, "expires", "Tue, 14 Feb 2040 17:41:14 GMT");
+ yield editCell(id, "value", "newValue3");
+ yield editCell(id, "isSecure", "true");
+ yield editCell(id, "isHttpOnly", "true");
yield finishTests();
});
diff --git a/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js b/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js
index 1208c4376..4bbb63fbe 100644
--- a/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js
+++ b/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js
@@ -10,10 +10,11 @@ add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
showAllColumns(true);
- yield startCellEdit("test4", "name");
+ let id = getCookieId("test4", "test1.example.org", "/browser");
+ yield startCellEdit(id, "name");
yield typeWithTerminator("test6", "VK_TAB");
- yield typeWithTerminator("/", "VK_TAB");
yield typeWithTerminator(".example.org", "VK_TAB");
+ yield typeWithTerminator("/", "VK_TAB");
yield typeWithTerminator("Tue, 25 Dec 2040 12:00:00 GMT", "VK_TAB");
yield typeWithTerminator("test6value", "VK_TAB");
yield typeWithTerminator("false", "VK_TAB");
diff --git a/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js b/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js
index 783a0c844..5da359b8d 100644
--- a/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js
+++ b/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js
@@ -10,7 +10,8 @@ add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
showAllColumns(true);
- yield startCellEdit("test1", "name");
+ let id = getCookieId("test1", ".test1.example.org", "/browser");
+ yield startCellEdit(id, "name");
PressKeyXTimes("VK_TAB", 18);
is(getCurrentEditorValue(), "value3",
diff --git a/devtools/client/storage/test/browser_storage_delete.js b/devtools/client/storage/test/browser_storage_delete.js
index c0e2b0ad7..306c33d24 100644
--- a/devtools/client/storage/test/browser_storage_delete.js
+++ b/devtools/client/storage/test/browser_storage_delete.js
@@ -13,9 +13,11 @@ const TEST_CASES = [
"ls1", "name"],
[["sessionStorage", "http://test1.example.org"],
"ss1", "name"],
- [["cookies", "test1.example.org"],
- "c1", "name"],
- [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+ [
+ ["cookies", "http://test1.example.org"],
+ getCookieId("c1", "test1.example.org", "/browser"), "name"
+ ],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
1, "name"],
[["Cache", "http://test1.example.org", "plop"],
MAIN_DOMAIN + "404_cached_file.js", "url"],
@@ -41,7 +43,7 @@ add_task(function* () {
yield waitForContextMenu(contextMenu, row[cellToClick], () => {
info(`Opened context menu in ${treeItemName}, row '${rowName}'`);
menuDeleteItem.click();
- let truncatedRowName = String(rowName).substr(0, 16);
+ let truncatedRowName = String(rowName).replace(SEPARATOR_GUID, "-").substr(0, 16);
ok(menuDeleteItem.getAttribute("label").includes(truncatedRowName),
`Context menu item label contains '${rowName}' (maybe truncated)`);
});
diff --git a/devtools/client/storage/test/browser_storage_delete_all.js b/devtools/client/storage/test/browser_storage_delete_all.js
index c4b6048fb..60b417bdb 100644
--- a/devtools/client/storage/test/browser_storage_delete_all.js
+++ b/devtools/client/storage/test/browser_storage_delete_all.js
@@ -29,7 +29,7 @@ add_task(function* () {
["iframe-u-ss1", "iframe-u-ss2"]],
[["sessionStorage", "https://sectest1.example.org"],
["iframe-s-ss1"]],
- [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
[1, 2, 3]],
[["Cache", "http://test1.example.org", "plop"],
[MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]],
@@ -41,7 +41,7 @@ add_task(function* () {
const deleteHosts = [
[["localStorage", "https://sectest1.example.org"], "iframe-s-ls1", "name"],
[["sessionStorage", "https://sectest1.example.org"], "iframe-s-ss1", "name"],
- [["indexedDB", "http://test1.example.org", "idb1", "obj1"], 1, "name"],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], 1, "name"],
[["Cache", "http://test1.example.org", "plop"],
MAIN_DOMAIN + "404_cached_file.js", "url"],
];
@@ -78,7 +78,7 @@ add_task(function* () {
["iframe-u-ss1", "iframe-u-ss2"]],
[["sessionStorage", "https://sectest1.example.org"],
[]],
- [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
[]],
[["Cache", "http://test1.example.org", "plop"],
[]],
diff --git a/devtools/client/storage/test/browser_storage_delete_tree.js b/devtools/client/storage/test/browser_storage_delete_tree.js
index 867a1c8b6..6705dba8a 100644
--- a/devtools/client/storage/test/browser_storage_delete_tree.js
+++ b/devtools/client/storage/test/browser_storage_delete_tree.js
@@ -17,20 +17,30 @@ add_task(function* () {
info("test state before delete");
yield checkState([
- [["cookies", "test1.example.org"], ["c1", "c3", "cs2", "uc1"]],
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c3", "test1.example.org", "/"),
+ getCookieId("c4", ".example.org", "/"),
+ getCookieId("uc1", ".example.org", "/"),
+ getCookieId("uc2", ".example.org", "/")
+ ]
+ ],
[["localStorage", "http://test1.example.org"], ["ls1", "ls2"]],
[["sessionStorage", "http://test1.example.org"], ["ss1"]],
- [["indexedDB", "http://test1.example.org", "idb1", "obj1"], [1, 2, 3]],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], [1, 2, 3]],
[["Cache", "http://test1.example.org", "plop"],
[MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]],
]);
info("do the delete");
const deleteHosts = [
- ["cookies", "test1.example.org"],
+ ["cookies", "http://test1.example.org"],
["localStorage", "http://test1.example.org"],
["sessionStorage", "http://test1.example.org"],
- ["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+ ["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
["Cache", "http://test1.example.org", "plop"],
];
@@ -56,10 +66,10 @@ add_task(function* () {
info("test state after delete");
yield checkState([
- [["cookies", "test1.example.org"], []],
+ [["cookies", "http://test1.example.org"], []],
[["localStorage", "http://test1.example.org"], []],
[["sessionStorage", "http://test1.example.org"], []],
- [["indexedDB", "http://test1.example.org", "idb1", "obj1"], []],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], []],
[["Cache", "http://test1.example.org", "plop"], []],
]);
diff --git a/devtools/client/storage/test/browser_storage_dom_cache_disabled.js b/devtools/client/storage/test/browser_storage_dom_cache_disabled.js
new file mode 100644
index 000000000..db0aca392
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_dom_cache_disabled.js
@@ -0,0 +1,37 @@
+/* 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/. */
+
+/* import-globals-from ../../framework/test/shared-head.js */
+
+"use strict";
+
+// Test the storage inspector when dom.caches.enabled=false.
+
+add_task(function* () {
+ // Disable the DOM cache
+ Services.prefs.setBoolPref(DOM_CACHE, false);
+
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
+
+ const state = [
+ [["localStorage", "http://test1.example.org"],
+ ["ls1", "ls2"]],
+ [["localStorage", "http://sectest1.example.org"],
+ ["iframe-u-ls1"]],
+ [["localStorage", "https://sectest1.example.org"],
+ ["iframe-s-ls1"]],
+ [["sessionStorage", "http://test1.example.org"],
+ ["ss1"]],
+ [["sessionStorage", "http://sectest1.example.org"],
+ ["iframe-u-ss1", "iframe-u-ss2"]],
+ [["sessionStorage", "https://sectest1.example.org"],
+ ["iframe-s-ss1"]],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
+ [1, 2, 3]],
+ ];
+
+ yield checkState(state);
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js b/devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js
new file mode 100644
index 000000000..032e7b7b9
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js
@@ -0,0 +1,188 @@
+/* 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";
+
+// Test dynamic updates in the storage inspector for cookies.
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-updates.html");
+
+ gUI.tree.expandAll();
+
+ ok(gUI.sidebar.hidden, "Sidebar is initially hidden");
+ let c1id = getCookieId("c1", "test1.example.org", "/browser");
+ yield selectTableItem(c1id);
+
+ // test that value is something initially
+ let initialValue = [[
+ {name: "c1", value: "1.2.3.4.5.6.7"},
+ {name: "c1.Path", value: "/browser"}
+ ], [
+ {name: "c1", value: "Array"},
+ {name: "c1.0", value: "1"},
+ {name: "c1.6", value: "7"}
+ ]];
+
+ // test that value is something initially
+ let finalValue = [[
+ {name: "c1", value: '{"foo": 4,"bar":6}'},
+ {name: "c1.Path", value: "/browser"}
+ ], [
+ {name: "c1", value: "Object"},
+ {name: "c1.foo", value: "4"},
+ {name: "c1.bar", value: "6"}
+ ]];
+
+ // Check that sidebar shows correct initial value
+ yield findVariableViewProperties(initialValue[0], false);
+
+ yield findVariableViewProperties(initialValue[1], true);
+ // Check if table shows correct initial value
+
+ yield checkState([
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("c2", "test1.example.org", "/browser")
+ ]
+ ],
+ ]);
+ checkCell(c1id, "value", "1.2.3.4.5.6.7");
+
+ gWindow.addCookie("c1", '{"foo": 4,"bar":6}', "/browser");
+ yield gUI.once("sidebar-updated");
+
+ yield findVariableViewProperties(finalValue[0], false);
+ yield findVariableViewProperties(finalValue[1], true);
+
+ yield checkState([
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("c2", "test1.example.org", "/browser")
+ ]
+ ],
+ ]);
+ checkCell(c1id, "value", '{"foo": 4,"bar":6}');
+
+ // Add a new entry
+ gWindow.addCookie("c3", "booyeah");
+
+ // Wait once for update and another time for value fetching
+ yield gUI.once("store-objects-updated");
+ yield gUI.once("store-objects-updated");
+
+ yield checkState([
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("c2", "test1.example.org", "/browser"),
+ getCookieId("c3", "test1.example.org",
+ "/browser/devtools/client/storage/test/")
+ ]
+ ],
+ ]);
+ let c3id = getCookieId("c3", "test1.example.org",
+ "/browser/devtools/client/storage/test/");
+ checkCell(c3id, "value", "booyeah");
+
+ // Add another
+ gWindow.addCookie("c4", "booyeah");
+
+ // Wait once for update and another time for value fetching
+ yield gUI.once("store-objects-updated");
+ yield gUI.once("store-objects-updated");
+
+ yield checkState([
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("c2", "test1.example.org", "/browser"),
+ getCookieId("c3", "test1.example.org",
+ "/browser/devtools/client/storage/test/"),
+ getCookieId("c4", "test1.example.org",
+ "/browser/devtools/client/storage/test/")
+ ]
+ ],
+ ]);
+ let c4id = getCookieId("c4", "test1.example.org",
+ "/browser/devtools/client/storage/test/");
+ checkCell(c4id, "value", "booyeah");
+
+ // Removing cookies
+ gWindow.removeCookie("c1", "/browser");
+
+ yield gUI.once("sidebar-updated");
+
+ yield checkState([
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c2", "test1.example.org", "/browser"),
+ getCookieId("c3", "test1.example.org",
+ "/browser/devtools/client/storage/test/"),
+ getCookieId("c4", "test1.example.org",
+ "/browser/devtools/client/storage/test/")
+ ]
+ ],
+ ]);
+
+ ok(!gUI.sidebar.hidden, "Sidebar still visible for next row");
+
+ // Check if next element's value is visible in sidebar
+ yield findVariableViewProperties([{name: "c2", value: "foobar"}]);
+
+ // Keep deleting till no rows
+ gWindow.removeCookie("c3");
+
+ yield gUI.once("store-objects-updated");
+
+ yield checkState([
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c2", "test1.example.org", "/browser"),
+ getCookieId("c4", "test1.example.org",
+ "/browser/devtools/client/storage/test/")
+ ]
+ ],
+ ]);
+
+ // Check if next element's value is visible in sidebar
+ yield findVariableViewProperties([{name: "c2", value: "foobar"}]);
+
+ gWindow.removeCookie("c2", "/browser");
+
+ yield gUI.once("sidebar-updated");
+
+ yield checkState([
+ [
+ ["cookies", "http://test1.example.org"],
+ [
+ getCookieId("c4", "test1.example.org",
+ "/browser/devtools/client/storage/test/")
+ ]
+ ],
+ ]);
+
+ // Check if next element's value is visible in sidebar
+ yield findVariableViewProperties([{name: "c4", value: "booyeah"}]);
+
+ gWindow.removeCookie("c4");
+
+ yield gUI.once("store-objects-updated");
+
+ yield checkState([
+ [["cookies", "http://test1.example.org"], [ ]],
+ ]);
+
+ ok(gUI.sidebar.hidden, "Sidebar is hidden when no rows");
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_dynamic_updates_localStorage.js b/devtools/client/storage/test/browser_storage_dynamic_updates_localStorage.js
new file mode 100644
index 000000000..35912ce3a
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_localStorage.js
@@ -0,0 +1,70 @@
+/* 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";
+
+// Test dynamic updates in the storage inspector for localStorage.
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-updates.html");
+
+ gUI.tree.expandAll();
+
+ ok(gUI.sidebar.hidden, "Sidebar is initially hidden");
+
+ yield checkState([
+ [
+ ["localStorage", "http://test1.example.org"],
+ ["ls1", "ls2", "ls3", "ls4", "ls5", "ls6", "ls7"]
+ ],
+ ]);
+
+ gWindow.localStorage.removeItem("ls4");
+
+ yield gUI.once("store-objects-updated");
+
+ yield checkState([
+ [
+ ["localStorage", "http://test1.example.org"],
+ ["ls1", "ls2", "ls3", "ls5", "ls6", "ls7"]
+ ],
+ ]);
+
+ gWindow.localStorage.setItem("ls4", "again");
+
+ yield gUI.once("store-objects-updated");
+ yield gUI.once("store-objects-updated");
+
+ yield checkState([
+ [
+ ["localStorage", "http://test1.example.org"],
+ ["ls1", "ls2", "ls3", "ls4", "ls5", "ls6", "ls7"]
+ ],
+ ]);
+ // Updating a row
+ gWindow.localStorage.setItem("ls2", "ls2-changed");
+
+ yield gUI.once("store-objects-updated");
+ yield gUI.once("store-objects-updated");
+
+ checkCell("ls2", "value", "ls2-changed");
+
+ // Clearing items. Bug 1233497 makes it so that we can no longer yield
+ // CPOWs from Tasks. We work around this by calling clear via a ContentTask
+ // instead.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ return Task.spawn(content.wrappedJSObject.clear);
+ });
+
+ yield gUI.once("store-objects-cleared");
+
+ yield checkState([
+ [
+ ["localStorage", "http://test1.example.org"],
+ [ ]
+ ],
+ ]);
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js b/devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js
new file mode 100644
index 000000000..8c2f2537e
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js
@@ -0,0 +1,83 @@
+/* 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";
+
+// Test dynamic updates in the storage inspector for sessionStorage.
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-updates.html");
+
+ gUI.tree.expandAll();
+
+ ok(gUI.sidebar.hidden, "Sidebar is initially hidden");
+ yield checkState([
+ [
+ ["sessionStorage", "http://test1.example.org"],
+ ["ss1", "ss2", "ss3"]
+ ],
+ ]);
+
+ gWindow.sessionStorage.setItem("ss4", "new-item");
+
+ yield gUI.once("store-objects-updated");
+ yield gUI.once("store-objects-updated");
+
+ yield checkState([
+ [
+ ["sessionStorage", "http://test1.example.org"],
+ ["ss1", "ss2", "ss3", "ss4"]
+ ],
+ ]);
+
+ // deleting item
+
+ gWindow.sessionStorage.removeItem("ss3");
+
+ yield gUI.once("store-objects-updated");
+
+ gWindow.sessionStorage.removeItem("ss1");
+
+ yield gUI.once("store-objects-updated");
+
+ yield checkState([
+ [
+ ["sessionStorage", "http://test1.example.org"],
+ ["ss2", "ss4"]
+ ],
+ ]);
+
+ yield selectTableItem("ss2");
+
+ ok(!gUI.sidebar.hidden, "sidebar is visible");
+
+ // Checking for correct value in sidebar before update
+ yield findVariableViewProperties([{name: "ss2", value: "foobar"}]);
+
+ gWindow.sessionStorage.setItem("ss2", "changed=ss2");
+
+ yield gUI.once("sidebar-updated");
+
+ checkCell("ss2", "value", "changed=ss2");
+
+ yield findVariableViewProperties([{name: "ss2", value: "changed=ss2"}]);
+
+ // Clearing items. Bug 1233497 makes it so that we can no longer yield
+ // CPOWs from Tasks. We work around this by calling clear via a ContentTask
+ // instead.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ return Task.spawn(content.wrappedJSObject.clear);
+ });
+
+ yield gUI.once("store-objects-cleared");
+
+ yield checkState([
+ [
+ ["sessionStorage", "http://test1.example.org"],
+ [ ]
+ ],
+ ]);
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_empty_objectstores.js b/devtools/client/storage/test/browser_storage_empty_objectstores.js
index 1749c91b8..e6f259742 100644
--- a/devtools/client/storage/test/browser_storage_empty_objectstores.js
+++ b/devtools/client/storage/test/browser_storage_empty_objectstores.js
@@ -21,14 +21,14 @@
// storage-secured-iframe.html and storage-unsecured-iframe.html
const storeItems = [
[["indexedDB", "http://test1.example.org"],
- ["idb1", "idb2"]],
- [["indexedDB", "http://test1.example.org", "idb1"],
+ ["idb1 (default)", "idb2 (default)"]],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)"],
["obj1", "obj2"]],
- [["indexedDB", "http://test1.example.org", "idb2"],
+ [["indexedDB", "http://test1.example.org", "idb2 (default)"],
[]],
- [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
[1, 2, 3]],
- [["indexedDB", "http://test1.example.org", "idb1", "obj2"],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
[1]]
];
diff --git a/devtools/client/storage/test/browser_storage_indexeddb_delete.js b/devtools/client/storage/test/browser_storage_indexeddb_delete.js
index 18a0daf69..5c499c9e9 100644
--- a/devtools/client/storage/test/browser_storage_indexeddb_delete.js
+++ b/devtools/client/storage/test/browser_storage_indexeddb_delete.js
@@ -16,11 +16,11 @@ add_task(function* () {
info("test state before delete");
yield checkState([
- [["indexedDB", "http://test1.example.org"], ["idb1", "idb2"]],
+ [["indexedDB", "http://test1.example.org"], ["idb1 (default)", "idb2 (default)"]],
]);
info("do the delete");
- const deletedDb = ["indexedDB", "http://test1.example.org", "idb1"];
+ const deletedDb = ["indexedDB", "http://test1.example.org", "idb1 (default)"];
yield selectTreeItem(deletedDb);
@@ -40,7 +40,7 @@ add_task(function* () {
info("test state after delete");
yield checkState([
- [["indexedDB", "http://test1.example.org"], ["idb2"]],
+ [["indexedDB", "http://test1.example.org"], ["idb2 (default)"]],
]);
yield finishTests();
diff --git a/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js b/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js
index 6e89c4f28..2d77896f3 100644
--- a/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js
+++ b/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js
@@ -13,19 +13,19 @@ add_task(function* () {
info("test state before delete");
yield checkState([
- [["indexedDB", "http://test1.example.org"], ["idb"]]
+ [["indexedDB", "http://test1.example.org"], ["idb (default)"]]
]);
info("do the delete");
yield selectTreeItem(["indexedDB", "http://test1.example.org"]);
- let actor = gUI.getCurrentActor();
- let result = yield actor.removeDatabase("http://test1.example.org", "idb");
+ let front = gUI.getCurrentFront();
+ let result = yield front.removeDatabase("http://test1.example.org", "idb (default)");
ok(result.blocked, "removeDatabase attempt is blocked");
info("test state after blocked delete");
yield checkState([
- [["indexedDB", "http://test1.example.org"], ["idb"]]
+ [["indexedDB", "http://test1.example.org"], ["idb (default)"]]
]);
let eventWait = gUI.once("store-objects-updated");
@@ -47,7 +47,7 @@ add_task(function* () {
info("try to delete database from nonexistent host");
let errorThrown = false;
try {
- result = yield actor.removeDatabase("http://test2.example.org", "idb");
+ result = yield front.removeDatabase("http://test2.example.org", "idb (default)");
} catch (ex) {
errorThrown = true;
}
diff --git a/devtools/client/storage/test/browser_storage_indexeddb_duplicate_names.js b/devtools/client/storage/test/browser_storage_indexeddb_duplicate_names.js
new file mode 100644
index 000000000..8316d22c5
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_indexeddb_duplicate_names.js
@@ -0,0 +1,31 @@
+/* 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/. */
+
+// Test to verify that indexedDBs with duplicate names (different types / paths)
+// work as expected.
+
+"use strict";
+
+add_task(function* () {
+ const TESTPAGE = MAIN_DOMAIN + "storage-indexeddb-duplicate-names.html";
+
+ setPermission(TESTPAGE, "indexedDB");
+
+ yield openTabAndSetupStorage(TESTPAGE);
+
+ yield checkState([
+ [
+ ["indexedDB", "http://test1.example.org"], [
+ "idb1 (default)",
+ "idb1 (temporary)",
+ "idb1 (persistent)",
+ "idb2 (default)",
+ "idb2 (temporary)",
+ "idb2 (persistent)"
+ ]
+ ]
+ ]);
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_localstorage_add.js b/devtools/client/storage/test/browser_storage_localstorage_add.js
new file mode 100644
index 000000000..de40957b8
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_localstorage_add.js
@@ -0,0 +1,20 @@
+/* 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/. */
+
+// Basic test to check the adding of localStorage entries.
+
+"use strict";
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-localstorage.html");
+ showAllColumns(true);
+
+ yield performAdd(["localStorage", "http://test1.example.org"]);
+ yield performAdd(["localStorage", "http://test1.example.org"]);
+ yield performAdd(["localStorage", "http://test1.example.org"]);
+ yield performAdd(["localStorage", "http://test1.example.org"]);
+ yield performAdd(["localStorage", "http://test1.example.org"]);
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_overflow.js b/devtools/client/storage/test/browser_storage_overflow.js
index 88181ca05..21b931c8e 100644
--- a/devtools/client/storage/test/browser_storage_overflow.js
+++ b/devtools/client/storage/test/browser_storage_overflow.js
@@ -2,40 +2,58 @@
// inspector table.
"use strict";
+const ITEMS_PER_PAGE = 50;
+
add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-overflow.html");
- let $ = id => gPanelWindow.document.querySelector(id);
- let $$ = sel => gPanelWindow.document.querySelectorAll(sel);
-
gUI.tree.expandAll();
yield selectTreeItem(["localStorage", "http://test1.example.org"]);
+ checkCellLength(ITEMS_PER_PAGE);
+
+ yield scroll();
+ checkCellLength(ITEMS_PER_PAGE * 2);
- let table = $("#storage-table .table-widget-body");
- let cellHeight = $(".table-widget-cell").getBoundingClientRect().height;
+ yield scroll();
+ checkCellLength(ITEMS_PER_PAGE * 3);
- is($$("#value .table-widget-cell").length, 50,
- "Table should initially display 50 items");
+ // Check that the columns are sorted in a human readable way (ascending).
+ checkCellValues("ASC");
- let onStoresUpdate = gUI.once("store-objects-updated");
- table.scrollTop += cellHeight * 50;
- yield onStoresUpdate;
+ // Sort descending.
+ clickColumnHeader("name");
- is($$("#value .table-widget-cell").length, 100,
- "Table should display 100 items after scrolling");
+ // Check that the columns are sorted in a human readable way (descending).
+ checkCellValues("DEC");
- onStoresUpdate = gUI.once("store-objects-updated");
- table.scrollTop += cellHeight * 50;
- yield onStoresUpdate;
+ yield finishTests();
+});
- is($$("#value .table-widget-cell").length, 150,
- "Table should display 150 items after scrolling");
+function checkCellLength(len) {
+ let cells = gPanelWindow.document
+ .querySelectorAll("#name .table-widget-cell");
+ let msg = `Table should initially display ${len} items`;
- onStoresUpdate = gUI.once("store-objects-updated");
+ is(cells.length, len, msg);
+}
+
+function checkCellValues(order) {
+ let cells = [...gPanelWindow.document
+ .querySelectorAll("#name .table-widget-cell")];
+ cells.forEach(function (cell, index, arr) {
+ let i = order === "ASC" ? index + 1 : arr.length - index;
+ is(cell.value, `item-${i}`, `Cell value is correct (${order}).`);
+ });
+}
+
+function* scroll() {
+ let $ = id => gPanelWindow.document.querySelector(id);
+
+ let table = $("#storage-table .table-widget-body");
+ let cell = $("#name .table-widget-cell");
+ let cellHeight = cell.getBoundingClientRect().height;
+
+ let onStoresUpdate = gUI.once("store-objects-updated");
table.scrollTop += cellHeight * 50;
yield onStoresUpdate;
-
- is($$("#value .table-widget-cell").length, 160,
- "Table should display all 160 items after scrolling");
- yield finishTests();
-});
+}
diff --git a/devtools/client/storage/test/browser_storage_sessionstorage_add.js b/devtools/client/storage/test/browser_storage_sessionstorage_add.js
new file mode 100644
index 000000000..8f220bc81
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_sessionstorage_add.js
@@ -0,0 +1,20 @@
+/* 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/. */
+
+// Basic test to check the adding of sessionStorage entries.
+
+"use strict";
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-sessionstorage.html");
+ showAllColumns(true);
+
+ yield performAdd(["sessionStorage", "http://test1.example.org"]);
+ yield performAdd(["sessionStorage", "http://test1.example.org"]);
+ yield performAdd(["sessionStorage", "http://test1.example.org"]);
+ yield performAdd(["sessionStorage", "http://test1.example.org"]);
+ yield performAdd(["sessionStorage", "http://test1.example.org"]);
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/browser_storage_sidebar.js b/devtools/client/storage/test/browser_storage_sidebar.js
index 9b60026a0..ed8a333e2 100644
--- a/devtools/client/storage/test/browser_storage_sidebar.js
+++ b/devtools/client/storage/test/browser_storage_sidebar.js
@@ -16,26 +16,26 @@
const testCases = [
{
- location: ["cookies", "sectest1.example.org"],
+ location: ["cookies", "https://sectest1.example.org"],
sidebarHidden: true
},
{
- location: "cs2",
+ location: getCookieId("cs2", ".example.org", "/"),
sidebarHidden: false
},
{
sendEscape: true
},
{
- location: "cs2",
+ location: getCookieId("cs2", ".example.org", "/"),
sidebarHidden: false
},
{
- location: "uc1",
+ location: getCookieId("uc1", ".example.org", "/"),
sidebarHidden: false
},
{
- location: "uc1",
+ location: getCookieId("uc1", ".example.org", "/"),
sidebarHidden: false
},
@@ -72,17 +72,17 @@ const testCases = [
sidebarHidden: true
},
{
- location: "idb2",
+ location: "idb2 (default)",
sidebarHidden: false
},
{
- location: ["indexedDB", "http://test1.example.org", "idb2", "obj3"],
+ location: ["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
sidebarHidden: true
},
{
- location: ["indexedDB", "https://sectest1.example.org", "idb-s2"],
+ location: ["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
sidebarHidden: true
},
{
diff --git a/devtools/client/storage/test/browser_storage_sidebar_update.js b/devtools/client/storage/test/browser_storage_sidebar_update.js
index 419d63020..92547815a 100644
--- a/devtools/client/storage/test/browser_storage_sidebar_update.js
+++ b/devtools/client/storage/test/browser_storage_sidebar_update.js
@@ -26,7 +26,7 @@ add_task(function* () {
for (let i = 0; i < UPDATE_COUNT; i++) {
info(`Performing update #${i}`);
updates.push(gUI.once("sidebar-updated"));
- gUI.displayObjectSidebar();
+ gUI.updateObjectSidebar();
}
yield promise.all(updates);
diff --git a/devtools/client/storage/test/browser_storage_values.js b/devtools/client/storage/test/browser_storage_values.js
index 920ce350e..1d3e9ff76 100644
--- a/devtools/client/storage/test/browser_storage_values.js
+++ b/devtools/client/storage/test/browser_storage_values.js
@@ -17,7 +17,7 @@
const LONG_WORD = "a".repeat(1000);
const testCases = [
- ["cs2", [
+ [getCookieId("cs2", ".example.org", "/"), [
{name: "cs2", value: "sessionCookie"},
{name: "cs2.Path", value: "/"},
{name: "cs2.HostOnly", value: "false"},
@@ -26,7 +26,7 @@ const testCases = [
{name: "cs2.Expires", value: "Session"},
{name: "cs2.Secure", value: "false"},
]],
- ["c1", [
+ [getCookieId("c1", "test1.example.org", "/browser"), [
{name: "c1", value: JSON.stringify(["foo", "Bar", {foo: "Bar"}])},
{name: "c1.Path", value: "/browser"},
{name: "c1.HostOnly", value: "true"},
@@ -42,9 +42,13 @@ const testCases = [
{name: "c1.2", value: "Object"},
{name: "c1.2.foo", value: "Bar"},
], true],
- ["c_encoded", [
- {name: "c_encoded", value: encodeURIComponent(JSON.stringify({foo: {foo1: "bar"}}))}
- ]],
+ [
+ getCookieId("c_encoded", "test1.example.org",
+ "/browser/devtools/client/storage/test/"),
+ [
+ {name: "c_encoded", value: encodeURIComponent(JSON.stringify({foo: {foo1: "bar"}}))}
+ ]
+ ],
[null, [
{name: "c_encoded", value: "Object"},
{name: "c_encoded.foo", value: "Object"},
@@ -120,7 +124,7 @@ const testCases = [
{name: "ss5.3", value: `${LONG_WORD}&${LONG_WORD}`},
{name: "ss5.4", value: `${LONG_WORD}&${LONG_WORD}`},
], true],
- [["indexedDB", "http://test1.example.org", "idb1", "obj1"]],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"]],
[1, [
{name: 1, value: JSON.stringify({id: 1, name: "foo", email: "foo@bar.com"})}
]],
@@ -129,7 +133,7 @@ const testCases = [
{name: "1.name", value: "foo"},
{name: "1.email", value: "foo@bar.com"},
], true],
- [["indexedDB", "http://test1.example.org", "idb1", "obj2"]],
+ [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"]],
[1, [
{name: 1, value: JSON.stringify({
id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"
diff --git a/devtools/client/storage/test/head.js b/devtools/client/storage/test/head.js
index 9662393cf..c734f7b8f 100644
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -15,15 +15,22 @@ Services.scriptloader.loadSubScript(
const {TableWidget} = require("devtools/client/shared/widgets/TableWidget");
const SPLIT_CONSOLE_PREF = "devtools.toolbox.splitconsoleEnabled";
const STORAGE_PREF = "devtools.storage.enabled";
+const DOM_CACHE = "dom.caches.enabled";
const DUMPEMIT_PREF = "devtools.dump.emit";
const DEBUGGERLOG_PREF = "devtools.debugger.log";
// Allows Cache API to be working on usage `http` test page
const CACHES_ON_HTTP_PREF = "dom.caches.testing.enabled";
const PATH = "browser/devtools/client/storage/test/";
const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
+const MAIN_DOMAIN_WITH_PORT = "http://test1.example.org:8000/" + PATH;
const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/server/actors/storage.js,
+// devtools/client/storage/ui.js and devtools/server/tests/browser/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
var gToolbox, gPanelWindow, gWindow, gUI;
// Services.prefs.setBoolPref(DUMPEMIT_PREF, true);
@@ -33,11 +40,12 @@ Services.prefs.setBoolPref(STORAGE_PREF, true);
Services.prefs.setBoolPref(CACHES_ON_HTTP_PREF, true);
registerCleanupFunction(() => {
gToolbox = gPanelWindow = gWindow = gUI = null;
- Services.prefs.clearUserPref(STORAGE_PREF);
- Services.prefs.clearUserPref(SPLIT_CONSOLE_PREF);
- Services.prefs.clearUserPref(DUMPEMIT_PREF);
- Services.prefs.clearUserPref(DEBUGGERLOG_PREF);
Services.prefs.clearUserPref(CACHES_ON_HTTP_PREF);
+ Services.prefs.clearUserPref(DEBUGGERLOG_PREF);
+ Services.prefs.clearUserPref(DOM_CACHE);
+ Services.prefs.clearUserPref(DUMPEMIT_PREF);
+ Services.prefs.clearUserPref(SPLIT_CONSOLE_PREF);
+ Services.prefs.clearUserPref(STORAGE_PREF);
});
/**
@@ -505,10 +513,16 @@ function* selectTreeItem(ids) {
* The id of the row in the table widget
*/
function* selectTableItem(id) {
- let selector = ".table-widget-cell[data-id='" + id + "']";
+ let table = gUI.table;
+ let selector = ".table-widget-column#" + table.uniqueId +
+ " .table-widget-cell[value='" + id + "']";
let target = gPanelWindow.document.querySelector(selector);
ok(target, "table item found with ids " + id);
+ if (!target) {
+ showAvailableIds();
+ }
+
yield click(target);
yield gUI.once("sidebar-updated");
}
@@ -586,22 +600,39 @@ function getRowCells(id, includeHidden = false) {
if (!item) {
ok(false, "Row id '" + id + "' exists");
+
+ showAvailableIds();
}
- let index = table.columns.get(table.uniqueId).visibleCellNodes.indexOf(item);
+ let index = table.columns.get(table.uniqueId).cellNodes.indexOf(item);
let cells = {};
for (let [name, column] of [...table.columns]) {
if (!includeHidden && column.column.parentNode.hidden) {
continue;
}
- cells[name] = column.visibleCellNodes[index];
+ cells[name] = column.cellNodes[index];
}
return cells;
}
/**
+ * Show available ids.
+ */
+function showAvailableIds() {
+ let doc = gPanelWindow.document;
+ let table = gUI.table;
+
+ info("Available ids:");
+ let cells = doc.querySelectorAll(".table-widget-column#" + table.uniqueId +
+ " .table-widget-cell");
+ for (let cell of cells) {
+ info(" - " + cell.getAttribute("value"));
+ }
+}
+
+/**
* Get a cell value.
*
* @param {String} id
@@ -704,6 +735,20 @@ function showColumn(id, state) {
}
/**
+ * Toggle sort direction on a column by clicking on the column header.
+ *
+ * @param {String} id
+ * The uniqueId of the given column.
+ */
+function clickColumnHeader(id) {
+ let columns = gUI.table.columns;
+ let column = columns.get(id);
+ let header = column.header;
+
+ header.click();
+}
+
+/**
* Show or hide all columns.
*
* @param {Boolean} state
@@ -798,9 +843,18 @@ function* checkState(state) {
is(items.size, names.length,
`There is correct number of rows in ${storeName}`);
+
+ if (names.length === 0) {
+ showAvailableIds();
+ }
+
for (let name of names) {
ok(items.has(name),
`There is item with name '${name}' in ${storeName}`);
+
+ if (!items.has(name)) {
+ showAvailableIds();
+ }
}
}
}
@@ -838,3 +892,59 @@ var focusSearchBoxUsingShortcut = Task.async(function* (panelWin, callback) {
callback();
}
});
+
+function getCookieId(name, domain, path) {
+ return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
+}
+
+function setPermission(url, permission) {
+ const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+
+ let uri = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI(url, null, null);
+ let ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let principal = ssm.createCodebasePrincipal(uri, {});
+
+ Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(nsIPermissionManager)
+ .addFromPrincipal(principal, permission,
+ nsIPermissionManager.ALLOW_ACTION);
+}
+
+/**
+ * Add an item.
+ * @param {Array} store
+ * An array containing the path to the store to which we wish to add an
+ * item.
+ */
+function* performAdd(store) {
+ let storeName = store.join(" > ");
+ let toolbar = gPanelWindow.document.getElementById("storage-toolbar");
+ let type = store[0];
+
+ yield selectTreeItem(store);
+
+ let menuAdd = toolbar.querySelector(
+ "#add-button");
+
+ if (menuAdd.hidden) {
+ is(menuAdd.hidden, false,
+ `performAdd called for ${storeName} but it is not supported`);
+ return;
+ }
+
+ let eventEdit = gUI.table.once("row-edit");
+ let eventWait = gUI.once("store-objects-updated");
+
+ menuAdd.click();
+
+ let rowId = yield eventEdit;
+ yield eventWait;
+
+ let key = type === "cookies" ? "uniqueKey" : "name";
+ let value = getCellValue(rowId, key);
+
+ is(rowId, value, `Row '${rowId}' was successfully added.`);
+}
diff --git a/devtools/client/storage/test/storage-indexeddb-duplicate-names.html b/devtools/client/storage/test/storage-indexeddb-duplicate-names.html
new file mode 100644
index 000000000..d8c76dc2a
--- /dev/null
+++ b/devtools/client/storage/test/storage-indexeddb-duplicate-names.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <title>Storage inspector IndexedDBs with duplicate names</title>
+
+ <script type="application/javascript;version=1.7">
+ "use strict";
+
+ function createIndexedDBs() {
+ createIndexedDB("idb1", "temporary");
+ createIndexedDB("idb1", "default");
+ createIndexedDB("idb1", "persistent");
+ createIndexedDB("idb2", "temporary");
+ createIndexedDB("idb2", "default");
+ createIndexedDB("idb2", "persistent");
+ }
+
+ function createIndexedDB(name, storage) {
+ let open = indexedDB.open(name, {storage: storage});
+
+ open.onsuccess = function () {
+ let db = open.result;
+ db.close();
+ };
+ }
+
+ function deleteDB(dbName, storage) {
+ return new Promise(resolve => {
+ dump(`removing database ${dbName} (${storage}) from ${document.location}\n`);
+ indexedDB.deleteDatabase(dbName, { storage: storage }).onsuccess = resolve;
+ });
+ }
+
+ window.clear = function* () {
+ yield deleteDB("idb1", "temporary");
+ yield deleteDB("idb1", "default");
+ yield deleteDB("idb1", "persistent");
+ yield deleteDB("idb2", "temporary");
+ yield deleteDB("idb2", "default");
+ yield deleteDB("idb2", "persistent");
+
+ dump(`removed indexedDB data from ${document.location}\n`);
+ };
+ </script>
+</head>
+<body onload="createIndexedDBs()">
+ <h1>storage-indexeddb-duplicate-names.html</h1>
+</body>
+</html>
diff --git a/devtools/client/storage/test/storage-listings-with-fragment.html b/devtools/client/storage/test/storage-listings-with-fragment.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/devtools/client/storage/test/storage-listings-with-fragment.html
diff --git a/devtools/client/storage/test/storage-listings.html b/devtools/client/storage/test/storage-listings.html
index de3054d3a..385d33193 100644
--- a/devtools/client/storage/test/storage-listings.html
+++ b/devtools/client/storage/test/storage-listings.html
@@ -1,7 +1,7 @@
-<!DOCTYPE HTML>
+<!DOCTYPE HTML>
<html>
<!--
-Bug 970517 - Storage inspector front end - tests
+Storage inspector front end - tests
-->
<head>
<meta charset="utf-8">
@@ -20,7 +20,10 @@ document.cookie = "c1=foobar; expires=" +
new Date(cookieExpiresTime1).toGMTString() + "; path=/browser";
document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname;
document.cookie = "c3=foobar-2; expires=" +
- new Date(cookieExpiresTime2).toGMTString() + "; path=/";
+ new Date(cookieExpiresTime1).toGMTString() + "; path=/";
+document.cookie = "c4=foobar-3; expires=" +
+ new Date(cookieExpiresTime2).toGMTString() + "; path=/; domain=" +
+ partialHostname;
// ... and some local storage items ..
localStorage.setItem("ls1", "foobar");
localStorage.setItem("ls2", "foobar-2");
@@ -110,14 +113,19 @@ let cacheGenerator = function*() {
window.setup = function*() {
yield idbGenerator();
- yield cacheGenerator();
+
+ if (window.caches) {
+ yield cacheGenerator();
+ }
};
window.clear = function*() {
yield deleteDB("idb1");
yield deleteDB("idb2");
- yield caches.delete("plop");
+ if (window.caches) {
+ yield caches.delete("plop");
+ }
dump("removed indexedDB and cache data from " + document.location + "\n");
};
diff --git a/devtools/client/storage/test/storage-overflow.html b/devtools/client/storage/test/storage-overflow.html
index ee8db36e6..6309046b3 100644
--- a/devtools/client/storage/test/storage-overflow.html
+++ b/devtools/client/storage/test/storage-overflow.html
@@ -11,7 +11,7 @@ Bug 1171903 - Storage Inspector endless scrolling
<script type="text/javascript;version=1.8">
"use strict";
-for (let i = 0; i < 160; i++) {
+for (let i = 1; i < 151; i++) {
localStorage.setItem(`item-${i}`, `value-${i}`);
}
</script>
diff --git a/devtools/client/storage/test/storage-secured-iframe.html b/devtools/client/storage/test/storage-secured-iframe.html
index 8424fd4cd..9e1ef60a0 100644
--- a/devtools/client/storage/test/storage-secured-iframe.html
+++ b/devtools/client/storage/test/storage-secured-iframe.html
@@ -1,4 +1,4 @@
-<!DOCTYPE HTML>
+<!DOCTYPE HTML>
<html>
<!--
Iframe for testing multiple host detetion in storage actor
@@ -9,7 +9,10 @@ Iframe for testing multiple host detetion in storage actor
<body>
<script type="application/javascript;version=1.7">
"use strict";
+let cookieExpiresTime = 2000000000000;
document.cookie = "sc1=foobar;";
+document.cookie = "sc2=foobar-2; expires=" +
+ new Date(cookieExpiresTime).toGMTString() + ";";
localStorage.setItem("iframe-s-ls1", "foobar");
sessionStorage.setItem("iframe-s-ss1", "foobar-2");
dump("added cookies and storage from secured iframe\n");
diff --git a/devtools/client/storage/test/storage-unsecured-iframe.html b/devtools/client/storage/test/storage-unsecured-iframe.html
index a69ffdfd1..cd08a6164 100644
--- a/devtools/client/storage/test/storage-unsecured-iframe.html
+++ b/devtools/client/storage/test/storage-unsecured-iframe.html
@@ -9,7 +9,10 @@ Iframe for testing multiple host detetion in storage actor
<body>
<script>
"use strict";
+let cookieExpiresTime = 2000000000000;
document.cookie = "uc1=foobar; domain=.example.org; path=/";
+document.cookie = "uc2=foobar-2; expires=" +
+ new Date(cookieExpiresTime).toGMTString() + "; path=/; domain=.example.org";
localStorage.setItem("iframe-u-ls1", "foobar");
sessionStorage.setItem("iframe-u-ss1", "foobar1");
sessionStorage.setItem("iframe-u-ss2", "foobar2");
diff --git a/devtools/client/storage/test/storage-updates.html b/devtools/client/storage/test/storage-updates.html
index a009814b2..341992f61 100644
--- a/devtools/client/storage/test/storage-updates.html
+++ b/devtools/client/storage/test/storage-updates.html
@@ -38,8 +38,10 @@ window.removeCookie = function(name, path) {
* can be tested.
*/
window.clear = function*() {
- sessionStorage.clear();
+ localStorage.clear();
+ dump("removed localStorage from " + document.location + "\n");
+ sessionStorage.clear();
dump("removed sessionStorage from " + document.location + "\n");
};
diff --git a/devtools/client/storage/ui.js b/devtools/client/storage/ui.js
index 6af493e44..7745c8da9 100644
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -12,6 +12,12 @@ const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
const JSOL = require("devtools/client/shared/vendor/jsol");
const {KeyCodes} = require("devtools/client/shared/keycodes");
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/server/actors/storage.js,
+// devtools/client/storage/test/head.js and
+// devtools/server/tests/browser/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
loader.lazyRequireGetter(this, "TreeWidget",
"devtools/client/shared/widgets/TreeWidget", true);
loader.lazyRequireGetter(this, "TableWidget",
@@ -36,13 +42,6 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = {
preventDescriptorModifiers: true
};
-// Columns which are hidden by default in the storage table
-const HIDDEN_COLUMNS = [
- "creationTime",
- "isDomain",
- "isSecure"
-];
-
const REASON = {
NEW_ROW: "new-row",
NEXT_50_ITEMS: "next-50-items",
@@ -114,8 +113,8 @@ function StorageUI(front, target, panelWin, toolbox) {
cellContextMenuId: "storage-table-popup"
});
- this.displayObjectSidebar = this.displayObjectSidebar.bind(this);
- this.table.on(TableWidget.EVENTS.ROW_SELECTED, this.displayObjectSidebar);
+ this.updateObjectSidebar = this.updateObjectSidebar.bind(this);
+ this.table.on(TableWidget.EVENTS.ROW_SELECTED, this.updateObjectSidebar);
this.handleScrollEnd = this.handleScrollEnd.bind(this);
this.table.on(TableWidget.EVENTS.SCROLL_END, this.handleScrollEnd);
@@ -161,11 +160,24 @@ function StorageUI(front, target, panelWin, toolbox) {
this._tablePopup = this._panelDoc.getElementById("storage-table-popup");
this._tablePopup.addEventListener("popupshowing", this.onTablePopupShowing);
+ this.onRefreshTable = this.onRefreshTable.bind(this);
+ this.onAddItem = this.onAddItem.bind(this);
this.onRemoveItem = this.onRemoveItem.bind(this);
this.onRemoveAllFrom = this.onRemoveAllFrom.bind(this);
this.onRemoveAll = this.onRemoveAll.bind(this);
+ this.onRemoveAllSessionCookies = this.onRemoveAllSessionCookies.bind(this);
this.onRemoveTreeItem = this.onRemoveTreeItem.bind(this);
+ this._refreshButton = this._panelDoc.getElementById("refresh-button");
+ this._refreshButton.addEventListener("command", this.onRefreshTable);
+
+ this._addButton = this._panelDoc.getElementById("add-button");
+ this._addButton.addEventListener("command", this.onAddItem);
+
+ this._tablePopupAddItem = this._panelDoc.getElementById(
+ "storage-table-popup-add");
+ this._tablePopupAddItem.addEventListener("command", this.onAddItem);
+
this._tablePopupDelete = this._panelDoc.getElementById(
"storage-table-popup-delete");
this._tablePopupDelete.addEventListener("command", this.onRemoveItem);
@@ -179,10 +191,20 @@ function StorageUI(front, target, panelWin, toolbox) {
"storage-table-popup-delete-all");
this._tablePopupDeleteAll.addEventListener("command", this.onRemoveAll);
+ this._tablePopupDeleteAllSessionCookies = this._panelDoc.getElementById(
+ "storage-table-popup-delete-all-session-cookies");
+ this._tablePopupDeleteAllSessionCookies.addEventListener("command",
+ this.onRemoveAllSessionCookies);
+
this._treePopupDeleteAll = this._panelDoc.getElementById(
"storage-tree-popup-delete-all");
this._treePopupDeleteAll.addEventListener("command", this.onRemoveAll);
+ this._treePopupDeleteAllSessionCookies = this._panelDoc.getElementById(
+ "storage-tree-popup-delete-all-session-cookies");
+ this._treePopupDeleteAllSessionCookies.addEventListener("command",
+ this.onRemoveAllSessionCookies);
+
this._treePopupDelete = this._panelDoc.getElementById("storage-tree-popup-delete");
this._treePopupDelete.addEventListener("command", this.onRemoveTreeItem);
}
@@ -199,7 +221,7 @@ StorageUI.prototype = {
},
destroy: function () {
- this.table.off(TableWidget.EVENTS.ROW_SELECTED, this.displayObjectSidebar);
+ this.table.off(TableWidget.EVENTS.ROW_SELECTED, this.updateObjectSidebar);
this.table.off(TableWidget.EVENTS.SCROLL_END, this.handleScrollEnd);
this.table.off(TableWidget.EVENTS.CELL_EDIT, this.editItem);
this.table.destroy();
@@ -211,13 +233,20 @@ StorageUI.prototype = {
this.searchBox = null;
this._treePopup.removeEventListener("popupshowing", this.onTreePopupShowing);
+ this._refreshButton.removeEventListener("command", this.onRefreshTable);
+ this._addButton.removeEventListener("command", this.onAddItem);
+ this._tablePopupAddItem.removeEventListener("command", this.onAddItem);
this._treePopupDeleteAll.removeEventListener("command", this.onRemoveAll);
+ this._treePopupDeleteAllSessionCookies.removeEventListener("command",
+ this.onRemoveAllSessionCookies);
this._treePopupDelete.removeEventListener("command", this.onRemoveTreeItem);
this._tablePopup.removeEventListener("popupshowing", this.onTablePopupShowing);
this._tablePopupDelete.removeEventListener("command", this.onRemoveItem);
this._tablePopupDeleteAllFrom.removeEventListener("command", this.onRemoveAllFrom);
this._tablePopupDeleteAll.removeEventListener("command", this.onRemoveAll);
+ this._tablePopupDeleteAllSessionCookies.removeEventListener("command",
+ this.onRemoveAllSessionCookies);
},
/**
@@ -229,7 +258,7 @@ StorageUI.prototype = {
this.table.clearSelection();
},
- getCurrentActor: function () {
+ getCurrentFront: function () {
let type = this.table.datatype;
return this.storageTypes[type];
@@ -250,9 +279,9 @@ StorageUI.prototype = {
},
editItem: function (eventType, data) {
- let actor = this.getCurrentActor();
+ let front = this.getCurrentFront();
- actor.editItem(data);
+ front.editItem(data);
},
/**
@@ -261,17 +290,16 @@ StorageUI.prototype = {
* being removed was selected.
*/
removeItemFromTable: function (name) {
- if (this.table.isSelected(name)) {
+ if (this.table.isSelected(name) && this.table.items.size > 1) {
if (this.table.selectedIndex == 0) {
this.table.selectNextRow();
} else {
this.table.selectPreviousRow();
}
- this.table.remove(name);
- this.displayObjectSidebar();
- } else {
- this.table.remove(name);
}
+
+ this.table.remove(name);
+ this.updateObjectSidebar();
},
/**
@@ -454,22 +482,24 @@ StorageUI.prototype = {
* @param {object} See onUpdate docs
*/
handleChangedItems: function (changed) {
- let [type, host, db, objectStore] = this.tree.selectedItem;
- if (!changed[type] || !changed[type][host] ||
- changed[type][host].length == 0) {
- return;
- }
- try {
- let toUpdate = [];
- for (let name of changed[type][host]) {
- let names = JSON.parse(name);
- if (names[0] == db && names[1] == objectStore && names[2]) {
- toUpdate.push(name);
+ if (this.tree.selectedItem) {
+ let [type, host, db, objectStore] = this.tree.selectedItem;
+ if (!changed[type] || !changed[type][host] ||
+ changed[type][host].length == 0) {
+ return;
+ }
+ try {
+ let toUpdate = [];
+ for (let name of changed[type][host]) {
+ let names = JSON.parse(name);
+ if (names[0] == db && names[1] == objectStore && names[2]) {
+ toUpdate.push(name);
+ }
}
+ this.fetchStorageObjects(type, host, toUpdate, REASON.UPDATE);
+ } catch (ex) {
+ this.fetchStorageObjects(type, host, changed[type][host], REASON.UPDATE);
}
- this.fetchStorageObjects(type, host, toUpdate, REASON.UPDATE);
- } catch (ex) {
- this.fetchStorageObjects(type, host, changed[type][host], REASON.UPDATE);
}
},
@@ -504,7 +534,7 @@ StorageUI.prototype = {
// The indexedDB type could have sub-type data to fetch.
// If having names specified, then it means
// we are fetching details of specific database or of object store.
- if (type == "indexedDB" && names) {
+ if (type === "indexedDB" && names) {
let [ dbName, objectStoreName ] = JSON.parse(names[0]);
if (dbName) {
subType = "database";
@@ -513,6 +543,15 @@ StorageUI.prototype = {
subType = "object store";
}
}
+
+ this.actorSupportsAddItem = yield this._target.actorHasMethod(type, "addItem");
+ this.actorSupportsRemoveItem =
+ yield this._target.actorHasMethod(type, "removeItem");
+ this.actorSupportsRemoveAll =
+ yield this._target.actorHasMethod(type, "removeAll");
+ this.actorSupportsRemoveAllSessionCookies =
+ yield this._target.actorHasMethod(type, "removeAllSessionCookies");
+
yield this.resetColumns(type, host, subType);
}
@@ -520,6 +559,7 @@ StorageUI.prototype = {
if (data.length) {
this.populateTable(data, reason);
}
+ yield this.updateToolbar();
this.emit("store-objects-updated");
} catch (ex) {
console.error(ex);
@@ -527,6 +567,27 @@ StorageUI.prototype = {
}),
/**
+ * Updates the toolbar hiding and showing buttons as appropriate.
+ */
+ updateToolbar: Task.async(function* () {
+ let item = this.tree.selectedItem;
+ let howManyNodesIn = item ? item.length : 0;
+
+ // The first node is just a title e.g. "Cookies" so we need to be at least
+ // 2 nodes in to show the add button.
+ let canAdd = this.actorSupportsAddItem && howManyNodesIn > 1;
+
+ if (canAdd) {
+ this._addButton.hidden = false;
+ this._addButton.setAttribute("tooltiptext",
+ L10N.getFormatStr("storage.popupMenu.addItemLabel"));
+ } else {
+ this._addButton.hidden = true;
+ this._addButton.removeAttribute("tooltiptext");
+ }
+ }),
+
+ /**
* Populates the storage tree which displays the list of storages present for
* the page.
*
@@ -574,23 +635,28 @@ StorageUI.prototype = {
},
/**
- * Populates the selected entry from teh table in the sidebar for a more
+ * Populates the selected entry from the table in the sidebar for a more
* detailed view.
*/
- displayObjectSidebar: Task.async(function* () {
+ updateObjectSidebar: Task.async(function* () {
let item = this.table.selectedRow;
- if (!item) {
- // Make sure that sidebar is hidden and return
- this.sidebar.hidden = true;
- return;
- }
+ let value;
// Get the string value (async action) and the update the UI synchronously.
- let value;
- if (item.name && item.valueActor) {
+ if (item && item.name && item.valueActor) {
value = yield item.valueActor.string();
}
+ // Bail if the selectedRow is no longer selected, the item doesn't exist or the state
+ // changed in another way during the above yield.
+ if (this.table.items.size === 0 ||
+ !item ||
+ !this.table.selectedRow ||
+ item.uniqueKey !== this.table.selectedRow.uniqueKey) {
+ this.hideSidebar();
+ return;
+ }
+
// Start updating the UI. Everything is sync beyond this point.
this.sidebar.hidden = false;
this.view.empty();
@@ -616,6 +682,11 @@ StorageUI.prototype = {
let otherProps = itemProps.filter(
e => !["name", "value", "valueActor"].includes(e));
for (let prop of otherProps) {
+ let column = this.table.columns.get(prop);
+ if (column && column.private) {
+ continue;
+ }
+
let cookieProp = COOKIE_KEY_MAP[prop] || prop;
// The pseduo property of HostOnly refers to converse of isDomain property
rawObject[cookieProp] = (prop === "isDomain") ? !item[prop] : item[prop];
@@ -627,6 +698,11 @@ StorageUI.prototype = {
} else {
// Case when displaying IndexedDB db/object store properties.
for (let key in item) {
+ let column = this.table.columns.get(key);
+ if (column && column.private) {
+ continue;
+ }
+
mainScope.addItem(key, {}, true).setGrip(item[key]);
this.parseItemValue(key, item[key]);
}
@@ -751,11 +827,19 @@ StorageUI.prototype = {
* the storage tree
*/
onHostSelect: function (event, item) {
+ if (!item) {
+ return;
+ }
this.table.clear();
this.hideSidebar();
this.searchBox.value = "";
let [type, host] = item;
+ this.table.host = host;
+ this.table.datatype = type;
+
+ this.updateToolbar();
+
let names = null;
if (!host) {
return;
@@ -786,7 +870,9 @@ StorageUI.prototype = {
let uniqueKey = null;
let columns = {};
let editableFields = [];
- let fields = yield this.getCurrentActor().getFields(subtype);
+ let hiddenFields = [];
+ let privateFields = [];
+ let fields = yield this.getCurrentFront().getFields(subtype);
fields.forEach(f => {
if (!uniqueKey) {
@@ -797,10 +883,21 @@ StorageUI.prototype = {
editableFields.push(f.name);
}
+ if (f.hidden) {
+ hiddenFields.push(f.name);
+ }
+
+ if (f.private) {
+ privateFields.push(f.name);
+ }
+
columns[f.name] = f.name;
let columnName;
try {
- columnName = L10N.getStr("table.headers." + type + "." + f.name);
+ // Path key names for l10n in the case of a string change.
+ let name = f.name === "keyPath" ? "keyPath2" : f.name;
+
+ columnName = L10N.getStr("table.headers." + type + "." + name);
} catch (e) {
columnName = COOKIE_KEY_MAP[f.name];
}
@@ -812,7 +909,7 @@ StorageUI.prototype = {
}
});
- this.table.setColumns(columns, null, HIDDEN_COLUMNS);
+ this.table.setColumns(columns, null, hiddenFields, privateFields);
this.hideSidebar();
yield this.makeFieldsEditable(editableFields);
@@ -857,7 +954,7 @@ StorageUI.prototype = {
case REASON.UPDATE:
this.table.update(item);
if (item == this.table.selectedRow && !this.sidebar.hidden) {
- this.displayObjectSidebar();
+ this.updateObjectSidebar();
}
break;
}
@@ -910,27 +1007,53 @@ StorageUI.prototype = {
},
/**
- * Fires before a cell context menu with the "Delete" action is shown.
- * If the currently selected storage object doesn't support removing items, prevent
- * showing the menu.
+ * Fires before a cell context menu with the "Add" or "Delete" action is
+ * shown. If the currently selected storage object doesn't support adding or
+ * removing items, prevent showing the menu.
*/
onTablePopupShowing: function (event) {
let selectedItem = this.tree.selectedItem;
let type = selectedItem[0];
- let actor = this.getCurrentActor();
// IndexedDB only supports removing items from object stores (level 4 of the tree)
- if (!actor.removeItem || (type === "indexedDB" && selectedItem.length !== 4)) {
+ if ((!this.actorSupportsAddItem && !this.actorSupportsRemoveItem &&
+ type !== "cookies") ||
+ (type === "indexedDB" && selectedItem.length !== 4)) {
event.preventDefault();
return;
}
let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId);
- let name = addEllipsis(data[this.table.uniqueId]);
- this._tablePopupDelete.setAttribute("label",
- L10N.getFormatStr("storage.popupMenu.deleteLabel", name));
+ if (this.actorSupportsRemoveItem) {
+ let name = data[this.table.uniqueId];
+ let separatorRegex = new RegExp(SEPARATOR_GUID, "g");
+ let label = addEllipsis((name + "").replace(separatorRegex, "-"));
+
+ this._tablePopupDelete.hidden = false;
+ this._tablePopupDelete.setAttribute("label",
+ L10N.getFormatStr("storage.popupMenu.deleteLabel", label));
+ } else {
+ this._tablePopupDelete.hidden = true;
+ }
+
+ if (this.actorSupportsAddItem) {
+ this._tablePopupAddItem.hidden = false;
+ this._tablePopupAddItem.setAttribute("label",
+ L10N.getFormatStr("storage.popupMenu.addItemLabel"));
+ } else {
+ this._tablePopupAddItem.hidden = true;
+ }
+
+ let showDeleteAllSessionCookies = false;
+ if (this.actorSupportsRemoveAllSessionCookies) {
+ if (type === "cookies" && selectedItem.length === 2) {
+ showDeleteAllSessionCookies = true;
+ }
+ }
+
+ this._tablePopupDeleteAllSessionCookies.hidden = !showDeleteAllSessionCookies;
if (type === "cookies") {
let host = addEllipsis(data.host);
@@ -949,13 +1072,12 @@ StorageUI.prototype = {
if (selectedItem) {
let type = selectedItem[0];
- let actor = this.storageTypes[type];
// The delete all (aka clear) action is displayed for IndexedDB object stores
// (level 4 of tree), for Cache objects (level 3) and for the whole host (level 2)
// for other storage types (cookies, localStorage, ...).
let showDeleteAll = false;
- if (actor.removeAll) {
+ if (this.actorSupportsRemoveAll) {
let level;
if (type == "indexedDB") {
level = 4;
@@ -972,6 +1094,17 @@ StorageUI.prototype = {
this._treePopupDeleteAll.hidden = !showDeleteAll;
+ // The delete all session cookies action is displayed for cookie object stores
+ // (level 2 of tree)
+ let showDeleteAllSessionCookies = false;
+ if (this.actorSupportsRemoveAllSessionCookies) {
+ if (type === "cookies" && selectedItem.length === 2) {
+ showDeleteAllSessionCookies = true;
+ }
+ }
+
+ this._treePopupDeleteAllSessionCookies.hidden = !showDeleteAllSessionCookies;
+
// The delete action is displayed for:
// - IndexedDB databases (level 3 of the tree)
// - Cache objects (level 3 of the tree)
@@ -993,31 +1126,61 @@ StorageUI.prototype = {
},
/**
+ * Handles refreshing the selected storage
+ */
+ onRefreshTable: function (event) {
+ this.onHostSelect(event, this.tree.selectedItem);
+ },
+
+ /**
+ * Handles adding an item from the storage
+ */
+ onAddItem: function () {
+ if (!this.tree.selectedItem) {
+ return;
+ }
+ let front = this.getCurrentFront();
+ let [, host] = this.tree.selectedItem;
+
+ // Prepare to scroll into view.
+ this.table.scrollIntoViewOnUpdate = true;
+ this.table.editBookmark = createGUID();
+ front.addItem(this.table.editBookmark, host);
+ },
+
+ /**
* Handles removing an item from the storage
*/
onRemoveItem: function () {
let [, host, ...path] = this.tree.selectedItem;
- let actor = this.getCurrentActor();
+ let front = this.getCurrentFront();
let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId);
let name = data[this.table.uniqueId];
if (path.length > 0) {
name = JSON.stringify([...path, name]);
}
- actor.removeItem(host, name);
+ front.removeItem(host, name);
},
/**
* Handles removing all items from the storage
*/
onRemoveAll: function () {
- // Cannot use this.currentActor() if the handler is called from the
- // tree context menu: it returns correct value only after the table
- // data from server are successfully fetched (and that's async).
- let [type, host, ...path] = this.tree.selectedItem;
- let actor = this.storageTypes[type];
+ let [, host, ...path] = this.tree.selectedItem;
+ let front = this.getCurrentFront();
let name = path.length > 0 ? JSON.stringify(path) : undefined;
- actor.removeAll(host, name);
+ front.removeAll(host, name);
+ },
+
+ /**
+ * Handles removing all session cookies from the storage
+ */
+ onRemoveAllSessionCookies: function () {
+ let [, host, ...path] = this.tree.selectedItem;
+ let front = this.getCurrentFront();
+ let name = path.length > 0 ? JSON.stringify(path) : undefined;
+ front.removeAllSessionCookies(host, name);
},
/**
@@ -1026,11 +1189,11 @@ StorageUI.prototype = {
*/
onRemoveAllFrom: function () {
let [, host] = this.tree.selectedItem;
- let actor = this.getCurrentActor();
+ let front = this.getCurrentFront();
let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId);
- actor.removeAll(host, data.host);
+ front.removeAll(host, data.host);
},
onRemoveTreeItem: function () {
@@ -1044,9 +1207,9 @@ StorageUI.prototype = {
},
removeDatabase: function (host, dbName) {
- let actor = this.storageTypes.indexedDB;
+ let front = this.getCurrentFront();
- actor.removeDatabase(host, dbName).then(result => {
+ front.removeDatabase(host, dbName).then(result => {
if (result.blocked) {
let notificationBox = this._toolbox.getNotificationBox();
notificationBox.appendNotification(
@@ -1066,8 +1229,17 @@ StorageUI.prototype = {
},
removeCache: function (host, cacheName) {
- let actor = this.storageTypes.Cache;
+ let front = this.getCurrentFront();
- actor.removeItem(host, JSON.stringify([ cacheName ]));
+ front.removeItem(host, JSON.stringify([ cacheName ]));
},
};
+
+// Helper Functions
+
+function createGUID() {
+ return "{cccccccc-cccc-4ccc-yccc-cccccccccccc}".replace(/[cy]/g, c => {
+ let r = Math.random() * 16 | 0, v = c == "c" ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
+}
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/images/reload.svg b/devtools/client/themes/images/reload.svg
index b04262784..c5d9bf991 100644
--- a/devtools/client/themes/images/reload.svg
+++ b/devtools/client/themes/images/reload.svg
@@ -1,6 +1,7 @@
<!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" width="14" height="14">
- <path d="M12,7H6l2.4-2.4C7.6,4,6.6,3.8,5.5,4.1C4.3,4.5,3.3,5.5,3,6.8 C2.6,9,4.3,11,6.5,11c1,0,2-0.5,2.6-1.2l1.7,1c-1.3,1.6-3.3,2.5-5.6,2c-2-0.5-3.6-2.1-4-4.1C0.4,5.1,3.1,2,6.5,2 c1.3,0,2.4,0.4,3.3,1.2L12,1V7z"/>
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path d="M13.917 7C13.44 4.162 10.973 2 8 2 4.686 2 2 4.686 2 8s2.686 6 6 6c2.22 0 4.16-1.207 5.197-3H12c-.912 1.214-2.364 2-4 2-2.76 0-5-2.24-5-5s2.24-5 5-5c2.42 0 4.437 1.718 4.9 4h1.017z"/>
+ <path d="M14 1L8 7h6V1zm-1 1L9 6h4V2z" fill-rule="evenodd"/>
</svg>
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/themes/storage.css b/devtools/client/themes/storage.css
index 1e611f842..314b6b7fe 100644
--- a/devtools/client/themes/storage.css
+++ b/devtools/client/themes/storage.css
@@ -32,6 +32,26 @@
min-width: 250px;
}
+#storage-toolbar .add-button::before {
+ margin: 0;
+ background-image: url("chrome://devtools/skin/images/add.svg");
+ -moz-user-focus: normal;
+}
+
+#storage-toolbar .refresh-button::before {
+ margin: 0;
+ background-image: url("chrome://devtools/skin/images/reload.svg");
+ -moz-user-focus: normal;
+}
+
+#storage-toolbar .devtools-button {
+ min-width: unset;
+}
+
+#storage-toolbar .devtools-button hbox {
+ display: none;
+}
+
/* Responsive sidebar */
@media (max-width: 700px) {
#storage-tree,
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/new-console-output/test/mochitest/head.js b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
index b71eaec4f..049f3d1ce 100644
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -14,6 +14,7 @@ Services.scriptloader.loadSubScript(
var {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils");
const WEBCONSOLE_STRINGS_URI = "devtools/client/locales/webconsole.properties";
+var {HUDService} = require("devtools/client/webconsole/hudservice");
var WCUL10n = new WebConsoleUtils.L10n(WEBCONSOLE_STRINGS_URI);
Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
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/browser_webconsole_closure_inspection.js b/devtools/client/webconsole/test/browser_webconsole_closure_inspection.js
index 6a29d61aa..769fc0fe5 100644
--- a/devtools/client/webconsole/test/browser_webconsole_closure_inspection.js
+++ b/devtools/client/webconsole/test/browser_webconsole_closure_inspection.js
@@ -58,7 +58,7 @@ function consoleOpened(hud) {
waitForMessages({
webconsole: gWebConsole,
messages: [{
- text: "function _pfactory/<.getName()",
+ text: "getName()",
category: CATEGORY_OUTPUT,
objects: true,
}],
diff --git a/devtools/client/webconsole/test/browser_webconsole_jsterm.js b/devtools/client/webconsole/test/browser_webconsole_jsterm.js
index 221c96fa6..ae5dc71fe 100644
--- a/devtools/client/webconsole/test/browser_webconsole_jsterm.js
+++ b/devtools/client/webconsole/test/browser_webconsole_jsterm.js
@@ -175,7 +175,6 @@ function* testJSTerm(hud) {
"JSMSG_BAD_RADIX": "(42).toString(0);",
"JSMSG_BAD_ARRAY_LENGTH": "([]).length = -1",
"JSMSG_NEGATIVE_REPETITION_COUNT": "'abc'.repeat(-1);",
- "JSMSG_BAD_FORMAL": "var f = Function('x y', 'return x + y;');",
"JSMSG_PRECISION_RANGE": "77.1234.toExponential(-1);",
};
diff --git a/devtools/client/webconsole/test/browser_webconsole_output_02.js b/devtools/client/webconsole/test/browser_webconsole_output_02.js
index 8018669a9..4c61cf041 100644
--- a/devtools/client/webconsole/test/browser_webconsole_output_02.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_02.js
@@ -36,10 +36,10 @@ var inputTests = [
suppressClick: true
},
- // 3 - anonymous function, but spidermonkey gives us an inferred name.
+ // 3 - anonymous function, but gets name.
{
input: "testobj1.testfn2",
- output: "function testobj1.testfn2()",
+ output: "function testfn2()",
printOutput: "function () { return 42; }",
suppressClick: true
},
diff --git a/devtools/client/webconsole/test/browser_webconsole_strict_mode_errors.js b/devtools/client/webconsole/test/browser_webconsole_strict_mode_errors.js
index c8f2200f9..cdaf2764e 100644
--- a/devtools/client/webconsole/test/browser_webconsole_strict_mode_errors.js
+++ b/devtools/client/webconsole/test/browser_webconsole_strict_mode_errors.js
@@ -55,7 +55,7 @@ add_task(function* () {
webconsole: hud,
messages: [
{
- text: "TypeError: setting a property that has only a getter",
+ text: 'TypeError: setting getter-only property "p"',
category: CATEGORY_JS,
severity: SEVERITY_ERROR,
},
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/moz.build b/devtools/moz.build
index 79787d019..8e368facb 100644
--- a/devtools/moz.build
+++ b/devtools/moz.build
@@ -4,13 +4,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/.
-if CONFIG['MOZ_DEVTOOLS'] and CONFIG['MOZ_DEVTOOLS'] not in ('all', 'server'):
- error('Unsupported MOZ_DEVTOOLS value: %s' % (CONFIG['MOZ_DEVTOOLS']))
+if CONFIG['MOZ_DEVTOOLS']:
+ DIRS += ['client']
-if CONFIG['MOZ_DEVTOOLS'] == 'all':
- DIRS += [
- 'client',
- ]
DIRS += [
'server',
diff --git a/devtools/server/actors/errordocs.js b/devtools/server/actors/errordocs.js
index 27f687dc7..7c4b3acff 100644
--- a/devtools/server/actors/errordocs.js
+++ b/devtools/server/actors/errordocs.js
@@ -18,7 +18,6 @@ const ErrorDocs = {
JSMSG_RESULTING_STRING_TOO_LARGE: "Resulting_string_too_large",
JSMSG_BAD_RADIX: "Bad_radix",
JSMSG_PRECISION_RANGE: "Precision_range",
- JSMSG_BAD_FORMAL: "Malformed_formal_parameter",
JSMSG_STMT_AFTER_RETURN: "Stmt_after_return",
JSMSG_NOT_A_CODEPOINT: "Not_a_codepoint",
JSMSG_BAD_SORT_ARG: "Array_sort_argument",
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/object.js b/devtools/server/actors/object.js
index 1f417b951..06e95a5f0 100644
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -1158,11 +1158,6 @@ DebuggerServer.ObjectActorPreviewers = {
}],
RegExp: [function ({obj, hooks}, grip) {
- // Avoid having any special preview for the RegExp.prototype itself.
- if (!obj.proto || obj.proto.class != "RegExp") {
- return false;
- }
-
let str = RegExp.prototype.toString.call(obj.unsafeDereference());
grip.displayString = hooks.createValueGrip(str);
return true;
diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js
index 572cd6b68..c702e8145 100644
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -15,6 +15,17 @@ const {isWindowIncluded} = require("devtools/shared/layout/utils");
const specs = require("devtools/shared/specs/storage");
const { Task } = require("devtools/shared/task");
+const DEFAULT_VALUE = "value";
+
+loader.lazyRequireGetter(this, "naturalSortCaseInsensitive",
+ "devtools/client/shared/natural-sort", true);
+
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/client/storage/ui.js,
+// devtools/client/storage/test/head.js and
+// devtools/server/tests/browser/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
loader.lazyImporter(this, "Sqlite", "resource://gre/modules/Sqlite.jsm");
@@ -87,7 +98,7 @@ var StorageActors = {};
* - observe : Method which gets triggered on the notificaiton of the watched
* topic.
* - getNamesForHost : Given a host, get list of all known store names.
- * - getValuesForHost : Given a host (and optianally a name) get all known
+ * - getValuesForHost : Given a host (and optionally a name) get all known
* store objects.
* - toStoreObject : Given a store object, convert it to the required format
* so that it can be transferred over wire.
@@ -118,7 +129,11 @@ StorageActors.defaults = function (typeName, observationTopic) {
get hosts() {
let hosts = new Set();
for (let {location} of this.storageActor.windows) {
- hosts.add(this.getHostName(location));
+ let host = this.getHostName(location);
+
+ if (host) {
+ hosts.add(host);
+ }
}
return hosts;
},
@@ -132,10 +147,35 @@ StorageActors.defaults = function (typeName, observationTopic) {
},
/**
- * Converts the window.location object into host.
+ * Converts the window.location object into a URL (e.g. http://domain.com).
*/
getHostName(location) {
- return location.hostname || location.href;
+ if (!location) {
+ // Debugging a legacy Firefox extension... no hostname available and no
+ // storage possible.
+ return null;
+ }
+
+ switch (location.protocol) {
+ case "data:":
+ // data: URLs do not support storage of any type.
+ return null;
+ case "about:":
+ // Fallthrough.
+ case "chrome:":
+ // Fallthrough.
+ case "file:":
+ return location.protocol + location.pathname;
+ case "resource:":
+ return location.origin + location.pathname;
+ case "moz-extension:":
+ return location.origin;
+ case "javascript:":
+ return location.href;
+ default:
+ // http: or unknown protocol.
+ return `${location.protocol}//${location.host}`;
+ }
},
initialize(storageActor) {
@@ -188,7 +228,7 @@ StorageActors.defaults = function (typeName, observationTopic) {
*/
onWindowReady: Task.async(function* (window) {
let host = this.getHostName(window.location);
- if (!this.hostVsStores.has(host)) {
+ if (host && !this.hostVsStores.has(host)) {
yield this.populateStoresForHost(host, window);
let data = {};
data[host] = this.getNamesForHost(host);
@@ -209,7 +249,7 @@ StorageActors.defaults = function (typeName, observationTopic) {
return;
}
let host = this.getHostName(window.location);
- if (!this.hosts.has(host)) {
+ if (host && !this.hosts.has(host)) {
this.hostVsStores.delete(host);
let data = {};
data[host] = [];
@@ -315,15 +355,20 @@ StorageActors.defaults = function (typeName, observationTopic) {
toReturn.data.push(...values);
}
}
+
toReturn.total = this.getObjectsSize(host, names, options);
+
if (offset > toReturn.total) {
// In this case, toReturn.data is an empty array.
toReturn.offset = toReturn.total;
toReturn.data = [];
} else {
- toReturn.data = toReturn.data.sort((a, b) => {
- return a[sortOn] - b[sortOn];
- }).slice(offset, offset + size).map(a => this.toStoreObject(a));
+ // We need to use natural sort before slicing.
+ let sorted = toReturn.data.sort((a, b) => {
+ return naturalSortCaseInsensitive(a[sortOn], b[sortOn]);
+ });
+ let sliced = sorted.slice(offset, offset + size);
+ toReturn.data = sliced.map(a => this.toStoreObject(a));
}
} else {
let obj = yield this.getValuesForHost(host, undefined, undefined,
@@ -333,15 +378,18 @@ StorageActors.defaults = function (typeName, observationTopic) {
}
toReturn.total = obj.length;
+
if (offset > toReturn.total) {
// In this case, toReturn.data is an empty array.
toReturn.offset = offset = toReturn.total;
toReturn.data = [];
} else {
- toReturn.data = obj.sort((a, b) => {
- return a[sortOn] - b[sortOn];
- }).slice(offset, offset + size)
- .map(object => this.toStoreObject(object));
+ // We need to use natural sort before slicing.
+ let sorted = obj.sort((a, b) => {
+ return naturalSortCaseInsensitive(a[sortOn], b[sortOn]);
+ });
+ let sliced = sorted.slice(offset, offset + size);
+ toReturn.data = sliced.map(object => this.toStoreObject(object));
}
}
@@ -445,6 +493,9 @@ StorageActors.createActor({
if (cookie.host == null) {
return host == null;
}
+
+ host = trimHttpHttpsPort(host);
+
if (cookie.host.startsWith(".")) {
return ("." + host).endsWith(cookie.host);
}
@@ -460,11 +511,13 @@ StorageActors.createActor({
}
return {
+ uniqueKey: `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+ `${SEPARATOR_GUID}${cookie.path}`,
name: cookie.name,
- path: cookie.path || "",
host: cookie.host || "",
+ path: cookie.path || "",
- // because expires is in seconds
+ // because creationTime is in micro seconds
expires: (cookie.expires || 0) * 1000,
// because it is in micro seconds
@@ -488,7 +541,10 @@ StorageActors.createActor({
for (let cookie of cookies) {
if (this.isCookieAtHost(cookie, host)) {
- this.hostVsStores.get(host).set(cookie.name, cookie);
+ let uniqueKey = `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+ `${SEPARATOR_GUID}${cookie.path}`;
+
+ this.hostVsStores.get(host).set(uniqueKey, cookie);
}
}
},
@@ -521,8 +577,11 @@ StorageActors.createActor({
case "changed":
if (hosts.length) {
for (let host of hosts) {
- this.hostVsStores.get(host).set(subject.name, subject);
- data[host] = [subject.name];
+ let uniqueKey = `${subject.name}${SEPARATOR_GUID}${subject.host}` +
+ `${SEPARATOR_GUID}${subject.path}`;
+
+ this.hostVsStores.get(host).set(uniqueKey, subject);
+ data[host] = [uniqueKey];
}
this.storageActor.update(action, "cookies", data);
}
@@ -531,8 +590,11 @@ StorageActors.createActor({
case "deleted":
if (hosts.length) {
for (let host of hosts) {
- this.hostVsStores.get(host).delete(subject.name);
- data[host] = [subject.name];
+ let uniqueKey = `${subject.name}${SEPARATOR_GUID}${subject.host}` +
+ `${SEPARATOR_GUID}${subject.path}`;
+
+ this.hostVsStores.get(host).delete(uniqueKey);
+ data[host] = [uniqueKey];
}
this.storageActor.update("deleted", "cookies", data);
}
@@ -543,8 +605,11 @@ StorageActors.createActor({
for (let host of hosts) {
let stores = [];
for (let cookie of subject) {
- this.hostVsStores.get(host).delete(cookie.name);
- stores.push(cookie.name);
+ let uniqueKey = `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+ `${SEPARATOR_GUID}${cookie.path}`;
+
+ this.hostVsStores.get(host).delete(uniqueKey);
+ stores.push(uniqueKey);
}
data[host] = stores;
}
@@ -566,15 +631,17 @@ StorageActors.createActor({
getFields: Task.async(function* () {
return [
- { name: "name", editable: 1},
- { name: "path", editable: 1},
- { name: "host", editable: 1},
- { name: "expires", editable: 1},
- { name: "lastAccessed", editable: 0},
- { name: "value", editable: 1},
- { name: "isDomain", editable: 0},
- { name: "isSecure", editable: 1},
- { name: "isHttpOnly", editable: 1}
+ { name: "uniqueKey", editable: false, private: true },
+ { name: "name", editable: true, hidden: false },
+ { name: "host", editable: true, hidden: false },
+ { name: "path", editable: true, hidden: false },
+ { name: "expires", editable: true, hidden: false },
+ { name: "lastAccessed", editable: false, hidden: false },
+ { name: "creationTime", editable: false, hidden: true },
+ { name: "value", editable: true, hidden: false },
+ { name: "isDomain", editable: false, hidden: true },
+ { name: "isSecure", editable: true, hidden: true },
+ { name: "isHttpOnly", editable: true, hidden: false }
];
}),
@@ -591,6 +658,14 @@ StorageActors.createActor({
this.editCookie(data);
}),
+ addItem: Task.async(function* (guid) {
+ let doc = this.storageActor.document;
+ let time = new Date().getTime();
+ let expiry = new Date(time + 3600 * 24 * 1000).toGMTString();
+
+ doc.cookie = `${guid}=${DEFAULT_VALUE};expires=${expiry}`;
+ }),
+
removeItem: Task.async(function* (host, name) {
let doc = this.storageActor.document;
this.removeCookie(host, name, doc.nodePrincipal
@@ -603,6 +678,12 @@ StorageActors.createActor({
.originAttributes);
}),
+ removeAllSessionCookies: Task.async(function* (host, domain) {
+ let doc = this.storageActor.document;
+ this.removeAllSessionCookies(host, domain, doc.nodePrincipal
+ .originAttributes);
+ }),
+
maybeSetupChildProcess() {
cookieHelpers.onCookieChanged = this.onCookieChanged.bind(this);
@@ -619,6 +700,8 @@ StorageActors.createActor({
cookieHelpers.removeCookie.bind(cookieHelpers);
this.removeAllCookies =
cookieHelpers.removeAllCookies.bind(cookieHelpers);
+ this.removeAllSessionCookies =
+ cookieHelpers.removeAllSessionCookies.bind(cookieHelpers);
return;
}
@@ -642,6 +725,8 @@ StorageActors.createActor({
callParentProcess.bind(null, "removeCookie");
this.removeAllCookies =
callParentProcess.bind(null, "removeAllCookies");
+ this.removeAllSessionCookies =
+ callParentProcess.bind(null, "removeAllSessionCookies");
addMessageListener("debug:storage-cookie-request-child",
cookieHelpers.handleParentRequest);
@@ -676,6 +761,8 @@ var cookieHelpers = {
host = "";
}
+ host = trimHttpHttpsPort(host);
+
let cookies = Services.cookies.getCookiesFromHost(host, originAttributes);
let store = [];
@@ -696,7 +783,7 @@ var cookieHelpers = {
* {
* host: "http://www.mozilla.org",
* field: "value",
- * key: "name",
+ * editCookie: "name",
* oldValue: "%7BHello%7D",
* newValue: "%7BHelloo%7D",
* items: {
@@ -720,10 +807,14 @@ var cookieHelpers = {
let origPath = field === "path" ? oldValue : data.items.path;
let cookie = null;
- let enumerator = Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {});
+ let enumerator =
+ Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {});
+
while (enumerator.hasMoreElements()) {
let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
- if (nsiCookie.name === origName && nsiCookie.host === origHost) {
+ if (nsiCookie.name === origName &&
+ nsiCookie.host === origHost &&
+ nsiCookie.path === origPath) {
cookie = {
host: nsiCookie.host,
path: nsiCookie.path,
@@ -743,7 +834,7 @@ var cookieHelpers = {
return;
}
- // If the date is expired set it for 1 minute in the future.
+ // If the date is expired set it for 10 seconds in the future.
let now = new Date();
if (!cookie.isSession && (cookie.expires * 1000) <= now) {
let tenSecondsFromNow = (now.getTime() + 10 * 1000) / 1000;
@@ -797,6 +888,17 @@ var cookieHelpers = {
},
_removeCookies(host, opts = {}) {
+ // We use a uniqueId to emulate compound keys for cookies. We need to
+ // extract the cookie name to remove the correct cookie.
+ if (opts.name) {
+ let split = opts.name.split(SEPARATOR_GUID);
+
+ opts.name = split[0];
+ opts.path = split[2];
+ }
+
+ host = trimHttpHttpsPort(host);
+
function hostMatches(cookieHost, matchHost) {
if (cookieHost == null) {
return matchHost == null;
@@ -807,12 +909,16 @@ var cookieHelpers = {
return cookieHost == host;
}
- let enumerator = Services.cookies.getCookiesFromHost(host, opts.originAttributes || {});
+ let enumerator =
+ Services.cookies.getCookiesFromHost(host, opts.originAttributes || {});
+
while (enumerator.hasMoreElements()) {
let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
if (hostMatches(cookie.host, host) &&
(!opts.name || cookie.name === opts.name) &&
- (!opts.domain || cookie.host === opts.domain)) {
+ (!opts.domain || cookie.host === opts.domain) &&
+ (!opts.path || cookie.path === opts.path) &&
+ (!opts.session || (!cookie.expires && !cookie.maxAge))) {
Services.cookies.remove(
cookie.host,
cookie.name,
@@ -834,6 +940,10 @@ var cookieHelpers = {
this._removeCookies(host, { domain, originAttributes });
},
+ removeAllSessionCookies(host, domain, originAttributes) {
+ this._removeCookies(host, { domain, originAttributes, session: true });
+ },
+
addCookieObservers() {
Services.obs.addObserver(cookieHelpers, "cookie-changed", false);
return null;
@@ -898,6 +1008,12 @@ var cookieHelpers = {
let rowdata = msg.data.args[0];
return cookieHelpers.editCookie(rowdata);
}
+ case "createNewCookie": {
+ let host = msg.data.args[0];
+ let guid = msg.data.args[1];
+ let originAttributes = msg.data.args[2];
+ return cookieHelpers.createNewCookie(host, guid, originAttributes);
+ }
case "removeCookie": {
let host = msg.data.args[0];
let name = msg.data.args[1];
@@ -910,6 +1026,12 @@ var cookieHelpers = {
let originAttributes = msg.data.args[2];
return cookieHelpers.removeAllCookies(host, domain, originAttributes);
}
+ case "removeAllSessionCookies": {
+ let host = msg.data.args[0];
+ let domain = msg.data.args[1];
+ let originAttributes = msg.data.args[2];
+ return cookieHelpers.removeAllSessionCookies(host, domain, originAttributes);
+ }
default:
console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method);
throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD");
@@ -1000,13 +1122,6 @@ function getObjectForLocalOrSessionStorage(type) {
}));
},
- getHostName(location) {
- if (!location.host) {
- return location.href;
- }
- return location.protocol + "//" + location.host;
- },
-
populateStoresForHost(host, window) {
try {
this.hostVsStores.set(host, window[type]);
@@ -1018,17 +1133,28 @@ function getObjectForLocalOrSessionStorage(type) {
populateStoresForHosts() {
this.hostVsStores = new Map();
for (let window of this.windows) {
- this.populateStoresForHost(this.getHostName(window.location), window);
+ let host = this.getHostName(window.location);
+ if (host) {
+ this.populateStoresForHost(host, window);
+ }
}
},
getFields: Task.async(function* () {
return [
- { name: "name", editable: 1},
- { name: "value", editable: 1}
+ { name: "name", editable: true },
+ { name: "value", editable: true }
];
}),
+ addItem: Task.async(function* (guid, host) {
+ let storage = this.hostVsStores.get(host);
+ if (!storage) {
+ return;
+ }
+ storage.setItem(guid, DEFAULT_VALUE);
+ }),
+
/**
* Edit localStorage or sessionStorage fields.
*
@@ -1143,6 +1269,11 @@ StorageActors.createActor({
// The |chrome| cache is the cache implicitely cached by the platform,
// hosting the source file of the service worker.
let { CacheStorage } = this.storageActor.window;
+
+ if (!CacheStorage) {
+ return [];
+ }
+
let cache = new CacheStorage("content", principal);
return cache;
}),
@@ -1205,18 +1336,11 @@ StorageActors.createActor({
getFields: Task.async(function* () {
return [
- { name: "url", editable: 0 },
- { name: "status", editable: 0 }
+ { name: "url", editable: false },
+ { name: "status", editable: false }
];
}),
- getHostName(location) {
- if (!location.host) {
- return location.href;
- }
- return location.protocol + "//" + location.host;
- },
-
populateStoresForHost: Task.async(function* (host) {
let storeMap = new Map();
let caches = yield this.getCachesForHost(host);
@@ -1386,12 +1510,15 @@ ObjectStoreMetadata.prototype = {
* The host associated with this indexed db.
* @param {IDBDatabase} db
* The particular indexed db.
+ * @param {String} storage
+ * Storage type, either "temporary", "default" or "persistent".
*/
-function DatabaseMetadata(origin, db) {
+function DatabaseMetadata(origin, db, storage) {
this._origin = origin;
this._name = db.name;
this._version = db.version;
this._objectStores = [];
+ this.storage = storage;
if (db.objectStoreNames.length) {
let transaction = db.transaction(db.objectStoreNames, "readonly");
@@ -1411,7 +1538,9 @@ DatabaseMetadata.prototype = {
toObject() {
return {
+ uniqueKey: `${this._name}${SEPARATOR_GUID}${this.storage}`,
name: this._name,
+ storage: this.storage,
origin: this._origin,
version: this._version,
objectStores: this._objectStores.size
@@ -1483,13 +1612,6 @@ StorageActors.createActor({
this.removeDBRecord(host, principal, db, store, id);
}),
- getHostName(location) {
- if (!location.host) {
- return location.href;
- }
- return location.protocol + "//" + location.host;
- },
-
/**
* This method is overriden and left blank as for indexedDB, this operation
* cannot be performed synchronously. Thus, the preListStores method exists to
@@ -1579,15 +1701,17 @@ StorageActors.createActor({
populateStoresForHost: Task.async(function* (host) {
let storeMap = new Map();
let {names} = yield this.getDBNamesForHost(host);
+
let win = this.storageActor.getWindowFromHost(host);
if (win) {
let principal = win.document.nodePrincipal;
- for (let name of names) {
- let metadata = yield this.getDBMetaData(host, principal, name);
+ for (let {name, storage} of names) {
+ let metadata = yield this.getDBMetaData(host, principal, name, storage);
metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata);
- storeMap.set(name, metadata);
+
+ storeMap.set(`${name} (${storage})`, metadata);
}
}
@@ -1614,16 +1738,30 @@ StorageActors.createActor({
if ("objectStores" in item) {
// DB meta data
return {
+ uniqueKey: `${item.name} (${item.storage})`,
db: item.name,
+ storage: item.storage,
origin: item.origin,
version: item.version,
objectStores: item.objectStores
};
}
+
+ let value = JSON.stringify(item.value);
+
+ // FIXME: Bug 1318029 - Due to a bug that is thrown whenever a
+ // LongStringActor string reaches DebuggerServer.LONG_STRING_LENGTH we need
+ // to trim the value. When the bug is fixed we should stop trimming the
+ // string here.
+ let maxLength = DebuggerServer.LONG_STRING_LENGTH - 1;
+ if (value.length > maxLength) {
+ value = value.substr(0, maxLength);
+ }
+
// Indexed db entry
return {
name: item.name,
- value: new LongStringActor(this.conn, JSON.stringify(item.value))
+ value: new LongStringActor(this.conn, value)
};
},
@@ -1659,16 +1797,20 @@ StorageActors.createActor({
maybeSetupChildProcess() {
if (!DebuggerServer.isInChildProcess) {
this.backToChild = (func, rv) => rv;
+ this.clearDBStore = indexedDBHelpers.clearDBStore;
+ this.findIDBPathsForHost = indexedDBHelpers.findIDBPathsForHost;
+ this.findSqlitePathsForHost = indexedDBHelpers.findSqlitePathsForHost;
+ this.findStorageTypePaths = indexedDBHelpers.findStorageTypePaths;
this.getDBMetaData = indexedDBHelpers.getDBMetaData;
- this.openWithPrincipal = indexedDBHelpers.openWithPrincipal;
this.getDBNamesForHost = indexedDBHelpers.getDBNamesForHost;
- this.getSanitizedHost = indexedDBHelpers.getSanitizedHost;
this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile;
- this.getValuesForHost = indexedDBHelpers.getValuesForHost;
this.getObjectStoreData = indexedDBHelpers.getObjectStoreData;
+ this.getSanitizedHost = indexedDBHelpers.getSanitizedHost;
+ this.getValuesForHost = indexedDBHelpers.getValuesForHost;
+ this.openWithPrincipal = indexedDBHelpers.openWithPrincipal;
this.removeDB = indexedDBHelpers.removeDB;
this.removeDBRecord = indexedDBHelpers.removeDBRecord;
- this.clearDBStore = indexedDBHelpers.clearDBStore;
+ this.splitNameAndStorage = indexedDBHelpers.splitNameAndStorage;
return;
}
@@ -1681,6 +1823,7 @@ StorageActors.createActor({
});
this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData");
+ this.splitNameAndStorage = callParentProcessAsync.bind(null, "splitNameAndStorage");
this.getDBNamesForHost = callParentProcessAsync.bind(null, "getDBNamesForHost");
this.getValuesForHost = callParentProcessAsync.bind(null, "getValuesForHost");
this.removeDB = callParentProcessAsync.bind(null, "removeDB");
@@ -1725,26 +1868,28 @@ StorageActors.createActor({
// Detail of database
case "database":
return [
- { name: "objectStore", editable: 0 },
- { name: "keyPath", editable: 0 },
- { name: "autoIncrement", editable: 0 },
- { name: "indexes", editable: 0 },
+ { name: "objectStore", editable: false },
+ { name: "keyPath", editable: false },
+ { name: "autoIncrement", editable: false },
+ { name: "indexes", editable: false },
];
// Detail of object store
case "object store":
return [
- { name: "name", editable: 0 },
- { name: "value", editable: 0 }
+ { name: "name", editable: false },
+ { name: "value", editable: false }
];
// Detail of indexedDB for one origin
default:
return [
- { name: "db", editable: 0 },
- { name: "origin", editable: 0 },
- { name: "version", editable: 0 },
- { name: "objectStores", editable: 0 },
+ { name: "uniqueKey", editable: false, private: true },
+ { name: "db", editable: false },
+ { name: "storage", editable: false },
+ { name: "origin", editable: false },
+ { name: "version", editable: false },
+ { name: "objectStores", editable: false },
];
}
})
@@ -1776,14 +1921,14 @@ var indexedDBHelpers = {
* `name` for the given `host` with its `principal`. The stored metadata
* information is of `DatabaseMetadata` type.
*/
- getDBMetaData: Task.async(function* (host, principal, name) {
- let request = this.openWithPrincipal(principal, name);
+ getDBMetaData: Task.async(function* (host, principal, name, storage) {
+ let request = this.openWithPrincipal(principal, name, storage);
let success = promise.defer();
request.onsuccess = event => {
let db = event.target.result;
- let dbData = new DatabaseMetadata(host, db);
+ let dbData = new DatabaseMetadata(host, db, storage);
db.close();
success.resolve(this.backToChild("getDBMetaData", dbData));
@@ -1796,21 +1941,37 @@ var indexedDBHelpers = {
return success.promise;
}),
+ splitNameAndStorage: function (name) {
+ let lastOpenBracketIndex = name.lastIndexOf("(");
+ let lastCloseBracketIndex = name.lastIndexOf(")");
+ let delta = lastCloseBracketIndex - lastOpenBracketIndex - 1;
+
+ let storage = name.substr(lastOpenBracketIndex + 1, delta);
+
+ name = name.substr(0, lastOpenBracketIndex - 1);
+
+ return { storage, name };
+ },
+
/**
* Opens an indexed db connection for the given `principal` and
* database `name`.
*/
- openWithPrincipal(principal, name) {
- return indexedDBForStorage.openForPrincipal(principal, name);
+ openWithPrincipal: function (principal, name, storage) {
+ return indexedDBForStorage.openForPrincipal(principal, name,
+ { storage: storage });
},
- removeDB: Task.async(function* (host, principal, name) {
+ removeDB: Task.async(function* (host, principal, dbName) {
let result = new promise(resolve => {
- let request = indexedDBForStorage.deleteForPrincipal(principal, name);
+ let {name, storage} = this.splitNameAndStorage(dbName);
+ let request =
+ indexedDBForStorage.deleteForPrincipal(principal, name,
+ { storage: storage });
request.onsuccess = () => {
resolve({});
- this.onItemUpdated("deleted", host, [name]);
+ this.onItemUpdated("deleted", host, [dbName]);
};
request.onblocked = () => {
@@ -1836,10 +1997,11 @@ var indexedDBHelpers = {
removeDBRecord: Task.async(function* (host, principal, dbName, storeName, id) {
let db;
+ let {name, storage} = this.splitNameAndStorage(dbName);
try {
db = yield new promise((resolve, reject) => {
- let request = this.openWithPrincipal(principal, dbName);
+ let request = this.openWithPrincipal(principal, name, storage);
request.onsuccess = ev => resolve(ev.target.result);
request.onerror = ev => reject(ev.target.error);
});
@@ -1868,10 +2030,11 @@ var indexedDBHelpers = {
clearDBStore: Task.async(function* (host, principal, dbName, storeName) {
let db;
+ let {name, storage} = this.splitNameAndStorage(dbName);
try {
db = yield new promise((resolve, reject) => {
- let request = this.openWithPrincipal(principal, dbName);
+ let request = this.openWithPrincipal(principal, name, storage);
request.onsuccess = ev => resolve(ev.target.result);
request.onerror = ev => reject(ev.target.error);
});
@@ -1903,46 +2066,101 @@ var indexedDBHelpers = {
*/
getDBNamesForHost: Task.async(function* (host) {
let sanitizedHost = this.getSanitizedHost(host);
- let directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
- "default", sanitizedHost, "idb");
-
- let exists = yield OS.File.exists(directory);
- if (!exists && host.startsWith("about:")) {
- // try for moz-safe-about directory
- sanitizedHost = this.getSanitizedHost("moz-safe-" + host);
- directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
- "permanent", sanitizedHost, "idb");
- exists = yield OS.File.exists(directory);
- }
- if (!exists) {
- return this.backToChild("getDBNamesForHost", {names: []});
+ let profileDir = OS.Constants.Path.profileDir;
+ let files = [];
+ let names = [];
+ let storagePath = OS.Path.join(profileDir, "storage");
+
+ // We expect sqlite DB paths to look something like this:
+ // - PathToProfileDir/storage/default/http+++www.example.com/
+ // idb/1556056096MeysDaabta.sqlite
+ // - PathToProfileDir/storage/permanent/http+++www.example.com/
+ // idb/1556056096MeysDaabta.sqlite
+ // - PathToProfileDir/storage/temporary/http+++www.example.com/
+ // idb/1556056096MeysDaabta.sqlite
+ // The subdirectory inside the storage folder is determined by the storage
+ // type:
+ // - default: { storage: "default" } or not specified.
+ // - permanent: { storage: "persistent" }.
+ // - temporary: { storage: "temporary" }.
+ let sqliteFiles = yield this.findSqlitePathsForHost(storagePath, sanitizedHost);
+
+ for (let file of sqliteFiles) {
+ let splitPath = OS.Path.split(file).components;
+ let idbIndex = splitPath.indexOf("idb");
+ let storage = splitPath[idbIndex - 2];
+ let relative = file.substr(profileDir.length + 1);
+
+ files.push({
+ file: relative,
+ storage: storage === "permanent" ? "persistent" : storage
+ });
}
- let names = [];
- let dirIterator = new OS.File.DirectoryIterator(directory);
- try {
- yield dirIterator.forEach(file => {
- // Skip directories.
- if (file.isDir) {
- return null;
+ if (files.length > 0) {
+ for (let {file, storage} of files) {
+ let name = yield this.getNameFromDatabaseFile(file);
+ if (name) {
+ names.push({
+ name,
+ storage
+ });
}
+ }
+ }
+ return this.backToChild("getDBNamesForHost", {names});
+ }),
- // Skip any non-sqlite files.
- if (!file.name.endsWith(".sqlite")) {
- return null;
+ /**
+ * Find all SQLite files that hold IndexedDB data for a host, such as:
+ * storage/temporary/http+++www.example.com/idb/1556056096MeysDaabta.sqlite
+ */
+ findSqlitePathsForHost: Task.async(function* (storagePath, sanitizedHost) {
+ let sqlitePaths = [];
+ let idbPaths = yield this.findIDBPathsForHost(storagePath, sanitizedHost);
+ for (let idbPath of idbPaths) {
+ let iterator = new OS.File.DirectoryIterator(idbPath);
+ yield iterator.forEach(entry => {
+ if (!entry.isDir && entry.path.endsWith(".sqlite")) {
+ sqlitePaths.push(entry.path);
}
-
- return this.getNameFromDatabaseFile(file.path).then(name => {
- if (name) {
- names.push(name);
- }
- return null;
- });
});
- } finally {
- dirIterator.close();
+ iterator.close();
+ }
+ return sqlitePaths;
+ }),
+
+ /**
+ * Find all paths that hold IndexedDB data for a host, such as:
+ * storage/temporary/http+++www.example.com/idb
+ */
+ findIDBPathsForHost: Task.async(function* (storagePath, sanitizedHost) {
+ let idbPaths = [];
+ let typePaths = yield this.findStorageTypePaths(storagePath);
+ for (let typePath of typePaths) {
+ let idbPath = OS.Path.join(typePath, sanitizedHost, "idb");
+ if (yield OS.File.exists(idbPath)) {
+ idbPaths.push(idbPath);
+ }
}
- return this.backToChild("getDBNamesForHost", {names: names});
+ return idbPaths;
+ }),
+
+ /**
+ * Find all the storage types, such as "default", "permanent", or "temporary".
+ * These names have changed over time, so it seems simpler to look through all types
+ * that currently exist in the profile.
+ */
+ findStorageTypePaths: Task.async(function* (storagePath) {
+ let iterator = new OS.File.DirectoryIterator(storagePath);
+ let typePaths = [];
+ yield iterator.forEach(entry => {
+ if (entry.isDir) {
+ typePaths.push(entry.path);
+ }
+ });
+ iterator.close();
+ return typePaths;
}),
/**
@@ -1950,6 +2168,9 @@ var indexedDBHelpers = {
* name.
*/
getSanitizedHost(host) {
+ if (host.startsWith("about:")) {
+ host = "moz-safe-" + host;
+ }
return host.replace(ILLEGAL_CHAR_REGEX, "+");
},
@@ -1963,7 +2184,7 @@ var indexedDBHelpers = {
// Content pages might be having an open transaction for the same indexed db
// which this sqlite file belongs to. In that case, sqlite.openConnection
- // will throw. Thus we retey for some time to see if lock is removed.
+ // will throw. Thus we retry for some time to see if lock is removed.
while (!connection && retryCount++ < 25) {
try {
connection = yield Sqlite.openConnection({ path: path });
@@ -2024,8 +2245,14 @@ var indexedDBHelpers = {
return this.backToChild("getValuesForHost", {objectStores: objectStores});
}
// Get either all entries from the object store, or a particular id
- let result = yield this.getObjectStoreData(host, principal, db2,
- objectStore, id, options.index, options.size);
+ let storage = hostVsStores.get(host).get(db2).storage;
+ let result = yield this.getObjectStoreData(host, principal, db2, storage, {
+ objectStore: objectStore,
+ id: id,
+ index: options.index,
+ offset: 0,
+ size: options.size
+ });
return this.backToChild("getValuesForHost", {result: result});
}),
@@ -2039,23 +2266,27 @@ var indexedDBHelpers = {
* The principal of the given document.
* @param {string} dbName
* The name of the indexed db from the above host.
- * @param {string} objectStore
- * The name of the object store from the above db.
- * @param {string} id
- * id of the requested entry from the above object store.
- * null if all entries from the above object store are requested.
- * @param {string} index
- * name of the IDBIndex to be iterated on while fetching entries.
- * null or "name" if no index is to be iterated.
- * @param {number} offset
- * ofsset of the entries to be fetched.
- * @param {number} size
- * The intended size of the entries to be fetched.
+ * @param {String} storage
+ * Storage type, either "temporary", "default" or "persistent".
+ * @param {Object} requestOptions
+ * An object in the following format:
+ * {
+ * objectStore: The name of the object store from the above db,
+ * id: Id of the requested entry from the above object
+ * store. null if all entries from the above object
+ * store are requested,
+ * index: Name of the IDBIndex to be iterated on while fetching
+ * entries. null or "name" if no index is to be
+ * iterated,
+ * offset: offset of the entries to be fetched,
+ * size: The intended size of the entries to be fetched
+ * }
*/
- getObjectStoreData(host, principal, dbName, objectStore, id, index,
- offset, size) {
- let request = this.openWithPrincipal(principal, dbName);
+ getObjectStoreData(host, principal, dbName, storage, requestOptions) {
+ let {name} = this.splitNameAndStorage(dbName);
+ let request = this.openWithPrincipal(principal, name, storage);
let success = promise.defer();
+ let {objectStore, id, index, offset, size} = requestOptions;
let data = [];
let db;
@@ -2157,8 +2388,12 @@ var indexedDBHelpers = {
switch (msg.json.method) {
case "getDBMetaData": {
- let [host, principal, name] = args;
- return indexedDBHelpers.getDBMetaData(host, principal, name);
+ let [host, principal, name, storage] = args;
+ return indexedDBHelpers.getDBMetaData(host, principal, name, storage);
+ }
+ case "splitNameAndStorage": {
+ let [name] = args;
+ return indexedDBHelpers.splitNameAndStorage(name);
}
case "getDBNamesForHost": {
let [host] = args;
@@ -2170,8 +2405,8 @@ var indexedDBHelpers = {
hostVsStores, principal);
}
case "removeDB": {
- let [host, principal, name] = args;
- return indexedDBHelpers.removeDB(host, principal, name);
+ let [host, principal, dbName] = args;
+ return indexedDBHelpers.removeDB(host, principal, dbName);
}
case "removeDBRecord": {
let [host, principal, db, store, id] = args;
@@ -2215,6 +2450,24 @@ exports.setupParentProcessForIndexedDB = function ({ mm, prefix }) {
};
/**
+ * General helpers
+ */
+function trimHttpHttpsPort(url) {
+ let match = url.match(/(.+):\d+$/);
+
+ if (match) {
+ url = match[1];
+ }
+ if (url.startsWith("http://")) {
+ return url.substr(7);
+ }
+ if (url.startsWith("https://")) {
+ return url.substr(8);
+ }
+ return url;
+}
+
+/**
* The main Storage Actor.
*/
let StorageActor = protocol.ActorClassWithSpec(specs.storageSpec, {
@@ -2480,6 +2733,7 @@ let StorageActor = protocol.ActorClassWithSpec(specs.storageSpec, {
// added or changed update
this.removeNamesFromUpdateList("added", storeType, data);
this.removeNamesFromUpdateList("changed", storeType, data);
+
for (let host in data) {
if (data[host].length == 0 && this.boundUpdate.added &&
this.boundUpdate.added[storeType] &&
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/server/event-parsers.js b/devtools/server/event-parsers.js
index a813d8e9b..6245af190 100644
--- a/devtools/server/event-parsers.js
+++ b/devtools/server/event-parsers.js
@@ -91,44 +91,58 @@ var parsers = [
return jQueryLiveGetListeners(node, false);
},
normalizeHandler: function (handlerDO) {
- let paths = [
- [".event.proxy/", ".event.proxy/", "*"],
- [".proxy/", "*"]
- ];
-
- let name = handlerDO.displayName;
+ function isFunctionInProxy(funcDO) {
+ // If the anonymous function is inside the |proxy| function and the
+ // function only has guessed atom, the guessed atom should starts with
+ // "proxy/".
+ let displayName = funcDO.displayName;
+ if (displayName && displayName.startsWith("proxy/")) {
+ return true;
+ }
- if (!name) {
- return handlerDO;
+ // If the anonymous function is inside the |proxy| function and the
+ // function gets name at compile time by SetFunctionName, its guessed
+ // atom doesn't contain "proxy/". In that case, check if the caller is
+ // "proxy" function, as a fallback.
+ let calleeDO = funcDO.environment.callee;
+ if (!calleeDO) {
+ return false;
+ }
+ let calleeName = calleeDO.displayName;
+ return calleeName == "proxy";
}
- for (let path of paths) {
- if (name.includes(path[0])) {
- path.splice(0, 1);
-
- for (let point of path) {
- let names = handlerDO.environment.names();
+ function getFirstFunctionVariable(funcDO) {
+ // The handler function inside the |proxy| function should point the
+ // unwrapped function via environment variable.
+ let names = funcDO.environment.names();
+ for (let varName of names) {
+ let varDO = handlerDO.environment.getVariable(varName);
+ if (!varDO) {
+ continue;
+ }
+ if (varDO.class == "Function") {
+ return varDO;
+ }
+ }
+ return null;
+ }
- for (let varName of names) {
- let temp = handlerDO.environment.getVariable(varName);
- if (!temp) {
- continue;
- }
+ if (!isFunctionInProxy(handlerDO)) {
+ return handlerDO;
+ }
- let displayName = temp.displayName;
- if (!displayName) {
- continue;
- }
+ const MAX_NESTED_HANDLER_COUNT = 2;
+ for (let i = 0; i < MAX_NESTED_HANDLER_COUNT; i++) {
+ let funcDO = getFirstFunctionVariable(handlerDO);
+ if (!funcDO)
+ return handlerDO;
- if (temp.class === "Function" &&
- (displayName.includes(point) || point === "*")) {
- handlerDO = temp;
- break;
- }
- }
- }
- break;
+ handlerDO = funcDO;
+ if (isFunctionInProxy(handlerDO)) {
+ continue;
}
+ break;
}
return handlerDO;
diff --git a/devtools/server/tests/browser/browser.ini b/devtools/server/tests/browser/browser.ini
index c05933230..b7929e2b0 100644
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -11,6 +11,7 @@ support-files =
doc_perf.html
navigate-first.html
navigate-second.html
+ storage-cookies-same-name.html
storage-dynamic-windows.html
storage-listings.html
storage-unsecured-iframe.html
@@ -80,6 +81,7 @@ skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still di
#[browser_perf-front-profiler-01.js] bug 1077464
#[browser_perf-front-profiler-05.js] bug 1077464
#[browser_perf-front-profiler-06.js]
+[browser_storage_cookies-duplicate-names.js]
[browser_storage_dynamic_windows.js]
[browser_storage_listings.js]
[browser_storage_updates.js]
diff --git a/devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js b/devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js
new file mode 100644
index 000000000..1cdc8b490
--- /dev/null
+++ b/devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js
@@ -0,0 +1,105 @@
+/* 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/ */
+
+"use strict";
+
+// Test that the storage panel is able to display multiple cookies with the same
+// name (and different paths).
+
+const {StorageFront} = require("devtools/shared/fronts/storage");
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", this);
+
+const TESTDATA = {
+ "http://test1.example.org": [
+ {
+ name: "name",
+ value: "value1",
+ expires: 0,
+ path: "/",
+ host: "test1.example.org",
+ isDomain: false,
+ isSecure: false,
+ },
+ {
+ name: "name",
+ value: "value2",
+ expires: 0,
+ path: "/path2/",
+ host: "test1.example.org",
+ isDomain: false,
+ isSecure: false,
+ },
+ {
+ name: "name",
+ value: "value3",
+ expires: 0,
+ path: "/path3/",
+ host: "test1.example.org",
+ isDomain: false,
+ isSecure: false,
+ }
+ ]
+};
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies-same-name.html");
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = StorageFront(client, form);
+ let data = yield front.listStores();
+
+ ok(data.cookies, "Cookies storage actor is present");
+
+ yield testCookies(data.cookies);
+ yield clearStorage();
+
+ // Forcing GC/CC to get rid of docshells and windows created by this test.
+ forceCollections();
+ yield client.close();
+ forceCollections();
+ DebuggerServer.destroy();
+ forceCollections();
+});
+
+function testCookies(cookiesActor) {
+ let numHosts = Object.keys(cookiesActor.hosts).length;
+ is(numHosts, 1, "Correct number of host entries for cookies");
+ return testCookiesObjects(0, cookiesActor.hosts, cookiesActor);
+}
+
+var testCookiesObjects = Task.async(function* (index, hosts, cookiesActor) {
+ let host = Object.keys(hosts)[index];
+ let matchItems = data => {
+ is(data.total, TESTDATA[host].length,
+ "Number of cookies in host " + host + " matches");
+ for (let item of data.data) {
+ let found = false;
+ for (let toMatch of TESTDATA[host]) {
+ if (item.name === toMatch.name &&
+ item.host === toMatch.host &&
+ item.path === toMatch.path) {
+ found = true;
+ ok(true, "Found cookie " + item.name + " in response");
+ is(item.value.str, toMatch.value, "The value matches.");
+ is(item.expires, toMatch.expires, "The expiry time matches.");
+ is(item.path, toMatch.path, "The path matches.");
+ is(item.host, toMatch.host, "The host matches.");
+ is(item.isSecure, toMatch.isSecure, "The isSecure value matches.");
+ is(item.isDomain, toMatch.isDomain, "The isDomain value matches.");
+ break;
+ }
+ }
+ ok(found, "cookie " + item.name + " should exist in response");
+ }
+ };
+
+ ok(!!TESTDATA[host], "Host is present in the list : " + host);
+ matchItems(yield cookiesActor.getStoreObjects(host));
+ if (index == Object.keys(hosts).length - 1) {
+ return;
+ }
+ yield testCookiesObjects(++index, hosts, cookiesActor);
+});
diff --git a/devtools/server/tests/browser/browser_storage_dynamic_windows.js b/devtools/server/tests/browser/browser_storage_dynamic_windows.js
index 440c91222..a8f791f15 100644
--- a/devtools/server/tests/browser/browser_storage_dynamic_windows.js
+++ b/devtools/server/tests/browser/browser_storage_dynamic_windows.js
@@ -9,8 +9,8 @@ Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtool
const beforeReload = {
cookies: {
- "test1.example.org": ["c1", "cs2", "c3", "uc1"],
- "sectest1.example.org": ["uc1", "cs2"]
+ "http://test1.example.org": ["c1", "cs2", "c3", "uc1"],
+ "http://sectest1.example.org": ["uc1", "cs2"]
},
localStorage: {
"http://test1.example.org": ["ls1", "ls2"],
@@ -66,6 +66,7 @@ function markOutMatched(toBeEmptied, data, deleted) {
info("Testing for " + storageType);
for (let host in data[storageType]) {
ok(toBeEmptied[storageType][host], "Host " + host + " found");
+
if (!deleted) {
for (let item of data[storageType][host]) {
let index = toBeEmptied[storageType][host].indexOf(item);
@@ -87,50 +88,6 @@ function markOutMatched(toBeEmptied, data, deleted) {
}
}
-// function testReload(front) {
-// info("Testing if reload works properly");
-
-// let shouldBeEmptyFirst = Cu.cloneInto(beforeReload, {});
-// let shouldBeEmptyLast = Cu.cloneInto(beforeReload, {});
-// return new Promise(resolve => {
-
-// let onStoresUpdate = data => {
-// info("in stores update of testReload");
-// // This might be second time stores update is happening, in which case,
-// // data.deleted will be null.
-// // OR.. This might be the first time on a super slow machine where both
-// // data.deleted and data.added is missing in the first update.
-// if (data.deleted) {
-// markOutMatched(shouldBeEmptyFirst, data.deleted, true);
-// }
-
-// if (!Object.keys(shouldBeEmptyFirst).length) {
-// info("shouldBeEmptyFirst is empty now");
-// }
-
-// // stores-update call might not have data.added for the first time on
-// // slow machines, in which case, data.added will be null
-// if (data.added) {
-// markOutMatched(shouldBeEmptyLast, data.added);
-// }
-
-// if (!Object.keys(shouldBeEmptyLast).length) {
-// info("Everything to be received is received.");
-// endTestReloaded();
-// }
-// };
-
-// let endTestReloaded = () => {
-// front.off("stores-update", onStoresUpdate);
-// resolve();
-// };
-
-// front.on("stores-update", onStoresUpdate);
-
-// content.location.reload();
-// });
-// }
-
function testAddIframe(front) {
info("Testing if new iframe addition works properly");
return new Promise(resolve => {
@@ -142,7 +99,15 @@ function testAddIframe(front) {
"https://sectest1.example.org": ["iframe-s-ss1"]
},
cookies: {
- "sectest1.example.org": ["sc1"]
+ "https://sectest1.example.org": [
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("sc1", "sectest1.example.org",
+ "/browser/devtools/server/tests/browser/")
+ ],
+ "http://sectest1.example.org": [
+ getCookieId("sc1", "sectest1.example.org",
+ "/browser/devtools/server/tests/browser/")
+ ]
},
indexedDB: {
// empty because indexed db creation happens after the page load, so at
@@ -150,7 +115,7 @@ function testAddIframe(front) {
"https://sectest1.example.org": []
},
Cache: {
- "https://sectest1.example.org":[]
+ "https://sectest1.example.org": []
}
};
diff --git a/devtools/server/tests/browser/browser_storage_listings.js b/devtools/server/tests/browser/browser_storage_listings.js
index 4ff3c3fc1..6c1668321 100644
--- a/devtools/server/tests/browser/browser_storage_listings.js
+++ b/devtools/server/tests/browser/browser_storage_listings.js
@@ -9,7 +9,7 @@ Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtool
const storeMap = {
cookies: {
- "test1.example.org": [
+ "http://test1.example.org": [
{
name: "c1",
value: "foobar",
@@ -20,15 +20,6 @@ const storeMap = {
isSecure: false,
},
{
- name: "cs2",
- value: "sessionCookie",
- path: "/",
- host: ".example.org",
- expires: 0,
- isDomain: true,
- isSecure: false,
- },
- {
name: "c3",
value: "foobar-2",
expires: 2000000001000,
@@ -47,7 +38,29 @@ const storeMap = {
isSecure: true,
}
],
- "sectest1.example.org": [
+
+ "http://sectest1.example.org": [
+ {
+ name: "cs2",
+ value: "sessionCookie",
+ path: "/",
+ host: ".example.org",
+ expires: 0,
+ isDomain: true,
+ isSecure: false,
+ },
+ {
+ name: "sc1",
+ value: "foobar",
+ path: "/browser/devtools/server/tests/browser/",
+ host: "sectest1.example.org",
+ expires: 0,
+ isDomain: false,
+ isSecure: false,
+ }
+ ],
+
+ "https://sectest1.example.org": [
{
name: "uc1",
value: "foobar",
@@ -130,24 +143,24 @@ const storeMap = {
const IDBValues = {
listStoresResponse: {
"http://test1.example.org": [
- ["idb1", "obj1"], ["idb1", "obj2"], ["idb2", "obj3"]
+ ["idb1 (default)", "obj1"], ["idb1 (default)", "obj2"], ["idb2 (default)", "obj3"]
],
"http://sectest1.example.org": [
],
"https://sectest1.example.org": [
- ["idb-s1", "obj-s1"], ["idb-s2", "obj-s2"]
+ ["idb-s1 (default)", "obj-s1"], ["idb-s2 (default)", "obj-s2"]
]
},
- dbDetails : {
+ dbDetails: {
"http://test1.example.org": [
{
- db: "idb1",
+ db: "idb1 (default)",
origin: "http://test1.example.org",
version: 1,
objectStores: 2
},
{
- db: "idb2",
+ db: "idb2 (default)",
origin: "http://test1.example.org",
version: 1,
objectStores: 1
@@ -157,13 +170,13 @@ const IDBValues = {
],
"https://sectest1.example.org": [
{
- db: "idb-s1",
+ db: "idb-s1 (default)",
origin: "https://sectest1.example.org",
version: 1,
objectStores: 1
},
{
- db: "idb-s2",
+ db: "idb-s2 (default)",
origin: "https://sectest1.example.org",
version: 1,
objectStores: 1
@@ -172,7 +185,7 @@ const IDBValues = {
},
objectStoreDetails: {
"http://test1.example.org": {
- idb1: [
+ "idb1 (default)": [
{
objectStore: "obj1",
keyPath: "id",
@@ -199,7 +212,7 @@ const IDBValues = {
indexes: []
}
],
- idb2: [
+ "idb2 (default)": [
{
objectStore: "obj3",
keyPath: "id3",
@@ -217,7 +230,7 @@ const IDBValues = {
},
"http://sectest1.example.org" : {},
"https://sectest1.example.org": {
- "idb-s1": [
+ "idb-s1 (default)": [
{
objectStore: "obj-s1",
keyPath: "id",
@@ -225,7 +238,7 @@ const IDBValues = {
indexes: []
},
],
- "idb-s2": [
+ "idb-s2 (default)": [
{
objectStore: "obj-s2",
keyPath: "id3",
@@ -245,7 +258,7 @@ const IDBValues = {
},
entries: {
"http://test1.example.org": {
- "idb1#obj1": [
+ "idb1 (default)#obj1": [
{
name: 1,
value: {
@@ -271,7 +284,7 @@ const IDBValues = {
}
}
],
- "idb1#obj2": [
+ "idb1 (default)#obj2": [
{
name: 1,
value: {
@@ -282,11 +295,11 @@ const IDBValues = {
}
}
],
- "idb2#obj3": []
+ "idb2 (default)#obj3": []
},
"http://sectest1.example.org" : {},
"https://sectest1.example.org": {
- "idb-s1#obj-s1": [
+ "idb-s1 (default)#obj-s1": [
{
name: 6,
value: {
@@ -304,7 +317,7 @@ const IDBValues = {
}
}
],
- "idb-s2#obj-s2": [
+ "idb-s2 (default)#obj-s2": [
{
name: 13,
value: {
@@ -337,7 +350,8 @@ function* testStores(data) {
}
function testCookies(cookiesActor) {
- is(Object.keys(cookiesActor.hosts).length, 2, "Correct number of host entries for cookies");
+ is(Object.keys(cookiesActor.hosts).length, 3,
+ "Correct number of host entries for cookies");
return testCookiesObjects(0, cookiesActor.hosts, cookiesActor);
}
@@ -346,9 +360,9 @@ var testCookiesObjects = Task.async(function* (index, hosts, cookiesActor) {
let matchItems = data => {
let cookiesLength = 0;
for (let secureCookie of storeMap.cookies[host]) {
- if (secureCookie.isSecure) {
- ++cookiesLength;
- }
+ if (secureCookie.isSecure) {
+ ++cookiesLength;
+ }
}
// Any secure cookies did not get stored in the database.
is(data.total, storeMap.cookies[host].length - cookiesLength,
@@ -478,17 +492,17 @@ var testIndexedDBs = Task.async(function* (index, hosts, indexedDBActor) {
for (let item of data.data) {
let found = false;
for (let toMatch of IDBValues.dbDetails[host]) {
- if (item.db == toMatch.db) {
+ if (item.uniqueKey == toMatch.db) {
found = true;
- ok(true, "Found indexed db " + item.db + " in response");
+ ok(true, "Found indexed db " + item.uniqueKey + " in response");
is(item.origin, toMatch.origin, "The origin matches.");
is(item.version, toMatch.version, "The version matches.");
is(item.objectStores, toMatch.objectStores,
- "The numebr of object stores matches.");
+ "The number of object stores matches.");
break;
}
}
- ok(found, "indexed db " + item.name + " should exist in response");
+ ok(found, "indexed db " + item.uniqueKey + " should exist in response");
}
};
diff --git a/devtools/server/tests/browser/browser_storage_updates.js b/devtools/server/tests/browser/browser_storage_updates.js
index 28b2e509f..4a1604787 100644
--- a/devtools/server/tests/browser/browser_storage_updates.js
+++ b/devtools/server/tests/browser/browser_storage_updates.js
@@ -6,7 +6,7 @@
const {StorageFront} = require("devtools/shared/fronts/storage");
const beforeReload = {
- cookies: ["test1.example.org", "sectest1.example.org"],
+ cookies: ["http://test1.example.org", "https://sectest1.example.org"],
localStorage: ["http://test1.example.org", "http://sectest1.example.org"],
sessionStorage: ["http://test1.example.org", "http://sectest1.example.org"],
};
@@ -27,7 +27,12 @@ const TESTS = [
expected: {
added: {
cookies: {
- "test1.example.org": ["c1", "c2"]
+ "http://test1.example.org": [
+ getCookieId("c1", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ getCookieId("c2", "test1.example.org",
+ "/browser/devtools/server/tests/browser/")
+ ]
},
localStorage: {
"http://test1.example.org": ["l1"]
@@ -48,7 +53,10 @@ const TESTS = [
expected: {
changed: {
cookies: {
- "test1.example.org": ["c1"]
+ "http://test1.example.org": [
+ getCookieId("c1", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
}
},
added: {
@@ -74,7 +82,10 @@ const TESTS = [
expected: {
deleted: {
cookies: {
- "test1.example.org": ["c2"]
+ "http://test1.example.org": [
+ getCookieId("c2", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
},
localStorage: {
"http://test1.example.org": ["l1"]
@@ -112,7 +123,10 @@ const TESTS = [
expected: {
added: {
cookies: {
- "test1.example.org": ["c3"]
+ "http://test1.example.org": [
+ getCookieId("c3", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
},
sessionStorage: {
"http://test1.example.org": ["s1", "s2"]
@@ -125,7 +139,10 @@ const TESTS = [
},
deleted: {
cookies: {
- "test1.example.org": ["c1"]
+ "http://test1.example.org": [
+ getCookieId("c1", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
},
localStorage: {
"http://test1.example.org": ["l2"]
@@ -158,7 +175,10 @@ const TESTS = [
expected: {
deleted: {
cookies: {
- "test1.example.org": ["c3"]
+ "http://test1.example.org": [
+ getCookieId("c3", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
}
}
}
diff --git a/devtools/server/tests/browser/head.js b/devtools/server/tests/browser/head.js
index 1e7f09d95..5cf98c2b0 100644
--- a/devtools/server/tests/browser/head.js
+++ b/devtools/server/tests/browser/head.js
@@ -2,6 +2,10 @@
* 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";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
@@ -19,6 +23,11 @@ const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/server/actors/storage.js,
+// devtools/client/storage/ui.js and devtools/client/storage/test/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
// All tests are asynchronous.
waitForExplicitFinish();
@@ -94,7 +103,6 @@ function once(target, eventName, useCapture = false) {
info("Waiting for event: '" + eventName + "' on " + target + ".");
return new Promise(resolve => {
-
for (let [add, remove] of [
["addEventListener", "removeEventListener"],
["addListener", "removeListener"],
@@ -137,6 +145,8 @@ function getMockTabActor(win) {
}
registerCleanupFunction(function tearDown() {
+ Services.cookies.removeAll();
+
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
@@ -148,8 +158,11 @@ function idleWait(time) {
function busyWait(time) {
let start = Date.now();
+ // eslint-disable-next-line
let stack;
- while (Date.now() - start < time) { stack = Components.stack; }
+ while (Date.now() - start < time) {
+ stack = Components.stack;
+ }
}
/**
@@ -172,11 +185,12 @@ function waitUntil(predicate, interval = 10) {
}
function waitForMarkerType(front, types, predicate,
- unpackFun = (name, data) => data.markers,
- eventName = "timeline-data")
-{
+ unpackFun = (name, data) => data.markers,
+ eventName = "timeline-data") {
types = [].concat(types);
- predicate = predicate || function () { return true; };
+ predicate = predicate || function () {
+ return true;
+ };
let filteredMarkers = [];
let { promise, resolve } = defer();
@@ -190,9 +204,11 @@ function waitForMarkerType(front, types, predicate,
let markers = unpackFun(name, data);
info("Got markers: " + JSON.stringify(markers, null, 2));
- filteredMarkers = filteredMarkers.concat(markers.filter(m => types.indexOf(m.name) !== -1));
+ filteredMarkers = filteredMarkers.concat(
+ markers.filter(m => types.indexOf(m.name) !== -1));
- if (types.every(t => filteredMarkers.some(m => m.name === t)) && predicate(filteredMarkers)) {
+ if (types.every(t => filteredMarkers.some(m => m.name === t)) &&
+ predicate(filteredMarkers)) {
front.off(eventName, handler);
resolve(filteredMarkers);
}
@@ -201,3 +217,7 @@ function waitForMarkerType(front, types, predicate,
return promise;
}
+
+function getCookieId(name, domain, path) {
+ return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
+}
diff --git a/devtools/server/tests/browser/storage-cookies-same-name.html b/devtools/server/tests/browser/storage-cookies-same-name.html
new file mode 100644
index 000000000..e3e092ec3
--- /dev/null
+++ b/devtools/server/tests/browser/storage-cookies-same-name.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Storage inspector cookies with duplicate names</title>
+</head>
+<body onload="createCookies()">
+<script type="application/javascript;version=1.7">
+"use strict";
+function createCookies() {
+ document.cookie = "name=value1;path=/;";
+ document.cookie = "name=value2;path=/path2/;";
+ document.cookie = "name=value3;path=/path3/;";
+}
+
+window.removeCookie = function (name) {
+ document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
+};
+
+window.clearCookies = function () {
+ let cookies = document.cookie;
+ for (let cookie of cookies.split(";")) {
+ removeCookie(cookie.split("=")[0]);
+ }
+};
+</script>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_05.html b/devtools/server/tests/mochitest/test_memory_allocations_05.html
index 0eeb7bd16..4b6b4f0ea 100644..100755
--- a/devtools/server/tests/mochitest/test_memory_allocations_05.html
+++ b/devtools/server/tests/mochitest/test_memory_allocations_05.html
@@ -70,8 +70,9 @@ window.onload = function() {
if (lastTimestamp) {
var delta = timestamp - lastTimestamp;
info("delta since last timestamp", delta);
- ok(delta >= 1 /* ms */,
- "The timestamp should be about 1 ms after the last timestamp.");
+ // If we're unlucky, we might have rounded down to 0ms
+ // ok(delta >= 1 /* ms */,
+ // "The timestamp should be about 1 ms after the last timestamp.");
}
lastTimestamp = timestamp;
diff --git a/devtools/server/tests/unit/test_functiongrips-01.js b/devtools/server/tests/unit/test_functiongrips-01.js
index c41a7cad5..81b1b7767 100644
--- a/devtools/server/tests/unit/test_functiongrips-01.js
+++ b/devtools/server/tests/unit/test_functiongrips-01.js
@@ -52,7 +52,7 @@ function test_inferred_name_function() {
do_check_eq(args[0].class, "Function");
// No name for an anonymous function, but it should have an inferred name.
do_check_eq(args[0].name, undefined);
- do_check_eq(args[0].displayName, "o.m");
+ do_check_eq(args[0].displayName, "m");
let objClient = gThreadClient.pauseGrip(args[0]);
objClient.getParameterNames(function (aResponse) {
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/qrcode/moz.build b/devtools/shared/qrcode/moz.build
index 68a093b1c..c9493a538 100644
--- a/devtools/shared/qrcode/moz.build
+++ b/devtools/shared/qrcode/moz.build
@@ -9,7 +9,7 @@ DIRS += [
]
# Save file size on Fennec until there are active plans to use the decoder there
-if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
+if not CONFIG['MOZ_FENNEC']:
DIRS += [
'decoder'
]
diff --git a/devtools/shared/specs/storage.js b/devtools/shared/specs/storage.js
index d6ddaefe5..42a35073a 100644
--- a/devtools/shared/specs/storage.js
+++ b/devtools/shared/specs/storage.js
@@ -40,6 +40,7 @@ function createStorageSpec(options) {
// Cookies store object
types.addDictType("cookieobject", {
+ uniqueKey: "string",
name: "string",
value: "longstring",
path: "nullable:string",
@@ -61,7 +62,7 @@ types.addDictType("cookiestoreobject", {
// Common methods for edit/remove
const editRemoveMethods = {
- getEditableFields: {
+ getFields: {
request: {},
response: {
value: RetVal("json")
@@ -89,6 +90,13 @@ createStorageSpec({
methods: Object.assign({},
editRemoveMethods,
{
+ addItem: {
+ request: {
+ guid: Arg(0, "string"),
+ },
+ response: {}
+ }
+ }, {
removeAll: {
request: {
host: Arg(0, "string"),
@@ -96,6 +104,14 @@ createStorageSpec({
},
response: {}
}
+ }, {
+ removeAllSessionCookies: {
+ request: {
+ host: Arg(0, "string"),
+ domain: Arg(1, "nullable:string")
+ },
+ response: {}
+ }
}
)
});
@@ -110,6 +126,14 @@ types.addDictType("storageobject", {
const storageMethods = Object.assign({},
editRemoveMethods,
{
+ addItem: {
+ request: {
+ guid: Arg(0, "string"),
+ host: Arg(1, "nullable:string")
+ },
+ response: {}
+ }
+ }, {
removeAll: {
request: {
host: Arg(0, "string")
@@ -176,11 +200,13 @@ createStorageSpec({
// This is a union on idb object, db metadata object and object store metadata
// object
types.addDictType("idbobject", {
+ uniqueKey: "string",
name: "nullable:string",
db: "nullable:string",
objectStore: "nullable:string",
origin: "nullable:string",
version: "nullable:number",
+ storage: "nullable:string",
objectStores: "nullable:number",
keyPath: "nullable:string",
autoIncrement: "nullable:boolean",
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) {
diff --git a/devtools/shared/webconsole/test/test_page_errors.html b/devtools/shared/webconsole/test/test_page_errors.html
index 19e5ba4b4..78138856e 100644
--- a/devtools/shared/webconsole/test/test_page_errors.html
+++ b/devtools/shared/webconsole/test/test_page_errors.html
@@ -102,16 +102,6 @@ function doPageErrors()
warning: false,
exception: true,
},
- "var f = Function('x y', 'return x + y;');": {
- errorMessage: /malformed formal/,
- errorMessageName: "JSMSG_BAD_FORMAL",
- sourceName: /test_page_errors/,
- category: "chrome javascript",
- timeStamp: /^\d+$/,
- error: false,
- warning: false,
- exception: true,
- },
"function a() { return; 1 + 1; }": {
errorMessage: /unreachable code/,
errorMessageName: "JSMSG_STMT_AFTER_RETURN",