forked from mirrors/gecko-dev
Bug 1811230 - [devtools] Consider extension storage inspection always enabled. r=devtools-reviewers,nchevobbe
This pref has been true for a while and isn't meant to be disabled by the user. Differential Revision: https://phabricator.services.mozilla.com/D166660
This commit is contained in:
parent
ec1c1ce4c1
commit
d42dc60869
6 changed files with 313 additions and 377 deletions
|
|
@ -70,8 +70,8 @@ pref("extensions.langpacks.signatures.required", true);
|
||||||
pref("xpinstall.signatures.required", true);
|
pref("xpinstall.signatures.required", true);
|
||||||
pref("xpinstall.signatures.devInfoURL", "https://wiki.mozilla.org/Addons/Extension_Signing");
|
pref("xpinstall.signatures.devInfoURL", "https://wiki.mozilla.org/Addons/Extension_Signing");
|
||||||
|
|
||||||
// Enable extensionStorage storage actor by default
|
// Enable the unified extensions UI by default.
|
||||||
pref("devtools.storage.extensionStorage.enabled", true);
|
pref("extensions.unifiedExtensions.enabled", true);
|
||||||
|
|
||||||
// Dictionary download preference
|
// Dictionary download preference
|
||||||
pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/language-tools/");
|
pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/language-tools/");
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
add_task(async function set_enable_extensionStorage_pref() {
|
add_setup(async function() {
|
||||||
await pushPref("devtools.storage.extensionStorage.enabled", true);
|
|
||||||
|
|
||||||
// Always on top mode mess up with toolbox focus and openStoragePanelForAddon would timeout
|
// Always on top mode mess up with toolbox focus and openStoragePanelForAddon would timeout
|
||||||
// waiting for toolbox focus.
|
// waiting for toolbox focus.
|
||||||
await pushPref("devtools.toolbox.alwaysOnTop", false);
|
await pushPref("devtools.toolbox.alwaysOnTop", false);
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
|
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
|
||||||
});
|
});
|
||||||
|
|
||||||
const EXTENSION_STORAGE_ENABLED_PREF =
|
|
||||||
"devtools.storage.extensionStorage.enabled";
|
|
||||||
|
|
||||||
const DEFAULT_VALUE = "value";
|
const DEFAULT_VALUE = "value";
|
||||||
|
|
||||||
loader.lazyRequireGetter(
|
loader.lazyRequireGetter(
|
||||||
|
|
@ -1784,369 +1781,363 @@ exports.setupParentProcessForExtensionStorage = function({ mm, prefix }) {
|
||||||
/**
|
/**
|
||||||
* The Extension Storage actor.
|
* The Extension Storage actor.
|
||||||
*/
|
*/
|
||||||
if (Services.prefs.getBoolPref(EXTENSION_STORAGE_ENABLED_PREF, false)) {
|
StorageActors.createActor(
|
||||||
StorageActors.createActor(
|
{
|
||||||
{
|
typeName: "extensionStorage",
|
||||||
typeName: "extensionStorage",
|
},
|
||||||
|
{
|
||||||
|
initialize(storageActor) {
|
||||||
|
protocol.Actor.prototype.initialize.call(this, null);
|
||||||
|
|
||||||
|
this.storageActor = storageActor;
|
||||||
|
|
||||||
|
this.addonId = this.storageActor.parentActor.addonId;
|
||||||
|
|
||||||
|
// Retrieve the base moz-extension url for the extension
|
||||||
|
// (and also remove the final '/' from it).
|
||||||
|
this.extensionHostURL = this.getExtensionPolicy()
|
||||||
|
.getURL()
|
||||||
|
.slice(0, -1);
|
||||||
|
|
||||||
|
// Map<host, ExtensionStorageIDB db connection>
|
||||||
|
// Bug 1542038, 1542039: Each storage area will need its own
|
||||||
|
// dbConnectionForHost, as they each have different storage backends.
|
||||||
|
// Anywhere dbConnectionForHost is used, we need to know the storage
|
||||||
|
// area to access the correct database.
|
||||||
|
this.dbConnectionForHost = new Map();
|
||||||
|
|
||||||
|
// Bug 1542038, 1542039: Each storage area will need its own
|
||||||
|
// this.hostVsStores or this actor will need to deviate from how
|
||||||
|
// this.hostVsStores is defined in the framework to associate each
|
||||||
|
// storage item with a storage area. Any methods that use it will also
|
||||||
|
// need to be updated (e.g. getNamesForHost).
|
||||||
|
this.hostVsStores = new Map();
|
||||||
|
|
||||||
|
this.onStorageChange = this.onStorageChange.bind(this);
|
||||||
|
|
||||||
|
this.setupChildProcess();
|
||||||
|
|
||||||
|
this.onWindowReady = this.onWindowReady.bind(this);
|
||||||
|
this.onWindowDestroyed = this.onWindowDestroyed.bind(this);
|
||||||
|
this.storageActor.on("window-ready", this.onWindowReady);
|
||||||
|
this.storageActor.on("window-destroyed", this.onWindowDestroyed);
|
||||||
},
|
},
|
||||||
{
|
|
||||||
initialize(storageActor) {
|
|
||||||
protocol.Actor.prototype.initialize.call(this, null);
|
|
||||||
|
|
||||||
this.storageActor = storageActor;
|
getExtensionPolicy() {
|
||||||
|
return WebExtensionPolicy.getByID(this.addonId);
|
||||||
|
},
|
||||||
|
|
||||||
this.addonId = this.storageActor.parentActor.addonId;
|
destroy() {
|
||||||
|
extensionStorageHelpers.onChangedChildListeners.delete(
|
||||||
|
this.onStorageChange
|
||||||
|
);
|
||||||
|
|
||||||
// Retrieve the base moz-extension url for the extension
|
this.storageActor.off("window-ready", this.onWindowReady);
|
||||||
// (and also remove the final '/' from it).
|
this.storageActor.off("window-destroyed", this.onWindowDestroyed);
|
||||||
this.extensionHostURL = this.getExtensionPolicy()
|
|
||||||
.getURL()
|
|
||||||
.slice(0, -1);
|
|
||||||
|
|
||||||
// Map<host, ExtensionStorageIDB db connection>
|
this.hostVsStores.clear();
|
||||||
// Bug 1542038, 1542039: Each storage area will need its own
|
protocol.Actor.prototype.destroy.call(this);
|
||||||
// dbConnectionForHost, as they each have different storage backends.
|
|
||||||
// Anywhere dbConnectionForHost is used, we need to know the storage
|
|
||||||
// area to access the correct database.
|
|
||||||
this.dbConnectionForHost = new Map();
|
|
||||||
|
|
||||||
// Bug 1542038, 1542039: Each storage area will need its own
|
this.storageActor = null;
|
||||||
// this.hostVsStores or this actor will need to deviate from how
|
},
|
||||||
// this.hostVsStores is defined in the framework to associate each
|
|
||||||
// storage item with a storage area. Any methods that use it will also
|
|
||||||
// need to be updated (e.g. getNamesForHost).
|
|
||||||
this.hostVsStores = new Map();
|
|
||||||
|
|
||||||
this.onStorageChange = this.onStorageChange.bind(this);
|
setupChildProcess() {
|
||||||
|
const ppmm = this.conn.parentMessageManager;
|
||||||
|
extensionStorageHelpers.setPpmm(ppmm);
|
||||||
|
|
||||||
this.setupChildProcess();
|
// eslint-disable-next-line no-restricted-properties
|
||||||
|
this.conn.setupInParent({
|
||||||
|
module: "devtools/server/actors/storage",
|
||||||
|
setupParent: "setupParentProcessForExtensionStorage",
|
||||||
|
});
|
||||||
|
|
||||||
this.onWindowReady = this.onWindowReady.bind(this);
|
extensionStorageHelpers.onChangedChildListeners.add(this.onStorageChange);
|
||||||
this.onWindowDestroyed = this.onWindowDestroyed.bind(this);
|
this.setupStorageInParent = extensionStorageHelpers.callParentProcessAsync.bind(
|
||||||
this.storageActor.on("window-ready", this.onWindowReady);
|
extensionStorageHelpers,
|
||||||
this.storageActor.on("window-destroyed", this.onWindowDestroyed);
|
"setupStorageInParent"
|
||||||
},
|
);
|
||||||
|
|
||||||
getExtensionPolicy() {
|
// Add a message listener in the child process to receive messages from the parent
|
||||||
return WebExtensionPolicy.getByID(this.addonId);
|
// process
|
||||||
},
|
ppmm.addMessageListener(
|
||||||
|
"debug:storage-extensionStorage-request-child",
|
||||||
|
extensionStorageHelpers.handleParentRequest.bind(
|
||||||
|
extensionStorageHelpers
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
destroy() {
|
/**
|
||||||
extensionStorageHelpers.onChangedChildListeners.delete(
|
* This fires when the extension changes storage data while the storage
|
||||||
this.onStorageChange
|
* inspector is open. Ensures this.hostVsStores stays up-to-date and
|
||||||
|
* passes the changes on to update the client.
|
||||||
|
*/
|
||||||
|
onStorageChange({ addonId, changes }) {
|
||||||
|
if (addonId !== this.addonId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = this.extensionHostURL;
|
||||||
|
const storeMap = this.hostVsStores.get(host);
|
||||||
|
|
||||||
|
function isStructuredCloneHolder(value) {
|
||||||
|
return (
|
||||||
|
value &&
|
||||||
|
typeof value === "object" &&
|
||||||
|
Cu.getClassName(value, true) === "StructuredCloneHolder"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.storageActor.off("window-ready", this.onWindowReady);
|
for (const key in changes) {
|
||||||
this.storageActor.off("window-destroyed", this.onWindowDestroyed);
|
const storageChange = changes[key];
|
||||||
|
let { newValue, oldValue } = storageChange;
|
||||||
this.hostVsStores.clear();
|
if (isStructuredCloneHolder(newValue)) {
|
||||||
protocol.Actor.prototype.destroy.call(this);
|
newValue = newValue.deserialize(this);
|
||||||
|
}
|
||||||
this.storageActor = null;
|
if (isStructuredCloneHolder(oldValue)) {
|
||||||
},
|
oldValue = oldValue.deserialize(this);
|
||||||
|
|
||||||
setupChildProcess() {
|
|
||||||
const ppmm = this.conn.parentMessageManager;
|
|
||||||
extensionStorageHelpers.setPpmm(ppmm);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-properties
|
|
||||||
this.conn.setupInParent({
|
|
||||||
module: "devtools/server/actors/storage",
|
|
||||||
setupParent: "setupParentProcessForExtensionStorage",
|
|
||||||
});
|
|
||||||
|
|
||||||
extensionStorageHelpers.onChangedChildListeners.add(
|
|
||||||
this.onStorageChange
|
|
||||||
);
|
|
||||||
this.setupStorageInParent = extensionStorageHelpers.callParentProcessAsync.bind(
|
|
||||||
extensionStorageHelpers,
|
|
||||||
"setupStorageInParent"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add a message listener in the child process to receive messages from the parent
|
|
||||||
// process
|
|
||||||
ppmm.addMessageListener(
|
|
||||||
"debug:storage-extensionStorage-request-child",
|
|
||||||
extensionStorageHelpers.handleParentRequest.bind(
|
|
||||||
extensionStorageHelpers
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This fires when the extension changes storage data while the storage
|
|
||||||
* inspector is open. Ensures this.hostVsStores stays up-to-date and
|
|
||||||
* passes the changes on to update the client.
|
|
||||||
*/
|
|
||||||
onStorageChange({ addonId, changes }) {
|
|
||||||
if (addonId !== this.addonId) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const host = this.extensionHostURL;
|
let action;
|
||||||
const storeMap = this.hostVsStores.get(host);
|
if (typeof newValue === "undefined") {
|
||||||
|
action = "deleted";
|
||||||
function isStructuredCloneHolder(value) {
|
storeMap.delete(key);
|
||||||
return (
|
} else if (typeof oldValue === "undefined") {
|
||||||
value &&
|
action = "added";
|
||||||
typeof value === "object" &&
|
storeMap.set(key, newValue);
|
||||||
Cu.getClassName(value, true) === "StructuredCloneHolder"
|
} else {
|
||||||
);
|
action = "changed";
|
||||||
|
storeMap.set(key, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in changes) {
|
this.storageActor.update(action, this.typeName, { [host]: [key] });
|
||||||
const storageChange = changes[key];
|
}
|
||||||
let { newValue, oldValue } = storageChange;
|
},
|
||||||
if (isStructuredCloneHolder(newValue)) {
|
|
||||||
newValue = newValue.deserialize(this);
|
|
||||||
}
|
|
||||||
if (isStructuredCloneHolder(oldValue)) {
|
|
||||||
oldValue = oldValue.deserialize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
let action;
|
/**
|
||||||
if (typeof newValue === "undefined") {
|
* Purpose of this method is same as populateStoresForHosts but this is async.
|
||||||
action = "deleted";
|
* This exact same operation cannot be performed in populateStoresForHosts
|
||||||
storeMap.delete(key);
|
* method, as that method is called in initialize method of the actor, which
|
||||||
} else if (typeof oldValue === "undefined") {
|
* cannot be asynchronous.
|
||||||
action = "added";
|
*/
|
||||||
storeMap.set(key, newValue);
|
async preListStores() {
|
||||||
} else {
|
// Ensure the actor's target is an extension and it is enabled
|
||||||
action = "changed";
|
if (!this.addonId || !WebExtensionPolicy.getByID(this.addonId)) {
|
||||||
storeMap.set(key, newValue);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.storageActor.update(action, this.typeName, { [host]: [key] });
|
await this.populateStoresForHost(this.extensionHostURL);
|
||||||
}
|
},
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Purpose of this method is same as populateStoresForHosts but this is async.
|
* This method is overriden and left blank as for extensionStorage, this operation
|
||||||
* This exact same operation cannot be performed in populateStoresForHosts
|
* cannot be performed synchronously. Thus, the preListStores method exists to
|
||||||
* method, as that method is called in initialize method of the actor, which
|
* do the same task asynchronously.
|
||||||
* cannot be asynchronous.
|
*/
|
||||||
*/
|
populateStoresForHosts() {},
|
||||||
async preListStores() {
|
|
||||||
// Ensure the actor's target is an extension and it is enabled
|
|
||||||
if (!this.addonId || !WebExtensionPolicy.getByID(this.addonId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.populateStoresForHost(this.extensionHostURL);
|
/**
|
||||||
},
|
* This method asynchronously reads the storage data for the target extension
|
||||||
|
* and caches this data into this.hostVsStores.
|
||||||
|
* @param {String} host - the hostname for the extension
|
||||||
|
*/
|
||||||
|
async populateStoresForHost(host) {
|
||||||
|
if (host !== this.extensionHostURL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
const extension = ExtensionProcessScript.getExtensionChild(this.addonId);
|
||||||
* This method is overriden and left blank as for extensionStorage, this operation
|
if (!extension || !extension.hasPermission("storage")) {
|
||||||
* cannot be performed synchronously. Thus, the preListStores method exists to
|
return;
|
||||||
* do the same task asynchronously.
|
}
|
||||||
*/
|
|
||||||
populateStoresForHosts() {},
|
|
||||||
|
|
||||||
/**
|
// Make sure storeMap is defined and set in this.hostVsStores before subscribing
|
||||||
* This method asynchronously reads the storage data for the target extension
|
// a storage onChanged listener in the parent process
|
||||||
* and caches this data into this.hostVsStores.
|
const storeMap = new Map();
|
||||||
* @param {String} host - the hostname for the extension
|
this.hostVsStores.set(host, storeMap);
|
||||||
*/
|
|
||||||
async populateStoresForHost(host) {
|
|
||||||
if (host !== this.extensionHostURL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const extension = ExtensionProcessScript.getExtensionChild(
|
const storagePrincipal = await this.getStoragePrincipal(extension.id);
|
||||||
this.addonId
|
|
||||||
);
|
|
||||||
if (!extension || !extension.hasPermission("storage")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure storeMap is defined and set in this.hostVsStores before subscribing
|
if (!storagePrincipal) {
|
||||||
// a storage onChanged listener in the parent process
|
// This could happen if the extension fails to be migrated to the
|
||||||
const storeMap = new Map();
|
// IndexedDB backend
|
||||||
this.hostVsStores.set(host, storeMap);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const storagePrincipal = await this.getStoragePrincipal(extension.id);
|
const db = await ExtensionStorageIDB.open(storagePrincipal);
|
||||||
|
this.dbConnectionForHost.set(host, db);
|
||||||
|
const data = await db.get();
|
||||||
|
|
||||||
if (!storagePrincipal) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
// This could happen if the extension fails to be migrated to the
|
storeMap.set(key, value);
|
||||||
// IndexedDB backend
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await ExtensionStorageIDB.open(storagePrincipal);
|
if (this.storageActor.parentActor.fallbackWindow) {
|
||||||
this.dbConnectionForHost.set(host, db);
|
// Show the storage actor in the add-on storage inspector even when there
|
||||||
const data = await db.get();
|
// is no extension page currently open
|
||||||
|
// This strategy may need to change depending on the outcome of Bug 1597900
|
||||||
|
const storageData = {};
|
||||||
|
storageData[host] = this.getNamesForHost(host);
|
||||||
|
this.storageActor.update("added", this.typeName, storageData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
async getStoragePrincipal(addonId) {
|
||||||
storeMap.set(key, value);
|
const {
|
||||||
}
|
backendEnabled,
|
||||||
|
storagePrincipal,
|
||||||
|
} = await this.setupStorageInParent(addonId);
|
||||||
|
|
||||||
if (this.storageActor.parentActor.fallbackWindow) {
|
if (!backendEnabled) {
|
||||||
// Show the storage actor in the add-on storage inspector even when there
|
// IDB backend disabled; give up.
|
||||||
// is no extension page currently open
|
return null;
|
||||||
// This strategy may need to change depending on the outcome of Bug 1597900
|
}
|
||||||
const storageData = {};
|
return storagePrincipal;
|
||||||
storageData[host] = this.getNamesForHost(host);
|
},
|
||||||
this.storageActor.update("added", this.typeName, storageData);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getStoragePrincipal(addonId) {
|
getValuesForHost(host, name) {
|
||||||
const {
|
const result = [];
|
||||||
backendEnabled,
|
|
||||||
storagePrincipal,
|
|
||||||
} = await this.setupStorageInParent(addonId);
|
|
||||||
|
|
||||||
if (!backendEnabled) {
|
if (!this.hostVsStores.has(host)) {
|
||||||
// IDB backend disabled; give up.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return storagePrincipal;
|
|
||||||
},
|
|
||||||
|
|
||||||
getValuesForHost(host, name) {
|
|
||||||
const result = [];
|
|
||||||
|
|
||||||
if (!this.hostVsStores.has(host)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
return [{ name, value: this.hostVsStores.get(host).get(name) }];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, value] of Array.from(
|
|
||||||
this.hostVsStores.get(host).entries()
|
|
||||||
)) {
|
|
||||||
result.push({ name: key, value });
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
if (name) {
|
||||||
* Converts a storage item to an "extensionobject" as defined in
|
return [{ name, value: this.hostVsStores.get(host).get(name) }];
|
||||||
* devtools/shared/specs/storage.js. Behavior largely mirrors the "indexedDB" storage actor,
|
}
|
||||||
* except where it would throw an unhandled error (i.e. for a `BigInt` or `undefined`
|
|
||||||
* `item.value`).
|
|
||||||
* @param {Object} item - The storage item to convert
|
|
||||||
* @param {String} item.name - The storage item key
|
|
||||||
* @param {*} item.value - The storage item value
|
|
||||||
* @return {extensionobject}
|
|
||||||
*/
|
|
||||||
toStoreObject(item) {
|
|
||||||
if (!item) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { name, value } = item;
|
for (const [key, value] of Array.from(
|
||||||
const isValueEditable = extensionStorageHelpers.isEditable(value);
|
this.hostVsStores.get(host).entries()
|
||||||
|
)) {
|
||||||
|
result.push({ name: key, value });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
// `JSON.stringify()` throws for `BigInt`, adds extra quotes to strings and `Date` strings,
|
/**
|
||||||
// and doesn't modify `undefined`.
|
* Converts a storage item to an "extensionobject" as defined in
|
||||||
switch (typeof value) {
|
* devtools/shared/specs/storage.js. Behavior largely mirrors the "indexedDB" storage actor,
|
||||||
case "bigint":
|
* except where it would throw an unhandled error (i.e. for a `BigInt` or `undefined`
|
||||||
value = `${value.toString()}n`;
|
* `item.value`).
|
||||||
|
* @param {Object} item - The storage item to convert
|
||||||
|
* @param {String} item.name - The storage item key
|
||||||
|
* @param {*} item.value - The storage item value
|
||||||
|
* @return {extensionobject}
|
||||||
|
*/
|
||||||
|
toStoreObject(item) {
|
||||||
|
if (!item) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { name, value } = item;
|
||||||
|
const isValueEditable = extensionStorageHelpers.isEditable(value);
|
||||||
|
|
||||||
|
// `JSON.stringify()` throws for `BigInt`, adds extra quotes to strings and `Date` strings,
|
||||||
|
// and doesn't modify `undefined`.
|
||||||
|
switch (typeof value) {
|
||||||
|
case "bigint":
|
||||||
|
value = `${value.toString()}n`;
|
||||||
|
break;
|
||||||
|
case "string":
|
||||||
|
break;
|
||||||
|
case "undefined":
|
||||||
|
value = "undefined";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
if (
|
||||||
|
// can't use `instanceof` across frame boundaries
|
||||||
|
Object.prototype.toString.call(item.value) === "[object Date]"
|
||||||
|
) {
|
||||||
|
value = JSON.parse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
value: new LongStringActor(this.conn, value),
|
||||||
|
area: "local", // Bug 1542038, 1542039: set the correct storage area
|
||||||
|
isValueEditable,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getFields() {
|
||||||
|
return [
|
||||||
|
{ name: "name", editable: false },
|
||||||
|
{ name: "value", editable: true },
|
||||||
|
{ name: "area", editable: false },
|
||||||
|
{ name: "isValueEditable", editable: false, private: true },
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemUpdated(action, host, names) {
|
||||||
|
this.storageActor.update(action, this.typeName, {
|
||||||
|
[host]: names,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async editItem({ host, field, items, oldValue }) {
|
||||||
|
const db = this.dbConnectionForHost.get(host);
|
||||||
|
if (!db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, value } = items;
|
||||||
|
|
||||||
|
let parsedValue = parseItemValue(value);
|
||||||
|
if (parsedValue === value) {
|
||||||
|
const { typesFromString } = extensionStorageHelpers;
|
||||||
|
for (const { test, parse } of Object.values(typesFromString)) {
|
||||||
|
if (test(value)) {
|
||||||
|
parsedValue = parse(value);
|
||||||
break;
|
break;
|
||||||
case "string":
|
|
||||||
break;
|
|
||||||
case "undefined":
|
|
||||||
value = "undefined";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
value = JSON.stringify(value);
|
|
||||||
if (
|
|
||||||
// can't use `instanceof` across frame boundaries
|
|
||||||
Object.prototype.toString.call(item.value) === "[object Date]"
|
|
||||||
) {
|
|
||||||
value = JSON.parse(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
value: new LongStringActor(this.conn, value),
|
|
||||||
area: "local", // Bug 1542038, 1542039: set the correct storage area
|
|
||||||
isValueEditable,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getFields() {
|
|
||||||
return [
|
|
||||||
{ name: "name", editable: false },
|
|
||||||
{ name: "value", editable: true },
|
|
||||||
{ name: "area", editable: false },
|
|
||||||
{ name: "isValueEditable", editable: false, private: true },
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
onItemUpdated(action, host, names) {
|
|
||||||
this.storageActor.update(action, this.typeName, {
|
|
||||||
[host]: names,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async editItem({ host, field, items, oldValue }) {
|
|
||||||
const db = this.dbConnectionForHost.get(host);
|
|
||||||
if (!db) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, value } = items;
|
|
||||||
|
|
||||||
let parsedValue = parseItemValue(value);
|
|
||||||
if (parsedValue === value) {
|
|
||||||
const { typesFromString } = extensionStorageHelpers;
|
|
||||||
for (const { test, parse } of Object.values(typesFromString)) {
|
|
||||||
if (test(value)) {
|
|
||||||
parsedValue = parse(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const changes = await db.set({ [name]: parsedValue });
|
}
|
||||||
this.fireOnChangedExtensionEvent(host, changes);
|
const changes = await db.set({ [name]: parsedValue });
|
||||||
|
this.fireOnChangedExtensionEvent(host, changes);
|
||||||
|
|
||||||
this.onItemUpdated("changed", host, [name]);
|
this.onItemUpdated("changed", host, [name]);
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeItem(host, name) {
|
async removeItem(host, name) {
|
||||||
const db = this.dbConnectionForHost.get(host);
|
const db = this.dbConnectionForHost.get(host);
|
||||||
if (!db) {
|
if (!db) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changes = await db.remove(name);
|
const changes = await db.remove(name);
|
||||||
this.fireOnChangedExtensionEvent(host, changes);
|
this.fireOnChangedExtensionEvent(host, changes);
|
||||||
|
|
||||||
this.onItemUpdated("deleted", host, [name]);
|
this.onItemUpdated("deleted", host, [name]);
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeAll(host) {
|
async removeAll(host) {
|
||||||
const db = this.dbConnectionForHost.get(host);
|
const db = this.dbConnectionForHost.get(host);
|
||||||
if (!db) {
|
if (!db) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changes = await db.clear();
|
const changes = await db.clear();
|
||||||
this.fireOnChangedExtensionEvent(host, changes);
|
this.fireOnChangedExtensionEvent(host, changes);
|
||||||
|
|
||||||
this.onItemUpdated("cleared", host, []);
|
this.onItemUpdated("cleared", host, []);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Let the extension know that storage data has been changed by the user from
|
* Let the extension know that storage data has been changed by the user from
|
||||||
* the storage inspector.
|
* the storage inspector.
|
||||||
*/
|
*/
|
||||||
fireOnChangedExtensionEvent(host, changes) {
|
fireOnChangedExtensionEvent(host, changes) {
|
||||||
// Bug 1542038, 1542039: Which message to send depends on the storage area
|
// Bug 1542038, 1542039: Which message to send depends on the storage area
|
||||||
const uuid = new URL(host).host;
|
const uuid = new URL(host).host;
|
||||||
Services.cpmm.sendAsyncMessage(
|
Services.cpmm.sendAsyncMessage(
|
||||||
`Extension:StorageLocalOnChanged:${uuid}`,
|
`Extension:StorageLocalOnChanged:${uuid}`,
|
||||||
changes
|
changes
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
StorageActors.createActor(
|
StorageActors.createActor(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,6 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Pref remains in effect until test completes and is automatically cleared afterwards
|
|
||||||
add_task(async function set_enable_extensionStorage_pref() {
|
|
||||||
await SpecialPowers.pushPrefEnv({
|
|
||||||
set: [["devtools.storage.extensionStorage.enabled", true]],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(
|
add_task(
|
||||||
async function test_extensionStorage_disabled_for_non_extension_target() {
|
async function test_extensionStorage_disabled_for_non_extension_target() {
|
||||||
if (isFissionEnabled()) {
|
if (isFissionEnabled()) {
|
||||||
|
|
|
||||||
|
|
@ -41,20 +41,12 @@ const { createAppInfo, promiseStartupManager } = AddonTestUtils;
|
||||||
|
|
||||||
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
|
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
|
||||||
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
|
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
|
||||||
const EXTENSION_STORAGE_ENABLED_PREF =
|
|
||||||
"devtools.storage.extensionStorage.enabled";
|
|
||||||
|
|
||||||
AddonTestUtils.init(this);
|
AddonTestUtils.init(this);
|
||||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
|
||||||
|
|
||||||
ExtensionTestUtils.init(this);
|
ExtensionTestUtils.init(this);
|
||||||
|
|
||||||
// This storage actor is gated behind a pref, so make sure it is enabled first
|
|
||||||
Services.prefs.setBoolPref(EXTENSION_STORAGE_ENABLED_PREF, true);
|
|
||||||
registerCleanupFunction(() => {
|
|
||||||
Services.prefs.clearUserPref(EXTENSION_STORAGE_ENABLED_PREF);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_setup(async function setup() {
|
add_setup(async function setup() {
|
||||||
await promiseStartupManager();
|
await promiseStartupManager();
|
||||||
const dir = createMissingIndexedDBDirs();
|
const dir = createMissingIndexedDBDirs();
|
||||||
|
|
@ -1157,32 +1149,3 @@ add_task(async function test_live_update_with_no_extension_listener() {
|
||||||
|
|
||||||
await shutdown(extension, target);
|
await shutdown(extension, target);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
* This task should be last, as it sets a pref to disable the extensionStorage
|
|
||||||
* storage actor. Since this pref is set at the beginning of the file, it
|
|
||||||
* already will be cleared via registerCleanupFunction when the test finishes.
|
|
||||||
*/
|
|
||||||
add_task(
|
|
||||||
{
|
|
||||||
// This test fails if the extension runs in the main process
|
|
||||||
// like in Thunderbird (see bug 1575183 comment #15 for details).
|
|
||||||
skip_if: () => !WebExtensionPolicy.useRemoteWebExtensions,
|
|
||||||
},
|
|
||||||
async function test_extensionStorage_store_disabled_on_pref() {
|
|
||||||
Services.prefs.setBoolPref(EXTENSION_STORAGE_ENABLED_PREF, false);
|
|
||||||
|
|
||||||
const extension = await startupExtension(getExtensionConfig());
|
|
||||||
|
|
||||||
const { target, extensionStorage } = await openAddonStoragePanel(
|
|
||||||
extension.id
|
|
||||||
);
|
|
||||||
|
|
||||||
ok(
|
|
||||||
extensionStorage === null,
|
|
||||||
"Should not have an extensionStorage store when pref disabled"
|
|
||||||
);
|
|
||||||
|
|
||||||
await shutdown(extension, target);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
|
||||||
|
|
@ -35,20 +35,11 @@ PromiseTestUtils.allowMatchingRejectionsGlobally(
|
||||||
|
|
||||||
const { createAppInfo, promiseStartupManager } = AddonTestUtils;
|
const { createAppInfo, promiseStartupManager } = AddonTestUtils;
|
||||||
|
|
||||||
const EXTENSION_STORAGE_ENABLED_PREF =
|
|
||||||
"devtools.storage.extensionStorage.enabled";
|
|
||||||
|
|
||||||
AddonTestUtils.init(this);
|
AddonTestUtils.init(this);
|
||||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
|
||||||
|
|
||||||
ExtensionTestUtils.init(this);
|
ExtensionTestUtils.init(this);
|
||||||
|
|
||||||
// This storage actor is gated behind a pref, so make sure it is enabled first
|
|
||||||
Services.prefs.setBoolPref(EXTENSION_STORAGE_ENABLED_PREF, true);
|
|
||||||
registerCleanupFunction(() => {
|
|
||||||
Services.prefs.clearUserPref(EXTENSION_STORAGE_ENABLED_PREF);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function setup() {
|
add_task(async function setup() {
|
||||||
await promiseStartupManager();
|
await promiseStartupManager();
|
||||||
const dir = createMissingIndexedDBDirs();
|
const dir = createMissingIndexedDBDirs();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue