Bug 1231445 - Part 3: Support for removing IndexedDB records in storage inspector r=mratcliffe

This commit is contained in:
Jarda Snajdr 2016-07-04 04:10:00 +02:00
parent 23366bd7b8
commit c8316a5361
3 changed files with 170 additions and 43 deletions

View File

@ -845,16 +845,20 @@ StorageUI.prototype = {
/**
* Fires before a cell context menu with the "Delete" action is shown.
* If the current storage actor doesn't support removing items, prevent
* If the currently selected storage object doesn't support removing items, prevent
* showing the menu.
*/
onTablePopupShowing: function (event) {
if (!this.getCurrentActor().removeItem) {
let selectedItem = this.tree.selectedItem;
let type = selectedItem[0];
let actor = this.getCurrentActor();
// IndexedDB only supports removing items from object stores (level 4 of the tree)
if (!actor.removeItem || (type === "indexedDB" && selectedItem.length !== 4)) {
event.preventDefault();
return;
}
let [type] = this.tree.selectedItem;
let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId);
let name = addEllipsis(data[this.table.uniqueId]);
@ -878,13 +882,20 @@ StorageUI.prototype = {
let selectedItem = this.tree.selectedItem;
if (selectedItem) {
// this.currentActor() would return wrong value here
let actor = this.storageTypes[selectedItem[0]];
let type = selectedItem[0];
let actor = this.storageTypes[type];
// The delete all (aka clear) action is displayed for IndexedDB object stores
// (level 4 of tree) 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));
let showDeleteAll = selectedItem.length == 2 && actor.removeAll;
this._treePopupDeleteAll.hidden = !showDeleteAll;
let showDeleteDb = selectedItem.length == 3 && actor.removeDatabase;
// 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]);
@ -904,12 +915,15 @@ StorageUI.prototype = {
* Handles removing an item from the storage
*/
onRemoveItem: function () {
let [, host] = this.tree.selectedItem;
let [, host, ...path] = this.tree.selectedItem;
let actor = this.getCurrentActor();
let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId);
actor.removeItem(host, data[this.table.uniqueId]);
let name = data[this.table.uniqueId];
if (path.length > 0) {
name = JSON.stringify([...path, name]);
}
actor.removeItem(host, name);
},
/**
@ -919,10 +933,10 @@ StorageUI.prototype = {
// 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 [type, host, ...path] = this.tree.selectedItem;
let actor = this.storageTypes[type];
actor.removeAll(host);
let name = path.length > 0 ? JSON.stringify(path) : undefined;
actor.removeAll(host, name);
},
/**

View File

@ -1393,6 +1393,30 @@ StorageActors.createActor({
return this.removeDB(host, principal, name);
}),
removeAll: Task.async(function* (host, name) {
let [db, store] = JSON.parse(name);
let win = this.storageActor.getWindowFromHost(host);
if (!win) {
return;
}
let principal = win.document.nodePrincipal;
this.clearDBStore(host, principal, db, store);
}),
removeItem: Task.async(function* (host, name) {
let [db, store, id] = JSON.parse(name);
let win = this.storageActor.getWindowFromHost(host);
if (!win) {
return;
}
let principal = win.document.nodePrincipal;
this.removeDBRecord(host, principal, db, store, id);
}),
getHostName(location) {
if (!location.host) {
return location.href;
@ -1553,13 +1577,16 @@ StorageActors.createActor({
};
},
onDatabaseRemoved(host, name) {
if (this.hostVsStores.has(host)) {
this.hostVsStores.get(host).delete(name);
onItemUpdated(action, host, path) {
// Database was removed, remove it from stores map
if (action === "deleted" && path.length === 1) {
if (this.hostVsStores.has(host)) {
this.hostVsStores.get(host).delete(path[0]);
}
}
this.storageActor.update("deleted", "indexedDB", {
[host]: [ JSON.stringify([name]) ]
this.storageActor.update(action, "indexedDB", {
[host]: [ JSON.stringify(path) ]
});
},
@ -1574,6 +1601,8 @@ StorageActors.createActor({
this.getValuesForHost = indexedDBHelpers.getValuesForHost;
this.getObjectStoreData = indexedDBHelpers.getObjectStoreData;
this.removeDB = indexedDBHelpers.removeDB;
this.removeDBRecord = indexedDBHelpers.removeDBRecord;
this.clearDBStore = indexedDBHelpers.clearDBStore;
return;
}
@ -1585,14 +1614,12 @@ StorageActors.createActor({
setupParent: "setupParentProcessForIndexedDB"
});
this.getDBMetaData =
callParentProcessAsync.bind(null, "getDBMetaData");
this.getDBNamesForHost =
callParentProcessAsync.bind(null, "getDBNamesForHost");
this.getValuesForHost =
callParentProcessAsync.bind(null, "getValuesForHost");
this.removeDB =
callParentProcessAsync.bind(null, "removeDB");
this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData");
this.getDBNamesForHost = callParentProcessAsync.bind(null, "getDBNamesForHost");
this.getValuesForHost = callParentProcessAsync.bind(null, "getValuesForHost");
this.removeDB = callParentProcessAsync.bind(null, "removeDB");
this.removeDBRecord = callParentProcessAsync.bind(null, "removeDBRecord");
this.clearDBStore = callParentProcessAsync.bind(null, "clearDBStore");
addMessageListener("storage:storage-indexedDB-request-child", msg => {
switch (msg.json.method) {
@ -1605,9 +1632,9 @@ StorageActors.createActor({
}
break;
}
case "onDatabaseRemoved": {
let [host, name] = msg.json.args;
this.onDatabaseRemoved(host, name);
case "onItemUpdated": {
let [action, host, path] = msg.json.args;
this.onItemUpdated(action, host, path);
}
}
});
@ -1639,13 +1666,13 @@ var indexedDBHelpers = {
});
},
onDatabaseRemoved: function (host, name) {
onItemUpdated(action, host, path) {
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
mm.broadcastAsyncMessage("storage:storage-indexedDB-request-child", {
method: "onDatabaseRemoved",
args: [ host, name ]
method: "onItemUpdated",
args: [ action, host, path ]
});
},
@ -1666,9 +1693,9 @@ var indexedDBHelpers = {
success.resolve(this.backToChild("getDBMetaData", dbData));
};
request.onerror = () => {
request.onerror = ({target}) => {
console.error(
`Error opening indexeddb database ${name} for host ${host}`);
`Error opening indexeddb database ${name} for host ${host}`, target.error);
success.resolve(this.backToChild("getDBMetaData", null));
};
return success.promise;
@ -1683,24 +1710,24 @@ var indexedDBHelpers = {
},
removeDB: Task.async(function* (host, principal, name) {
let request = require("indexedDB").deleteForPrincipal(principal, name);
let result = new promise(resolve => {
let request = require("indexedDB").deleteForPrincipal(principal, name);
request.onsuccess = () => {
resolve({});
this.onDatabaseRemoved(host, name);
this.onItemUpdated("deleted", host, [name]);
};
request.onblocked = () => {
console.error(
`Deleting indexedDB database ${name} for host ${host} is blocked`);
console.warn(`Deleting indexedDB database ${name} for host ${host} is blocked`);
resolve({ blocked: true });
};
request.onerror = () => {
console.error(
`Error deleting indexedDB database ${name} for host ${host}`);
resolve({ error: request.error });
let { error } = request;
console.warn(
`Error deleting indexedDB database ${name} for host ${host}: ${error}`);
resolve({ error: error.message });
};
// If the database is blocked repeatedly, the onblocked event will not
@ -1712,6 +1739,70 @@ var indexedDBHelpers = {
return this.backToChild("removeDB", yield result);
}),
removeDBRecord: Task.async(function* (host, principal, dbName, storeName, id) {
let db;
try {
db = yield new promise((resolve, reject) => {
let request = this.openWithPrincipal(principal, dbName);
request.onsuccess = ev => resolve(ev.target.result);
request.onerror = ev => reject(ev.target.error);
});
let transaction = db.transaction(storeName, "readwrite");
let store = transaction.objectStore(storeName);
yield new promise((resolve, reject) => {
let request = store.delete(id);
request.onsuccess = () => resolve();
request.onerror = ev => reject(ev.target.error);
});
this.onItemUpdated("deleted", host, [dbName, storeName, id]);
} catch (error) {
let recordPath = [dbName, storeName, id].join("/");
console.error(`Failed to delete indexedDB record: ${recordPath}: ${error}`);
}
if (db) {
db.close();
}
return this.backToChild("removeDBRecord", null);
}),
clearDBStore: Task.async(function* (host, principal, dbName, storeName) {
let db;
try {
db = yield new promise((resolve, reject) => {
let request = this.openWithPrincipal(principal, dbName);
request.onsuccess = ev => resolve(ev.target.result);
request.onerror = ev => reject(ev.target.error);
});
let transaction = db.transaction(storeName, "readwrite");
let store = transaction.objectStore(storeName);
yield new promise((resolve, reject) => {
let request = store.clear();
request.onsuccess = () => resolve();
request.onerror = ev => reject(ev.target.error);
});
this.onItemUpdated("cleared", host, [dbName, storeName]);
} catch (error) {
let storePath = [dbName, storeName].join("/");
console.error(`Failed to clear indexedDB store: ${storePath}: ${error}`);
}
if (db) {
db.close();
}
return this.backToChild("clearDBStore", null);
}),
/**
* Fetches all the databases and their metadata for the given `host`.
*/
@ -1987,6 +2078,14 @@ var indexedDBHelpers = {
let [host, principal, name] = args;
return indexedDBHelpers.removeDB(host, principal, name);
}
case "removeDBRecord": {
let [host, principal, db, store, id] = args;
return indexedDBHelpers.removeDBRecord(host, principal, db, store, id);
}
case "clearDBStore": {
let [host, principal, db, store] = args;
return indexedDBHelpers.clearDBStore(host, principal, db, store);
}
default:
console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method);
throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD");

View File

@ -187,7 +187,21 @@ createStorageSpec({
name: Arg(1, "string"),
},
response: RetVal("idbdeleteresult")
}
},
removeAll: {
request: {
host: Arg(0, "string"),
name: Arg(1, "string"),
},
response: {}
},
removeItem: {
request: {
host: Arg(0, "string"),
name: Arg(1, "string"),
},
response: {}
},
}
});