diff --git a/devtools/client/locales/en-US/storage.dtd b/devtools/client/locales/en-US/storage.dtd index 097b1b744e41..211c794369f4 100644 --- a/devtools/client/locales/en-US/storage.dtd +++ b/devtools/client/locales/en-US/storage.dtd @@ -6,3 +6,6 @@ + + + diff --git a/devtools/client/locales/en-US/storage.properties b/devtools/client/locales/en-US/storage.properties index efade402492d..1a22bead4f8c 100644 --- a/devtools/client/locales/en-US/storage.properties +++ b/devtools/client/locales/en-US/storage.properties @@ -119,3 +119,7 @@ storage.parsedValue.label=Parsed Value # LOCALIZATION NOTE (storage.popupMenu.deleteLabel): # Label of popup menu action to delete storage item. storage.popupMenu.deleteLabel=Delete ā€œ%Sā€ + +# LOCALIZATION NOTE (storage.popupMenu.deleteAllLabel): +# Label of popup menu action to delete all storage items. +storage.popupMenu.deleteAllFromLabel=Delete All From ā€œ%Sā€ diff --git a/devtools/client/shared/widgets/TableWidget.js b/devtools/client/shared/widgets/TableWidget.js index b70803abbd56..0606ef9c1692 100644 --- a/devtools/client/shared/widgets/TableWidget.js +++ b/devtools/client/shared/widgets/TableWidget.js @@ -803,6 +803,9 @@ TableWidget.prototype = { if (typeof item == "string") { item = this.items.get(item); } + if (!item) { + return; + } let removed = this.items.delete(item[this.uniqueId]); if (!removed) { diff --git a/devtools/client/shared/widgets/TreeWidget.js b/devtools/client/shared/widgets/TreeWidget.js index c270bf2c72bf..a92f13ca15fd 100644 --- a/devtools/client/shared/widgets/TreeWidget.js +++ b/devtools/client/shared/widgets/TreeWidget.js @@ -17,9 +17,11 @@ const EventEmitter = require("devtools/shared/event-emitter"); * @param {Object} options * - emptyText {string}: text to display when no entries in the table. * - defaultType {string}: The default type of the tree items. For ex. - * 'js' + * 'js' * - sorted {boolean}: Defaults to true. If true, tree items are kept in - * lexical order. If false, items will be kept in insertion order. + * lexical order. If false, items will be kept in insertion order. + * - contextMenuId {string}: ID of context menu to be displayed on + * tree items. */ function TreeWidget(node, options = {}) { EventEmitter.decorate(this); @@ -31,6 +33,7 @@ function TreeWidget(node, options = {}) { this.emptyText = options.emptyText || ""; this.defaultType = options.defaultType; this.sorted = options.sorted !== false; + this.contextMenuId = options.contextMenuId; this.setupRoot(); @@ -53,30 +56,31 @@ TreeWidget.prototype = { /** * Select any node in the tree. * - * @param {array} id + * @param {array} ids * An array of ids leading upto the selected item */ - set selectedItem(id) { + set selectedItem(ids) { if (this._selectedLabel) { this._selectedLabel.classList.remove("theme-selected"); } let currentSelected = this._selectedLabel; - if (id == -1) { + if (ids == -1) { this._selectedLabel = this._selectedItem = null; return; } - if (!Array.isArray(id)) { + if (!Array.isArray(ids)) { return; } - this._selectedLabel = this.root.setSelectedItem(id); + this._selectedLabel = this.root.setSelectedItem(ids); if (!this._selectedLabel) { this._selectedItem = null; } else { if (currentSelected != this._selectedLabel) { this.ensureSelectedVisible(); } - this._selectedItem = - JSON.parse(this._selectedLabel.parentNode.getAttribute("data-id")); + this._selectedItem = ids; + this.emit("select", this._selectedItem, + this.attachments.get(JSON.stringify(ids))); } }, @@ -120,9 +124,16 @@ TreeWidget.prototype = { */ setupRoot: function() { this.root = new TreeItem(this.document); + if (this.contextMenuId) { + this.root.children.addEventListener("contextmenu", (event) => { + let menu = this.document.getElementById(this.contextMenuId); + menu.openPopupAtScreen(event.screenX, event.screenY, true); + }); + } + this._parent.appendChild(this.root.children); - this.root.children.addEventListener("click", e => this.onClick(e)); + this.root.children.addEventListener("mousedown", e => this.onClick(e)); this.root.children.addEventListener("keypress", e => this.onKeypress(e)); }, @@ -315,21 +326,17 @@ TreeWidget.prototype = { if (!target) { return; } + if (target.hasAttribute("expanded")) { target.removeAttribute("expanded"); } else { target.setAttribute("expanded", "true"); } - if (this._selectedLabel) { - this._selectedLabel.classList.remove("theme-selected"); - } + if (this._selectedLabel != target) { let ids = target.parentNode.getAttribute("data-id"); - this._selectedItem = JSON.parse(ids); - this.emit("select", this._selectedItem, this.attachments.get(ids)); - this._selectedLabel = target; + this.selectedItem = JSON.parse(ids); } - target.classList.add("theme-selected"); }, /** @@ -337,7 +344,6 @@ TreeWidget.prototype = { * items, as well as collapsing and expanding any item. */ onKeypress: function(event) { - let currentSelected = this._selectedLabel; switch (event.keyCode) { case event.DOM_VK_UP: this.selectPreviousItem(); @@ -367,11 +373,6 @@ TreeWidget.prototype = { default: return; } event.preventDefault(); - if (this._selectedLabel != currentSelected) { - let ids = JSON.stringify(this._selectedItem); - this.emit("select", this._selectedItem, this.attachments.get(ids)); - this.ensureSelectedVisible(); - } }, /** diff --git a/devtools/client/storage/storage.xul b/devtools/client/storage/storage.xul index 9738c4ba4a05..702f3687ad55 100644 --- a/devtools/client/storage/storage.xul +++ b/devtools/client/storage/storage.xul @@ -23,8 +23,15 @@ + + + + + diff --git a/devtools/client/storage/test/browser.ini b/devtools/client/storage/test/browser.ini index 3ab650a2bcb2..a7773bd207ad 100644 --- a/devtools/client/storage/test/browser.ini +++ b/devtools/client/storage/test/browser.ini @@ -15,12 +15,15 @@ support-files = head.js [browser_storage_basic.js] +[browser_storage_cookies_delete_all.js] [browser_storage_cookies_edit.js] [browser_storage_cookies_edit_keyboard.js] [browser_storage_cookies_tab_navigation.js] [browser_storage_dynamic_updates.js] [browser_storage_localstorage_edit.js] [browser_storage_delete.js] +[browser_storage_delete_all.js] +[browser_storage_delete_tree.js] [browser_storage_overflow.js] [browser_storage_search.js] skip-if = os == "linux" && e10s # Bug 1240804 - unhandled promise rejections diff --git a/devtools/client/storage/test/browser_storage_cookies_delete_all.js b/devtools/client/storage/test/browser_storage_cookies_delete_all.js new file mode 100644 index 000000000000..cc94fb7fe2a0 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_cookies_delete_all.js @@ -0,0 +1,74 @@ +/* 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 */ + +"use strict"; + +// Test deleting all cookies + +function* performDelete(store, rowName, deleteAll) { + let contextMenu = gPanelWindow.document.getElementById( + "storage-table-popup"); + let menuDeleteAllItem = contextMenu.querySelector( + "#storage-table-popup-delete-all"); + 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); + 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), + `Context menu item label contains '${hostName}'`); + } + }); + + 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"]], + ]); + + info("delete all from domain"); + // delete only cookies that match the host exactly + yield performDelete(["cookies", "test1.example.org"], "c1", 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"]], + ]); + + info("delete all"); + // delete all cookies for host, including domain cookies + yield performDelete(["cookies", "sectest1.example.org"], "uc1", 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"], []], + ]); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_delete.js b/devtools/client/storage/test/browser_storage_delete.js index 466377cc21a8..b38147968e72 100644 --- a/devtools/client/storage/test/browser_storage_delete.js +++ b/devtools/client/storage/test/browser_storage_delete.js @@ -28,10 +28,11 @@ add_task(function* () { yield selectTreeItem([store, host]); let row = getRowCells(rowName); - ok(gUI.table.items.has(rowName), `There is a row '${rowName}' in ${store} > ${host}`); + let eventWait = gUI.once("store-objects-updated"); + yield waitForContextMenu(contextMenu, row[cellToClick], () => { info(`Opened context menu in ${store} > ${host}, row '${rowName}'`); menuDeleteItem.click(); @@ -39,7 +40,7 @@ add_task(function* () { `Context menu item label contains '${rowName}'`); }); - yield gUI.once("store-objects-updated"); + yield eventWait; ok(!gUI.table.items.has(rowName), `There is no row '${rowName}' in ${store} > ${host} after deletion`); diff --git a/devtools/client/storage/test/browser_storage_delete_all.js b/devtools/client/storage/test/browser_storage_delete_all.js new file mode 100644 index 000000000000..af4d38e8a8eb --- /dev/null +++ b/devtools/client/storage/test/browser_storage_delete_all.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/. */ + +/* import-globals-from head.js */ + +"use strict"; + +// Test deleting all storage items + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html"); + + let contextMenu = gPanelWindow.document.getElementById("storage-table-popup"); + let menuDeleteAllItem = contextMenu.querySelector( + "#storage-table-popup-delete-all"); + + info("test state before delete"); + const beforeState = [ + [["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"]], + ]; + + yield checkState(beforeState); + + info("do the delete"); + const deleteHosts = [ + [["localStorage", "https://sectest1.example.org"], "iframe-s-ls1"], + [["sessionStorage", "https://sectest1.example.org"], "iframe-s-ss1"], + ]; + + for (let [store, rowName] of deleteHosts) { + let storeName = store.join(" > "); + + yield selectTreeItem(store); + + let eventWait = gUI.once("store-objects-cleared"); + + let cell = getRowCells(rowName).name; + yield waitForContextMenu(contextMenu, cell, () => { + info(`Opened context menu in ${storeName}, row '${rowName}'`); + menuDeleteAllItem.click(); + }); + + yield eventWait; + } + + info("test state after delete"); + const afterState = [ + // iframes from the same host, one secure, one unsecure, are independent + // from each other. Delete all in one doesn't touch the other one. + [["localStorage", "http://test1.example.org"], + ["ls1", "ls2"]], + [["localStorage", "http://sectest1.example.org"], + ["iframe-u-ls1"]], + [["localStorage", "https://sectest1.example.org"], + []], + [["sessionStorage", "http://test1.example.org"], + ["ss1"]], + [["sessionStorage", "http://sectest1.example.org"], + ["iframe-u-ss1", "iframe-u-ss2"]], + [["sessionStorage", "https://sectest1.example.org"], + []], + ]; + + yield checkState(afterState); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_delete_tree.js b/devtools/client/storage/test/browser_storage_delete_tree.js new file mode 100644 index 000000000000..ae897b31724c --- /dev/null +++ b/devtools/client/storage/test/browser_storage_delete_tree.js @@ -0,0 +1,60 @@ +/* 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 */ + +"use strict"; + +// Test deleting all storage items from the tree. + +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"]], + [["localStorage", "http://test1.example.org"], ["ls1", "ls2"]], + [["sessionStorage", "http://test1.example.org"], ["ss1"]], + ]); + + info("do the delete"); + const deleteHosts = [ + ["cookies", "test1.example.org"], + ["localStorage", "http://test1.example.org"], + ["sessionStorage", "http://test1.example.org"], + ]; + + for (let store of deleteHosts) { + let storeName = store.join(" > "); + + yield selectTreeItem(store); + + let eventName = "store-objects-" + + (store[0] == "cookies" ? "updated" : "cleared"); + let eventWait = gUI.once(eventName); + + let selector = `[data-id='${JSON.stringify(store)}'] > .tree-widget-item`; + let target = gPanelWindow.document.querySelector(selector); + ok(target, `tree item found in ${storeName}`); + yield waitForContextMenu(contextMenu, target, () => { + info(`Opened tree context menu in ${storeName}`); + menuDeleteAllItem.click(); + }); + + yield eventWait; + } + + info("test state after delete"); + yield checkState([ + [["cookies", "test1.example.org"], []], + [["localStorage", "http://test1.example.org"], []], + [["sessionStorage", "http://test1.example.org"], []], + ]); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/head.js b/devtools/client/storage/test/head.js index f75625540e85..e1038a200d6b 100644 --- a/devtools/client/storage/test/head.js +++ b/devtools/client/storage/test/head.js @@ -509,17 +509,13 @@ function matchVariablesViewProperty(prop, rule) { * The array id of the item in the tree */ function* selectTreeItem(ids) { - // Expand tree as some/all items could be collapsed leading to click on an - // incorrect tree item - gUI.tree.expandAll(); - - let selector = "[data-id='" + JSON.stringify(ids) + "'] > .tree-widget-item"; - let target = gPanelWindow.document.querySelector(selector); - ok(target, "tree item found with ids " + JSON.stringify(ids)); + /* If this item is already selected, return */ + if (gUI.tree.isSelected(ids)) { + return; + } let updated = gUI.once("store-objects-updated"); - - yield click(target); + gUI.tree.selectedItem = ids; yield updated; } @@ -845,8 +841,35 @@ function waitForContextMenu(popup, button, onShown, onHidden) { popup.addEventListener("popupshown", onPopupShown); info("wait for the context menu to open"); + button.scrollIntoView(); let eventDetails = {type: "contextmenu", button: 2}; EventUtils.synthesizeMouse(button, 2, 2, eventDetails, button.ownerDocument.defaultView); return deferred.promise; } + +/** + * Verify the storage inspector state: check that given type/host exists + * in the tree, and that the table contains rows with specified names. + * + * @param {Array} state Array of state specifications. For example, + * [["cookies", "example.com"], ["c1", "c2"]] means to select the + * "example.com" host in cookies and then verify there are "c1" and "c2" + * cookies (and no other ones). + */ +function* checkState(state) { + for (let [store, names] of 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}`); + for (let name of names) { + ok(items.has(name), + `There is item with name '${name}' in ${storeName}`); + } + } +} diff --git a/devtools/client/storage/ui.js b/devtools/client/storage/ui.js index 5460f6eeca78..fefe6390e770 100644 --- a/devtools/client/storage/ui.js +++ b/devtools/client/storage/ui.js @@ -70,7 +70,10 @@ var StorageUI = this.StorageUI = function StorageUI(front, target, panelWin) { this.front = front; let treeNode = this._panelDoc.getElementById("storage-tree"); - this.tree = new TreeWidget(treeNode, {defaultType: "dir"}); + this.tree = new TreeWidget(treeNode, { + defaultType: "dir", + contextMenuId: "storage-tree-popup" + }); this.onHostSelect = this.onHostSelect.bind(this); this.tree.on("select", this.onHostSelect); @@ -111,14 +114,34 @@ var StorageUI = this.StorageUI = function StorageUI(front, target, panelWin) { this.handleKeypress = this.handleKeypress.bind(this); this._panelDoc.addEventListener("keypress", this.handleKeypress); - this.onPopupShowing = this.onPopupShowing.bind(this); + this.onTreePopupShowing = this.onTreePopupShowing.bind(this); + this._treePopup = this._panelDoc.getElementById("storage-tree-popup"); + this._treePopup.addEventListener("popupshowing", this.onTreePopupShowing); + + this.onTablePopupShowing = this.onTablePopupShowing.bind(this); this._tablePopup = this._panelDoc.getElementById("storage-table-popup"); - this._tablePopup.addEventListener("popupshowing", this.onPopupShowing, false); + this._tablePopup.addEventListener("popupshowing", this.onTablePopupShowing); this.onRemoveItem = this.onRemoveItem.bind(this); + this.onRemoveAllFrom = this.onRemoveAllFrom.bind(this); + this.onRemoveAll = this.onRemoveAll.bind(this); + this._tablePopupDelete = this._panelDoc.getElementById( "storage-table-popup-delete"); - this._tablePopupDelete.addEventListener("command", this.onRemoveItem, false); + this._tablePopupDelete.addEventListener("command", this.onRemoveItem); + + this._tablePopupDeleteAllFrom = this._panelDoc.getElementById( + "storage-table-popup-delete-all-from"); + this._tablePopupDeleteAllFrom.addEventListener("command", + this.onRemoveAllFrom); + + this._tablePopupDeleteAll = this._panelDoc.getElementById( + "storage-table-popup-delete-all"); + this._tablePopupDeleteAll.addEventListener("command", this.onRemoveAll); + + this._treePopupDeleteAll = this._panelDoc.getElementById( + "storage-tree-popup-delete-all"); + this._treePopupDeleteAll.addEventListener("command", this.onRemoveAll); }; exports.StorageUI = StorageUI; @@ -126,7 +149,6 @@ exports.StorageUI = StorageUI; StorageUI.prototype = { storageTypes: null, - shouldResetColumns: true, shouldLoadMoreItems: true, set animationsEnabled(value) { @@ -145,8 +167,16 @@ StorageUI.prototype = { this.searchBox.removeEventListener("input", this.filterItems); this.searchBox = null; - this._tablePopup.removeEventListener("popupshowing", this.onPopupShowing); + this._treePopup.removeEventListener("popupshowing", + this.onTreePopupShowing); + this._treePopupDeleteAll.removeEventListener("command", this.onRemoveAll); + + this._tablePopup.removeEventListener("popupshowing", + this.onTablePopupShowing); this._tablePopupDelete.removeEventListener("command", this.onRemoveItem); + this._tablePopupDeleteAllFrom.removeEventListener("command", + this.onRemoveAllFrom); + this._tablePopupDeleteAll.removeEventListener("command", this.onRemoveAll); }, /** @@ -391,10 +421,10 @@ StorageUI.prototype = { this.emit("store-objects-updated"); return; } - if (this.shouldResetColumns) { + if (reason === REASON.POPULATE) { this.resetColumns(data[0], type); + this.table.host = host; } - this.table.host = host; this.populateTable(data, reason); this.emit("store-objects-updated"); @@ -437,7 +467,6 @@ StorageUI.prototype = { this.tree.add([type, host, ...names]); if (!this.tree.selectedItem) { this.tree.selectedItem = [type, host, names[0], names[1]]; - this.fetchStorageObjects(type, host, [name], REASON.POPULATE); } } catch (ex) { // Do Nothing @@ -445,7 +474,6 @@ StorageUI.prototype = { } if (!this.tree.selectedItem) { this.tree.selectedItem = [type, host]; - this.fetchStorageObjects(type, host, null, REASON.POPULATE); } } } @@ -625,7 +653,6 @@ StorageUI.prototype = { if (item.length > 2) { names = [JSON.stringify(item.slice(2))]; } - this.shouldResetColumns = true; this.fetchStorageObjects(type, host, names, REASON.POPULATE); this.itemOffset = 0; }, @@ -657,7 +684,6 @@ StorageUI.prototype = { } this.table.setColumns(columns, null, HIDDEN_COLUMNS); this.table.datatype = type; - this.shouldResetColumns = false; this.hideSidebar(); }, @@ -757,23 +783,54 @@ StorageUI.prototype = { * If the current storage actor doesn't support removing items, prevent * showing the menu. */ - onPopupShowing: function(event) { + onTablePopupShowing: function(event) { if (!this.getCurrentActor().removeItem) { event.preventDefault(); return; } + const maxLen = ITEM_NAME_MAX_LENGTH; + let [type] = this.tree.selectedItem; let rowId = this.table.contextMenuRowId; let data = this.table.items.get(rowId); let name = data[this.table.uniqueId]; - const maxLen = ITEM_NAME_MAX_LENGTH; if (name.length > maxLen) { name = name.substr(0, maxLen) + L10N.ellipsis; } this._tablePopupDelete.setAttribute("label", L10N.getFormatStr("storage.popupMenu.deleteLabel", name)); + + if (type === "cookies") { + let host = data.host; + if (host.length > maxLen) { + host = host.substr(0, maxLen) + L10N.ellipsis; + } + + this._tablePopupDeleteAllFrom.hidden = false; + this._tablePopupDeleteAllFrom.setAttribute("label", + L10N.getFormatStr("storage.popupMenu.deleteAllFromLabel", host)); + } else { + this._tablePopupDeleteAllFrom.hidden = true; + } + }, + + onTreePopupShowing: function(event) { + let showMenu = false; + let selectedItem = this.tree.selectedItem; + // Never show menu on the 1st level item + if (selectedItem && selectedItem.length > 1) { + // this.currentActor() would return wrong value here + let actor = this.storageTypes[selectedItem[0]]; + if (actor.removeAll) { + showMenu = true; + } + } + + if (!showMenu) { + event.preventDefault(); + } }, /** @@ -787,4 +844,30 @@ StorageUI.prototype = { actor.removeItem(host, data[this.table.uniqueId]); }, + + /** + * 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] = this.tree.selectedItem; + let actor = this.storageTypes[type]; + + actor.removeAll(host); + }, + + /** + * Handles removing all cookies with exactly the same domain as the + * cookie in the selected row. + */ + onRemoveAllFrom: function() { + let [, host] = this.tree.selectedItem; + let actor = this.getCurrentActor(); + let rowId = this.table.contextMenuRowId; + let data = this.table.items.get(rowId); + + actor.removeAll(host, data.host); + }, }; diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index dcd20140f738..38e71bbae2bd 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -686,8 +686,18 @@ StorageActors.createActor({ this.removeCookie(host, name); }), { request: { - host: Arg(0), - name: Arg(1), + host: Arg(0, "string"), + name: Arg(1, "string"), + }, + response: {} + }), + + removeAll: method(Task.async(function*(host, domain) { + this.removeAllCookies(host, domain); + }), { + request: { + host: Arg(0, "string"), + domain: Arg(1, "nullable:string") }, response: {} }), @@ -696,11 +706,18 @@ StorageActors.createActor({ cookieHelpers.onCookieChanged = this.onCookieChanged.bind(this); if (!DebuggerServer.isInChildProcess) { - this.getCookiesFromHost = cookieHelpers.getCookiesFromHost; - this.addCookieObservers = cookieHelpers.addCookieObservers; - this.removeCookieObservers = cookieHelpers.removeCookieObservers; - this.editCookie = cookieHelpers.editCookie; - this.removeCookie = cookieHelpers.removeCookie; + this.getCookiesFromHost = + cookieHelpers.getCookiesFromHost.bind(cookieHelpers); + this.addCookieObservers = + cookieHelpers.addCookieObservers.bind(cookieHelpers); + this.removeCookieObservers = + cookieHelpers.removeCookieObservers.bind(cookieHelpers); + this.editCookie = + cookieHelpers.editCookie.bind(cookieHelpers); + this.removeCookie = + cookieHelpers.removeCookie.bind(cookieHelpers); + this.removeAllCookies = + cookieHelpers.removeAllCookies.bind(cookieHelpers); return; } @@ -722,6 +739,8 @@ StorageActors.createActor({ callParentProcess.bind(null, "editCookie"); this.removeCookie = callParentProcess.bind(null, "removeCookie"); + this.removeAllCookies = + callParentProcess.bind(null, "removeAllCookies"); addMessageListener("storage:storage-cookie-request-child", cookieHelpers.handleParentRequest); @@ -875,7 +894,7 @@ var cookieHelpers = { ); }, - removeCookie: function(host, name) { + _removeCookies: function(host, opts = {}) { function hostMatches(cookieHost, matchHost) { if (cookieHost == null) { return matchHost == null; @@ -889,7 +908,9 @@ var cookieHelpers = { let enumerator = Services.cookies.getCookiesFromHost(host); while (enumerator.hasMoreElements()) { let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); - if (hostMatches(cookie.host, host) && cookie.name === name) { + if (hostMatches(cookie.host, host) && + (!opts.name || cookie.name === opts.name) && + (!opts.domain || cookie.host === opts.domain)) { Services.cookies.remove( cookie.host, cookie.name, @@ -901,6 +922,16 @@ var cookieHelpers = { } }, + removeCookie: function(host, name) { + if (name !== undefined) { + this._removeCookies(host, { name }); + } + }, + + removeAllCookies: function(host, domain) { + this._removeCookies(host, { domain }); + }, + addCookieObservers: function() { Services.obs.addObserver(cookieHelpers, "cookie-changed", false); return null; @@ -969,6 +1000,11 @@ var cookieHelpers = { let name = msg.data.args[1]; return cookieHelpers.removeCookie(host, name); } + case "removeAllCookies": { + let host = msg.data.args[0]; + let domain = msg.data.args[1]; + return cookieHelpers.removeAllCookies(host, domain); + } default: console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method); throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD"); @@ -1180,6 +1216,16 @@ function getObjectForLocalOrSessionStorage(type) { }, response: {} }), + + removeAll: method(Task.async(function*(host) { + let storage = this.hostVsStores.get(host); + storage.clear(); + }), { + request: { + host: Arg(0) + }, + response: {} + }), }; } @@ -2078,7 +2124,7 @@ var StorageActor = exports.StorageActor = protocol.ActorClass({ data: Arg(0, "json") }, "stores-reloaded": { - type: "storesRelaoded", + type: "storesReloaded", data: Arg(0, "json") } }, @@ -2310,11 +2356,13 @@ var StorageActor = exports.StorageActor = protocol.ActorClass({ this.boundUpdate[action][storeType] = {}; } for (let host in data) { - if (!this.boundUpdate[action][storeType][host] || action == "deleted") { - this.boundUpdate[action][storeType][host] = data[host]; - } else { - this.boundUpdate[action][storeType][host] = - this.boundUpdate[action][storeType][host].concat(data[host]); + if (!this.boundUpdate[action][storeType][host]) { + this.boundUpdate[action][storeType][host] = []; + } + for (let name of data[host]) { + if (!this.boundUpdate[action][storeType][host].includes(name)) { + this.boundUpdate[action][storeType][host].push(name); + } } } if (action == "added") {