forked from mirrors/gecko-dev
Bug 1477015 - Select storage.local backend on startup when the extension is not migrating its data. r=aswan,mixedpuppy
MozReview-Commit-ID: WzW2bFlYNg --HG-- extra : rebase_source : 048dbd36e6bf1bfc64d02e11bf26af1392071139
This commit is contained in:
parent
51abd4dba1
commit
f55e636331
6 changed files with 205 additions and 80 deletions
|
|
@ -44,6 +44,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
|
ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
|
||||||
ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.jsm",
|
ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.jsm",
|
||||||
ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
|
ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
|
||||||
|
ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.jsm",
|
||||||
ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.jsm",
|
ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.jsm",
|
||||||
FileSource: "resource://gre/modules/L10nRegistry.jsm",
|
FileSource: "resource://gre/modules/L10nRegistry.jsm",
|
||||||
L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
|
L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
|
||||||
|
|
@ -147,7 +148,6 @@ const LOGGER_ID_BASE = "addons.webextension.";
|
||||||
const UUID_MAP_PREF = "extensions.webextensions.uuids";
|
const UUID_MAP_PREF = "extensions.webextensions.uuids";
|
||||||
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
|
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
|
||||||
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
|
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
|
||||||
const IDB_MIGRATED_PREF_BRANCH = "extensions.webextensions.ExtensionStorageIDB.migrated";
|
|
||||||
|
|
||||||
const COMMENT_REGEXP = new RegExp(String.raw`
|
const COMMENT_REGEXP = new RegExp(String.raw`
|
||||||
^
|
^
|
||||||
|
|
@ -252,8 +252,7 @@ var UninstallObserver = {
|
||||||
});
|
});
|
||||||
Services.qms.clearStoragesForPrincipal(storagePrincipal);
|
Services.qms.clearStoragesForPrincipal(storagePrincipal);
|
||||||
|
|
||||||
// Clear the preference set for the extensions migrated to the IDBBackend.
|
ExtensionStorageIDB.clearMigratedExtensionPref(addon.id);
|
||||||
Services.prefs.clearUserPref(`${IDB_MIGRATED_PREF_BRANCH}.${addon.id}`);
|
|
||||||
|
|
||||||
// Clear localStorage created by the extension
|
// Clear localStorage created by the extension
|
||||||
let storage = Services.domStorageManager.getStorage(null, principal);
|
let storage = Services.domStorageManager.getStorage(null, principal);
|
||||||
|
|
@ -1773,6 +1772,23 @@ class Extension extends ExtensionData {
|
||||||
|
|
||||||
this.updatePermissions(this.startupReason);
|
this.updatePermissions(this.startupReason);
|
||||||
|
|
||||||
|
// Select the storage.local backend if it is already known,
|
||||||
|
// and start the data migration if needed.
|
||||||
|
if (this.hasPermission("storage")) {
|
||||||
|
if (!ExtensionStorageIDB.isBackendEnabled) {
|
||||||
|
this.setSharedData("storageIDBBackend", false);
|
||||||
|
} else if (ExtensionStorageIDB.isMigratedExtension(this)) {
|
||||||
|
this.setSharedData("storageIDBBackend", true);
|
||||||
|
this.setSharedData("storageIDBPrincipal", ExtensionStorageIDB.getStoragePrincipal(this));
|
||||||
|
} else {
|
||||||
|
// If the extension has to migrate backend, ensure that the data migration
|
||||||
|
// starts once Firefox is idle after the extension has been started.
|
||||||
|
this.once("ready", () => ChromeUtils.idleDispatch(() => {
|
||||||
|
ExtensionStorageIDB.selectBackend({extension: this});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The "startup" Management event sent on the extension instance itself
|
// The "startup" Management event sent on the extension instance itself
|
||||||
// is emitted just before the Management "startup" event,
|
// is emitted just before the Management "startup" event,
|
||||||
// and it is used to run code that needs to be executed before
|
// and it is used to run code that needs to be executed before
|
||||||
|
|
|
||||||
|
|
@ -373,8 +373,7 @@ async function migrateJSONFileData(extension, storagePrincipal) {
|
||||||
let dataMigrateCompleted = false;
|
let dataMigrateCompleted = false;
|
||||||
let hasOldData = false;
|
let hasOldData = false;
|
||||||
|
|
||||||
const isMigratedExtension = Services.prefs.getBoolPref(`${IDB_MIGRATED_PREF_BRANCH}.${extension.id}`, false);
|
if (ExtensionStorageIDB.isMigratedExtension(extension)) {
|
||||||
if (isMigratedExtension) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,7 +386,7 @@ async function migrateJSONFileData(extension, storagePrincipal) {
|
||||||
// there is no "going back": any data that has not been migrated will be still on disk
|
// there is no "going back": any data that has not been migrated will be still on disk
|
||||||
// but it is not going to be migrated anymore, it could be eventually used to allow
|
// but it is not going to be migrated anymore, it could be eventually used to allow
|
||||||
// a user to manually retrieve the old data file).
|
// a user to manually retrieve the old data file).
|
||||||
Services.prefs.setBoolPref(`${IDB_MIGRATED_PREF_BRANCH}.${extension.id}`, true);
|
ExtensionStorageIDB.setMigratedExtensionPref(extension, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -485,7 +484,7 @@ async function migrateJSONFileData(extension, storagePrincipal) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Services.prefs.setBoolPref(`${IDB_MIGRATED_PREF_BRANCH}.${extension.id}`, true);
|
ExtensionStorageIDB.setMigratedExtensionPref(extension, true);
|
||||||
|
|
||||||
DataMigrationTelemetry.recordResult({
|
DataMigrationTelemetry.recordResult({
|
||||||
backend: "IndexedDB",
|
backend: "IndexedDB",
|
||||||
|
|
@ -521,6 +520,18 @@ this.ExtensionStorageIDB = {
|
||||||
XPCOMUtils.defineLazyPreferenceGetter(this, "isBackendEnabled", BACKEND_ENABLED_PREF, false);
|
XPCOMUtils.defineLazyPreferenceGetter(this, "isBackendEnabled", BACKEND_ENABLED_PREF, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isMigratedExtension(extension) {
|
||||||
|
return Services.prefs.getBoolPref(`${IDB_MIGRATED_PREF_BRANCH}.${extension.id}`, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
setMigratedExtensionPref(extension, val) {
|
||||||
|
Services.prefs.setBoolPref(`${IDB_MIGRATED_PREF_BRANCH}.${extension.id}`, !!val);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearMigratedExtensionPref(extensionId) {
|
||||||
|
Services.prefs.clearUserPref(`${IDB_MIGRATED_PREF_BRANCH}.${extensionId}`);
|
||||||
|
},
|
||||||
|
|
||||||
getStoragePrincipal(extension) {
|
getStoragePrincipal(extension) {
|
||||||
return extension.createPrincipal(extension.baseURI, {
|
return extension.createPrincipal(extension.baseURI, {
|
||||||
userContextId: WEBEXT_STORAGE_USER_CONTEXT_ID,
|
userContextId: WEBEXT_STORAGE_USER_CONTEXT_ID,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ ChromeUtils.defineModuleGetter(this, "ExtensionStorage",
|
||||||
"resource://gre/modules/ExtensionStorage.jsm");
|
"resource://gre/modules/ExtensionStorage.jsm");
|
||||||
ChromeUtils.defineModuleGetter(this, "ExtensionStorageIDB",
|
ChromeUtils.defineModuleGetter(this, "ExtensionStorageIDB",
|
||||||
"resource://gre/modules/ExtensionStorageIDB.jsm");
|
"resource://gre/modules/ExtensionStorageIDB.jsm");
|
||||||
|
ChromeUtils.defineModuleGetter(this, "Services",
|
||||||
|
"resource://gre/modules/Services.jsm");
|
||||||
ChromeUtils.defineModuleGetter(this, "TelemetryStopwatch",
|
ChromeUtils.defineModuleGetter(this, "TelemetryStopwatch",
|
||||||
"resource://gre/modules/TelemetryStopwatch.jsm");
|
"resource://gre/modules/TelemetryStopwatch.jsm");
|
||||||
|
|
||||||
|
|
@ -55,7 +57,7 @@ this.storage = class extends ExtensionAPI {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getLocalIDBBackend(context, {hasParentListeners, serialize, storagePrincipal}) {
|
getLocalIDBBackend(context, {fireOnChanged, serialize, storagePrincipal}) {
|
||||||
let dbPromise;
|
let dbPromise;
|
||||||
async function getDB() {
|
async function getDB() {
|
||||||
if (dbPromise) {
|
if (dbPromise) {
|
||||||
|
|
@ -86,14 +88,8 @@ this.storage = class extends ExtensionAPI {
|
||||||
serialize: ExtensionStorage.serialize,
|
serialize: ExtensionStorage.serialize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!changes) {
|
if (changes) {
|
||||||
return;
|
fireOnChanged(changes);
|
||||||
}
|
|
||||||
|
|
||||||
const hasListeners = await hasParentListeners();
|
|
||||||
if (hasListeners) {
|
|
||||||
await context.childManager.callParentAsyncFunction(
|
|
||||||
"storage.local.IDBBackend.fireOnChanged", [changes]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -101,34 +97,23 @@ this.storage = class extends ExtensionAPI {
|
||||||
const db = await getDB();
|
const db = await getDB();
|
||||||
const changes = await db.remove(keys);
|
const changes = await db.remove(keys);
|
||||||
|
|
||||||
if (!changes) {
|
if (changes) {
|
||||||
return;
|
fireOnChanged(changes);
|
||||||
}
|
|
||||||
|
|
||||||
const hasListeners = await hasParentListeners();
|
|
||||||
if (hasListeners) {
|
|
||||||
await context.childManager.callParentAsyncFunction(
|
|
||||||
"storage.local.IDBBackend.fireOnChanged", [changes]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async clear() {
|
async clear() {
|
||||||
const db = await getDB();
|
const db = await getDB();
|
||||||
const changes = await db.clear(context.extension);
|
const changes = await db.clear(context.extension);
|
||||||
|
|
||||||
if (!changes) {
|
if (changes) {
|
||||||
return;
|
fireOnChanged(changes);
|
||||||
}
|
|
||||||
|
|
||||||
const hasListeners = await hasParentListeners();
|
|
||||||
if (hasListeners) {
|
|
||||||
await context.childManager.callParentAsyncFunction(
|
|
||||||
"storage.local.IDBBackend.fireOnChanged", [changes]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getAPI(context) {
|
getAPI(context) {
|
||||||
|
const {extension} = context;
|
||||||
const serialize = ExtensionStorage.serializeForContext.bind(null, context);
|
const serialize = ExtensionStorage.serializeForContext.bind(null, context);
|
||||||
const deserialize = ExtensionStorage.deserializeForContext.bind(null, context);
|
const deserialize = ExtensionStorage.deserializeForContext.bind(null, context);
|
||||||
|
|
||||||
|
|
@ -152,9 +137,17 @@ this.storage = class extends ExtensionAPI {
|
||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect the actual storage.local enabled backend for the extension (as soon as the
|
function fireOnChanged(changes) {
|
||||||
// storage.local API has been accessed for the first time).
|
// This call is used (by the storage.local API methods for the IndexedDB backend) to fire a storage.onChanged event,
|
||||||
let promiseStorageLocalBackend;
|
// it uses the underlying message manager since the child context (or its ProxyContentParent counterpart
|
||||||
|
// running in the main process) may be gone by the time we call this, and so we can't use the childManager
|
||||||
|
// abstractions (e.g. callParentAsyncFunction or callParentFunctionNoReturn).
|
||||||
|
Services.cpmm.sendAsyncMessage(`Extension:StorageLocalOnChanged:${extension.uuid}`, changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the selected backend for the extension is not known yet, we have to lazily detect it
|
||||||
|
// by asking to the main process (as soon as the storage.local API has been accessed for
|
||||||
|
// the first time).
|
||||||
const getStorageLocalBackend = async () => {
|
const getStorageLocalBackend = async () => {
|
||||||
const {
|
const {
|
||||||
backendEnabled,
|
backendEnabled,
|
||||||
|
|
@ -167,33 +160,58 @@ this.storage = class extends ExtensionAPI {
|
||||||
|
|
||||||
return this.getLocalIDBBackend(context, {
|
return this.getLocalIDBBackend(context, {
|
||||||
storagePrincipal,
|
storagePrincipal,
|
||||||
hasParentListeners() {
|
fireOnChanged,
|
||||||
// We spare a good amount of memory if there are no listeners around
|
|
||||||
// (e.g. because they have never been subscribed or they have been removed
|
|
||||||
// in the meantime).
|
|
||||||
return context.childManager.callParentAsyncFunction(
|
|
||||||
"storage.local.IDBBackend.hasListeners", []);
|
|
||||||
},
|
|
||||||
serialize,
|
serialize,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Synchronously select the backend if it is already known.
|
||||||
|
let selectedBackend;
|
||||||
|
|
||||||
|
const useStorageIDBBackend = extension.getSharedData("storageIDBBackend");
|
||||||
|
if (useStorageIDBBackend === false) {
|
||||||
|
selectedBackend = this.getLocalFileBackend(context, {deserialize, serialize});
|
||||||
|
} else if (useStorageIDBBackend === true) {
|
||||||
|
selectedBackend = this.getLocalIDBBackend(context, {
|
||||||
|
storagePrincipal: extension.getSharedData("storageIDBPrincipal"),
|
||||||
|
fireOnChanged,
|
||||||
|
serialize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let promiseStorageLocalBackend;
|
||||||
|
|
||||||
// Generate the backend-agnostic local API wrapped methods.
|
// Generate the backend-agnostic local API wrapped methods.
|
||||||
const local = {};
|
const local = {};
|
||||||
for (let method of ["get", "set", "remove", "clear"]) {
|
for (let method of ["get", "set", "remove", "clear"]) {
|
||||||
local[method] = async function(...args) {
|
local[method] = async function(...args) {
|
||||||
try {
|
try {
|
||||||
if (!promiseStorageLocalBackend) {
|
// Discover the selected backend if it is not known yet.
|
||||||
promiseStorageLocalBackend = getStorageLocalBackend();
|
if (!selectedBackend) {
|
||||||
|
if (!promiseStorageLocalBackend) {
|
||||||
|
promiseStorageLocalBackend = getStorageLocalBackend().catch(err => {
|
||||||
|
// Clear the cached promise if it has been rejected.
|
||||||
|
promiseStorageLocalBackend = null;
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the storage.local method is not 'get' (which doesn't change any of the stored data),
|
||||||
|
// fall back to call the method in the parent process, so that it can be completed even
|
||||||
|
// if this context has been destroyed in the meantime.
|
||||||
|
if (method !== "get") {
|
||||||
|
// Let the outer try to catch rejections returned by the backend methods.
|
||||||
|
const result = await context.childManager.callParentAsyncFunction(
|
||||||
|
"storage.local.callMethodInParentProcess", [method, args]);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the selected backend and cache it for the next API calls from this context.
|
||||||
|
selectedBackend = await promiseStorageLocalBackend;
|
||||||
}
|
}
|
||||||
const backend = await promiseStorageLocalBackend.catch(err => {
|
|
||||||
// Clear the cached promise if it has been rejected.
|
|
||||||
promiseStorageLocalBackend = null;
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Let the outer try to catch rejections returned by the backend methods.
|
// Let the outer try to catch rejections returned by the backend methods.
|
||||||
const result = await backend[method](...args);
|
const result = await selectedBackend[method](...args);
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ensure that the error we throw is converted into an ExtensionError
|
// Ensure that the error we throw is converted into an ExtensionError
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,52 @@ const lookupManagedStorage = async (extensionId, context) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.storage = class extends ExtensionAPI {
|
this.storage = class extends ExtensionAPI {
|
||||||
|
constructor(extension) {
|
||||||
|
super(extension);
|
||||||
|
|
||||||
|
const messageName = `Extension:StorageLocalOnChanged:${extension.uuid}`;
|
||||||
|
Services.ppmm.addMessageListener(messageName, this);
|
||||||
|
this.clearStorageChangedListener = () => {
|
||||||
|
Services.ppmm.removeMessageListener(messageName, this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onShutdown() {
|
||||||
|
const {clearStorageChangedListener} = this;
|
||||||
|
this.clearStorageChangedListener = null;
|
||||||
|
|
||||||
|
if (clearStorageChangedListener) {
|
||||||
|
clearStorageChangedListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveMessage({name, data}) {
|
||||||
|
if (name !== `Extension:StorageLocalOnChanged:${this.extension.uuid}`) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionStorageIDB.notifyListeners(this.extension.id, data);
|
||||||
|
}
|
||||||
|
|
||||||
getAPI(context) {
|
getAPI(context) {
|
||||||
let {extension} = context;
|
let {extension} = context;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
storage: {
|
storage: {
|
||||||
local: {
|
local: {
|
||||||
|
async callMethodInParentProcess(method, args) {
|
||||||
|
const res = await ExtensionStorageIDB.selectBackend({extension});
|
||||||
|
if (!res.backendEnabled) {
|
||||||
|
return ExtensionStorage[method](extension.id, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await ExtensionStorageIDB.open(res.storagePrincipal.deserialize(this));
|
||||||
|
const changes = await db[method](...args);
|
||||||
|
if (changes) {
|
||||||
|
ExtensionStorageIDB.notifyListeners(extension.id, changes);
|
||||||
|
}
|
||||||
|
return changes;
|
||||||
|
},
|
||||||
// Private storage.local JSONFile backend methods (used internally by the child
|
// Private storage.local JSONFile backend methods (used internally by the child
|
||||||
// ext-storage.js module).
|
// ext-storage.js module).
|
||||||
JSONFileBackend: {
|
JSONFileBackend: {
|
||||||
|
|
@ -62,15 +102,6 @@ this.storage = class extends ExtensionAPI {
|
||||||
selectBackend() {
|
selectBackend() {
|
||||||
return ExtensionStorageIDB.selectBackend(context);
|
return ExtensionStorageIDB.selectBackend(context);
|
||||||
},
|
},
|
||||||
hasListeners() {
|
|
||||||
return ExtensionStorageIDB.hasListeners(extension.id);
|
|
||||||
},
|
|
||||||
fireOnChanged(changes) {
|
|
||||||
ExtensionStorageIDB.notifyListeners(extension.id, changes);
|
|
||||||
},
|
|
||||||
onceDataMigrated() {
|
|
||||||
return ExtensionStorageIDB.onceDataMigrated(context);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,15 @@ add_task(async function test_storage_local_idb_backend_from_tab() {
|
||||||
async function test_storage_local_call_from_destroying_context() {
|
async function test_storage_local_call_from_destroying_context() {
|
||||||
let extension = ExtensionTestUtils.loadExtension({
|
let extension = ExtensionTestUtils.loadExtension({
|
||||||
async background() {
|
async background() {
|
||||||
|
let numberOfChanges = 0;
|
||||||
|
browser.storage.onChanged.addListener((changes, areaName) => {
|
||||||
|
if (areaName !== "local") {
|
||||||
|
browser.test.fail(`Received unexpected storage changes for "${areaName}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
numberOfChanges++;
|
||||||
|
});
|
||||||
|
|
||||||
browser.test.onMessage.addListener(async ({msg, values}) => {
|
browser.test.onMessage.addListener(async ({msg, values}) => {
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
case "storage-set": {
|
case "storage-set": {
|
||||||
|
|
@ -116,6 +125,10 @@ async function test_storage_local_call_from_destroying_context() {
|
||||||
browser.test.sendMessage("storage-get:done", res);
|
browser.test.sendMessage("storage-get:done", res);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "storage-changes": {
|
||||||
|
browser.test.sendMessage("storage-changes-count", numberOfChanges);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
browser.test.fail(`Received unexpected message: ${msg}`);
|
browser.test.fail(`Received unexpected message: ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
@ -135,10 +148,8 @@ async function test_storage_local_call_from_destroying_context() {
|
||||||
"tab.js"() {
|
"tab.js"() {
|
||||||
browser.test.log("Extension tab - calling storage.local API method");
|
browser.test.log("Extension tab - calling storage.local API method");
|
||||||
// Call the storage.local API from a tab that is going to be quickly closed.
|
// Call the storage.local API from a tab that is going to be quickly closed.
|
||||||
browser.storage.local.get({}).then(() => {
|
browser.storage.local.set({
|
||||||
// This call should never be reached (because the tab should have been
|
"test-key-from-destroying-context": "testvalue2",
|
||||||
// destroyed in the meantime).
|
|
||||||
browser.test.fail("Extension tab - Unexpected storage.local promise resolved");
|
|
||||||
});
|
});
|
||||||
// Navigate away from the extension page, so that the storage.local API call will be unable
|
// Navigate away from the extension page, so that the storage.local API call will be unable
|
||||||
// to send the call to the caller context (because it has been destroyed in the meantime).
|
// to send the call to the caller context (because it has been destroyed in the meantime).
|
||||||
|
|
@ -154,17 +165,25 @@ async function test_storage_local_call_from_destroying_context() {
|
||||||
const url = await extension.awaitMessage("ext-page-url");
|
const url = await extension.awaitMessage("ext-page-url");
|
||||||
|
|
||||||
let contentPage = await ExtensionTestUtils.loadContentPage(url, {extension});
|
let contentPage = await ExtensionTestUtils.loadContentPage(url, {extension});
|
||||||
let expectedData = {"test-key": "test-value"};
|
let expectedBackgroundPageData = {"test-key-from-background-page": "test-value"};
|
||||||
|
let expectedTabData = {"test-key-from-destroying-context": "testvalue2"};
|
||||||
|
|
||||||
info("Call storage.local.set from the background page and wait it to be completed");
|
info("Call storage.local.set from the background page and wait it to be completed");
|
||||||
extension.sendMessage({msg: "storage-set", values: expectedData});
|
extension.sendMessage({msg: "storage-set", values: expectedBackgroundPageData});
|
||||||
await extension.awaitMessage("storage-set:done");
|
await extension.awaitMessage("storage-set:done");
|
||||||
|
|
||||||
info("Call storage.local.get from the background page and wait it to be completed");
|
info("Call storage.local.get from the background page and wait it to be completed");
|
||||||
extension.sendMessage({msg: "storage-get"});
|
extension.sendMessage({msg: "storage-get"});
|
||||||
let res = await extension.awaitMessage("storage-get:done");
|
let res = await extension.awaitMessage("storage-get:done");
|
||||||
|
|
||||||
Assert.deepEqual(res, expectedData, "Got the expected data set in the storage.local backend");
|
Assert.deepEqual(res, {
|
||||||
|
...expectedBackgroundPageData,
|
||||||
|
...expectedTabData,
|
||||||
|
}, "Got the expected data set in the storage.local backend");
|
||||||
|
|
||||||
|
extension.sendMessage({msg: "storage-changes"});
|
||||||
|
equal(await extension.awaitMessage("storage-changes-count"), 2,
|
||||||
|
"Got the expected number of storage.onChanged event received");
|
||||||
|
|
||||||
contentPage.close();
|
contentPage.close();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ const HISTOGRAM_IDB_IDS = [
|
||||||
|
|
||||||
const HISTOGRAM_IDS = [].concat(HISTOGRAM_JSON_IDS, HISTOGRAM_IDB_IDS);
|
const HISTOGRAM_IDS = [].concat(HISTOGRAM_JSON_IDS, HISTOGRAM_IDB_IDS);
|
||||||
|
|
||||||
|
const EXTENSION_ID1 = "@test-extension1";
|
||||||
|
const EXTENSION_ID2 = "@test-extension2";
|
||||||
|
|
||||||
async function test_telemetry_background() {
|
async function test_telemetry_background() {
|
||||||
const expectedEmptyHistograms = ExtensionStorageIDB.isBackendEnabled ?
|
const expectedEmptyHistograms = ExtensionStorageIDB.isBackendEnabled ?
|
||||||
HISTOGRAM_JSON_IDS : HISTOGRAM_IDB_IDS;
|
HISTOGRAM_JSON_IDS : HISTOGRAM_IDB_IDS;
|
||||||
|
|
@ -32,16 +35,17 @@ async function test_telemetry_background() {
|
||||||
browser.runtime.sendMessage("contentDone");
|
browser.runtime.sendMessage("contentDone");
|
||||||
}
|
}
|
||||||
|
|
||||||
let extInfo = {
|
let baseManifest = {
|
||||||
manifest: {
|
permissions: ["storage"],
|
||||||
permissions: ["storage"],
|
content_scripts: [
|
||||||
content_scripts: [
|
{
|
||||||
{
|
"matches": ["http://*/*/file_sample.html"],
|
||||||
"matches": ["http://*/*/file_sample.html"],
|
"js": ["content_script.js"],
|
||||||
"js": ["content_script.js"],
|
},
|
||||||
},
|
],
|
||||||
],
|
};
|
||||||
},
|
|
||||||
|
let baseExtInfo = {
|
||||||
async background() {
|
async background() {
|
||||||
browser.runtime.onMessage.addListener(msg => {
|
browser.runtime.onMessage.addListener(msg => {
|
||||||
browser.test.sendMessage(msg);
|
browser.test.sendMessage(msg);
|
||||||
|
|
@ -56,8 +60,27 @@ async function test_telemetry_background() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let extension1 = ExtensionTestUtils.loadExtension(extInfo);
|
let extInfo1 = {
|
||||||
let extension2 = ExtensionTestUtils.loadExtension(extInfo);
|
...baseExtInfo,
|
||||||
|
manifest: {
|
||||||
|
...baseManifest,
|
||||||
|
applications: {
|
||||||
|
gecko: {id: EXTENSION_ID1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let extInfo2 = {
|
||||||
|
...baseExtInfo,
|
||||||
|
manifest: {
|
||||||
|
...baseManifest,
|
||||||
|
applications: {
|
||||||
|
gecko: {id: EXTENSION_ID2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let extension1 = ExtensionTestUtils.loadExtension(extInfo1);
|
||||||
|
let extension2 = ExtensionTestUtils.loadExtension(extInfo2);
|
||||||
|
|
||||||
clearHistograms();
|
clearHistograms();
|
||||||
|
|
||||||
|
|
@ -129,6 +152,13 @@ add_task(function test_telemetry_background_file_backend() {
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_telemetry_background_idb_backend() {
|
add_task(function test_telemetry_background_idb_backend() {
|
||||||
return runWithPrefs([[ExtensionStorageIDB.BACKEND_ENABLED_PREF, true]],
|
return runWithPrefs([
|
||||||
test_telemetry_background);
|
[ExtensionStorageIDB.BACKEND_ENABLED_PREF, true],
|
||||||
|
// Set the migrated preference for the two test extension, because the
|
||||||
|
// first storage.local call fallbacks to run in the parent process when we
|
||||||
|
// don't know which is the selected backend during the extension startup
|
||||||
|
// and so we can't choose the telemetry histogram to use.
|
||||||
|
[`${ExtensionStorageIDB.IDB_MIGRATED_PREF_BRANCH}.${EXTENSION_ID1}`, true],
|
||||||
|
[`${ExtensionStorageIDB.IDB_MIGRATED_PREF_BRANCH}.${EXTENSION_ID2}`, true],
|
||||||
|
], test_telemetry_background);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue