Bug 1276339: Storage inspector doesn't work on chrome:// pages and web extensions

Issue #31
This commit is contained in:
janekptacijarabaci 2018-03-02 14:33:20 +01:00 • committed by Roy Tam
commit d0ef0781ff
18 changed files with 361 additions and 129 deletions

View file

@ -60,7 +60,7 @@ table.headers.indexedDB.value=Value
table.headers.indexedDB.origin=Origin table.headers.indexedDB.origin=Origin
table.headers.indexedDB.version=Version table.headers.indexedDB.version=Version
table.headers.indexedDB.objectStores=Object Stores 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.autoIncrement=Auto Increment
table.headers.indexedDB.indexes=Indexes table.headers.indexedDB.indexes=Indexes

View file

@ -7,6 +7,7 @@ support-files =
storage-cookies.html storage-cookies.html
storage-empty-objectstores.html storage-empty-objectstores.html
storage-idb-delete-blocked.html storage-idb-delete-blocked.html
storage-indexeddb-duplicate-names.html
storage-listings.html storage-listings.html
storage-localstorage.html storage-localstorage.html
storage-overflow.html storage-overflow.html
@ -35,6 +36,7 @@ support-files =
[browser_storage_empty_objectstores.js] [browser_storage_empty_objectstores.js]
[browser_storage_indexeddb_delete.js] [browser_storage_indexeddb_delete.js]
[browser_storage_indexeddb_delete_blocked.js] [browser_storage_indexeddb_delete_blocked.js]
[browser_storage_indexeddb_duplicate_names.js]
[browser_storage_localstorage_edit.js] [browser_storage_localstorage_edit.js]
[browser_storage_localstorage_error.js] [browser_storage_localstorage_error.js]
[browser_storage_overflow.js] [browser_storage_overflow.js]

View file

