Bug 1304297 - Support for deleting Cache and Response objects in Storage Inspector r=miker

MozReview-Commit-ID: BdK4rKhmzTo

--HG--
extra : rebase_source : f03fe260d8719a890a263d7674c8d880a0257f16
This commit is contained in:
Jarda Snajdr 2016-09-22 12:29:48 +02:00
parent c29d9ac401
commit d928014d00
10 changed files with 209 additions and 51 deletions

View File

@ -26,7 +26,7 @@
<menupopup id="storage-tree-popup">
<menuitem id="storage-tree-popup-delete-all"
label="&storage.popupMenu.deleteAllLabel;"/>
<menuitem id="storage-tree-popup-delete-database"/>
<menuitem id="storage-tree-popup-delete"/>
</menupopup>
<menupopup id="storage-table-popup">
<menuitem id="storage-table-popup-delete"/>

View File

@ -19,6 +19,7 @@ support-files =
!/devtools/client/framework/test/shared-head.js
[browser_storage_basic.js]
[browser_storage_cache_delete.js]
[browser_storage_cache_error.js]
[browser_storage_cookies_delete_all.js]
[browser_storage_cookies_domain.js]

View File

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from ../../framework/test/shared-head.js */
"use strict";
// Test deleting a Cache object from the tree using context menu
add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
let contextMenu = gPanelWindow.document.getElementById("storage-tree-popup");
let menuDeleteItem = contextMenu.querySelector("#storage-tree-popup-delete");
let cacheToDelete = ["Cache", "http://test1.example.org", "plop"];
info("test state before delete");
yield selectTreeItem(cacheToDelete);
ok(gUI.tree.isSelected(cacheToDelete), "Cache item is present in the tree");
info("do the delete");
let eventWait = gUI.once("store-objects-updated");
let selector = `[data-id='${JSON.stringify(cacheToDelete)}'] > .tree-widget-item`;
let target = gPanelWindow.document.querySelector(selector);
ok(target, "Cache item's tree element is present");
yield waitForContextMenu(contextMenu, target, () => {
info("Opened tree context menu");
menuDeleteItem.click();
let cacheName = cacheToDelete[2];
ok(menuDeleteItem.getAttribute("label").includes(cacheName),
`Context menu item label contains '${cacheName}')`);
});
yield eventWait;
info("test state after delete");
yield selectTreeItem(cacheToDelete);
ok(!gUI.tree.isSelected(cacheToDelete), "Cache item is no longer present in the tree");
yield finishTests();
});

View File

