diff options
18 files changed, 361 insertions, 129 deletions
diff --git a/devtools/client/locales/en-US/storage.properties b/devtools/client/locales/en-US/storage.properties index fd3b3ec52..3efd7c84a 100644 --- a/devtools/client/locales/en-US/storage.properties +++ b/devtools/client/locales/en-US/storage.properties @@ -60,7 +60,7 @@ table.headers.indexedDB.value=Value table.headers.indexedDB.origin=Origin table.headers.indexedDB.version=Version table.headers.indexedDB.objectStores=Object Stores -table.headers.indexedDB.keyPath=Key +table.headers.indexedDB.keyPath2=Key Path table.headers.indexedDB.autoIncrement=Auto Increment table.headers.indexedDB.indexes=Indexes diff --git a/devtools/client/storage/test/browser.ini b/devtools/client/storage/test/browser.ini index 0ac28a92d..08ee7f176 100644 --- a/devtools/client/storage/test/browser.ini +++ b/devtools/client/storage/test/browser.ini @@ -7,6 +7,7 @@ support-files = storage-cookies.html storage-empty-objectstores.html storage-idb-delete-blocked.html + storage-indexeddb-duplicate-names.html storage-listings.html storage-localstorage.html storage-overflow.html @@ -35,6 +36,7 @@ support-files = [browser_storage_empty_objectstores.js] [browser_storage_indexeddb_delete.js] [browser_storage_indexeddb_delete_blocked.js] +[browser_storage_indexeddb_duplicate_names.js] [browser_storage_localstorage_edit.js] [browser_storage_localstorage_error.js] [browser_storage_overflow.js] diff --git a/devtools/client/storage/test/browser_storage_basic.js b/devtools/client/storage/test/browser_storage_basic.js index 10eb8d0ca..72582e32f 100644 --- a/devtools/client/storage/test/browser_storage_basic.js +++ b/devtools/client/storage/test/browser_storage_basic.js @@ -53,28 +53,28 @@ const testCases = [ [["sessionStorage", "https://sectest1.example.org"], ["iframe-s-ss1"]], [["indexedDB", "http://test1.example.org"], - ["idb1", "idb2"]], - [["indexedDB", "http://test1.example.org", "idb1"], + ["idb1 (default)", "idb2 (default)"]], + [["indexedDB", "http://test1.example.org", "idb1 (default)"], ["obj1", "obj2"]], - [["indexedDB", "http://test1.example.org", "idb2"], + [["indexedDB", "http://test1.example.org", "idb2 (default)"], ["obj3"]], - [["indexedDB", "http://test1.example.org", "idb1", "obj1"], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], [1, 2, 3]], - [["indexedDB", "http://test1.example.org", "idb1", "obj2"], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"], [1]], - [["indexedDB", "http://test1.example.org", "idb2", "obj3"], + [["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"], []], [["indexedDB", "http://sectest1.example.org"], []], [["indexedDB", "https://sectest1.example.org"], - ["idb-s1", "idb-s2"]], - [["indexedDB", "https://sectest1.example.org", "idb-s1"], + ["idb-s1 (default)", "idb-s2 (default)"]], + [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)"], ["obj-s1"]], - [["indexedDB", "https://sectest1.example.org", "idb-s2"], + [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"], ["obj-s2"]], - [["indexedDB", "https://sectest1.example.org", "idb-s1", "obj-s1"], + [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)", "obj-s1"], [6, 7]], - [["indexedDB", "https://sectest1.example.org", "idb-s2", "obj-s2"], + [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)", "obj-s2"], [16]], [["Cache", "http://test1.example.org", "plop"], [MAIN_DOMAIN + "404_cached_file.js", diff --git a/devtools/client/storage/test/browser_storage_delete.js b/devtools/client/storage/test/browser_storage_delete.js index d5cb59a10..a3ef7c290 100644 --- a/devtools/client/storage/test/browser_storage_delete.js +++ b/devtools/client/storage/test/browser_storage_delete.js @@ -17,7 +17,7 @@ const TEST_CASES = [ ["cookies", "test1.example.org"], getCookieId("c1", "test1.example.org", "/browser"), "name" ], - [["indexedDB", "http://test1.example.org", "idb1", "obj1"], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], 1, "name"], [["Cache", "http://test1.example.org", "plop"], MAIN_DOMAIN + "404_cached_file.js", "url"], diff --git a/devtools/client/storage/test/browser_storage_delete_all.js b/devtools/client/storage/test/browser_storage_delete_all.js index c4b6048fb..60b417bdb 100644 --- a/devtools/client/storage/test/browser_storage_delete_all.js +++ b/devtools/client/storage/test/browser_storage_delete_all.js @@ -29,7 +29,7 @@ add_task(function* () { ["iframe-u-ss1", "iframe-u-ss2"]], [["sessionStorage", "https://sectest1.example.org"], ["iframe-s-ss1"]], - [["indexedDB", "http://test1.example.org", "idb1", "obj1"], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], [1, 2, 3]], [["Cache", "http://test1.example.org", "plop"], [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]], @@ -41,7 +41,7 @@ add_task(function* () { const deleteHosts = [ [["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"], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], 1, "name"], [["Cache", "http://test1.example.org", "plop"], MAIN_DOMAIN + "404_cached_file.js", "url"], ]; @@ -78,7 +78,7 @@ add_task(function* () { ["iframe-u-ss1", "iframe-u-ss2"]], [["sessionStorage", "https://sectest1.example.org"], []], - [["indexedDB", "http://test1.example.org", "idb1", "obj1"], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], []], [["Cache", "http://test1.example.org", "plop"], []], diff --git a/devtools/client/storage/test/browser_storage_delete_tree.js b/devtools/client/storage/test/browser_storage_delete_tree.js index 3e866a2f5..0a7a7e645 100644 --- a/devtools/client/storage/test/browser_storage_delete_tree.js +++ b/devtools/client/storage/test/browser_storage_delete_tree.js @@ -28,7 +28,7 @@ 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]], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], [1, 2, 3]], [["Cache", "http://test1.example.org", "plop"], [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]], ]); @@ -38,7 +38,7 @@ add_task(function* () { ["cookies", "test1.example.org"], ["localStorage", "http://test1.example.org"], ["sessionStorage", "http://test1.example.org"], - ["indexedDB", "http://test1.example.org", "idb1", "obj1"], + ["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], ["Cache", "http://test1.example.org", "plop"], ]; @@ -67,7 +67,7 @@ add_task(function* () { [["cookies", "test1.example.org"], []], [["localStorage", "http://test1.example.org"], []], [["sessionStorage", "http://test1.example.org"], []], - [["indexedDB", "http://test1.example.org", "idb1", "obj1"], []], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], []], [["Cache", "http://test1.example.org", "plop"], []], ]); diff --git a/devtools/client/storage/test/browser_storage_empty_objectstores.js b/devtools/client/storage/test/browser_storage_empty_objectstores.js index 1749c91b8..e6f259742 100644 --- a/devtools/client/storage/test/browser_storage_empty_objectstores.js +++ b/devtools/client/storage/test/browser_storage_empty_objectstores.js @@ -21,14 +21,14 @@ // storage-secured-iframe.html and storage-unsecured-iframe.html const storeItems = [ [["indexedDB", "http://test1.example.org"], - ["idb1", "idb2"]], - [["indexedDB", "http://test1.example.org", "idb1"], + ["idb1 (default)", "idb2 (default)"]], + [["indexedDB", "http://test1.example.org", "idb1 (default)"], ["obj1", "obj2"]], - [["indexedDB", "http://test1.example.org", "idb2"], + [["indexedDB", "http://test1.example.org", "idb2 (default)"], []], - [["indexedDB", "http://test1.example.org", "idb1", "obj1"], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], [1, 2, 3]], - [["indexedDB", "http://test1.example.org", "idb1", "obj2"], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"], [1]] ]; diff --git a/devtools/client/storage/test/browser_storage_indexeddb_delete.js b/devtools/client/storage/test/browser_storage_indexeddb_delete.js index 18a0daf69..5c499c9e9 100644 --- a/devtools/client/storage/test/browser_storage_indexeddb_delete.js +++ b/devtools/client/storage/test/browser_storage_indexeddb_delete.js @@ -16,11 +16,11 @@ add_task(function* () { info("test state before delete"); yield checkState([ - [["indexedDB", "http://test1.example.org"], ["idb1", "idb2"]], + [["indexedDB", "http://test1.example.org"], ["idb1 (default)", "idb2 (default)"]], ]); info("do the delete"); - const deletedDb = ["indexedDB", "http://test1.example.org", "idb1"]; + const deletedDb = ["indexedDB", "http://test1.example.org", "idb1 (default)"]; yield selectTreeItem(deletedDb); @@ -40,7 +40,7 @@ add_task(function* () { info("test state after delete"); yield checkState([ - [["indexedDB", "http://test1.example.org"], ["idb2"]], + [["indexedDB", "http://test1.example.org"], ["idb2 (default)"]], ]); yield finishTests(); diff --git a/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js b/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js index 6e89c4f28..f87f15d82 100644 --- a/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js +++ b/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js @@ -13,19 +13,19 @@ add_task(function* () { info("test state before delete"); yield checkState([ - [["indexedDB", "http://test1.example.org"], ["idb"]] + [["indexedDB", "http://test1.example.org"], ["idb (default)"]] ]); info("do the delete"); yield selectTreeItem(["indexedDB", "http://test1.example.org"]); let actor = gUI.getCurrentActor(); - let result = yield actor.removeDatabase("http://test1.example.org", "idb"); + let result = yield actor.removeDatabase("http://test1.example.org", "idb (default)"); ok(result.blocked, "removeDatabase attempt is blocked"); info("test state after blocked delete"); yield checkState([ - [["indexedDB", "http://test1.example.org"], ["idb"]] + [["indexedDB", "http://test1.example.org"], ["idb (default)"]] ]); let eventWait = gUI.once("store-objects-updated"); @@ -47,7 +47,7 @@ add_task(function* () { info("try to delete database from nonexistent host"); let errorThrown = false; try { - result = yield actor.removeDatabase("http://test2.example.org", "idb"); + result = yield actor.removeDatabase("http://test2.example.org", "idb (default)"); } catch (ex) { errorThrown = true; } diff --git a/devtools/client/storage/test/browser_storage_indexeddb_duplicate_names.js b/devtools/client/storage/test/browser_storage_indexeddb_duplicate_names.js new file mode 100644 index 000000000..8316d22c5 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_indexeddb_duplicate_names.js @@ -0,0 +1,31 @@ +/* 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/. */ + +// Test to verify that indexedDBs with duplicate names (different types / paths) +// work as expected. + +"use strict"; + +add_task(function* () { + const TESTPAGE = MAIN_DOMAIN + "storage-indexeddb-duplicate-names.html"; + + setPermission(TESTPAGE, "indexedDB"); + + yield openTabAndSetupStorage(TESTPAGE); + + yield checkState([ + [ + ["indexedDB", "http://test1.example.org"], [ + "idb1 (default)", + "idb1 (temporary)", + "idb1 (persistent)", + "idb2 (default)", + "idb2 (temporary)", + "idb2 (persistent)" + ] + ] + ]); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_sidebar.js b/devtools/client/storage/test/browser_storage_sidebar.js index d1a71c667..6712ac013 100644 --- a/devtools/client/storage/test/browser_storage_sidebar.js +++ b/devtools/client/storage/test/browser_storage_sidebar.js @@ -72,17 +72,17 @@ const testCases = [ sidebarHidden: true }, { - location: "idb2", + location: "idb2 (default)", sidebarHidden: false }, { - location: ["indexedDB", "http://test1.example.org", "idb2", "obj3"], + location: ["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"], sidebarHidden: true }, { - location: ["indexedDB", "https://sectest1.example.org", "idb-s2"], + location: ["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"], sidebarHidden: true }, { diff --git a/devtools/client/storage/test/browser_storage_values.js b/devtools/client/storage/test/browser_storage_values.js index 122a867bb..1d3e9ff76 100644 --- a/devtools/client/storage/test/browser_storage_values.js +++ b/devtools/client/storage/test/browser_storage_values.js @@ -124,7 +124,7 @@ const testCases = [ {name: "ss5.3", value: `${LONG_WORD}&${LONG_WORD}`}, {name: "ss5.4", value: `${LONG_WORD}&${LONG_WORD}`}, ], true], - [["indexedDB", "http://test1.example.org", "idb1", "obj1"]], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"]], [1, [ {name: 1, value: JSON.stringify({id: 1, name: "foo", email: "foo@bar.com"})} ]], @@ -133,7 +133,7 @@ const testCases = [ {name: "1.name", value: "foo"}, {name: "1.email", value: "foo@bar.com"}, ], true], - [["indexedDB", "http://test1.example.org", "idb1", "obj2"]], + [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"]], [1, [ {name: 1, value: JSON.stringify({ id2: 1, name: "foo", email: "foo@bar.com", extra: "baz" diff --git a/devtools/client/storage/test/head.js b/devtools/client/storage/test/head.js index 894056c9e..0d7d13e17 100644 --- a/devtools/client/storage/test/head.js +++ b/devtools/client/storage/test/head.js @@ -879,3 +879,19 @@ var focusSearchBoxUsingShortcut = Task.async(function* (panelWin, callback) { function getCookieId(name, domain, path) { return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`; } + +function setPermission(url, permission) { + const nsIPermissionManager = Components.interfaces.nsIPermissionManager; + + let uri = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService) + .newURI(url, null, null); + let ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"] + .getService(Ci.nsIScriptSecurityManager); + let principal = ssm.createCodebasePrincipal(uri, {}); + + Components.classes["@mozilla.org/permissionmanager;1"] + .getService(nsIPermissionManager) + .addFromPrincipal(principal, permission, + nsIPermissionManager.ALLOW_ACTION); +} diff --git a/devtools/client/storage/test/storage-indexeddb-duplicate-names.html b/devtools/client/storage/test/storage-indexeddb-duplicate-names.html new file mode 100644 index 000000000..d8c76dc2a --- /dev/null +++ b/devtools/client/storage/test/storage-indexeddb-duplicate-names.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html><head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <meta charset="utf-8"> + <title>Storage inspector IndexedDBs with duplicate names</title> + + <script type="application/javascript;version=1.7"> + "use strict"; + + function createIndexedDBs() { + createIndexedDB("idb1", "temporary"); + createIndexedDB("idb1", "default"); + createIndexedDB("idb1", "persistent"); + createIndexedDB("idb2", "temporary"); + createIndexedDB("idb2", "default"); + createIndexedDB("idb2", "persistent"); + } + + function createIndexedDB(name, storage) { + let open = indexedDB.open(name, {storage: storage}); + + open.onsuccess = function () { + let db = open.result; + db.close(); + }; + } + + function deleteDB(dbName, storage) { + return new Promise(resolve => { + dump(`removing database ${dbName} (${storage}) from ${document.location}\n`); + indexedDB.deleteDatabase(dbName, { storage: storage }).onsuccess = resolve; + }); + } + + window.clear = function* () { + yield deleteDB("idb1", "temporary"); + yield deleteDB("idb1", "default"); + yield deleteDB("idb1", "persistent"); + yield deleteDB("idb2", "temporary"); + yield deleteDB("idb2", "default"); + yield deleteDB("idb2", "persistent"); + + dump(`removed indexedDB data from ${document.location}\n`); + }; + </script> +</head> +<body onload="createIndexedDBs()"> + <h1>storage-indexeddb-duplicate-names.html</h1> +</body> +</html> diff --git a/devtools/client/storage/ui.js b/devtools/client/storage/ui.js index c4dd57c6e..1241d0120 100644 --- a/devtools/client/storage/ui.js +++ b/devtools/client/storage/ui.js @@ -809,7 +809,10 @@ StorageUI.prototype = { columns[f.name] = f.name; let columnName; try { - columnName = L10N.getStr("table.headers." + type + "." + f.name); + // Path key names for l10n in the case of a string change. + let name = f.name === "keyPath" ? "keyPath2" : f.name; + + columnName = L10N.getStr("table.headers." + type + "." + name); } catch (e) { columnName = COOKIE_KEY_MAP[f.name]; } diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 894e282f0..22f4eaabe 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -2,6 +2,8 @@ * 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/. */ +/* globals StopIteration */ + "use strict"; const {Cc, Ci, Cu, CC} = require("chrome"); @@ -93,7 +95,7 @@ var StorageActors = {}; * - observe : Method which gets triggered on the notificaiton of the watched * topic. * - getNamesForHost : Given a host, get list of all known store names. - * - getValuesForHost : Given a host (and optianally a name) get all known + * - getValuesForHost : Given a host (and optionally a name) get all known * store objects. * - toStoreObject : Given a store object, convert it to the required format * so that it can be transferred over wire. @@ -141,6 +143,9 @@ StorageActors.defaults = function (typeName, observationTopic) { * Converts the window.location object into host. */ getHostName(location) { + if (location.protocol === "chrome:") { + return location.href; + } return location.hostname || location.href; }, @@ -744,6 +749,7 @@ var cookieHelpers = { let enumerator = Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {}); + while (enumerator.hasMoreElements()) { let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); if (nsiCookie.name === origName && @@ -1041,6 +1047,9 @@ function getObjectForLocalOrSessionStorage(type) { if (!location.host) { return location.href; } + if (location.protocol === "chrome:") { + return location.href; + } return location.protocol + "//" + location.host; }, @@ -1251,6 +1260,9 @@ StorageActors.createActor({ if (!location.host) { return location.href; } + if (location.protocol === "chrome:") { + return location.href; + } return location.protocol + "//" + location.host; }, @@ -1423,12 +1435,15 @@ ObjectStoreMetadata.prototype = { * The host associated with this indexed db. * @param {IDBDatabase} db * The particular indexed db. + * @param {String} storage + * Storage type, either "temporary", "default" or "persistent". */ -function DatabaseMetadata(origin, db) { +function DatabaseMetadata(origin, db, storage) { this._origin = origin; this._name = db.name; this._version = db.version; this._objectStores = []; + this.storage = storage; if (db.objectStoreNames.length) { let transaction = db.transaction(db.objectStoreNames, "readonly"); @@ -1448,7 +1463,7 @@ DatabaseMetadata.prototype = { toObject() { return { - name: this._name, + name: `${this._name} (${this.storage})`, origin: this._origin, version: this._version, objectStores: this._objectStores.size @@ -1524,6 +1539,9 @@ StorageActors.createActor({ if (!location.host) { return location.href; } + if (location.protocol === "chrome:") { + return location.href; + } return location.protocol + "//" + location.host; }, @@ -1616,15 +1634,17 @@ StorageActors.createActor({ populateStoresForHost: Task.async(function* (host) { let storeMap = new Map(); let {names} = yield this.getDBNamesForHost(host); + let win = this.storageActor.getWindowFromHost(host); if (win) { let principal = win.document.nodePrincipal; - for (let name of names) { - let metadata = yield this.getDBMetaData(host, principal, name); + for (let {name, storage} of names) { + let metadata = yield this.getDBMetaData(host, principal, name, storage); metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata); - storeMap.set(name, metadata); + + storeMap.set(`${name} (${storage})`, metadata); } } @@ -1657,10 +1677,22 @@ StorageActors.createActor({ objectStores: item.objectStores }; } + + let value = JSON.stringify(item.value); + + // FIXME: Bug 1318029 - Due to a bug that is thrown whenever a + // LongStringActor string reaches DebuggerServer.LONG_STRING_LENGTH we need + // to trim the value. When the bug is fixed we should stop trimming the + // string here. + let maxLength = DebuggerServer.LONG_STRING_LENGTH - 1; + if (value.length > maxLength) { + value = value.substr(0, maxLength); + } + // Indexed db entry return { name: item.name, - value: new LongStringActor(this.conn, JSON.stringify(item.value)) + value: new LongStringActor(this.conn, value) }; }, @@ -1696,16 +1728,18 @@ StorageActors.createActor({ maybeSetupChildProcess() { if (!DebuggerServer.isInChildProcess) { this.backToChild = (func, rv) => rv; + this.clearDBStore = indexedDBHelpers.clearDBStore; + this.gatherFilesOrFolders = indexedDBHelpers.gatherFilesOrFolders; this.getDBMetaData = indexedDBHelpers.getDBMetaData; - this.openWithPrincipal = indexedDBHelpers.openWithPrincipal; this.getDBNamesForHost = indexedDBHelpers.getDBNamesForHost; - this.getSanitizedHost = indexedDBHelpers.getSanitizedHost; this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile; - this.getValuesForHost = indexedDBHelpers.getValuesForHost; this.getObjectStoreData = indexedDBHelpers.getObjectStoreData; + this.getSanitizedHost = indexedDBHelpers.getSanitizedHost; + this.getValuesForHost = indexedDBHelpers.getValuesForHost; + this.openWithPrincipal = indexedDBHelpers.openWithPrincipal; this.removeDB = indexedDBHelpers.removeDB; this.removeDBRecord = indexedDBHelpers.removeDBRecord; - this.clearDBStore = indexedDBHelpers.clearDBStore; + this.splitNameAndStorage = indexedDBHelpers.splitNameAndStorage; return; } @@ -1718,6 +1752,7 @@ StorageActors.createActor({ }); this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData"); + this.splitNameAndStorage = callParentProcessAsync.bind(null, "splitNameAndStorage"); this.getDBNamesForHost = callParentProcessAsync.bind(null, "getDBNamesForHost"); this.getValuesForHost = callParentProcessAsync.bind(null, "getValuesForHost"); this.removeDB = callParentProcessAsync.bind(null, "removeDB"); @@ -1813,14 +1848,14 @@ var indexedDBHelpers = { * `name` for the given `host` with its `principal`. The stored metadata * information is of `DatabaseMetadata` type. */ - getDBMetaData: Task.async(function* (host, principal, name) { - let request = this.openWithPrincipal(principal, name); + getDBMetaData: Task.async(function* (host, principal, name, storage) { + let request = this.openWithPrincipal(principal, name, storage); let success = promise.defer(); request.onsuccess = event => { let db = event.target.result; - let dbData = new DatabaseMetadata(host, db); + let dbData = new DatabaseMetadata(host, db, storage); db.close(); success.resolve(this.backToChild("getDBMetaData", dbData)); @@ -1833,21 +1868,37 @@ var indexedDBHelpers = { return success.promise; }), + splitNameAndStorage: function (name) { + let lastOpenBracketIndex = name.lastIndexOf("("); + let lastCloseBracketIndex = name.lastIndexOf(")"); + let delta = lastCloseBracketIndex - lastOpenBracketIndex - 1; + + let storage = name.substr(lastOpenBracketIndex + 1, delta); + + name = name.substr(0, lastOpenBracketIndex - 1); + + return { storage, name }; + }, + /** * Opens an indexed db connection for the given `principal` and * database `name`. */ - openWithPrincipal(principal, name) { - return indexedDBForStorage.openForPrincipal(principal, name); + openWithPrincipal: function (principal, name, storage) { + return indexedDBForStorage.openForPrincipal(principal, name, + { storage: storage }); }, - removeDB: Task.async(function* (host, principal, name) { + removeDB: Task.async(function* (host, principal, dbName) { let result = new promise(resolve => { - let request = indexedDBForStorage.deleteForPrincipal(principal, name); + let {name, storage} = this.splitNameAndStorage(dbName); + let request = + indexedDBForStorage.deleteForPrincipal(principal, name, + { storage: storage }); request.onsuccess = () => { resolve({}); - this.onItemUpdated("deleted", host, [name]); + this.onItemUpdated("deleted", host, [dbName]); }; request.onblocked = () => { @@ -1873,10 +1924,11 @@ var indexedDBHelpers = { removeDBRecord: Task.async(function* (host, principal, dbName, storeName, id) { let db; + let {name, storage} = this.splitNameAndStorage(dbName); try { db = yield new promise((resolve, reject) => { - let request = this.openWithPrincipal(principal, dbName); + let request = this.openWithPrincipal(principal, name, storage); request.onsuccess = ev => resolve(ev.target.result); request.onerror = ev => reject(ev.target.error); }); @@ -1905,10 +1957,11 @@ var indexedDBHelpers = { clearDBStore: Task.async(function* (host, principal, dbName, storeName) { let db; + let {name, storage} = this.splitNameAndStorage(dbName); try { db = yield new promise((resolve, reject) => { - let request = this.openWithPrincipal(principal, dbName); + let request = this.openWithPrincipal(principal, name, storage); request.onsuccess = ev => resolve(ev.target.result); request.onerror = ev => reject(ev.target.error); }); @@ -1940,46 +1993,106 @@ var indexedDBHelpers = { */ getDBNamesForHost: Task.async(function* (host) { let sanitizedHost = this.getSanitizedHost(host); - let directory = OS.Path.join(OS.Constants.Path.profileDir, "storage", - "default", sanitizedHost, "idb"); + let profileDir = OS.Constants.Path.profileDir; + let files = []; + let names = []; + let storagePath = OS.Path.join(profileDir, "storage"); + + // We expect sqlite DB paths to look something like this: + // - PathToProfileDir/storage/default/http+++www.example.com/ + // idb/1556056096MeysDaabta.sqlite + // - PathToProfileDir/storage/permanent/http+++www.example.com/ + // idb/1556056096MeysDaabta.sqlite + // - PathToProfileDir/storage/temporary/http+++www.example.com/ + // idb/1556056096MeysDaabta.sqlite + // + // The subdirectory inside the storage folder is determined by the storage + // type: + // - default: { storage: "default" } or not specified. + // - permanent: { storage: "persistent" }. + // - temporary: { storage: "temporary" }. + let sqliteFiles = yield this.gatherFilesOrFolders(storagePath, path => { + if (path.endsWith(".sqlite")) { + let { components } = OS.Path.split(path); + let isIDB = components[components.length - 2] === "idb"; + + return isIDB; + } + return false; + }); - let exists = yield OS.File.exists(directory); - if (!exists && host.startsWith("about:")) { - // try for moz-safe-about directory - sanitizedHost = this.getSanitizedHost("moz-safe-" + host); - directory = OS.Path.join(OS.Constants.Path.profileDir, "storage", - "permanent", sanitizedHost, "idb"); - exists = yield OS.File.exists(directory); - } - if (!exists) { - return this.backToChild("getDBNamesForHost", {names: []}); + for (let file of sqliteFiles) { + let splitPath = OS.Path.split(file).components; + let idbIndex = splitPath.indexOf("idb"); + let name = splitPath[idbIndex - 1]; + let storage = splitPath[idbIndex - 2]; + let relative = file.substr(profileDir.length + 1); + + if (name.startsWith(sanitizedHost)) { + files.push({ + file: relative, + storage: storage === "permanent" ? "persistent" : storage + }); + } } - let names = []; - let dirIterator = new OS.File.DirectoryIterator(directory); - try { - yield dirIterator.forEach(file => { - // Skip directories. - if (file.isDir) { - return null; + if (files.length > 0) { + for (let {file, storage} of files) { + let name = yield this.getNameFromDatabaseFile(file); + if (name) { + names.push({ + name, + storage + }); } + } + } + return this.backToChild("getDBNamesForHost", {names}); + }), - // Skip any non-sqlite files. - if (!file.name.endsWith(".sqlite")) { - return null; - } + /** + * Gather together all of the files in path and pass each path through a + * validation function. + * + * @param {String} + * Path in which to begin searching. + * @param {Function} + * Validation function, which checks each file path. If this function + * Returns true the file path is kept. + * + * @returns {Array} + * An array of file paths. + */ + gatherFilesOrFolders: Task.async(function* (path, validationFunc) { + let files = []; + let iterator; + let paths = [path]; + + while (paths.length > 0) { + try { + iterator = new OS.File.DirectoryIterator(paths.pop()); - return this.getNameFromDatabaseFile(file.path).then(name => { - if (name) { - names.push(name); + for (let child in iterator) { + child = yield child; + + path = child.path; + + if (child.isDir) { + paths.push(path); + } else if (validationFunc(path)) { + files.push(path); } - return null; - }); - }); - } finally { - dirIterator.close(); + } + } catch (ex) { + // Ignore StopIteration to prevent exiting the loop. + if (ex != StopIteration) { + throw ex; + } + } } - return this.backToChild("getDBNamesForHost", {names: names}); + iterator.close(); + + return files; }), /** @@ -1987,6 +2100,9 @@ var indexedDBHelpers = { * name. */ getSanitizedHost(host) { + if (host.startsWith("about:")) { + host = "moz-safe-" + host; + } return host.replace(ILLEGAL_CHAR_REGEX, "+"); }, @@ -2000,7 +2116,7 @@ var indexedDBHelpers = { // Content pages might be having an open transaction for the same indexed db // which this sqlite file belongs to. In that case, sqlite.openConnection - // will throw. Thus we retey for some time to see if lock is removed. + // will throw. Thus we retry for some time to see if lock is removed. while (!connection && retryCount++ < 25) { try { connection = yield Sqlite.openConnection({ path: path }); @@ -2061,8 +2177,14 @@ var indexedDBHelpers = { return this.backToChild("getValuesForHost", {objectStores: objectStores}); } // Get either all entries from the object store, or a particular id - let result = yield this.getObjectStoreData(host, principal, db2, - objectStore, id, options.index, options.size); + let storage = hostVsStores.get(host).get(db2).storage; + let result = yield this.getObjectStoreData(host, principal, db2, storage, { + objectStore: objectStore, + id: id, + index: options.index, + offset: 0, + size: options.size + }); return this.backToChild("getValuesForHost", {result: result}); }), @@ -2076,23 +2198,27 @@ var indexedDBHelpers = { * The principal of the given document. * @param {string} dbName * The name of the indexed db from the above host. - * @param {string} objectStore - * The name of the object store from the above db. - * @param {string} id - * id of the requested entry from the above object store. - * null if all entries from the above object store are requested. - * @param {string} index - * name of the IDBIndex to be iterated on while fetching entries. - * null or "name" if no index is to be iterated. - * @param {number} offset - * ofsset of the entries to be fetched. - * @param {number} size - * The intended size of the entries to be fetched. + * @param {String} storage + * Storage type, either "temporary", "default" or "persistent". + * @param {Object} requestOptions + * An object in the following format: + * { + * objectStore: The name of the object store from the above db, + * id: Id of the requested entry from the above object + * store. null if all entries from the above object + * store are requested, + * index: Name of the IDBIndex to be iterated on while fetching + * entries. null or "name" if no index is to be + * iterated, + * offset: offset of the entries to be fetched, + * size: The intended size of the entries to be fetched + * } */ - getObjectStoreData(host, principal, dbName, objectStore, id, index, - offset, size) { - let request = this.openWithPrincipal(principal, dbName); + getObjectStoreData(host, principal, dbName, storage, requestOptions) { + let {name} = this.splitNameAndStorage(dbName); + let request = this.openWithPrincipal(principal, name, storage); let success = promise.defer(); + let {objectStore, id, index, offset, size} = requestOptions; let data = []; let db; @@ -2194,8 +2320,12 @@ var indexedDBHelpers = { switch (msg.json.method) { case "getDBMetaData": { - let [host, principal, name] = args; - return indexedDBHelpers.getDBMetaData(host, principal, name); + let [host, principal, name, storage] = args; + return indexedDBHelpers.getDBMetaData(host, principal, name, storage); + } + case "splitNameAndStorage": { + let [name] = args; + return indexedDBHelpers.splitNameAndStorage(name); } case "getDBNamesForHost": { let [host] = args; @@ -2207,8 +2337,8 @@ var indexedDBHelpers = { hostVsStores, principal); } case "removeDB": { - let [host, principal, name] = args; - return indexedDBHelpers.removeDB(host, principal, name); + let [host, principal, dbName] = args; + return indexedDBHelpers.removeDB(host, principal, dbName); } case "removeDBRecord": { let [host, principal, db, store, id] = args; diff --git a/devtools/server/tests/browser/browser_storage_listings.js b/devtools/server/tests/browser/browser_storage_listings.js index 2e4bd00a4..15e5ccd50 100644 --- a/devtools/server/tests/browser/browser_storage_listings.js +++ b/devtools/server/tests/browser/browser_storage_listings.js @@ -121,24 +121,24 @@ const storeMap = { const IDBValues = { listStoresResponse: { "http://test1.example.org": [ - ["idb1", "obj1"], ["idb1", "obj2"], ["idb2", "obj3"] + ["idb1 (default)", "obj1"], ["idb1 (default)", "obj2"], ["idb2 (default)", "obj3"] ], "http://sectest1.example.org": [ ], "https://sectest1.example.org": [ - ["idb-s1", "obj-s1"], ["idb-s2", "obj-s2"] + ["idb-s1 (default)", "obj-s1"], ["idb-s2 (default)", "obj-s2"] ] }, - dbDetails : { + dbDetails: { "http://test1.example.org": [ { - db: "idb1", + db: "idb1 (default)", origin: "http://test1.example.org", version: 1, objectStores: 2 }, { - db: "idb2", + db: "idb2 (default)", origin: "http://test1.example.org", version: 1, objectStores: 1 @@ -148,13 +148,13 @@ const IDBValues = { ], "https://sectest1.example.org": [ { - db: "idb-s1", + db: "idb-s1 (default)", origin: "https://sectest1.example.org", version: 1, objectStores: 1 }, { - db: "idb-s2", + db: "idb-s2 (default)", origin: "https://sectest1.example.org", version: 1, objectStores: 1 @@ -163,7 +163,7 @@ const IDBValues = { }, objectStoreDetails: { "http://test1.example.org": { - idb1: [ + "idb1 (default)": [ { objectStore: "obj1", keyPath: "id", @@ -190,7 +190,7 @@ const IDBValues = { indexes: [] } ], - idb2: [ + "idb2 (default)": [ { objectStore: "obj3", keyPath: "id3", @@ -208,7 +208,7 @@ const IDBValues = { }, "http://sectest1.example.org" : {}, "https://sectest1.example.org": { - "idb-s1": [ + "idb-s1 (default)": [ { objectStore: "obj-s1", keyPath: "id", @@ -216,7 +216,7 @@ const IDBValues = { indexes: [] }, ], - "idb-s2": [ + "idb-s2 (default)": [ { objectStore: "obj-s2", keyPath: "id3", @@ -236,7 +236,7 @@ const IDBValues = { }, entries: { "http://test1.example.org": { - "idb1#obj1": [ + "idb1 (default)#obj1": [ { name: 1, value: { @@ -262,7 +262,7 @@ const IDBValues = { } } ], - "idb1#obj2": [ + "idb1 (default)#obj2": [ { name: 1, value: { @@ -273,11 +273,11 @@ const IDBValues = { } } ], - "idb2#obj3": [] + "idb2 (default)#obj3": [] }, "http://sectest1.example.org" : {}, "https://sectest1.example.org": { - "idb-s1#obj-s1": [ + "idb-s1 (default)#obj-s1": [ { name: 6, value: { @@ -295,7 +295,7 @@ const IDBValues = { } } ], - "idb-s2#obj-s2": [ + "idb-s2 (default)#obj-s2": [ { name: 13, value: { diff --git a/devtools/shared/specs/storage.js b/devtools/shared/specs/storage.js index d6ddaefe5..3674992ed 100644 --- a/devtools/shared/specs/storage.js +++ b/devtools/shared/specs/storage.js @@ -61,7 +61,7 @@ types.addDictType("cookiestoreobject", { // Common methods for edit/remove const editRemoveMethods = { - getEditableFields: { + getFields: { request: {}, response: { value: RetVal("json") |