@ -53,28 +53,28 @@ const testCases = [
[["sessionStorage", "https://sectest1.example.org"], [["sessionStorage", "https://sectest1.example.org"],
["iframe-s-ss1"]], ["iframe-s-ss1"]],
[["indexedDB", "http://test1.example.org"], [["indexedDB", "http://test1.example.org"],
["idb1", "idb2"]], ["idb1 (default)", "idb2 (default)"]],
[["indexedDB", "http://test1.example.org", "idb1"], [["indexedDB", "http://test1.example.org", "idb1 (default)"],
["obj1", "obj2"]], ["obj1", "obj2"]],
[["indexedDB", "http://test1.example.org", "idb2"], [["indexedDB", "http://test1.example.org", "idb2 (default)"],
["obj3"]], ["obj3"]],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"], [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
[1, 2, 3]], [1, 2, 3]],
[["indexedDB", "http://test1.example.org", "idb1", "obj2"], [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
[1]], [1]],
[["indexedDB", "http://test1.example.org", "idb2", "obj3"], [["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
[]], []],
[["indexedDB", "http://sectest1.example.org"], [["indexedDB", "http://sectest1.example.org"],
[]], []],
[["indexedDB", "https://sectest1.example.org"], [["indexedDB", "https://sectest1.example.org"],
["idb-s1", "idb-s2"]], ["idb-s1 (default)", "idb-s2 (default)"]],
[["indexedDB", "https://sectest1.example.org", "idb-s1"], [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)"],
["obj-s1"]], ["obj-s1"]],
[["indexedDB", "https://sectest1.example.org", "idb-s2"], [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
["obj-s2"]], ["obj-s2"]],
[["indexedDB", "https://sectest1.example.org", "idb-s1", "obj-s1"], [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)", "obj-s1"],
[6, 7]], [6, 7]],
[["indexedDB", "https://sectest1.example.org", "idb-s2", "obj-s2"], [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)", "obj-s2"],
[16]], [16]],
[["Cache", "http://test1.example.org", "plop"], [["Cache", "http://test1.example.org", "plop"],
[MAIN_DOMAIN + "404_cached_file.js", [MAIN_DOMAIN + "404_cached_file.js",

View file

@ -17,7 +17,7 @@ const TEST_CASES = [
["cookies", "test1.example.org"], ["cookies", "test1.example.org"],
getCookieId("c1", "test1.example.org", "/browser"), "name" getCookieId("c1", "test1.example.org", "/browser"), "name"
], ],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"], [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
1, "name"], 1, "name"],
[["Cache", "http://test1.example.org", "plop"], [["Cache", "http://test1.example.org", "plop"],
MAIN_DOMAIN + "404_cached_file.js", "url"], MAIN_DOMAIN + "404_cached_file.js", "url"],

View file

@ -29,7 +29,7 @@ add_task(function* () {
["iframe-u-ss1", "iframe-u-ss2"]], ["iframe-u-ss1", "iframe-u-ss2"]],
[["sessionStorage", "https://sectest1.example.org"], [["sessionStorage", "https://sectest1.example.org"],
["iframe-s-ss1"]], ["iframe-s-ss1"]],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"], [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
[1, 2, 3]], [1, 2, 3]],
[["Cache", "http://test1.example.org", "plop"], [["Cache", "http://test1.example.org", "plop"],
[MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]], [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]],
@ -41,7 +41,7 @@ add_task(function* () {
const deleteHosts = [ const deleteHosts = [
[["localStorage", "https://sectest1.example.org"], "iframe-s-ls1", "name"], [["localStorage", "https://sectest1.example.org"], "iframe-s-ls1", "name"],
[["sessionStorage", "https://sectest1.example.org"], "iframe-s-ss1", "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"], [["Cache", "http://test1.example.org", "plop"],
MAIN_DOMAIN + "404_cached_file.js", "url"], MAIN_DOMAIN + "404_cached_file.js", "url"],
]; ];
@ -78,7 +78,7 @@ add_task(function* () {
["iframe-u-ss1", "iframe-u-ss2"]], ["iframe-u-ss1", "iframe-u-ss2"]],
[["sessionStorage", "https://sectest1.example.org"], [["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"], [["Cache", "http://test1.example.org", "plop"],
[]], []],

View file

@ -28,7 +28,7 @@ add_task(function* () {
], ],
[["localStorage", "http://test1.example.org"], ["ls1", "ls2"]], [["localStorage", "http://test1.example.org"], ["ls1", "ls2"]],
[["sessionStorage", "http://test1.example.org"], ["ss1"]], [["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"], [["Cache", "http://test1.example.org", "plop"],
[MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]], [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]],
]); ]);
@ -38,7 +38,7 @@ add_task(function* () {
["cookies", "test1.example.org"], ["cookies", "test1.example.org"],
["localStorage", "http://test1.example.org"], ["localStorage", "http://test1.example.org"],
["sessionStorage", "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"], ["Cache", "http://test1.example.org", "plop"],
]; ];
@ -67,7 +67,7 @@ add_task(function* () {
[["cookies", "test1.example.org"], []], [["cookies", "test1.example.org"], []],
[["localStorage", "http://test1.example.org"], []], [["localStorage", "http://test1.example.org"], []],
[["sessionStorage", "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"], []], [["Cache", "http://test1.example.org", "plop"], []],
]); ]);

View file