@ -16,7 +16,9 @@ const TEST_CASES = [
[["cookies", "test1.example.org"],
"c1", "name"],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"],
1, "name"]
1, "name"],
[["Cache", "http://test1.example.org", "plop"],
MAIN_DOMAIN + "404_cached_file.js", "url"],
];
add_task(function* () {
@ -39,8 +41,9 @@ add_task(function* () {
yield waitForContextMenu(contextMenu, row[cellToClick], () => {
info(`Opened context menu in ${treeItemName}, row '${rowName}'`);
menuDeleteItem.click();
ok(menuDeleteItem.getAttribute("label").includes(rowName),
`Context menu item label contains '${rowName}'`);
let truncatedRowName = String(rowName).substr(0, 16);
ok(menuDeleteItem.getAttribute("label").includes(truncatedRowName),
`Context menu item label contains '${rowName}' (maybe truncated)`);
});
yield eventWait;

View File

@ -31,25 +31,29 @@ add_task(function* () {
["iframe-s-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"]],
];
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"],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"], 1],
[["localStorage", "https://sectest1.example.org"], "iframe-s-ls1", "name"],
[["sessionStorage", "https://sectest1.example.org"], "iframe-s-ss1", "name"],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"], 1, "name"],
[["Cache", "http://test1.example.org", "plop"],
MAIN_DOMAIN + "404_cached_file.js", "url"],
];
for (let [store, rowName] of deleteHosts) {
for (let [store, rowName, cellToClick] of deleteHosts) {
let storeName = store.join(" > ");
yield selectTreeItem(store);
let eventWait = gUI.once("store-objects-cleared");
let cell = getRowCells(rowName).name;
let cell = getRowCells(rowName)[cellToClick];
yield waitForContextMenu(contextMenu, cell, () => {
info(`Opened context menu in ${storeName}, row '${rowName}'`);
menuDeleteAllItem.click();
@ -76,6 +80,8 @@ add_task(function* () {
[]],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"],
[]],
[["Cache", "http://test1.example.org", "plop"],
[]],
];
yield checkState(afterState);

View File

@ -21,6 +21,8 @@ add_task(function* () {
[["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");
@ -29,6 +31,7 @@ add_task(function* () {
["localStorage", "http://test1.example.org"],
["sessionStorage", "http://test1.example.org"],
["indexedDB", "http://test1.example.org", "idb1", "obj1"],
["Cache", "http://test1.example.org", "plop"],
];
for (let store of deleteHosts) {
@ -57,6 +60,7 @@ add_task(function* () {
[["localStorage", "http://test1.example.org"], []],
[["sessionStorage", "http://test1.example.org"], []],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"], []],
[["Cache", "http://test1.example.org", "plop"], []],
]);
yield finishTests();

View File

@ -12,8 +12,7 @@ add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-empty-objectstores.html");
let contextMenu = gPanelWindow.document.getElementById("storage-tree-popup");
let menuDeleteDb = contextMenu.querySelector(
"#storage-tree-popup-delete-database");
let menuDeleteDb = contextMenu.querySelector("#storage-tree-popup-delete");
info("test state before delete");
yield checkState([

View File

@ -67,6 +67,13 @@ const ITEM_NAME_MAX_LENGTH = 32;
function addEllipsis(name) {
if (name.length > ITEM_NAME_MAX_LENGTH) {
if (/^https?:/.test(name)) {
// For URLs, add ellipsis in the middle
const halfLen = ITEM_NAME_MAX_LENGTH / 2;
return name.slice(0, halfLen) + ELLIPSIS + name.slice(-halfLen);
}
// For other strings, add ellipsis at the end
return name.substr(0, ITEM_NAME_MAX_LENGTH) + ELLIPSIS;
}
@ -157,7 +164,7 @@ function StorageUI(front, target, panelWin, toolbox) {
this.onRemoveItem = this.onRemoveItem.bind(this);
this.onRemoveAllFrom = this.onRemoveAllFrom.bind(this);
this.onRemoveAll = this.onRemoveAll.bind(this);
this.onRemoveDatabase = this.onRemoveDatabase.bind(this);
this.onRemoveTreeItem = this.onRemoveTreeItem.bind(this);
this._tablePopupDelete = this._panelDoc.getElementById(
"storage-table-popup-delete");
@ -176,10 +183,8 @@ function StorageUI(front, target, panelWin, toolbox) {
"storage-tree-popup-delete-all");
this._treePopupDeleteAll.addEventListener("command", this.onRemoveAll);
this._treePopupDeleteDatabase = this._panelDoc.getElementById(
"storage-tree-popup-delete-database");
this._treePopupDeleteDatabase.addEventListener("command",
this.onRemoveDatabase);
this._treePopupDelete = this._panelDoc.getElementById("storage-tree-popup-delete");
this._treePopupDelete.addEventListener("command", this.onRemoveTreeItem);
}
exports.StorageUI = StorageUI;
@ -205,21 +210,14 @@ StorageUI.prototype = {
this.searchBox.removeEventListener("input", this.filterItems);
this.searchBox = null;
this._treePopup.removeEventListener("popupshowing",
this.onTreePopupShowing);
this._treePopupDeleteAll.removeEventListener("command",
this.onRemoveAll);
this._treePopupDeleteDatabase.removeEventListener("command",
this.onRemoveDatabase);
this._treePopup.removeEventListener("popupshowing", this.onTreePopupShowing);
this._treePopupDeleteAll.removeEventListener("command", this.onRemoveAll);
this._treePopupDelete.removeEventListener("command", this.onRemoveTreeItem);
this._tablePopup.removeEventListener("popupshowing",
this.onTablePopupShowing);
this._tablePopupDelete.removeEventListener("command",
this.onRemoveItem);
this._tablePopupDeleteAllFrom.removeEventListener("command",
this.onRemoveAllFrom);
this._tablePopupDeleteAll.removeEventListener("command",
this.onRemoveAll);
this._tablePopup.removeEventListener("popupshowing", this.onTablePopupShowing);
this._tablePopupDelete.removeEventListener("command", this.onRemoveItem);
this._tablePopupDeleteAllFrom.removeEventListener("command", this.onRemoveAllFrom);
this._tablePopupDeleteAll.removeEventListener("command", this.onRemoveAll);
},
/**
@ -944,24 +942,39 @@ StorageUI.prototype = {
let actor = this.storageTypes[type];
// The delete all (aka clear) action is displayed for IndexedDB object stores
// (level 4 of tree) and for the whole host (level 2 of tree) of other storage
// types (cookies, localStorage, ...).
let showDeleteAll = actor.removeAll &&
(selectedItem.length === (type === "indexedDB" ? 4 : 2));
// (level 4 of tree), for Cache objects (level 3) and for the whole host (level 2)
// for other storage types (cookies, localStorage, ...).
let showDeleteAll = false;
if (actor.removeAll) {
let level;
if (type == "indexedDB") {
level = 4;
} else if (type == "Cache") {
level = 3;
} else {
level = 2;
}
if (selectedItem.length == level) {
showDeleteAll = true;
}
}
this._treePopupDeleteAll.hidden = !showDeleteAll;
// The action to delete database is available for IndexedDB databases, i.e.,
// at level 3 of the tree.
let showDeleteDb = actor.removeDatabase && selectedItem.length === 3;
this._treePopupDeleteDatabase.hidden = !showDeleteDb;
if (showDeleteDb) {
let dbName = addEllipsis(selectedItem[2]);
this._treePopupDeleteDatabase.setAttribute("label",
L10N.getFormatStr("storage.popupMenu.deleteLabel", dbName));
// The delete action is displayed for:
// - IndexedDB databases (level 3 of the tree)
// - Cache objects (level 3 of the tree)
let showDelete = (type == "indexedDB" || type == "Cache") &&
selectedItem.length == 3;
this._treePopupDelete.hidden = !showDelete;
if (showDelete) {
let itemName = addEllipsis(selectedItem[selectedItem.length - 1]);
this._treePopupDelete.setAttribute("label",
L10N.getFormatStr("storage.popupMenu.deleteLabel", itemName));
}
showMenu = showDeleteAll || showDeleteDb;
showMenu = showDeleteAll || showDelete;
}
if (!showMenu) {
@ -1010,15 +1023,24 @@ StorageUI.prototype = {
actor.removeAll(host, data.host);
},
onRemoveDatabase: function () {
let [type, host, name] = this.tree.selectedItem;
let actor = this.storageTypes[type];
onRemoveTreeItem: function () {
let [type, host, ...path] = this.tree.selectedItem;
actor.removeDatabase(host, name).then(result => {
if (type == "indexedDB" && path.length == 1) {
this.removeDatabase(host, path[0]);
} else if (type == "Cache" && path.length == 1) {
this.removeCache(host, path[0]);
}
},
removeDatabase: function (host, dbName) {
let actor = this.storageTypes.indexedDB;
actor.removeDatabase(host, dbName).then(result => {
if (result.blocked) {
let notificationBox = this._toolbox.getNotificationBox();
notificationBox.appendNotification(
L10N.getFormatStr("storage.idb.deleteBlocked", name),
L10N.getFormatStr("storage.idb.deleteBlocked", dbName),
"storage-idb-delete-blocked",
null,
notificationBox.PRIORITY_WARNING_LOW);
@ -1026,10 +1048,16 @@ StorageUI.prototype = {
}).catch(error => {
let notificationBox = this._toolbox.getNotificationBox();
notificationBox.appendNotification(
L10N.getFormatStr("storage.idb.deleteError", name),
L10N.getFormatStr("storage.idb.deleteError", dbName),
"storage-idb-delete-error",
null,
notificationBox.PRIORITY_CRITICAL_LOW);
});
}
},
removeCache: function (host, cacheName) {
let actor = this.storageTypes.Cache;
actor.removeItem(host, JSON.stringify([ cacheName ]));
},
};

View File

@ -1250,6 +1250,61 @@ StorageActors.createActor({
toStoreObject(item) {
return item;
},
removeItem: Task.async(function* (host, name) {
const cacheMap = this.hostVsStores.get(host);
if (!cacheMap) {
return;
}
const parsedName = JSON.parse(name);
if (parsedName.length == 1) {
// Delete the whole Cache object
const [ cacheName ] = parsedName;
cacheMap.delete(cacheName);
const cacheStorage = yield this.getCachesForHost(host);
yield cacheStorage.delete(cacheName);
this.onItemUpdated("deleted", host, [ cacheName ]);
} else if (parsedName.length == 2) {
// Delete one cached request
const [ cacheName, url ] = parsedName;
const cache = cacheMap.get(cacheName);
if (cache) {
yield cache.delete(url);
this.onItemUpdated("deleted", host, [ cacheName, url ]);
}
}
}),
removeAll: Task.async(function* (host, name) {
const cacheMap = this.hostVsStores.get(host);
if (!cacheMap) {
return;
}
const parsedName = JSON.parse(name);
// Only a Cache object is a valid object to clear
if (parsedName.length == 1) {
const [ cacheName ] = parsedName;
const cache = cacheMap.get(cacheName);
if (cache) {
let keys = yield cache.keys();
yield promise.all(keys.map(key => cache.delete(key)));
this.onItemUpdated("cleared", host, [ cacheName ]);
}
}
}),
/**
* CacheStorage API doesn't support any notifications, we must fake them
*/
onItemUpdated(action, host, path) {
this.storageActor.update(action, "Cache", {
[host]: [ JSON.stringify(path) ]
});
},
});
/**

View File

@ -153,7 +153,23 @@ types.addDictType("cachestoreobject", {
// Cache storage spec
createStorageSpec({
typeName: "Cache",
storeObjectType: "cachestoreobject"
storeObjectType: "cachestoreobject",
methods: {
removeAll: {
request: {
host: Arg(0, "string"),
name: Arg(1, "string"),
},
response: {}
},
removeItem: {
request: {
host: Arg(0, "string"),
name: Arg(1, "string"),
},
response: {}
},
}
});
// Indexed DB store object