Bug 1666534 - [devtools] Listen to extension storages via a server side watcher. r=devtools-reviewers,nchevobbe,jdescottes

This was the last resource type requiring to keep the old storage actor as-is.
This will help drastically simplify it and move storage type code into each Resource Watcher class.

Differential Revision: https://phabricator.services.mozilla.com/D166661
This commit is contained in:
Alexandre Poirot 2023-01-29 20:50:47 +00:00
parent 7539d4e3f4
commit 94e02496ef
14 changed files with 191 additions and 307 deletions

View file

@ -315,17 +315,22 @@ class StorageUI {
this._onResourceListAvailable = this._onResourceListAvailable.bind(this);
const { resourceCommand } = this._toolbox;
await this._toolbox.resourceCommand.watchResources(
[
this._listenedResourceTypes = [
// The first item in this list will be the first selected storage item
// Tests assume Cookie -- moving cookie will break tests
resourceCommand.TYPES.COOKIE,
resourceCommand.TYPES.CACHE_STORAGE,
resourceCommand.TYPES.EXTENSION_STORAGE,
resourceCommand.TYPES.INDEXED_DB,
resourceCommand.TYPES.LOCAL_STORAGE,
resourceCommand.TYPES.SESSION_STORAGE,
],
];
// EXTENSION_STORAGE is only relevant when debugging web extensions
if (this._commands.descriptorFront.isWebExtensionDescriptor) {
this._listenedResourceTypes.push(resourceCommand.TYPES.EXTENSION_STORAGE);
}
await this._toolbox.resourceCommand.watchResources(
this._listenedResourceTypes,
{
onAvailable: this._onResourceListAvailable,
}
@ -443,19 +448,9 @@ class StorageUI {
this._destroyed = true;
const { resourceCommand } = this._toolbox;
resourceCommand.unwatchResources(
[
resourceCommand.TYPES.COOKIE,
resourceCommand.TYPES.CACHE_STORAGE,
resourceCommand.TYPES.EXTENSION_STORAGE,
resourceCommand.TYPES.INDEXED_DB,
resourceCommand.TYPES.LOCAL_STORAGE,
resourceCommand.TYPES.SESSION_STORAGE,
],
{
resourceCommand.unwatchResources(this._listenedResourceTypes, {
onAvailable: this._onResourceListAvailable,
}
);
});
this.table.off(TableWidget.EVENTS.ROW_SELECTED, this.updateObjectSidebar);
this.table.off(TableWidget.EVENTS.SCROLL_END, this.loadMoreItems);

View file

@ -26,6 +26,7 @@ const TYPES = {
// storage types
CACHE_STORAGE: "Cache",
COOKIE: "cookies",
EXTENSION_STORAGE: "extension-storage",
INDEXED_DB: "indexed-db",
LOCAL_STORAGE: "local-storage",
SESSION_STORAGE: "session-storage",
@ -160,6 +161,9 @@ const ParentProcessResources = augmentResourceDictionary({
[TYPES.COOKIE]: {
path: "devtools/server/actors/resources/storage-cookie",
},
[TYPES.EXTENSION_STORAGE]: {
path: "devtools/server/actors/resources/storage-extension",
},
[TYPES.INDEXED_DB]: {
path: "devtools/server/actors/resources/storage-indexed-db",
},

View file

@ -27,6 +27,7 @@ DevToolsModules(
"sources.js",
"storage-cache.js",
"storage-cookie.js",
"storage-extension.js",
"storage-indexed-db.js",
"storage-local-storage.js",
"storage-session-storage.js",

View file

@ -0,0 +1,27 @@
/* 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 {
TYPES: { EXTENSION_STORAGE },
} = require("resource://devtools/server/actors/resources/index.js");
const ParentProcessStorage = require("resource://devtools/server/actors/resources/utils/parent-process-storage.js");
class ExtensionStorageWatcher extends ParentProcessStorage {
constructor() {
super("extensionStorage", EXTENSION_STORAGE);
}
async watch(watcherActor, { onAvailable }) {
if (watcherActor.sessionContext.type != "webextension") {
throw new Error(
"EXTENSION_STORAGE should only be listened when debugging a webextension"
);
}
return super.watch(watcherActor, { onAvailable });
}
}
module.exports = ExtensionStorageWatcher;

View file

@ -407,7 +407,10 @@ class StorageActorMock extends EventEmitter {
}
get parentActor() {
return { isRootActor: this.watcherActor.sessionContext.type == "all" };
return {
isRootActor: this.watcherActor.sessionContext.type == "all",
addonId: this.watcherActor.sessionContext.addonId,
};
}
/**

View file

@ -1488,14 +1488,6 @@ StorageActors.createActor(
);
const extensionStorageHelpers = {
unresolvedPromises: new Map(),
// Map of addonId => onStorageChange listeners in the parent process. Each addon toolbox targets
// a single addon, and multiple addon toolboxes could be open at the same time.
onChangedParentListeners: new Map(),
// Set of onStorageChange listeners in the extension child process. Each addon toolbox will create
// a separate extensionStorage actor targeting that addon. The addonId is passed into the listener,
// so that changes propagate only if the storage actor has a matching addonId.
onChangedChildListeners: new Set(),
/**
* Editing is supported only for serializable types. Examples of unserializable
* types include Map, Set and ArrayBuffer.
@ -1610,172 +1602,6 @@ const extensionStorageHelpers = {
},
},
},
// Sets the parent process message manager
setPpmm(ppmm) {
this.ppmm = ppmm;
},
// A promise in the main process has resolved, and we need to pass the return value(s)
// back to the child process
backToChild(...args) {
Services.mm.broadcastAsyncMessage(
"debug:storage-extensionStorage-request-child",
{
method: "backToChild",
args,
}
);
},
// Send a message from the main process to a listener in the child process that the
// extension has modified storage local data
fireStorageOnChanged({ addonId, changes }) {
Services.mm.broadcastAsyncMessage(
"debug:storage-extensionStorage-request-child",
{
addonId,
changes,
method: "storageOnChanged",
}
);
},
// Subscribe a listener for event notifications from the WE storage API when
// storage local data has been changed by the extension, and keep track of the
// listener to remove it when the debugger is being disconnected.
subscribeOnChangedListenerInParent(addonId) {
if (!this.onChangedParentListeners.has(addonId)) {
const onChangedListener = changes => {
this.fireStorageOnChanged({ addonId, changes });
};
ExtensionStorageIDB.addOnChangedListener(addonId, onChangedListener);
this.onChangedParentListeners.set(addonId, onChangedListener);
}
},
// The main process does not require an extension context to select the backend
// Bug 1542038, 1542039: Each storage area will need its own implementation, as
// they use different storage backends.
async setupStorageInParent(addonId) {
const { extension } = WebExtensionPolicy.getByID(addonId);
try {
// Make sure the extension storage APIs have been loaded,
// otherwise the DevTools storage panel would not be updated
// automatically when the extension storage data is being changed
// if the parent ext-storage.js module wasn't already loaded
// (See Bug 1802929).
await extension.apiManager.asyncGetAPI("storage", extension);
} catch (err) {
console.error(err);
}
const parentResult = await ExtensionStorageIDB.selectBackend({ extension });
const result = {
...parentResult,
// Received as a StructuredCloneHolder, so we need to deserialize
storagePrincipal: parentResult.storagePrincipal.deserialize(this, true),
};
this.subscribeOnChangedListenerInParent(addonId);
return this.backToChild("setupStorageInParent", result);
},
onDisconnected() {
for (const [addonId, listener] of this.onChangedParentListeners) {
ExtensionStorageIDB.removeOnChangedListener(addonId, listener);
}
this.onChangedParentListeners.clear();
},
// Runs in the main process. This determines what code to execute based on the message
// received from the child process.
async handleChildRequest(msg) {
switch (msg.json.method) {
case "setupStorageInParent":
const addonId = msg.data.args[0];
const result = await extensionStorageHelpers.setupStorageInParent(
addonId
);
return result;
default:
console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method);
throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD");
}
},
// Runs in the child process. This determines what code to execute based on the message
// received from the parent process.
handleParentRequest(msg) {
switch (msg.json.method) {
case "backToChild": {
const [func, rv] = msg.json.args;
const resolve = this.unresolvedPromises.get(func);
if (resolve) {
this.unresolvedPromises.delete(func);
resolve(rv);
}
break;
}
case "storageOnChanged": {
const { addonId, changes } = msg.data;
for (const listener of this.onChangedChildListeners) {
try {
listener({ addonId, changes });
} catch (err) {
console.error(err);
// Ignore errors raised from listeners.
}
}
break;
}
default:
console.error("ERR_DIRECTOR_CLIENT_UNKNOWN_METHOD", msg.json.method);
throw new Error("ERR_DIRECTOR_CLIENT_UNKNOWN_METHOD");
}
},
callParentProcessAsync(methodName, ...args) {
const promise = new Promise(resolve => {
this.unresolvedPromises.set(methodName, resolve);
});
this.ppmm.sendAsyncMessage(
"debug:storage-extensionStorage-request-parent",
{
method: methodName,
args,
}
);
return promise;
},
};
/**
* E10S parent/child setup helpers
* Add a message listener in the parent process to receive messages from the child
* process.
*/
exports.setupParentProcessForExtensionStorage = function({ mm, prefix }) {
// listen for director-script requests from the child process
mm.addMessageListener(
"debug:storage-extensionStorage-request-parent",
extensionStorageHelpers.handleChildRequest
);
return {
onDisconnected: () => {
// Although "disconnected-from-child" implies that the child is already
// disconnected this is not the case. The disconnection takes place after
// this method has finished. This gives us chance to clean up items within
// the parent process e.g. observers.
mm.removeMessageListener(
"debug:storage-extensionStorage-request-parent",
extensionStorageHelpers.handleChildRequest
);
extensionStorageHelpers.onDisconnected();
},
};
};
/**
@ -1815,8 +1641,6 @@ StorageActors.createActor(
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);
@ -1828,7 +1652,8 @@ StorageActors.createActor(
},
destroy() {
extensionStorageHelpers.onChangedChildListeners.delete(
ExtensionStorageIDB.removeOnChangedListener(
this.addonId,
this.onStorageChange
);
@ -1841,42 +1666,12 @@ StorageActors.createActor(
this.storageActor = null;
},
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;
}
onStorageChange(changes) {
const host = this.extensionHostURL;
const storeMap = this.hostVsStores.get(host);
@ -1922,10 +1717,32 @@ StorageActors.createActor(
*/
async preListStores() {
// Ensure the actor's target is an extension and it is enabled
if (!this.addonId || !WebExtensionPolicy.getByID(this.addonId)) {
if (!this.addonId || !this.getExtensionPolicy()) {
return;
}
// Subscribe a listener for event notifications from the WE storage API when
// storage local data has been changed by the extension, and keep track of the
// listener to remove it when the debugger is being disconnected.
ExtensionStorageIDB.addOnChangedListener(
this.addonId,
this.onStorageChange
);
try {
// Make sure the extension storage APIs have been loaded,
// otherwise the DevTools storage panel would not be updated
// automatically when the extension storage data is being changed
// if the parent ext-storage.js module wasn't already loaded
// (See Bug 1802929).
const { extension } = WebExtensionPolicy.getByID(this.addonId);
await extension.apiManager.asyncGetAPI("storage", extension);
} catch (e) {
console.error(
"Exception while trying to initialize webext storage API",
e
);
}
await this.populateStoresForHost(this.extensionHostURL);
},
@ -1956,7 +1773,7 @@ StorageActors.createActor(
const storeMap = new Map();
this.hostVsStores.set(host, storeMap);
const storagePrincipal = await this.getStoragePrincipal(extension.id);
const storagePrincipal = await this.getStoragePrincipal();
if (!storagePrincipal) {
// This could happen if the extension fails to be migrated to the
@ -1982,17 +1799,20 @@ StorageActors.createActor(
}
},
async getStoragePrincipal(addonId) {
async getStoragePrincipal() {
const { extension } = this.getExtensionPolicy();
const {
backendEnabled,
storagePrincipal,
} = await this.setupStorageInParent(addonId);
} = await ExtensionStorageIDB.selectBackend({ extension });
if (!backendEnabled) {
// IDB backend disabled; give up.
return null;
}
return storagePrincipal;
// Received as a StructuredCloneHolder, so we need to deserialize
return storagePrincipal.deserialize(this, true);
},
getValuesForHost(host, name) {

View file

@ -188,6 +188,7 @@ function getWatcherSupportedResources(type) {
[Resources.TYPES.CACHE_STORAGE]: true,
[Resources.TYPES.COOKIE]: true,
[Resources.TYPES.ERROR_MESSAGE]: true,
[Resources.TYPES.EXTENSION_STORAGE]: true,
[Resources.TYPES.INDEXED_DB]: true,
[Resources.TYPES.LOCAL_STORAGE]: true,
[Resources.TYPES.SESSION_STORAGE]: true,

View file

@ -60,13 +60,13 @@ add_setup(async function setup() {
add_task(async function test_extension_store_exists() {
const extension = await startupExtension(getExtensionConfig());
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
ok(extensionStorage, "Should have an extensionStorage store");
await shutdown(extension, target);
await shutdown(extension, commands);
});
add_task(
@ -86,7 +86,7 @@ add_task(
getExtensionConfig({ background })
);
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -97,7 +97,7 @@ add_task(
"Should have the expected extension host in the extensionStorage store"
);
await shutdown(extension, target);
await shutdown(extension, commands);
}
);
@ -118,7 +118,7 @@ add_task(async function test_panel_live_updates() {
getExtensionConfig({ background: extensionScriptWithMessageListener })
);
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -319,7 +319,7 @@ add_task(async function test_panel_live_updates() {
"Got the expected results on populated storage.local"
);
await shutdown(extension, target);
await shutdown(extension, commands);
});
/**
@ -347,7 +347,7 @@ add_task(
extension.sendMessage("storage-local-set", { a: 123 });
await extension.awaitMessage("storage-local-set:done");
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -366,7 +366,7 @@ add_task(
);
await contentPage.close();
await shutdown(extension, target);
await shutdown(extension, commands);
}
);
@ -396,7 +396,7 @@ add_task(async function test_panel_data_matches_extension_with_no_pages_open() {
await extension.awaitMessage("storage-local-onChanged");
await contentPage.close();
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -414,7 +414,7 @@ add_task(async function test_panel_data_matches_extension_with_no_pages_open() {
"Got the expected results on populated storage.local"
);
await shutdown(extension, target);
await shutdown(extension, commands);
});
/**
@ -434,7 +434,7 @@ add_task(
getExtensionConfig({ files: ext_no_bg.files })
);
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -493,7 +493,7 @@ add_task(
);
await contentPage.close();
await shutdown(extension, target);
await shutdown(extension, commands);
}
);
@ -516,7 +516,7 @@ add_task(
const host = await extension.awaitMessage("extension-origin");
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -588,7 +588,7 @@ add_task(
);
}
await shutdown(extension, target);
await shutdown(extension, commands);
}
);
@ -610,11 +610,9 @@ add_task(
const host = await extension.awaitMessage("extension-origin");
const {
target,
extensionStorage,
storageFront,
} = await openAddonStoragePanel(extension.id);
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
const DEFAULT_VALUE = "value"; // global in devtools/server/actors/storage.js
let items = {
@ -624,7 +622,7 @@ add_task(
};
info("Adding storage items from the extension");
let storesUpdate = storageFront.once("stores-update");
let storesUpdate = extensionStorage.once("single-store-update");
extension.sendMessage("storage-local-set", items);
await extension.awaitMessage("storage-local-set:done");
@ -637,13 +635,15 @@ add_task(
[host]: ["guid_1", "guid_2", "guid_3"],
},
},
changed: undefined,
deleted: undefined,
},
data,
"The change data from the storage actor's 'stores-update' event matches the changes made in the client."
);
info("Waiting for panel to edit some items");
storesUpdate = storageFront.once("stores-update");
storesUpdate = extensionStorage.once("single-store-update");
await extensionStorage.editItem({
host,
field: "value",
@ -655,11 +655,13 @@ add_task(
data = await storesUpdate;
Assert.deepEqual(
{
added: undefined,
changed: {
extensionStorage: {
[host]: ["guid_1"],
},
},
deleted: undefined,
},
data,
"The change data from the storage actor's 'stores-update' event matches the changes made in the client."
@ -679,13 +681,15 @@ add_task(
);
info("Waiting for panel to remove an item");
storesUpdate = storageFront.once("stores-update");
storesUpdate = extensionStorage.once("single-store-update");
await extensionStorage.removeItem(host, "guid_3");
info("Waiting for the storage actor to emit a 'stores-update' event");
data = await storesUpdate;
Assert.deepEqual(
{
added: undefined,
changed: undefined,
deleted: {
extensionStorage: {
[host]: ["guid_3"],
@ -709,14 +713,14 @@ add_task(
);
info("Waiting for panel to remove all items");
const storesCleared = storageFront.once("stores-cleared");
const storesCleared = extensionStorage.once("single-store-cleared");
await extensionStorage.removeAll(host);
info("Waiting for the storage actor to emit a 'stores-cleared' event");
data = await storesCleared;
Assert.deepEqual(
{
extensionStorage: {
clearedHostsOrPaths: {
[host]: [],
},
},
@ -733,7 +737,7 @@ add_task(
`The storage items in the extension match the items in the panel`
);
await shutdown(extension, target);
await shutdown(extension, commands);
}
);
@ -780,7 +784,7 @@ add_task(
await extension.awaitMessage("extension-origin");
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -829,7 +833,7 @@ add_task(
Services.prefs.setBoolPref(LEAVE_STORAGE_PREF, false);
Services.prefs.setBoolPref(LEAVE_UUID_PREF, false);
await shutdown(extension, target);
await shutdown(extension, commands);
}
);
@ -884,7 +888,7 @@ add_task(async function test_panel_live_reload_for_extension_without_bg_page() {
await contentPage.close();
info("Opening storage panel");
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -915,7 +919,7 @@ add_task(async function test_panel_live_reload_for_extension_without_bg_page() {
"Got the expected results on populated storage.local"
);
await shutdown(extension, target);
await shutdown(extension, commands);
});
/**
@ -953,7 +957,7 @@ add_task(
const host = await extension.awaitMessage("extension-origin");
info("Opening storage panel");
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -994,7 +998,7 @@ add_task(
"Got the expected results on populated storage.local"
);
await shutdown(extension, target);
await shutdown(extension, commands);
}
);
@ -1034,7 +1038,7 @@ add_task(
const host = await extension.awaitMessage("extension-origin");
info("Opening storage panel");
const { target, extensionStorage } = await openAddonStoragePanel(
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
@ -1052,7 +1056,7 @@ add_task(
"Got the expected results on populated storage.local"
);
await shutdown(extension, target);
await shutdown(extension, commands);
}
);

View file

@ -83,11 +83,9 @@ add_task(async function test_panel_live_reload() {
extension.sendMessage("storage-local-set", { a: 123 });
await extension.awaitMessage("storage-local-set:done");
const {
target,
extensionStorage,
storageFront,
} = await openAddonStoragePanel(extension.id);
const { commands, extensionStorage } = await openAddonStoragePanel(
extension.id
);
manifest = {
...manifest,
@ -99,11 +97,13 @@ add_task(async function test_panel_live_reload() {
// Wait for the storage front to receive an event for the storage panel refresh
// when the extension has been reloaded.
const promiseStoragePanelUpdated = new Promise(resolve => {
storageFront.on("stores-update", function updateListener(updates) {
extensionStorage.on("single-store-update", function updateListener(
updates
) {
info(`Got stores-update event: ${JSON.stringify(updates)}`);
const extStorageAdded = updates.added?.extensionStorage;
if (host in extStorageAdded && extStorageAdded[host].length) {
storageFront.off("stores-update", updateListener);
extensionStorage.off("single-store-update", updateListener);
resolve();
}
});
@ -137,5 +137,5 @@ add_task(async function test_panel_live_reload() {
"Got the expected results on populated storage.local"
);
await shutdown(extension, target);
await shutdown(extension, commands);
});

View file

@ -20,21 +20,6 @@ const {
CommandsFactory,
} = require("resource://devtools/shared/commands/commands-factory.js");
/**
* Set up the equivalent of an `about:debugging` toolbox for a given extension, minus
* the toolbox.
*
* @param {String} id - The id for the extension to be targeted by the toolbox.
* @return {Object} Resolves with the web extension actor front and target objects when
* the debugger has been connected to the extension.
*/
async function setupExtensionDebugging(id) {
const commands = await CommandsFactory.forAddon(id);
const target = await commands.descriptorFront.getTarget();
return { front: commands.descriptorFront, target };
}
exports.setupExtensionDebugging = setupExtensionDebugging;
/**
* Loads and starts up a test extension given the provided extension configuration.
*
@ -52,21 +37,32 @@ async function startupExtension(extConfig) {
exports.startupExtension = startupExtension;
/**
* Initializes the extensionStorage actor for a target extension. This is effectively
* Initializes the extensionStorage actor for a given extension. This is effectively
* what happens when the addon storage panel is opened in the browser.
*
* @param {String} - id, The addon id
* @return {Object} - Resolves with the web extension actor target and extensionStorage
* store objects when the panel has been opened.
* @return {Object} - Resolves with the DevTools "commands" objact and the extensionStorage
* resource/front.
*/
async function openAddonStoragePanel(id) {
const { target } = await setupExtensionDebugging(id);
const commands = await CommandsFactory.forAddon(id);
await commands.targetCommand.startListening();
const storageFront = await target.getFront("storage");
const stores = await storageFront.listStores();
const extensionStorage = stores.extensionStorage || null;
// Fetch the EXTENSION_STORAGE resource.
// Unfortunately, we can't use resourceCommand.waitForNextResource as it would destroy
// the actor by immediately unwatching for the resource type.
const extensionStorage = await new Promise(resolve => {
commands.resourceCommand.watchResources(
[commands.resourceCommand.TYPES.EXTENSION_STORAGE],
{
onAvailable(resources) {
resolve(resources[0]);
},
}
);
});
return { target, extensionStorage, storageFront };
return { commands, extensionStorage };
}
exports.openAddonStoragePanel = openAddonStoragePanel;
@ -170,11 +166,11 @@ exports.extensionScriptWithMessageListener = extensionScriptWithMessageListener;
* Shutdown procedure common to all tasks.
*
* @param {Object} extension - The test extension
* @param {Object} target - The web extension actor targeted by the DevTools client
* @param {Object} commands - The web extension commands used by the DevTools to interact with the backend
*/
async function shutdown(extension, target) {
if (target) {
await target.destroy();
async function shutdown(extension, commands) {
if (commands) {
await commands.destroy();
}
await extension.unload();
}

View file

@ -1372,6 +1372,11 @@ loader.lazyRequireGetter(
ResourceCommand.TYPES.COOKIE,
"resource://devtools/shared/commands/resource/transformers/storage-cookie.js"
);
loader.lazyRequireGetter(
ResourceTransformers,
ResourceCommand.TYPES.EXTENSION_STORAGE,
"resource://devtools/shared/commands/resource/transformers/storage-extension.js"
);
loader.lazyRequireGetter(
ResourceTransformers,
ResourceCommand.TYPES.INDEXED_DB,

View file

@ -8,6 +8,7 @@ DevToolsModules(
"network-events.js",
"storage-cache.js",
"storage-cookie.js",
"storage-extension.js",
"storage-indexed-db.js",
"storage-local-storage.js",
"storage-session-storage.js",

View file

@ -0,0 +1,26 @@
/* 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 {
TYPES: { EXTENSION_STORAGE },
} = require("resource://devtools/shared/commands/resource/resource-command.js");
const { Front, types } = require("resource://devtools/shared/protocol.js");
module.exports = function({ resource, watcherFront, targetFront }) {
if (!(resource instanceof Front) && watcherFront) {
const { innerWindowId } = resource;
// it's safe to instantiate the front now, so we do it.
resource = types.getType("extensionStorage").read(resource, targetFront);
resource.resourceType = EXTENSION_STORAGE;
resource.resourceId = `${EXTENSION_STORAGE}-${targetFront.browsingContextID}`;
resource.resourceKey = "extensionStorage";
resource.innerWindowId = innerWindowId;
}
return resource;
};

View file

@ -227,11 +227,12 @@ const Types = (exports.__TypesForTests = [
},
{
types: [
"Cache",
"cookies",
"localStorage",
"sessionStorage",
"Cache",
"extensionStorage",
"indexedDB",
"sessionStorage",
"storage",
],
spec: "devtools/shared/specs/storage",