зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1231434 - Add 'Delete All' context menu entry to storage inspector. r=mratcliffe
This commit is contained in:
Родитель
d8f23fae58
Коммит
4840f48bea
|
@ -6,3 +6,6 @@
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE : Placeholder for the searchbox that allows you to filter the table items. -->
|
<!-- LOCALIZATION NOTE : Placeholder for the searchbox that allows you to filter the table items. -->
|
||||||
<!ENTITY searchBox.placeholder "Filter items">
|
<!ENTITY searchBox.placeholder "Filter items">
|
||||||
|
|
||||||
|
<!-- LOCALIZATION NOTE : Label of popup menu action to delete all storage items. -->
|
||||||
|
<!ENTITY storage.popupMenu.deleteAllLabel "Delete All">
|
||||||
|
|
|
@ -119,3 +119,7 @@ storage.parsedValue.label=Parsed Value
|
||||||
# LOCALIZATION NOTE (storage.popupMenu.deleteLabel):
|
# LOCALIZATION NOTE (storage.popupMenu.deleteLabel):
|
||||||
# Label of popup menu action to delete storage item.
|
# Label of popup menu action to delete storage item.
|
||||||
storage.popupMenu.deleteLabel=Delete “%S”
|
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”
|
||||||
|
|
|
@ -803,6 +803,9 @@ TableWidget.prototype = {
|
||||||
if (typeof item == "string") {
|
if (typeof item == "string") {
|
||||||
item = this.items.get(item);
|
item = this.items.get(item);
|
||||||
}
|
}
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let removed = this.items.delete(item[this.uniqueId]);
|
let removed = this.items.delete(item[this.uniqueId]);
|
||||||
|
|
||||||
if (!removed) {
|
if (!removed) {
|
||||||
|
|
|
@ -20,6 +20,8 @@ const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
* 'js'
|
* 'js'
|
||||||
* - sorted {boolean}: Defaults to true. If true, tree items are kept in
|
* - 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 = {}) {
|
function TreeWidget(node, options = {}) {
|
||||||
EventEmitter.decorate(this);
|
EventEmitter.decorate(this);
|
||||||
|
@ -31,6 +33,7 @@ function TreeWidget(node, options = {}) {
|
||||||
this.emptyText = options.emptyText || "";
|
this.emptyText = options.emptyText || "";
|
||||||
this.defaultType = options.defaultType;
|
this.defaultType = options.defaultType;
|
||||||
this.sorted = options.sorted !== false;
|
this.sorted = options.sorted !== false;
|
||||||
|
this.contextMenuId = options.contextMenuId;
|
||||||
|
|
||||||
this.setupRoot();
|
this.setupRoot();
|
||||||
|
|
||||||
|
@ -53,30 +56,31 @@ TreeWidget.prototype = {
|
||||||
/**
|
/**
|
||||||
* Select any node in the tree.
|
* Select any node in the tree.
|
||||||
*
|
*
|
||||||
* @param {array} id
|
* @param {array} ids
|
||||||
* An array of ids leading upto the selected item
|
* An array of ids leading upto the selected item
|
||||||
*/
|
*/
|
||||||
set selectedItem(id) {
|
set selectedItem(ids) {
|
||||||
if (this._selectedLabel) {
|
if (this._selectedLabel) {
|
||||||
this._selectedLabel.classList.remove("theme-selected");
|
this._selectedLabel.classList.remove("theme-selected");
|
||||||
}
|
}
|
||||||
let currentSelected = this._selectedLabel;
|
let currentSelected = this._selectedLabel;
|
||||||
if (id == -1) {
|
if (ids == -1) {
|
||||||
this._selectedLabel = this._selectedItem = null;
|
this._selectedLabel = this._selectedItem = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Array.isArray(id)) {
|
if (!Array.isArray(ids)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._selectedLabel = this.root.setSelectedItem(id);
|
this._selectedLabel = this.root.setSelectedItem(ids);
|
||||||
if (!this._selectedLabel) {
|
if (!this._selectedLabel) {
|
||||||
this._selectedItem = null;
|
this._selectedItem = null;
|
||||||
} else {
|
} else {
|
||||||
if (currentSelected != this._selectedLabel) {
|
if (currentSelected != this._selectedLabel) {
|
||||||
this.ensureSelectedVisible();
|
this.ensureSelectedVisible();
|
||||||
}
|
}
|
||||||
this._selectedItem =
|
this._selectedItem = ids;
|
||||||
JSON.parse(this._selectedLabel.parentNode.getAttribute("data-id"));
|
this.emit("select", this._selectedItem,
|
||||||
|
this.attachments.get(JSON.stringify(ids)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -120,9 +124,16 @@ TreeWidget.prototype = {
|
||||||
*/
|
*/
|
||||||
setupRoot: function() {
|
setupRoot: function() {
|
||||||
this.root = new TreeItem(this.document);
|
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._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));
|
this.root.children.addEventListener("keypress", e => this.onKeypress(e));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -315,21 +326,17 @@ TreeWidget.prototype = {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.hasAttribute("expanded")) {
|
if (target.hasAttribute("expanded")) {
|
||||||
target.removeAttribute("expanded");
|
target.removeAttribute("expanded");
|
||||||
} else {
|
} else {
|
||||||
target.setAttribute("expanded", "true");
|
target.setAttribute("expanded", "true");
|
||||||
}
|
}
|
||||||
if (this._selectedLabel) {
|
|
||||||
this._selectedLabel.classList.remove("theme-selected");
|
|
||||||
}
|
|
||||||
if (this._selectedLabel != target) {
|
if (this._selectedLabel != target) {
|
||||||
let ids = target.parentNode.getAttribute("data-id");
|
let ids = target.parentNode.getAttribute("data-id");
|
||||||
this._selectedItem = JSON.parse(ids);
|
this.selectedItem = JSON.parse(ids);
|
||||||
this.emit("select", this._selectedItem, this.attachments.get(ids));
|
|
||||||
this._selectedLabel = target;
|
|
||||||
}
|
}
|
||||||
target.classList.add("theme-selected");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -337,7 +344,6 @@ TreeWidget.prototype = {
|
||||||
* items, as well as collapsing and expanding any item.
|
* items, as well as collapsing and expanding any item.
|
||||||
*/
|
*/
|
||||||
onKeypress: function(event) {
|
onKeypress: function(event) {
|
||||||
let currentSelected = this._selectedLabel;
|
|
||||||
switch (event.keyCode) {
|
switch (event.keyCode) {
|
||||||
case event.DOM_VK_UP:
|
case event.DOM_VK_UP:
|
||||||
this.selectPreviousItem();
|
this.selectPreviousItem();
|
||||||
|
@ -367,11 +373,6 @@ TreeWidget.prototype = {
|
||||||
default: return;
|
default: return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this._selectedLabel != currentSelected) {
|
|
||||||
let ids = JSON.stringify(this._selectedItem);
|
|
||||||
this.emit("select", this._selectedItem, this.attachments.get(ids));
|
|
||||||
this.ensureSelectedVisible();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,8 +23,15 @@
|
||||||
<commandset id="editMenuCommands"/>
|
<commandset id="editMenuCommands"/>
|
||||||
|
|
||||||
<popupset id="storagePopupSet">
|
<popupset id="storagePopupSet">
|
||||||
|
<menupopup id="storage-tree-popup">
|
||||||
|
<menuitem id="storage-tree-popup-delete-all"
|
||||||
|
label="&storage.popupMenu.deleteAllLabel;"/>
|
||||||
|
</menupopup>
|
||||||
<menupopup id="storage-table-popup">
|
<menupopup id="storage-table-popup">
|
||||||
<menuitem id="storage-table-popup-delete"/>
|
<menuitem id="storage-table-popup-delete"/>
|
||||||
|
<menuitem id="storage-table-popup-delete-all-from"/>
|
||||||
|
<menuitem id="storage-table-popup-delete-all"
|
||||||
|
label="&storage.popupMenu.deleteAllLabel;"/>
|
||||||
</menupopup>
|
</menupopup>
|
||||||
</popupset>
|
</popupset>
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,15 @@ support-files =
|
||||||
head.js
|
head.js
|
||||||
|
|
||||||
[browser_storage_basic.js]
|
[browser_storage_basic.js]
|
||||||
|
[browser_storage_cookies_delete_all.js]
|
||||||
[browser_storage_cookies_edit.js]
|
[browser_storage_cookies_edit.js]
|
||||||
[browser_storage_cookies_edit_keyboard.js]
|
[browser_storage_cookies_edit_keyboard.js]
|
||||||
[browser_storage_cookies_tab_navigation.js]
|
[browser_storage_cookies_tab_navigation.js]
|
||||||
[browser_storage_dynamic_updates.js]
|
[browser_storage_dynamic_updates.js]
|
||||||
[browser_storage_localstorage_edit.js]
|
[browser_storage_localstorage_edit.js]
|
||||||
[browser_storage_delete.js]
|
[browser_storage_delete.js]
|
||||||
|
[browser_storage_delete_all.js]
|
||||||
|
[browser_storage_delete_tree.js]
|
||||||
[browser_storage_overflow.js]
|
[browser_storage_overflow.js]
|
||||||
[browser_storage_search.js]
|
[browser_storage_search.js]
|
||||||
skip-if = os == "linux" && e10s # Bug 1240804 - unhandled promise rejections
|
skip-if = os == "linux" && e10s # Bug 1240804 - unhandled promise rejections
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
|
@ -28,10 +28,11 @@ add_task(function* () {
|
||||||
yield selectTreeItem([store, host]);
|
yield selectTreeItem([store, host]);
|
||||||
|
|
||||||
let row = getRowCells(rowName);
|
let row = getRowCells(rowName);
|
||||||
|
|
||||||
ok(gUI.table.items.has(rowName),
|
ok(gUI.table.items.has(rowName),
|
||||||
`There is a row '${rowName}' in ${store} > ${host}`);
|
`There is a row '${rowName}' in ${store} > ${host}`);
|
||||||
|
|
||||||
|
let eventWait = gUI.once("store-objects-updated");
|
||||||
|
|
||||||
yield waitForContextMenu(contextMenu, row[cellToClick], () => {
|
yield waitForContextMenu(contextMenu, row[cellToClick], () => {
|
||||||
info(`Opened context menu in ${store} > ${host}, row '${rowName}'`);
|
info(`Opened context menu in ${store} > ${host}, row '${rowName}'`);
|
||||||
menuDeleteItem.click();
|
menuDeleteItem.click();
|
||||||
|
@ -39,7 +40,7 @@ add_task(function* () {
|
||||||
`Context menu item label contains '${rowName}'`);
|
`Context menu item label contains '${rowName}'`);
|
||||||
});
|
});
|
||||||
|
|
||||||
yield gUI.once("store-objects-updated");
|
yield eventWait;
|
||||||
|
|
||||||
ok(!gUI.table.items.has(rowName),
|
ok(!gUI.table.items.has(rowName),
|
||||||
`There is no row '${rowName}' in ${store} > ${host} after deletion`);
|
`There is no row '${rowName}' in ${store} > ${host} after deletion`);
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
|
@ -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();
|
||||||
|
});
|
|
@ -509,17 +509,13 @@ function matchVariablesViewProperty(prop, rule) {
|
||||||
* The array id of the item in the tree
|
* The array id of the item in the tree
|
||||||
*/
|
*/
|
||||||
function* selectTreeItem(ids) {
|
function* selectTreeItem(ids) {
|
||||||
// Expand tree as some/all items could be collapsed leading to click on an
|
/* If this item is already selected, return */
|
||||||
// incorrect tree item
|
if (gUI.tree.isSelected(ids)) {
|
||||||
gUI.tree.expandAll();
|
return;
|
||||||
|
}
|
||||||
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));
|
|
||||||
|
|
||||||
let updated = gUI.once("store-objects-updated");
|
let updated = gUI.once("store-objects-updated");
|
||||||
|
gUI.tree.selectedItem = ids;
|
||||||
yield click(target);
|
|
||||||
yield updated;
|
yield updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -845,8 +841,35 @@ function waitForContextMenu(popup, button, onShown, onHidden) {
|
||||||
popup.addEventListener("popupshown", onPopupShown);
|
popup.addEventListener("popupshown", onPopupShown);
|
||||||
|
|
||||||
info("wait for the context menu to open");
|
info("wait for the context menu to open");
|
||||||
|
button.scrollIntoView();
|
||||||
let eventDetails = {type: "contextmenu", button: 2};
|
let eventDetails = {type: "contextmenu", button: 2};
|
||||||
EventUtils.synthesizeMouse(button, 2, 2, eventDetails,
|
EventUtils.synthesizeMouse(button, 2, 2, eventDetails,
|
||||||
button.ownerDocument.defaultView);
|
button.ownerDocument.defaultView);
|
||||||
return deferred.promise;
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -70,7 +70,10 @@ var StorageUI = this.StorageUI = function StorageUI(front, target, panelWin) {
|
||||||
this.front = front;
|
this.front = front;
|
||||||
|
|
||||||
let treeNode = this._panelDoc.getElementById("storage-tree");
|
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.onHostSelect = this.onHostSelect.bind(this);
|
||||||
this.tree.on("select", this.onHostSelect);
|
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.handleKeypress = this.handleKeypress.bind(this);
|
||||||
this._panelDoc.addEventListener("keypress", this.handleKeypress);
|
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 = 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.onRemoveItem = this.onRemoveItem.bind(this);
|
||||||
|
this.onRemoveAllFrom = this.onRemoveAllFrom.bind(this);
|
||||||
|
this.onRemoveAll = this.onRemoveAll.bind(this);
|
||||||
|
|
||||||
this._tablePopupDelete = this._panelDoc.getElementById(
|
this._tablePopupDelete = this._panelDoc.getElementById(
|
||||||
"storage-table-popup-delete");
|
"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;
|
exports.StorageUI = StorageUI;
|
||||||
|
@ -126,7 +149,6 @@ exports.StorageUI = StorageUI;
|
||||||
StorageUI.prototype = {
|
StorageUI.prototype = {
|
||||||
|
|
||||||
storageTypes: null,
|
storageTypes: null,
|
||||||
shouldResetColumns: true,
|
|
||||||
shouldLoadMoreItems: true,
|
shouldLoadMoreItems: true,
|
||||||
|
|
||||||
set animationsEnabled(value) {
|
set animationsEnabled(value) {
|
||||||
|
@ -145,8 +167,16 @@ StorageUI.prototype = {
|
||||||
this.searchBox.removeEventListener("input", this.filterItems);
|
this.searchBox.removeEventListener("input", this.filterItems);
|
||||||
this.searchBox = null;
|
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._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");
|
this.emit("store-objects-updated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.shouldResetColumns) {
|
if (reason === REASON.POPULATE) {
|
||||||
this.resetColumns(data[0], type);
|
this.resetColumns(data[0], type);
|
||||||
}
|
|
||||||
this.table.host = host;
|
this.table.host = host;
|
||||||
|
}
|
||||||
this.populateTable(data, reason);
|
this.populateTable(data, reason);
|
||||||
this.emit("store-objects-updated");
|
this.emit("store-objects-updated");
|
||||||
|
|
||||||
|
@ -437,7 +467,6 @@ StorageUI.prototype = {
|
||||||
this.tree.add([type, host, ...names]);
|
this.tree.add([type, host, ...names]);
|
||||||
if (!this.tree.selectedItem) {
|
if (!this.tree.selectedItem) {
|
||||||
this.tree.selectedItem = [type, host, names[0], names[1]];
|
this.tree.selectedItem = [type, host, names[0], names[1]];
|
||||||
this.fetchStorageObjects(type, host, [name], REASON.POPULATE);
|
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
// Do Nothing
|
// Do Nothing
|
||||||
|
@ -445,7 +474,6 @@ StorageUI.prototype = {
|
||||||
}
|
}
|
||||||
if (!this.tree.selectedItem) {
|
if (!this.tree.selectedItem) {
|
||||||
this.tree.selectedItem = [type, host];
|
this.tree.selectedItem = [type, host];
|
||||||
this.fetchStorageObjects(type, host, null, REASON.POPULATE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -625,7 +653,6 @@ StorageUI.prototype = {
|
||||||
if (item.length > 2) {
|
if (item.length > 2) {
|
||||||
names = [JSON.stringify(item.slice(2))];
|
names = [JSON.stringify(item.slice(2))];
|
||||||
}
|
}
|
||||||
this.shouldResetColumns = true;
|
|
||||||
this.fetchStorageObjects(type, host, names, REASON.POPULATE);
|
this.fetchStorageObjects(type, host, names, REASON.POPULATE);
|
||||||
this.itemOffset = 0;
|
this.itemOffset = 0;
|
||||||
},
|
},
|
||||||
|
@ -657,7 +684,6 @@ StorageUI.prototype = {
|
||||||
}
|
}
|
||||||
this.table.setColumns(columns, null, HIDDEN_COLUMNS);
|
this.table.setColumns(columns, null, HIDDEN_COLUMNS);
|
||||||
this.table.datatype = type;
|
this.table.datatype = type;
|
||||||
this.shouldResetColumns = false;
|
|
||||||
this.hideSidebar();
|
this.hideSidebar();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -757,23 +783,54 @@ StorageUI.prototype = {
|
||||||
* If the current storage actor doesn't support removing items, prevent
|
* If the current storage actor doesn't support removing items, prevent
|
||||||
* showing the menu.
|
* showing the menu.
|
||||||
*/
|
*/
|
||||||
onPopupShowing: function(event) {
|
onTablePopupShowing: function(event) {
|
||||||
if (!this.getCurrentActor().removeItem) {
|
if (!this.getCurrentActor().removeItem) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxLen = ITEM_NAME_MAX_LENGTH;
|
||||||
|
let [type] = this.tree.selectedItem;
|
||||||
let rowId = this.table.contextMenuRowId;
|
let rowId = this.table.contextMenuRowId;
|
||||||
let data = this.table.items.get(rowId);
|
let data = this.table.items.get(rowId);
|
||||||
let name = data[this.table.uniqueId];
|
let name = data[this.table.uniqueId];
|
||||||
|
|
||||||
const maxLen = ITEM_NAME_MAX_LENGTH;
|
|
||||||
if (name.length > maxLen) {
|
if (name.length > maxLen) {
|
||||||
name = name.substr(0, maxLen) + L10N.ellipsis;
|
name = name.substr(0, maxLen) + L10N.ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._tablePopupDelete.setAttribute("label",
|
this._tablePopupDelete.setAttribute("label",
|
||||||
L10N.getFormatStr("storage.popupMenu.deleteLabel", name));
|
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]);
|
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);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -686,8 +686,18 @@ StorageActors.createActor({
|
||||||
this.removeCookie(host, name);
|
this.removeCookie(host, name);
|
||||||
}), {
|
}), {
|
||||||
request: {
|
request: {
|
||||||
host: Arg(0),
|
host: Arg(0, "string"),
|
||||||
name: Arg(1),
|
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: {}
|
response: {}
|
||||||
}),
|
}),
|
||||||
|
@ -696,11 +706,18 @@ StorageActors.createActor({
|
||||||
cookieHelpers.onCookieChanged = this.onCookieChanged.bind(this);
|
cookieHelpers.onCookieChanged = this.onCookieChanged.bind(this);
|
||||||
|
|
||||||
if (!DebuggerServer.isInChildProcess) {
|
if (!DebuggerServer.isInChildProcess) {
|
||||||
this.getCookiesFromHost = cookieHelpers.getCookiesFromHost;
|
this.getCookiesFromHost =
|
||||||
this.addCookieObservers = cookieHelpers.addCookieObservers;
|
cookieHelpers.getCookiesFromHost.bind(cookieHelpers);
|
||||||
this.removeCookieObservers = cookieHelpers.removeCookieObservers;
|
this.addCookieObservers =
|
||||||
this.editCookie = cookieHelpers.editCookie;
|
cookieHelpers.addCookieObservers.bind(cookieHelpers);
|
||||||
this.removeCookie = cookieHelpers.removeCookie;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -722,6 +739,8 @@ StorageActors.createActor({
|
||||||
callParentProcess.bind(null, "editCookie");
|
callParentProcess.bind(null, "editCookie");
|
||||||
this.removeCookie =
|
this.removeCookie =
|
||||||
callParentProcess.bind(null, "removeCookie");
|
callParentProcess.bind(null, "removeCookie");
|
||||||
|
this.removeAllCookies =
|
||||||
|
callParentProcess.bind(null, "removeAllCookies");
|
||||||
|
|
||||||
addMessageListener("storage:storage-cookie-request-child",
|
addMessageListener("storage:storage-cookie-request-child",
|
||||||
cookieHelpers.handleParentRequest);
|
cookieHelpers.handleParentRequest);
|
||||||
|
@ -875,7 +894,7 @@ var cookieHelpers = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeCookie: function(host, name) {
|
_removeCookies: function(host, opts = {}) {
|
||||||
function hostMatches(cookieHost, matchHost) {
|
function hostMatches(cookieHost, matchHost) {
|
||||||
if (cookieHost == null) {
|
if (cookieHost == null) {
|
||||||
return matchHost == null;
|
return matchHost == null;
|
||||||
|
@ -889,7 +908,9 @@ var cookieHelpers = {
|
||||||
let enumerator = Services.cookies.getCookiesFromHost(host);
|
let enumerator = Services.cookies.getCookiesFromHost(host);
|
||||||
while (enumerator.hasMoreElements()) {
|
while (enumerator.hasMoreElements()) {
|
||||||
let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
|
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(
|
Services.cookies.remove(
|
||||||
cookie.host,
|
cookie.host,
|
||||||
cookie.name,
|
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() {
|
addCookieObservers: function() {
|
||||||
Services.obs.addObserver(cookieHelpers, "cookie-changed", false);
|
Services.obs.addObserver(cookieHelpers, "cookie-changed", false);
|
||||||
return null;
|
return null;
|
||||||
|
@ -969,6 +1000,11 @@ var cookieHelpers = {
|
||||||
let name = msg.data.args[1];
|
let name = msg.data.args[1];
|
||||||
return cookieHelpers.removeCookie(host, name);
|
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:
|
default:
|
||||||
console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method);
|
console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method);
|
||||||
throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD");
|
throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD");
|
||||||
|
@ -1180,6 +1216,16 @@ function getObjectForLocalOrSessionStorage(type) {
|
||||||
},
|
},
|
||||||
response: {}
|
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")
|
data: Arg(0, "json")
|
||||||
},
|
},
|
||||||
"stores-reloaded": {
|
"stores-reloaded": {
|
||||||
type: "storesRelaoded",
|
type: "storesReloaded",
|
||||||
data: Arg(0, "json")
|
data: Arg(0, "json")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2310,11 +2356,13 @@ var StorageActor = exports.StorageActor = protocol.ActorClass({
|
||||||
this.boundUpdate[action][storeType] = {};
|
this.boundUpdate[action][storeType] = {};
|
||||||
}
|
}
|
||||||
for (let host in data) {
|
for (let host in data) {
|
||||||
if (!this.boundUpdate[action][storeType][host] || action == "deleted") {
|
if (!this.boundUpdate[action][storeType][host]) {
|
||||||
this.boundUpdate[action][storeType][host] = data[host];
|
this.boundUpdate[action][storeType][host] = [];
|
||||||
} else {
|
}
|
||||||
this.boundUpdate[action][storeType][host] =
|
for (let name of data[host]) {
|
||||||
this.boundUpdate[action][storeType][host].concat(data[host]);
|
if (!this.boundUpdate[action][storeType][host].includes(name)) {
|
||||||
|
this.boundUpdate[action][storeType][host].push(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (action == "added") {
|
if (action == "added") {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче