--- a/devtools/client/locales/en-US/storage.properties
+++ b/devtools/client/locales/en-US/storage.properties
@@ -30,16 +30,17 @@ tree.labels.cookies=Cookies
tree.labels.localStorage=Local Storage
tree.labels.sessionStorage=Session Storage
tree.labels.indexedDB=Indexed DB
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
table.headers.cookies.expires=Expires on
table.headers.cookies.value=Value
table.headers.cookies.lastAccessed=Last accessed on
table.headers.cookies.creationTime=Created on
--- a/devtools/client/shared/widgets/TableWidget.js
+++ b/devtools/client/shared/widgets/TableWidget.js
@@ -610,27 +610,36 @@ TableWidget.prototype = {
this.menupopup.addEventListener("command", this.onPopupCommand);
popupset.appendChild(this.menupopup);
this.populateMenuPopup();
},
/**
* 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;
}
while (this.menupopup.firstChild) {
this.menupopup.firstChild.remove();
}
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);
menuitem.setAttribute("type", "checkbox");
menuitem.setAttribute("checked", !column.wrapper.getAttribute("hidden"));
if (column.id == this.uniqueId) {
menuitem.setAttribute("disabled", "true");
}
@@ -658,26 +667,31 @@ TableWidget.prototype = {
disabled[disabled.length - 1].removeAttribute("disabled");
}
},
/**
* 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();
}
this.columns.clear();
if (!(sortOn in columns)) {
sortOn = null;
@@ -697,23 +711,24 @@ TableWidget.prototype = {
sortOn = id;
}
if (this.firstColumn && id == this.firstColumn) {
continue;
}
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();
}
}
this.sortedOn = sortOn;
this.sortBy(this.sortedOn);
- this.populateMenuPopup();
+ this.populateMenuPopup(privateColumns);
},
/**
* Returns true if the passed string or the row json object corresponds to the
* selected item in the table.
*/
isSelected: function (item) {
if (typeof item == "object") {
@@ -773,16 +788,21 @@ TableWidget.prototype = {
return;
}
if (this.items.has(item[this.uniqueId])) {
this.update(item);
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) {
column.insertAt(item, index);
}
column.updateZebra();
}
this.items.set(item[this.uniqueId], item);
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -24,17 +24,19 @@ support-files =
[browser_storage_cookies_delete_all.js]
[browser_storage_cookies_domain.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_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_localstorage_edit.js]
[browser_storage_localstorage_error.js]
[browser_storage_overflow.js]
[browser_storage_search.js]
[browser_storage_search_keyboard_trap.js]
--- a/devtools/client/storage/test/browser_storage_basic.js
+++ b/devtools/client/storage/test/browser_storage_basic.js
@@ -1,12 +1,14 @@
/* 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 */
+
// Basic test to assert that the storage tree and table corresponding to each
// item in the storage tree is correctly displayed
// 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
@@ -16,20 +18,33 @@
// ]
// 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", "test1.example.org"],
- ["c1", "cs2", "c3", "uc1"]],
- [["cookies", "sectest1.example.org"],
- ["uc1", "cs2", "sc1"]],
+ [
+ ["cookies", "test1.example.org"],
+ [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c3", "test1.example.org", "/"),
+ getCookieId("uc1", ".example.org", "/")
+ ]
+ ],
+ [
+ ["cookies", "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"]],
--- a/devtools/client/storage/test/browser_storage_cookies_delete_all.js
+++ b/devtools/client/storage/test/browser_storage_cookies_delete_all.js
@@ -16,18 +16,18 @@ function* performDelete(store, rowName,
let menuDeleteAllFromItem = contextMenu.querySelector(
"#storage-table-popup-delete-all-from");
let storeName = store.join(" > ");
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),
@@ -38,34 +38,64 @@ function* performDelete(store, rowName,
yield eventWait;
}
add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
info("test state before delete");
yield checkState([
- [["cookies", "test1.example.org"], ["c1", "c3", "cs2", "uc1"]],
- [["cookies", "sectest1.example.org"], ["cs2", "sc1", "uc1"]],
+ [
+ ["cookies", "test1.example.org"], [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("c3", "test1.example.org", "/"),
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("uc1", ".example.org", "/")
+ ]
+ ],
+ [
+ ["cookies", "sectest1.example.org"], [
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("sc1", "sectest1.example.org",
+ "/browser/devtools/client/storage/test/"),
+ getCookieId("uc1", ".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", "test1.example.org"], id, false);
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", "test1.example.org"],
+ [
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("uc1", ".example.org", "/")
+ ]
+ ],
+ [
+ ["cookies", "sectest1.example.org"],
+ [
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("uc1", ".example.org", "/"),
+ getCookieId("sc1", "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("uc1", ".example.org", "/");
+ yield performDelete(["cookies", "sectest1.example.org"], id, true);
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"], []],
]);
--- a/devtools/client/storage/test/browser_storage_cookies_domain.js
+++ b/devtools/client/storage/test/browser_storage_cookies_domain.js
@@ -8,14 +8,22 @@
// Test that cookies with domain equal to full host name are listed.
// E.g., ".example.org" vs. example.org). Bug 1149497.
add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
yield checkState([
- [["cookies", "test1.example.org"],
- ["test1", "test2", "test3", "test4", "test5"]],
+ [
+ ["cookies", "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();
});
--- a/devtools/client/storage/test/browser_storage_cookies_edit.js
+++ b/devtools/client/storage/test/browser_storage_cookies_edit.js
@@ -5,18 +5,25 @@
// Basic test to check the editing of cookies.
"use strict";
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();
});
--- a/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js
+++ b/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js
@@ -5,19 +5,20 @@
// Basic test to check the editing of cookies with the keyboard.
"use strict";
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(".example.org", "VK_TAB");
yield typeWithTerminator("/", "VK_TAB");
- yield typeWithTerminator(".example.org", "VK_TAB");
yield typeWithTerminator("Tue, 25 Dec 2040 12:00:00 GMT", "VK_TAB");
yield typeWithTerminator("test6value", "VK_TAB");
yield typeWithTerminator("false", "VK_TAB");
yield typeWithTerminator("false", "VK_TAB");
yield finishTests();
});
--- a/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js
+++ b/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js
@@ -5,17 +5,18 @@
// Basic test to check cookie table tab navigation.
"use strict";
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",
"We have tabbed to the correct cell.");
PressKeyXTimes("VK_TAB", 18, {shiftKey: true});
is(getCurrentEditorValue(), "test1",
"We have shift-tabbed to the correct cell.");
--- a/devtools/client/storage/test/browser_storage_delete.js
+++ b/devtools/client/storage/test/browser_storage_delete.js
@@ -8,18 +8,20 @@
// Test deleting storage items
const TEST_CASES = [
[["localStorage", "http://test1.example.org"],
"ls1", "name"],
[["sessionStorage", "http://test1.example.org"],
"ss1", "name"],
- [["cookies", "test1.example.org"],
- "c1", "name"],
+ [
+ ["cookies", "test1.example.org"],
+ getCookieId("c1", "test1.example.org", "/browser"), "name"
+ ],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"],
1, "name"],
[["Cache", "http://test1.example.org", "plop"],
MAIN_DOMAIN + "404_cached_file.js", "url"],
];
add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
@@ -36,17 +38,17 @@ add_task(function* () {
let row = getRowCells(rowName);
ok(gUI.table.items.has(rowName), `There is a row '${rowName}' in ${treeItemName}`);
let eventWait = gUI.once("store-objects-updated");
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)`);
});
yield eventWait;
ok(!gUI.table.items.has(rowName),
`There is no row '${rowName}' in ${treeItemName} after deletion`);
--- a/devtools/client/storage/test/browser_storage_delete_tree.js
+++ b/devtools/client/storage/test/browser_storage_delete_tree.js
@@ -12,17 +12,25 @@ add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
let contextMenu = gPanelWindow.document.getElementById("storage-tree-popup");
let menuDeleteAllItem = contextMenu.querySelector(
"#storage-tree-popup-delete-all");
info("test state before delete");
yield checkState([
- [["cookies", "test1.example.org"], ["c1", "c3", "cs2", "uc1"]],
+ [
+ ["cookies", "test1.example.org"],
+ [
+ getCookieId("c1", "test1.example.org", "/browser"),
+ getCookieId("cs2", ".example.org", "/"),
+ getCookieId("c3", "test1.example.org", "/"),
+ getCookieId("uc1", ".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]],
[["Cache", "http://test1.example.org", "plop"],
[MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]],
]);
info("do the delete");
new file mode 100644
--- /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", "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", "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", "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", "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", "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", "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", "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", "test1.example.org"], [ ]],
+ ]);
+
+ ok(gUI.sidebar.hidden, "Sidebar is hidden when no rows");
+
+ yield finishTests();
+});
new file mode 100644
--- /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();
+});
rename from devtools/client/storage/test/browser_storage_dynamic_updates.js
rename to devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js
--- a/devtools/client/storage/test/browser_storage_dynamic_updates.js
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js
@@ -1,213 +1,84 @@
/* 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");
- let $ = id => gPanelWindow.document.querySelector(id);
- let $$ = sel => gPanelWindow.document.querySelectorAll(sel);
-
gUI.tree.expandAll();
ok(gUI.sidebar.hidden, "Sidebar is initially hidden");
- yield selectTableItem("c1");
- // 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
- ok($("#value [data-id='c1'].table-widget-cell"), "cell is present");
- is($("#value [data-id='c1'].table-widget-cell").value, "1.2.3.4.5.6.7",
- "correct initial value in table");
- gWindow.addCookie("c1", '{"foo": 4,"bar":6}', "/browser");
- yield gUI.once("sidebar-updated");
-
- yield findVariableViewProperties(finalValue[0], false);
- yield findVariableViewProperties(finalValue[1], true);
- ok($("#value [data-id='c1'].table-widget-cell"),
- "cell is present after update");
- is($("#value [data-id='c1'].table-widget-cell").value, '{"foo": 4,"bar":6}',
- "correct final value in table");
-
- // Add a new entry
- is($$("#value .table-widget-cell").length, 2,
- "Correct number of rows before update 0");
-
- 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");
-
- is($$("#value .table-widget-cell").length, 3,
- "Correct number of rows after update 1");
-
- // 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");
-
- is($$("#value .table-widget-cell").length, 4,
- "Correct number of rows after update 2");
-
- // Removing cookies
- gWindow.removeCookie("c1", "/browser");
-
- yield gUI.once("sidebar-updated");
-
- is($$("#value .table-widget-cell").length, 3,
- "Correct number of rows after delete update 3");
-
- ok(!$("#c1"), "Correct row got deleted");
-
- 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");
-
- is($$("#value .table-widget-cell").length, 2,
- "Correct number of rows after delete update 4");
-
- // 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 findVariableViewProperties([{name: "c4", value: "booyeah"}]);
-
- is($$("#value .table-widget-cell").length, 1,
- "Correct number of rows after delete update 5");
-
- gWindow.removeCookie("c4");
-
- yield gUI.once("store-objects-updated");
-
- is($$("#value .table-widget-cell").length, 0,
- "Correct number of rows after delete update 6");
- ok(gUI.sidebar.hidden, "Sidebar is hidden when no rows");
-
- // Testing in local storage
- yield selectTreeItem(["localStorage", "http://test1.example.org"]);
-
- is($$("#value .table-widget-cell").length, 7,
- "Correct number of rows after delete update 7");
-
- ok($(".table-widget-cell[data-id='ls4']"), "ls4 exists before deleting");
-
- gWindow.localStorage.removeItem("ls4");
-
- yield gUI.once("store-objects-updated");
-
- is($$("#value .table-widget-cell").length, 6,
- "Correct number of rows after delete update 8");
- ok(!$(".table-widget-cell[data-id='ls4']"),
- "ls4 does not exists after deleting");
-
- gWindow.localStorage.setItem("ls4", "again");
-
- yield gUI.once("store-objects-updated");
- yield gUI.once("store-objects-updated");
-
- is($$("#value .table-widget-cell").length, 7,
- "Correct number of rows after delete update 9");
- ok($(".table-widget-cell[data-id='ls4']"),
- "ls4 came back after adding it again");
-
- // Updating a row
- gWindow.localStorage.setItem("ls2", "ls2-changed");
-
- yield gUI.once("store-objects-updated");
- yield gUI.once("store-objects-updated");
-
- is($("#value [data-id='ls2']").value, "ls2-changed",
- "Value got updated for local storage");
-
- // Testing in session storage
- yield selectTreeItem(["sessionStorage", "http://test1.example.org"]);
-
- is($$("#value .table-widget-cell").length, 3,
- "Correct number of rows for session storage");
+ 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");
- is($$("#value .table-widget-cell").length, 4,
- "Correct number of rows after session storage update");
+ 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");
- is($$("#value .table-widget-cell").length, 2,
- "Correct number of rows after removing items from session storage");
+ 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");
- is($("#value [data-id='ss2']").value, "changed=ss2",
- "Value got updated for session storage in the table");
+ 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");
- is($$("#value .table-widget-cell").length, 0,
- "Table should be cleared");
+ yield checkState([
+ [
+ ["sessionStorage", "http://test1.example.org"],
+ [ ]
+ ],
+ ]);
yield finishTests();
});
--- a/devtools/client/storage/test/browser_storage_sidebar.js
+++ b/devtools/client/storage/test/browser_storage_sidebar.js
@@ -15,32 +15,32 @@
"use strict";
const testCases = [
{
location: ["cookies", "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
},
{
location: ["localStorage", "http://sectest1.example.org"],
sidebarHidden: true
},
{
--- a/devtools/client/storage/test/browser_storage_values.js
+++ b/devtools/client/storage/test/browser_storage_values.js
@@ -12,44 +12,48 @@
// true if the check is to be made in the parsed value section
// ]
"use strict";
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"},
{name: "cs2.HttpOnly", value: "false"},
{name: "cs2.Domain", value: ".example.org"},
{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"},
{name: "c1.HttpOnly", value: "false"},
{name: "c1.Domain", value: "test1.example.org"},
{name: "c1.Expires", value: new Date(2000000000000).toUTCString()},
{name: "c1.Secure", value: "false"},
]],
[null, [
{name: "c1", value: "Array"},
{name: "c1.0", value: "foo"},
{name: "c1.1", value: "Bar"},
{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"},
{name: "c_encoded.foo.foo1", value: "bar"}
], true],
[["localStorage", "http://test1.example.org"]],
["ls2", [
{name: "ls2", value: "foobar-2"}
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -19,16 +19,21 @@ const DUMPEMIT_PREF = "devtools.dump.emi
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 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);
// Services.prefs.setBoolPref(DEBUGGERLOG_PREF, true);
Services.prefs.setBoolPref(STORAGE_PREF, true);
Services.prefs.setBoolPref(CACHES_ON_HTTP_PREF, true);
registerCleanupFunction(() => {
@@ -500,20 +505,27 @@ function* selectTreeItem(ids) {
/**
* Click selects a row in the table.
*
* @param {String} id
* 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");
}
/**
* Wait for eventName on target.
* @param {Object} target An observable object that either supports on/off or
* addEventListener/removeEventListener
@@ -581,32 +593,49 @@ function getRowValues(id, includeHidden
function getRowCells(id, includeHidden = false) {
let doc = gPanelWindow.document;
let table = gUI.table;
let item = doc.querySelector(".table-widget-column#" + table.uniqueId +
" .table-widget-cell[value='" + id + "']");
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
* The uniqueId of the row.
* @param {String} column
* The id of the column
*
* @yield {String}
@@ -793,19 +822,28 @@ function* checkState(state) {
let storeName = store.join(" > ");
info(`Selecting tree item ${storeName}`);
yield selectTreeItem(store);
let items = gUI.table.items;
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();
+ }
}
}
}
/**
* Checks if document's active element is within the given element.
* @param {HTMLDocument} doc document with active element in question
* @param {DOMNode} container element tested on focus containment
@@ -833,8 +871,12 @@ var focusSearchBoxUsingShortcut = Task.a
synthesizeKeyShortcut(strings.GetStringFromName("storage.filter.key"));
yield focused;
if (callback) {
callback();
}
});
+
+function getCookieId(name, domain, path) {
+ return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
+}
--- a/devtools/client/storage/test/storage-updates.html
+++ b/devtools/client/storage/test/storage-updates.html
@@ -33,18 +33,20 @@ window.removeCookie = function(name, pat
};
/**
* We keep this method here even though these items are automatically cleared
* after the test is complete. this is so that the store-objects-cleared event
* can be tested.
*/
window.clear = function*() {
+ localStorage.clear();
+ dump("removed localStorage from " + document.location + "\n");
+
sessionStorage.clear();
-
dump("removed sessionStorage from " + document.location + "\n");
};
window.onload = function() {
addCookie("c1", "1.2.3.4.5.6.7", "/browser");
addCookie("c2", "foobar", "/browser");
localStorage.setItem("ls1", "testing");
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -7,16 +7,22 @@
const {Task} = require("devtools/shared/task");
const EventEmitter = require("devtools/shared/event-emitter");
const {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
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",
"devtools/client/shared/widgets/TableWidget", true);
loader.lazyRequireGetter(this, "ViewHelpers",
"devtools/client/shared/widgets/view-helpers");
loader.lazyImporter(this, "VariablesView",
"resource://devtools/client/shared/widgets/VariablesView.jsm");
@@ -31,23 +37,16 @@ const GENERIC_VARIABLES_VIEW_SETTINGS =
lazyEmpty: true,
// ms
lazyEmptyDelay: 10,
searchEnabled: true,
searchPlaceholder: L10N.getStr("storage.search.placeholder"),
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",
POPULATE: "populate",
UPDATE: "update"
};
const COOKIE_KEY_MAP = {
@@ -781,43 +780,53 @@ StorageUI.prototype = {
*/
resetColumns: function* (type, host, subtype) {
this.table.host = host;
this.table.datatype = type;
let uniqueKey = null;
let columns = {};
let editableFields = [];
+ let hiddenFields = [];
+ let privateFields = [];
let fields = yield this.getCurrentActor().getFields(subtype);
fields.forEach(f => {
if (!uniqueKey) {
this.table.uniqueId = uniqueKey = f.name;
}
if (f.editable) {
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);
} catch (e) {
columnName = COOKIE_KEY_MAP[f.name];
}
if (!columnName) {
console.error("Unable to localize table header type:" + type + " key:" + f.name);
} else {
columns[f.name] = columnName;
}
});
- this.table.setColumns(columns, null, HIDDEN_COLUMNS);
+ this.table.setColumns(columns, null, hiddenFields, privateFields);
this.hideSidebar();
yield this.makeFieldsEditable(editableFields);
},
/**
* Populates or updates the rows in the storage table.
*
@@ -922,20 +931,23 @@ StorageUI.prototype = {
// IndexedDB only supports removing items from object stores (level 4 of the tree)
if (!actor.removeItem || (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]);
+ let name = data[this.table.uniqueId];
+
+ let separatorRegex = new RegExp(SEPARATOR_GUID, "g");
+ let label = addEllipsis((name + "").replace(separatorRegex, "-"));
this._tablePopupDelete.setAttribute("label",
- L10N.getFormatStr("storage.popupMenu.deleteLabel", name));
+ L10N.getFormatStr("storage.popupMenu.deleteLabel", label));
if (type === "cookies") {
let host = addEllipsis(data.host);
this._tablePopupDeleteAllFrom.hidden = false;
this._tablePopupDeleteAllFrom.setAttribute("label",
L10N.getFormatStr("storage.popupMenu.deleteAllFromLabel", host));
} else {
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -10,16 +10,22 @@ const protocol = require("devtools/share
const {LongStringActor} = require("devtools/server/actors/string");
const {DebuggerServer} = require("devtools/server/main");
const Services = require("Services");
const promise = require("promise");
const {isWindowIncluded} = require("devtools/shared/layout/utils");
const specs = require("devtools/shared/specs/storage");
const { Task } = require("devtools/shared/task");
+// 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");
// We give this a funny name to avoid confusion with the global
// indexedDB.
loader.lazyGetter(this, "indexedDBForStorage", () => {
// On xpcshell, we can't instantiate indexedDB without crashing
try {
@@ -462,24 +468,26 @@ StorageActors.createActor({
},
toStoreObject(cookie) {
if (!cookie) {
return null;
}
return {
+ uniqueKey: `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+ `${SEPARATOR_GUID}${cookie.path}`,
name: cookie.name,
+ host: cookie.host || "",
path: cookie.path || "",
- host: cookie.host || "",
// because expires is in seconds
expires: (cookie.expires || 0) * 1000,
- // because it is in micro seconds
+ // because creationTime is in micro seconds
creationTime: cookie.creationTime / 1000,
// - do -
lastAccessed: cookie.lastAccessed / 1000,
value: new LongStringActor(this.conn, cookie.value || ""),
isDomain: cookie.isDomain,
isSecure: cookie.isSecure,
isHttpOnly: cookie.isHttpOnly
@@ -490,17 +498,20 @@ StorageActors.createActor({
this.hostVsStores.set(host, new Map());
let doc = this.storageActor.document;
let cookies = this.getCookiesFromHost(host, doc.nodePrincipal
.originAttributes);
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);
}
}
},
/**
* Notification observer for "cookie-change".
*
* @param subject
@@ -523,40 +534,49 @@ StorageActors.createActor({
let hosts = this.getMatchingHosts(subject);
let data = {};
switch (action) {
case "added":
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);
}
break;
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);
}
break;
case "batch-deleted":
if (hosts.length) {
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;
}
this.storageActor.update("deleted", "cookies", data);
}
break;
case "cleared":
@@ -568,25 +588,27 @@ StorageActors.createActor({
}
break;
}
return null;
},
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 }
];
}),
/**
* Pass the editItem command from the content to the chrome process.
*
* @param {Object} data
* See editCookie() for format details.
@@ -698,17 +720,17 @@ var cookieHelpers = {
/**
* Apply the results of a cookie edit.
*
* @param {Object} data
* An object in the following format:
* {
* host: "http://www.mozilla.org",
* field: "value",
- * key: "name",
+ * editCookie: "name",
* oldValue: "%7BHello%7D",
* newValue: "%7BHelloo%7D",
* items: {
* name: "optimizelyBuckets",
* path: "/",
* host: ".mozilla.org",
* expires: "Mon, 02 Jun 2025 12:37:37 GMT",
* creationTime: "Tue, 18 Nov 2014 16:21:18 GMT",
@@ -722,20 +744,23 @@ var cookieHelpers = {
*/
editCookie(data) {
let {field, oldValue, newValue} = data;
let origName = field === "name" ? oldValue : data.items.name;
let origHost = field === "host" ? oldValue : data.items.host;
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,
name: nsiCookie.name,
value: nsiCookie.value,
isSecure: nsiCookie.isSecure,
isHttpOnly: nsiCookie.isHttpOnly,
isSession: nsiCookie.isSession,
@@ -745,17 +770,17 @@ var cookieHelpers = {
break;
}
}
if (!cookie) {
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;
cookie.expires = tenSecondsFromNow;
}
switch (field) {
@@ -799,32 +824,44 @@ var cookieHelpers = {
cookie.isHttpOnly,
cookie.isSession,
cookie.isSession ? MAX_COOKIE_EXPIRY : cookie.expires,
cookie.originAttributes
);
},
_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];
+ }
+
function hostMatches(cookieHost, matchHost) {
if (cookieHost == null) {
return matchHost == null;
}
if (cookieHost.startsWith(".")) {
return ("." + matchHost).endsWith(cookieHost);
}
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)) {
Services.cookies.remove(
cookie.host,
cookie.name,
cookie.path,
false,
cookie.originAttributes
);
}
@@ -1026,18 +1063,18 @@ function getObjectForLocalOrSessionStora
this.hostVsStores = new Map();
for (let window of this.windows) {
this.populateStoresForHost(this.getHostName(window.location), window);
}
},
getFields: Task.async(function* () {
return [
- { name: "name", editable: 1},
- { name: "value", editable: 1}
+ { name: "name", editable: true },
+ { name: "value", editable: true }
];
}),
/**
* Edit localStorage or sessionStorage fields.
*
* @param {Object} data
* See editCookie() for format details.
@@ -1207,18 +1244,18 @@ StorageActors.createActor({
return {
url: String(request.url),
status: String(response.statusText),
};
}),
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;
@@ -1731,36 +1768,36 @@ StorageActors.createActor({
}
},
getFields: Task.async(function* (subType) {
switch (subType) {
// 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: "db", editable: false },
+ { name: "origin", editable: false },
+ { name: "version", editable: false },
+ { name: "objectStores", editable: false },
];
}
})
});
var indexedDBHelpers = {
backToChild(...args) {
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
@@ -2489,16 +2526,17 @@ let StorageActor = protocol.ActorClassWi
// items from changed instead.
this.removeNamesFromUpdateList("changed", storeType,
this.boundUpdate.added[storeType]);
} else if (action == "deleted") {
// If any item got delete, or a host got delete, no point in sending
// 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] &&
this.boundUpdate.added[storeType][host]) {
delete this.boundUpdate.added[storeType][host];
}
if (data[host].length == 0 && this.boundUpdate.changed &&
this.boundUpdate.changed[storeType] &&
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -6,16 +6,17 @@ support-files =
animation.html
doc_allocations.html
doc_force_cc.html
doc_force_gc.html
doc_innerHTML.html
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
storage-updates.html
storage-secured-iframe.html
stylesheets-nested-iframes.html
timeline-iframe-child.html
timeline-iframe-parent.html
@@ -75,16 +76,17 @@ skip-if = true # Needs to be updated for
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
[browser_perf-recording-actor-02.js]
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
[browser_perf-samples-01.js]
[browser_perf-samples-02.js]
#[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]
[browser_stylesheets_getTextEmpty.js]
[browser_stylesheets_nested-iframes.js]
[browser_timeline.js]
[browser_timeline_actors.js]
[browser_timeline_iframes.js]
new file mode 100644
--- /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 = {
+ "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);
+});
--- a/devtools/server/tests/browser/browser_storage_dynamic_windows.js
+++ b/devtools/server/tests/browser/browser_storage_dynamic_windows.js
@@ -61,16 +61,17 @@ function markOutMatched(toBeEmptied, dat
"At least one storage type should be present");
for (let storageType in toBeEmptied) {
if (!data[storageType]) {
continue;
}
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);
ok(index > -1, "Item found - " + item);
if (index > -1) {
toBeEmptied[storageType][host].splice(index, 1);
}
}
@@ -82,80 +83,39 @@ function markOutMatched(toBeEmptied, dat
}
}
if (!Object.keys(toBeEmptied[storageType]).length) {
delete toBeEmptied[storageType];
}
}
}
-// 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 => {
let shouldBeEmpty = {
localStorage: {
"https://sectest1.example.org": ["iframe-s-ls1"]
},
sessionStorage: {
"https://sectest1.example.org": ["iframe-s-ss1"]
},
cookies: {
- "sectest1.example.org": ["sc1"]
+ "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
// the time of window-ready, there was no indexed db present.
"https://sectest1.example.org": []
},
Cache: {
- "https://sectest1.example.org":[]
+ "https://sectest1.example.org": []
}
};
let onStoresUpdate = data => {
info("checking if the hosts list is correct for this iframe addition");
markOutMatched(shouldBeEmpty, data.added);
--- a/devtools/server/tests/browser/browser_storage_listings.js
+++ b/devtools/server/tests/browser/browser_storage_listings.js
@@ -30,25 +30,16 @@ const storeMap = {
},
{
name: "c3",
value: "foobar-2",
expires: 2000000001000,
path: "/",
host: "test1.example.org",
isDomain: false,
- isSecure: false,
- },
- {
- name: "uc1",
- value: "foobar",
- host: ".example.org",
- path: "/",
- expires: 0,
- isDomain: true,
isSecure: true,
}
],
"sectest1.example.org": [
{
name: "uc1",
value: "foobar",
host: ".example.org",
@@ -332,28 +323,29 @@ function* testStores(data) {
ok(data.indexedDB, "Indexed DB storage actor is present");
yield testCookies(data.cookies);
yield testLocalStorage(data.localStorage);
yield testSessionStorage(data.sessionStorage);
yield testIndexedDB(data.indexedDB);
}
function testCookies(cookiesActor) {
- is(Object.keys(cookiesActor.hosts).length, 2, "Correct number of host entries for cookies");
+ is(Object.keys(cookiesActor.hosts).length, 2,
+ "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 => {
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,
"Number of cookies in host " + host + " matches");
for (let item of data.data) {
let found = false;
for (let toMatch of storeMap.cookies[host]) {
if (item.name == toMatch.name) {
--- a/devtools/server/tests/browser/browser_storage_updates.js
+++ b/devtools/server/tests/browser/browser_storage_updates.js
@@ -22,17 +22,22 @@ const TESTS = [
win.addCookie("c2", "foobar2");
info('win.localStorage.setItem("l1", "foobar1")');
win.localStorage.setItem("l1", "foobar1");
},
expected: {
added: {
cookies: {
- "test1.example.org": ["c1", "c2"]
+ "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"]
}
}
}
},
@@ -43,17 +48,20 @@ const TESTS = [
win.addCookie("c1", "new_foobar1");
info('win.localStorage.setItem("l2", "foobar2")');
win.localStorage.setItem("l2", "foobar2");
},
expected: {
changed: {
cookies: {
- "test1.example.org": ["c1"]
+ "test1.example.org": [
+ getCookieId("c1", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
}
},
added: {
localStorage: {
"http://test1.example.org": ["l2"]
}
}
}
@@ -69,17 +77,20 @@ const TESTS = [
win.localStorage.removeItem("l1");
info('win.localStorage.setItem("l3", "foobar3")');
win.localStorage.setItem("l3", "foobar3");
},
expected: {
deleted: {
cookies: {
- "test1.example.org": ["c2"]
+ "test1.example.org": [
+ getCookieId("c2", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
},
localStorage: {
"http://test1.example.org": ["l1"]
}
},
added: {
localStorage: {
"http://test1.example.org": ["l3"]
@@ -107,30 +118,36 @@ const TESTS = [
win.sessionStorage.setItem("s2", "foobar2");
info('win.localStorage.setItem("l3", "new_foobar3")');
win.localStorage.setItem("l3", "new_foobar3");
},
expected: {
added: {
cookies: {
- "test1.example.org": ["c3"]
+ "test1.example.org": [
+ getCookieId("c3", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
},
sessionStorage: {
"http://test1.example.org": ["s1", "s2"]
}
},
changed: {
localStorage: {
"http://test1.example.org": ["l3"]
}
},
deleted: {
cookies: {
- "test1.example.org": ["c1"]
+ "test1.example.org": [
+ getCookieId("c1", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
},
localStorage: {
"http://test1.example.org": ["l2"]
}
}
}
},
@@ -153,17 +170,20 @@ const TESTS = [
{
action: function (win) {
info("win.clearCookies()");
win.clearCookies();
},
expected: {
deleted: {
cookies: {
- "test1.example.org": ["c3"]
+ "test1.example.org": [
+ getCookieId("c3", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
}
}
}
}
];
function markOutMatched(toBeEmptied, data) {
if (!Object.keys(toBeEmptied).length) {
--- a/devtools/server/tests/browser/head.js
+++ b/devtools/server/tests/browser/head.js
@@ -1,12 +1,16 @@
/* 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";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {DebuggerClient} = require("devtools/shared/client/main");
const {DebuggerServer} = require("devtools/server/main");
@@ -14,16 +18,21 @@ const {defer} = require("promise");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const Services = require("Services");
const PATH = "browser/devtools/server/tests/browser/";
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();
/**
* Add a new test tab in the browser and load the given url.
* @param {String} url The url to be loaded in the new tab
* @return a promise that resolves to the new browser that the document
* is loaded in. Note that we cannot return the document
@@ -89,17 +98,16 @@ function connectDebuggerClient(client) {
* @param {String} eventName
* @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
* @return A promise that resolves when the event has been handled
*/
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"],
["on", "off"]
]) {
if ((add in target) && (remove in target)) {
target[add](eventName, function onEvent(...aArgs) {
info("Got event: '" + eventName + "' on " + target + ".");
@@ -132,29 +140,34 @@ function forceCollections() {
function getMockTabActor(win) {
return {
window: win,
isRootActor: true
};
}
registerCleanupFunction(function tearDown() {
+ Services.cookies.removeAll();
+
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});
function idleWait(time) {
return DevToolsUtils.waitForTime(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;
+ }
}
/**
* Waits until a predicate returns true.
*
* @param function predicate
* Invoked once in a while until it returns true.
* @param number interval [optional]
@@ -167,37 +180,44 @@ function waitUntil(predicate, interval =
return new Promise(resolve => {
setTimeout(function () {
waitUntil(predicate).then(() => resolve(true));
}, interval);
});
}
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();
info("Waiting for markers of type: " + types);
function handler(name, data) {
if (typeof name === "string" && name !== "markers") {
return;
}
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);
}
}
front.on(eventName, handler);
return promise;
}
+
+function getCookieId(name, domain, path) {
+ return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
+}
new file mode 100644
--- /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>