@ -21,14 +21,14 @@
// storage-secured-iframe.html and storage-unsecured-iframe.html // storage-secured-iframe.html and storage-unsecured-iframe.html
const storeItems = [ const storeItems = [
[["indexedDB", "http://test1.example.org"], [["indexedDB", "http://test1.example.org"],
["idb1", "idb2"]], ["idb1 (default)", "idb2 (default)"]],
[["indexedDB", "http://test1.example.org", "idb1"], [["indexedDB", "http://test1.example.org", "idb1 (default)"],
["obj1", "obj2"]], ["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]], [1, 2, 3]],
[["indexedDB", "http://test1.example.org", "idb1", "obj2"], [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
[1]] [1]]
]; ];

View file

@ -16,11 +16,11 @@ add_task(function* () {
info("test state before delete"); info("test state before delete");
yield checkState([ yield checkState([
[["indexedDB", "http://test1.example.org"], ["idb1", "idb2"]], [["indexedDB", "http://test1.example.org"], ["idb1 (default)", "idb2 (default)"]],
]); ]);
info("do the delete"); info("do the delete");
const deletedDb = ["indexedDB", "http://test1.example.org", "idb1"]; const deletedDb = ["indexedDB", "http://test1.example.org", "idb1 (default)"];
yield selectTreeItem(deletedDb); yield selectTreeItem(deletedDb);
@ -40,7 +40,7 @@ add_task(function* () {
info("test state after delete"); info("test state after delete");
yield checkState([ yield checkState([
[["indexedDB", "http://test1.example.org"], ["idb2"]], [["indexedDB", "http://test1.example.org"], ["idb2 (default)"]],
]); ]);
yield finishTests(); yield finishTests();

View file

@ -13,19 +13,19 @@ add_task(function* () {
info("test state before delete"); info("test state before delete");
yield checkState([ yield checkState([
[["indexedDB", "http://test1.example.org"], ["idb"]] [["indexedDB", "http://test1.example.org"], ["idb (default)"]]
]); ]);
info("do the delete"); info("do the delete");
yield selectTreeItem(["indexedDB", "http://test1.example.org"]); yield selectTreeItem(["indexedDB", "http://test1.example.org"]);
let actor = gUI.getCurrentActor(); 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"); ok(result.blocked, "removeDatabase attempt is blocked");
info("test state after blocked delete"); info("test state after blocked delete");
yield checkState([ yield checkState([
[["indexedDB", "http://test1.example.org"], ["idb"]] [["indexedDB", "http://test1.example.org"], ["idb (default)"]]
]); ]);
let eventWait = gUI.once("store-objects-updated"); let eventWait = gUI.once("store-objects-updated");
@ -47,7 +47,7 @@ add_task(function* () {
info("try to delete database from nonexistent host"); info("try to delete database from nonexistent host");
let errorThrown = false; let errorThrown = false;
try { try {
result = yield actor.removeDatabase("http://test2.example.org", "idb"); result = yield actor.removeDatabase("http://test2.example.org", "idb (default)");
} catch (ex) { } catch (ex) {
errorThrown = true; errorThrown = true;
} }

View file

@ -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();
});

View file

@ -72,17 +72,17 @@ const testCases = [
sidebarHidden: true sidebarHidden: true
}, },
{ {
location: "idb2", location: "idb2 (default)",
sidebarHidden: false sidebarHidden: false
}, },
{ {
location: ["indexedDB", "http://test1.example.org", "idb2", "obj3"], location: ["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
sidebarHidden: true sidebarHidden: true
}, },
{ {
location: ["indexedDB", "https://sectest1.example.org", "idb-s2"], location: ["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
sidebarHidden: true sidebarHidden: true
}, },
{ {

View file

@ -124,7 +124,7 @@ const testCases = [
{name: "ss5.3", value: `${LONG_WORD}&${LONG_WORD}`}, {name: "ss5.3", value: `${LONG_WORD}&${LONG_WORD}`},
{name: "ss5.4", value: `${LONG_WORD}&${LONG_WORD}`}, {name: "ss5.4", value: `${LONG_WORD}&${LONG_WORD}`},
], true], ], true],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"]], [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"]],
[1, [ [1, [
{name: 1, value: JSON.stringify({id: 1, name: "foo", email: "foo@bar.com"})} {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.name", value: "foo"},
{name: "1.email", value: "foo@bar.com"}, {name: "1.email", value: "foo@bar.com"},
], true], ], true],
[["indexedDB", "http://test1.example.org", "idb1", "obj2"]], [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"]],
[1, [ [1, [
{name: 1, value: JSON.stringify({ {name: 1, value: JSON.stringify({
id2: 1, name: "foo", email: "foo@bar.com", extra: "baz" id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"

View file

@ -879,3 +879,19 @@ var focusSearchBoxUsingShortcut = Task.async(function* (panelWin, callback) {
function getCookieId(name, domain, path) { function getCookieId(name, domain, path) {
return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${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);
}

View file

@ -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>

View file

@ -809,7 +809,10 @@ StorageUI.prototype = {
columns[f.name] = f.name; columns[f.name] = f.name;
let columnName; let columnName;
try { 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) { } catch (e) {
columnName = COOKIE_KEY_MAP[f.name]; columnName = COOKIE_KEY_MAP[f.name];
} }

View file

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* globals StopIteration */
"use strict"; "use strict";
const {Cc, Ci, Cu, CC} = require("chrome"); const {Cc, Ci, Cu, CC} = require("chrome");
@ -93,7 +95,7 @@ var StorageActors = {};
* - observe : Method which gets triggered on the notificaiton of the watched * - observe : Method which gets triggered on the notificaiton of the watched
* topic. * topic.
* - getNamesForHost : Given a host, get list of all known store names. * - 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. * store objects.
* - toStoreObject : Given a store object, convert it to the required format * - toStoreObject : Given a store object, convert it to the required format
* so that it can be transferred over wire. * so that it can be transferred over wire.
@ -141,6 +143,9 @@ StorageActors.defaults = function (typeName, observationTopic) {
* Converts the window.location object into host. * Converts the window.location object into host.
*/ */
getHostName(location) { getHostName(location) {
if (location.protocol === "chrome:") {
return location.href;
}
return location.hostname || location.href; return location.hostname || location.href;
}, },
@ -744,6 +749,7 @@ var cookieHelpers = {
let enumerator = let enumerator =
Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {}); Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {});
while (enumerator.hasMoreElements()) { while (enumerator.hasMoreElements()) {
let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
if (nsiCookie.name === origName && if (nsiCookie.name === origName &&
@ -1041,6 +1047,9 @@ function getObjectForLocalOrSessionStorage(type) {
if (!location.host) { if (!location.host) {
return location.href; return location.href;
} }
if (location.protocol === "chrome:") {
return location.href;
}
return location.protocol + "//" + location.host; return location.protocol + "//" + location.host;
}, },
@ -1251,6 +1260,9 @@ StorageActors.createActor({
if (!location.host) { if (!location.host) {
return location.href; return location.href;
} }
if (location.protocol === "chrome:") {
return location.href;
}
return location.protocol + "//" + location.host; return location.protocol + "//" + location.host;
}, },
@ -1423,12 +1435,15 @@ ObjectStoreMetadata.prototype = {
* The host associated with this indexed db. * The host associated with this indexed db.
* @param {IDBDatabase} db * @param {IDBDatabase} db
* The particular indexed 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._origin = origin;
this._name = db.name; this._name = db.name;
this._version = db.version; this._version = db.version;
this._objectStores = []; this._objectStores = [];
this.storage = storage;
if (db.objectStoreNames.length) { if (db.objectStoreNames.length) {
let transaction = db.transaction(db.objectStoreNames, "readonly"); let transaction = db.transaction(db.objectStoreNames, "readonly");
@ -1448,7 +1463,7 @@ DatabaseMetadata.prototype = {
toObject() { toObject() {
return { return {
name: this._name, name: `${this._name} (${this.storage})`,
origin: this._origin, origin: this._origin,
version: this._version, version: this._version,
objectStores: this._objectStores.size objectStores: this._objectStores.size
@ -1524,6 +1539,9 @@ StorageActors.createActor({
if (!location.host) { if (!location.host) {
return location.href; return location.href;
} }
if (location.protocol === "chrome:") {
return location.href;
}
return location.protocol + "//" + location.host; return location.protocol + "//" + location.host;
}, },
@ -1616,15 +1634,17 @@ StorageActors.createActor({
populateStoresForHost: Task.async(function* (host) { populateStoresForHost: Task.async(function* (host) {
let storeMap = new Map(); let storeMap = new Map();
let {names} = yield this.getDBNamesForHost(host); let {names} = yield this.getDBNamesForHost(host);
let win = this.storageActor.getWindowFromHost(host); let win = this.storageActor.getWindowFromHost(host);
if (win) { if (win) {
let principal = win.document.nodePrincipal; let principal = win.document.nodePrincipal;
for (let name of names) { for (let {name, storage} of names) {
let metadata = yield this.getDBMetaData(host, principal, name); let metadata = yield this.getDBMetaData(host, principal, name, storage);
metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata); metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata);
storeMap.set(name, metadata);
storeMap.set(`${name} (${storage})`, metadata);
} }
} }
@ -1657,10 +1677,22 @@ StorageActors.createActor({
objectStores: item.objectStores 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 // Indexed db entry
return { return {
name: item.name, 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() { maybeSetupChildProcess() {
if (!DebuggerServer.isInChildProcess) { if (!DebuggerServer.isInChildProcess) {
this.backToChild = (func, rv) => rv; this.backToChild = (func, rv) => rv;
this.clearDBStore = indexedDBHelpers.clearDBStore;
this.gatherFilesOrFolders = indexedDBHelpers.gatherFilesOrFolders;
this.getDBMetaData = indexedDBHelpers.getDBMetaData; this.getDBMetaData = indexedDBHelpers.getDBMetaData;
this.openWithPrincipal = indexedDBHelpers.openWithPrincipal;
this.getDBNamesForHost = indexedDBHelpers.getDBNamesForHost; this.getDBNamesForHost = indexedDBHelpers.getDBNamesForHost;
this.getSanitizedHost = indexedDBHelpers.getSanitizedHost;
this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile; this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile;
this.getValuesForHost = indexedDBHelpers.getValuesForHost;
this.getObjectStoreData = indexedDBHelpers.getObjectStoreData; this.getObjectStoreData = indexedDBHelpers.getObjectStoreData;
this.getSanitizedHost = indexedDBHelpers.getSanitizedHost;
this.getValuesForHost = indexedDBHelpers.getValuesForHost;
this.openWithPrincipal = indexedDBHelpers.openWithPrincipal;
this.removeDB = indexedDBHelpers.removeDB; this.removeDB = indexedDBHelpers.removeDB;
this.removeDBRecord = indexedDBHelpers.removeDBRecord; this.removeDBRecord = indexedDBHelpers.removeDBRecord;
this.clearDBStore = indexedDBHelpers.clearDBStore; this.splitNameAndStorage = indexedDBHelpers.splitNameAndStorage;
return; return;
} }
@ -1718,6 +1752,7 @@ StorageActors.createActor({
}); });
this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData"); this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData");
this.splitNameAndStorage = callParentProcessAsync.bind(null, "splitNameAndStorage");
this.getDBNamesForHost = callParentProcessAsync.bind(null, "getDBNamesForHost"); this.getDBNamesForHost = callParentProcessAsync.bind(null, "getDBNamesForHost");
this.getValuesForHost = callParentProcessAsync.bind(null, "getValuesForHost"); this.getValuesForHost = callParentProcessAsync.bind(null, "getValuesForHost");
this.removeDB = callParentProcessAsync.bind(null, "removeDB"); this.removeDB = callParentProcessAsync.bind(null, "removeDB");
@ -1813,14 +1848,14 @@ var indexedDBHelpers = {
* `name` for the given `host` with its `principal`. The stored metadata * `name` for the given `host` with its `principal`. The stored metadata
* information is of `DatabaseMetadata` type. * information is of `DatabaseMetadata` type.
*/ */
getDBMetaData: Task.async(function* (host, principal, name) { getDBMetaData: Task.async(function* (host, principal, name, storage) {
let request = this.openWithPrincipal(principal, name); let request = this.openWithPrincipal(principal, name, storage);
let success = promise.defer(); let success = promise.defer();
request.onsuccess = event => { request.onsuccess = event => {
let db = event.target.result; let db = event.target.result;
let dbData = new DatabaseMetadata(host, db); let dbData = new DatabaseMetadata(host, db, storage);
db.close(); db.close();
success.resolve(this.backToChild("getDBMetaData", dbData)); success.resolve(this.backToChild("getDBMetaData", dbData));
@ -1833,21 +1868,37 @@ var indexedDBHelpers = {
return success.promise; 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 * Opens an indexed db connection for the given `principal` and
* database `name`. * database `name`.
*/ */
openWithPrincipal(principal, name) { openWithPrincipal: function (principal, name, storage) {
return indexedDBForStorage.openForPrincipal(principal, name); 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 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 = () => { request.onsuccess = () => {
resolve({}); resolve({});
this.onItemUpdated("deleted", host, [name]); this.onItemUpdated("deleted", host, [dbName]);
}; };
request.onblocked = () => { request.onblocked = () => {
@ -1873,10 +1924,11 @@ var indexedDBHelpers = {
removeDBRecord: Task.async(function* (host, principal, dbName, storeName, id) { removeDBRecord: Task.async(function* (host, principal, dbName, storeName, id) {
let db; let db;
let {name, storage} = this.splitNameAndStorage(dbName);
try { try {
db = yield new promise((resolve, reject) => { 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.onsuccess = ev => resolve(ev.target.result);
request.onerror = ev => reject(ev.target.error); request.onerror = ev => reject(ev.target.error);
}); });
@ -1905,10 +1957,11 @@ var indexedDBHelpers = {
clearDBStore: Task.async(function* (host, principal, dbName, storeName) { clearDBStore: Task.async(function* (host, principal, dbName, storeName) {
let db; let db;
let {name, storage} = this.splitNameAndStorage(dbName);
try { try {
db = yield new promise((resolve, reject) => { 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.onsuccess = ev => resolve(ev.target.result);
request.onerror = ev => reject(ev.target.error); request.onerror = ev => reject(ev.target.error);
}); });
@ -1940,46 +1993,106 @@ var indexedDBHelpers = {
*/ */
getDBNamesForHost: Task.async(function* (host) { getDBNamesForHost: Task.async(function* (host) {
let sanitizedHost = this.getSanitizedHost(host); let sanitizedHost = this.getSanitizedHost(host);
let directory = OS.Path.join(OS.Constants.Path.profileDir, "storage", let profileDir = OS.Constants.Path.profileDir;
"default", sanitizedHost, "idb"); let files = [];
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: []});
}
let names = []; let names = [];
let dirIterator = new OS.File.DirectoryIterator(directory); let storagePath = OS.Path.join(profileDir, "storage");
try {
yield dirIterator.forEach(file => {
// Skip directories.
if (file.isDir) {
return null;
}
// Skip any non-sqlite files. // We expect sqlite DB paths to look something like this:
if (!file.name.endsWith(".sqlite")) { // - PathToProfileDir/storage/default/http+++www.example.com/
return null; // 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 this.getNameFromDatabaseFile(file.path).then(name => { return isIDB;
if (name) { }
names.push(name); return false;
} });
return null;
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
}); });
}); }
} finally {
dirIterator.close();
} }
return this.backToChild("getDBNamesForHost", {names: names});
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});
}),
/**
* 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());
for (let child in iterator) {
child = yield child;
path = child.path;
if (child.isDir) {
paths.push(path);
} else if (validationFunc(path)) {
files.push(path);
}
}
} catch (ex) {
// Ignore StopIteration to prevent exiting the loop.
if (ex != StopIteration) {
throw ex;
}
}
}
iterator.close();
return files;
}), }),
/** /**
@ -1987,6 +2100,9 @@ var indexedDBHelpers = {
* name. * name.
*/ */
getSanitizedHost(host) { getSanitizedHost(host) {
if (host.startsWith("about:")) {
host = "moz-safe-" + host;
}
return host.replace(ILLEGAL_CHAR_REGEX, "+"); 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 // Content pages might be having an open transaction for the same indexed db
// which this sqlite file belongs to. In that case, sqlite.openConnection // 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) { while (!connection && retryCount++ < 25) {
try { try {
connection = yield Sqlite.openConnection({ path: path }); connection = yield Sqlite.openConnection({ path: path });
@ -2061,8 +2177,14 @@ var indexedDBHelpers = {
return this.backToChild("getValuesForHost", {objectStores: objectStores}); return this.backToChild("getValuesForHost", {objectStores: objectStores});
} }
// Get either all entries from the object store, or a particular id // Get either all entries from the object store, or a particular id
let result = yield this.getObjectStoreData(host, principal, db2, let storage = hostVsStores.get(host).get(db2).storage;
objectStore, id, options.index, options.size); 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}); return this.backToChild("getValuesForHost", {result: result});
}), }),
@ -2076,23 +2198,27 @@ var indexedDBHelpers = {
* The principal of the given document. * The principal of the given document.
* @param {string} dbName * @param {string} dbName
* The name of the indexed db from the above host. * The name of the indexed db from the above host.
* @param {string} objectStore * @param {String} storage
* The name of the object store from the above db. * Storage type, either "temporary", "default" or "persistent".
* @param {string} id * @param {Object} requestOptions
* id of the requested entry from the above object store. * An object in the following format:
* null if all entries from the above object store are requested. * {
* @param {string} index * objectStore: The name of the object store from the above db,
* name of the IDBIndex to be iterated on while fetching entries. * id: Id of the requested entry from the above object
* null or "name" if no index is to be iterated. * store. null if all entries from the above object
* @param {number} offset * store are requested,
* ofsset of the entries to be fetched. * index: Name of the IDBIndex to be iterated on while fetching
* @param {number} size * entries. null or "name" if no index is to be
* The intended size of the entries to be fetched. * 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, getObjectStoreData(host, principal, dbName, storage, requestOptions) {
offset, size) { let {name} = this.splitNameAndStorage(dbName);
let request = this.openWithPrincipal(principal, dbName); let request = this.openWithPrincipal(principal, name, storage);
let success = promise.defer(); let success = promise.defer();
let {objectStore, id, index, offset, size} = requestOptions;
let data = []; let data = [];
let db; let db;
@ -2194,8 +2320,12 @@ var indexedDBHelpers = {
switch (msg.json.method) { switch (msg.json.method) {
case "getDBMetaData": { case "getDBMetaData": {
let [host, principal, name] = args; let [host, principal, name, storage] = args;
return indexedDBHelpers.getDBMetaData(host, principal, name); return indexedDBHelpers.getDBMetaData(host, principal, name, storage);
}
case "splitNameAndStorage": {
let [name] = args;
return indexedDBHelpers.splitNameAndStorage(name);
} }
case "getDBNamesForHost": { case "getDBNamesForHost": {
let [host] = args; let [host] = args;
@ -2207,8 +2337,8 @@ var indexedDBHelpers = {
hostVsStores, principal); hostVsStores, principal);
} }
case "removeDB": { case "removeDB": {
let [host, principal, name] = args; let [host, principal, dbName] = args;
return indexedDBHelpers.removeDB(host, principal, name); return indexedDBHelpers.removeDB(host, principal, dbName);
} }
case "removeDBRecord": { case "removeDBRecord": {
let [host, principal, db, store, id] = args; let [host, principal, db, store, id] = args;

View file

@ -121,24 +121,24 @@ const storeMap = {
const IDBValues = { const IDBValues = {
listStoresResponse: { listStoresResponse: {
"http://test1.example.org": [ "http://test1.example.org": [
["idb1", "obj1"], ["idb1", "obj2"], ["idb2", "obj3"] ["idb1 (default)", "obj1"], ["idb1 (default)", "obj2"], ["idb2 (default)", "obj3"]
], ],
"http://sectest1.example.org": [ "http://sectest1.example.org": [
], ],
"https://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": [ "http://test1.example.org": [
{ {
db: "idb1", db: "idb1 (default)",
origin: "http://test1.example.org", origin: "http://test1.example.org",
version: 1, version: 1,
objectStores: 2 objectStores: 2
}, },
{ {
db: "idb2", db: "idb2 (default)",
origin: "http://test1.example.org", origin: "http://test1.example.org",
version: 1, version: 1,
objectStores: 1 objectStores: 1
@ -148,13 +148,13 @@ const IDBValues = {
], ],
"https://sectest1.example.org": [ "https://sectest1.example.org": [
{ {
db: "idb-s1", db: "idb-s1 (default)",
origin: "https://sectest1.example.org", origin: "https://sectest1.example.org",
version: 1, version: 1,
objectStores: 1 objectStores: 1
}, },
{ {
db: "idb-s2", db: "idb-s2 (default)",
origin: "https://sectest1.example.org", origin: "https://sectest1.example.org",
version: 1, version: 1,
objectStores: 1 objectStores: 1
@ -163,7 +163,7 @@ const IDBValues = {
}, },
objectStoreDetails: { objectStoreDetails: {
"http://test1.example.org": { "http://test1.example.org": {
idb1: [ "idb1 (default)": [
{ {
objectStore: "obj1", objectStore: "obj1",
keyPath: "id", keyPath: "id",
@ -190,7 +190,7 @@ const IDBValues = {
indexes: [] indexes: []
} }
], ],
idb2: [ "idb2 (default)": [
{ {
objectStore: "obj3", objectStore: "obj3",
keyPath: "id3", keyPath: "id3",
@ -208,7 +208,7 @@ const IDBValues = {
}, },
"http://sectest1.example.org" : {}, "http://sectest1.example.org" : {},
"https://sectest1.example.org": { "https://sectest1.example.org": {
"idb-s1": [ "idb-s1 (default)": [
{ {
objectStore: "obj-s1", objectStore: "obj-s1",
keyPath: "id", keyPath: "id",
@ -216,7 +216,7 @@ const IDBValues = {
indexes: [] indexes: []
}, },
], ],
"idb-s2": [ "idb-s2 (default)": [
{ {
objectStore: "obj-s2", objectStore: "obj-s2",
keyPath: "id3", keyPath: "id3",
@ -236,7 +236,7 @@ const IDBValues = {
}, },
entries: { entries: {
"http://test1.example.org": { "http://test1.example.org": {
"idb1#obj1": [ "idb1 (default)#obj1": [
{ {
name: 1, name: 1,
value: { value: {
@ -262,7 +262,7 @@ const IDBValues = {
} }
} }
], ],
"idb1#obj2": [ "idb1 (default)#obj2": [
{ {
name: 1, name: 1,
value: { value: {
@ -273,11 +273,11 @@ const IDBValues = {
} }
} }
], ],
"idb2#obj3": [] "idb2 (default)#obj3": []
}, },
"http://sectest1.example.org" : {}, "http://sectest1.example.org" : {},
"https://sectest1.example.org": { "https://sectest1.example.org": {
"idb-s1#obj-s1": [ "idb-s1 (default)#obj-s1": [
{ {
name: 6, name: 6,
value: { value: {
@ -295,7 +295,7 @@ const IDBValues = {
} }
} }
], ],
"idb-s2#obj-s2": [ "idb-s2 (default)#obj-s2": [
{ {
name: 13, name: 13,
value: { value: {

View file

@ -61,7 +61,7 @@ types.addDictType("cookiestoreobject", {
// Common methods for edit/remove // Common methods for edit/remove
const editRemoveMethods = { const editRemoveMethods = {
getEditableFields: { getFields: {
request: {}, request: {},
response: { response: {
value: RetVal("json") value: RetVal("json")