--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -1,36 +1,42 @@
/* 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/. */
"use strict";
-const {Ci, Cu, CC} = require("chrome");
+const {Cc, Ci, Cu, CC} = require("chrome");
const protocol = require("devtools/shared/protocol");
const {LongStringActor} = require("devtools/server/actors/string");
const {DebuggerServer} = require("devtools/server/main");
const Services = require("Services");
const defer = require("devtools/shared/defer");
const {isWindowIncluded} = require("devtools/shared/layout/utils");
const specs = require("devtools/shared/specs/storage");
+const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
+const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
+
const DEFAULT_VALUE = "value";
loader.lazyRequireGetter(this, "naturalSortCaseInsensitive",
"devtools/client/shared/natural-sort", true);
// "Lax", "Strict" and "Unset" are special values of the sameSite property
// that should not be translated.
const COOKIE_SAMESITE = {
LAX: "Lax",
STRICT: "Strict",
UNSET: "Unset"
};
+const SAFE_HOSTS_PREFIXES_REGEX =
+ /^(about\+|https?\+|file\+|moz-extension\+)/;
+
// GUID to be used as a separator in compound keys. This must match the same
// constant in devtools/client/storage/ui.js,
// devtools/client/storage/test/head.js and
// devtools/server/tests/browser/head.js
const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
loader.lazyImporter(this, "Sqlite", "resource://gre/modules/Sqlite.jsm");
@@ -124,28 +130,34 @@ StorageActors.defaults = function(typeNa
return {
typeName: typeName,
get conn() {
return this.storageActor.conn;
},
/**
- * Returns a list of currently knwon hosts for the target window. This list
- * contains unique hosts from the window + all inner windows.
+ * Returns a list of currently known hosts for the target window. This list
+ * contains unique hosts from the window + all inner windows. If
+ * this._internalHosts is defined then these will also be added to the list.
*/
get hosts() {
let hosts = new Set();
for (let {location} of this.storageActor.windows) {
let host = this.getHostName(location);
if (host) {
hosts.add(host);
}
}
+ if (this._internalHosts) {
+ for (let host of this._internalHosts) {
+ hosts.add(host);
+ }
+ }
return hosts;
},
/**
* Returns all the windows present on the page. Includes main window + inner
* iframe windows.
*/
get windows() {
@@ -338,19 +350,17 @@ StorageActors.defaults = function(typeNa
data: []
};
let principal = null;
if (this.typeName === "indexedDB") {
// We only acquire principal when the type of the storage is indexedDB
// because the principal only matters the indexedDB.
let win = this.storageActor.getWindowFromHost(host);
- if (win) {
- principal = win.document.nodePrincipal;
- }
+ principal = this.getPrincipal(win);
}
if (names) {
for (let name of names) {
let values = await this.getValuesForHost(host, name, options,
this.hostVsStores, principal);
let {result, objectStores} = values;
@@ -403,16 +413,26 @@ StorageActors.defaults = function(typeNa
return naturalSortCaseInsensitive(a[sortOn], b[sortOn]);
});
let sliced = sorted.slice(offset, offset + size);
toReturn.data = sliced.map(object => this.toStoreObject(object));
}
}
return toReturn;
+ },
+
+ getPrincipal(win) {
+ if (win) {
+ return win.document.nodePrincipal;
+ }
+ // We are running in the browser toolbox and viewing system DBs so we
+ // need to use system principal.
+ return Cc["@mozilla.org/systemprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
}
};
};
/**
* Creates an actor and its corresponding front and registers it to the Storage
* Actor.
*
@@ -1613,16 +1633,31 @@ StorageActors.createActor({
this.storageActor.off("window-destroyed", this.onWindowDestroyed);
protocol.Actor.prototype.destroy.call(this);
this.storageActor = null;
},
/**
+ * Returns a list of currently known hosts for the target window. This list
+ * contains unique hosts from the window, all inner windows and all permanent
+ * indexedDB hosts defined inside the browser.
+ */
+ async getHosts() {
+ // Add internal hosts to this._internalHosts, which will be picked up by
+ // the this.hosts getter. Because this.hosts is a property on the default
+ // storage actor and inherited by all storage actors we have to do it this
+ // way.
+ this._internalHosts = await this.getInternalHosts();
+
+ return this.hosts;
+ },
+
+ /**
* Remove an indexedDB database from given host with a given name.
*/
async removeDatabase(host, name) {
let win = this.storageActor.getWindowFromHost(host);
if (!win) {
return { error: `Window for host ${host} not found` };
}
@@ -1730,36 +1765,35 @@ StorageActors.createActor({
* Purpose of this method is same as populateStoresForHosts but this is async.
* This exact same operation cannot be performed in populateStoresForHosts
* method, as that method is called in initialize method of the actor, which
* cannot be asynchronous.
*/
async preListStores() {
this.hostVsStores = new Map();
- for (let host of this.hosts) {
+ for (let host of await this.getHosts()) {
await this.populateStoresForHost(host);
}
},
async populateStoresForHost(host) {
let storeMap = new Map();
let win = this.storageActor.getWindowFromHost(host);
- if (win) {
- let principal = win.document.nodePrincipal;
- let {names} = await this.getDBNamesForHost(host, principal);
-
- for (let {name, storage} of names) {
- let metadata = await this.getDBMetaData(host, principal, name, storage);
-
- metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata);
-
- storeMap.set(`${name} (${storage})`, metadata);
- }
+ let principal = this.getPrincipal(win);
+
+ let {names} = await this.getDBNamesForHost(host, principal);
+
+ for (let {name, storage} of names) {
+ let metadata = await this.getDBMetaData(host, principal, name, storage);
+
+ metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata);
+
+ storeMap.set(`${name} (${storage})`, metadata);
}
this.hostVsStores.set(host, storeMap);
},
/**
* Returns the over-the-wire implementation of the indexed db entity.
*/
@@ -1848,29 +1882,31 @@ StorageActors.createActor({
this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile;
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.splitNameAndStorage = indexedDBHelpers.splitNameAndStorage;
+ this.getInternalHosts = indexedDBHelpers.getInternalHosts;
return;
}
const { sendAsyncMessage, addMessageListener } =
this.conn.parentMessageManager;
this.conn.setupInParent({
module: "devtools/server/actors/storage",
setupParent: "setupParentProcessForIndexedDB"
});
this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData");
this.splitNameAndStorage = callParentProcessAsync.bind(null, "splitNameAndStorage");
+ this.getInternalHosts = callParentProcessAsync.bind(null, "getInternalHosts");
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("debug:storage-indexedDB-request-child", msg => {
switch (msg.json.method) {
@@ -1984,16 +2020,42 @@ var indexedDBHelpers = {
let storage = name.substr(lastOpenBracketIndex + 1, delta);
name = name.substr(0, lastOpenBracketIndex - 1);
return { storage, name };
},
/**
+ * Get all "internal" hosts. Internal hosts are database namespaces used by
+ * the browser.
+ */
+ async getInternalHosts() {
+ // Return an empty array if the browser toolbox is not enabled.
+ if (!Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
+ !Services.prefs.getBoolPref(REMOTE_ENABLED_PREF)) {
+ return this.backToChild("getInternalHosts", []);
+ }
+
+ let profileDir = OS.Constants.Path.profileDir;
+ let storagePath = OS.Path.join(profileDir, "storage", "permanent");
+ let iterator = new OS.File.DirectoryIterator(storagePath);
+ let hosts = [];
+
+ await iterator.forEach(entry => {
+ if (entry.isDir && !SAFE_HOSTS_PREFIXES_REGEX.test(entry.name)) {
+ hosts.push(entry.name);
+ }
+ });
+ iterator.close();
+
+ return this.backToChild("getInternalHosts", hosts);
+ },
+
+ /**
* Opens an indexed db connection for the given `principal` and
* database `name`.
*/
openWithPrincipal: function(principal, name, storage) {
return indexedDBForStorage.openForPrincipal(principal, name,
{ storage: storage });
},
@@ -2422,16 +2484,19 @@ var indexedDBHelpers = {
handleChildRequest(msg) {
let args = msg.data.args;
switch (msg.json.method) {
case "getDBMetaData": {
let [host, principal, name, storage] = args;
return indexedDBHelpers.getDBMetaData(host, principal, name, storage);
}
+ case "getInternalHosts": {
+ return indexedDBHelpers.getInternalHosts();
+ }
case "splitNameAndStorage": {
let [name] = args;
return indexedDBHelpers.splitNameAndStorage(name);
}
case "getDBNamesForHost": {
let [host, principal] = args;
return indexedDBHelpers.getDBNamesForHost(host, principal);
}