Bug 1541496 - Ensure storage.sync does sync the data for previously synced extensions without an active extension context. r=glasserc

Differential Revision: https://phabricator.services.mozilla.com/D37413

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Luca Greco 2019-07-11 14:30:20 +00:00
Родитель b41031dd54
Коммит 55b47893cf
6 изменённых файлов: 197 добавлений и 20 удалений

Просмотреть файл

@ -46,6 +46,7 @@ add_task(async function test_missing_crypto_collection() {
];
// Disable addon sync because AddonManager won't be initialized here.
await Service.engineManager.unregister("addons");
await Service.engineManager.unregister("extension-storage");
for (let coll of collections) {
handlers["/1.1/johndoe/storage/" + coll] = johnU(

Просмотреть файл

@ -40,7 +40,9 @@ add_task(async function test_locally_changed_keys() {
Service.clusterURL = Service.identity._token.endpoint;
await Service.engineManager.register(HistoryEngine);
// Disable addon sync because AddonManager won't be initialized here.
await Service.engineManager.unregister("addons");
await Service.engineManager.unregister("extension-storage");
async function corrupt_local_keys() {
Service.collectionKeys._default.keyPair = [

Просмотреть файл

@ -26,7 +26,9 @@ async function cleanup(engine, server) {
}
add_task(async function setup() {
await Service.engineManager.unregister("addons"); // To silence errors.
// Disable addon sync because AddonManager won't be initialized here.
await Service.engineManager.unregister("addons");
await Service.engineManager.unregister("extension-storage");
});
add_task(async function test_ignored_fields() {

Просмотреть файл

@ -91,6 +91,7 @@ async function cleanAndGo(engine, server) {
add_task(async function setup() {
// Avoid addon manager complaining about not being initialized
await Service.engineManager.unregister("addons");
await Service.engineManager.unregister("extension-storage");
});
add_task(async function test_basic() {

Просмотреть файл

@ -51,6 +51,7 @@ const { ExtensionUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
AddonManager: "resource://gre/modules/AddonManager.jsm",
BulkKeyBundle: "resource://services-sync/keys.js",
CollectionKeyManager: "resource://services-sync/record.js",
CommonUtils: "resource://services-common/utils.js",
@ -781,8 +782,41 @@ class ExtensionStorageSync {
this.listeners = new WeakMap();
}
/**
* Get a set of extensions to sync (including the ones with an
* active extension context that used the storage.sync API and
* the extensions that are enabled and have been synced before).
*
* @returns {Promise<Set<Extension>>}
* A promise which resolves to the set of the extensions to sync.
*/
async getExtensions() {
// Start from the set of the extensions with an active
// context that used the storage.sync APIs.
const extensions = new Set(extensionContexts.keys());
const allEnabledExtensions = await AddonManager.getAddonsByTypes([
"extension",
]);
// Get the existing extension collections salts.
const keysRecord = await this.cryptoCollection.getKeyRingRecord();
// Add any enabled extensions that have been synced before.
for (const addon of allEnabledExtensions) {
if (this.hasSaltsFor(keysRecord, [addon.id])) {
const policy = WebExtensionPolicy.getByID(addon.id);
if (policy && policy.extension) {
extensions.add(policy.extension);
}
}
}
return extensions;
}
async syncAll() {
const extensions = extensionContexts.keys();
const extensions = await this.getExtensions();
const extIds = Array.from(extensions, extension => extension.id);
log.debug(`Syncing extension settings for ${JSON.stringify(extIds)}`);
if (extIds.length == 0) {
@ -791,7 +825,7 @@ class ExtensionStorageSync {
}
await this.ensureCanSync(extIds);
await this.checkSyncKeyRing();
const promises = Array.from(extensionContexts.keys(), extension => {
const promises = Array.from(extensions, extension => {
return openCollection(this.cryptoCollection, extension).then(coll => {
return this.sync(extension, coll);
});
@ -1296,11 +1330,11 @@ class ExtensionStorageSync {
/* Wipe local data for all collections without causing the changes to be synced */
async clearAll() {
const extensions = extensionContexts.keys();
const extensions = await this.getExtensions();
const extIds = Array.from(extensions, extension => extension.id);
log.debug(`Clearing extension data for ${JSON.stringify(extIds)}`);
if (extIds.length) {
const promises = Array.from(extensionContexts.keys(), extension => {
const promises = Array.from(extensions, extension => {
return openCollection(this.cryptoCollection, extension).then(coll => {
return coll.clear();
});

Просмотреть файл

@ -10,6 +10,7 @@ const { CommonUtils } = ChromeUtils.import(
"resource://services-common/utils.js"
);
const {
cleanUpForContext,
CollectionKeyEncryptionRemoteTransformer,
CryptoCollection,
ExtensionStorageSync,
@ -22,6 +23,12 @@ const { BulkKeyBundle } = ChromeUtils.import(
);
const { Utils } = ChromeUtils.import("resource://services-sync/util.js");
const { createAppInfo, promiseStartupManager } = AddonTestUtils;
AddonTestUtils.init(this);
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "69");
/* globals BulkKeyBundle, CommonUtils, EncryptionRemoteTransformer */
/* globals Utils */
@ -637,6 +644,10 @@ function uuid() {
return uuidgen.generateUUID().toString();
}
add_task(async function test_setup() {
await promiseStartupManager();
});
add_task(async function test_key_to_id() {
equal(keyToId("foo"), "key-foo");
equal(keyToId("my-new-key"), "key-my_2D_new_2D_key");
@ -684,46 +695,92 @@ add_task(async function test_extension_id_to_collection_id() {
});
add_task(async function ensureCanSync_clearAll() {
const extensionId = uuid();
const extension = { id: extensionId };
// A test extension that will not have any active context around
// but it is returned from a call to AddonManager.getExtensionsByType.
const extensionId = "test-wipe-on-enabled-and-synced@mochi.test";
const testExtension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
permissions: ["storage"],
applications: { gecko: { id: extensionId } },
},
});
await testExtension.startup();
// Retrieve the Extension class instance from the test extension.
const { extension } = testExtension;
// Another test extension that will have an active extension context.
const extensionId2 = "test-wipe-on-active-context@mochi.test";
const extension2 = { id: extensionId2 };
await withContextAndServer(async function(context, server) {
await withSignedInUser(loggedInUser, async function(
extensionStorageSync,
fxaService
) {
async function assertSetAndGetData(extension, data) {
await extensionStorageSync.set(extension, data, context);
let storedData = await extensionStorageSync.get(
extension,
Object.keys(data),
context
);
const extId = extensionId;
deepEqual(storedData, data, `${extId} should get back the data we set`);
}
async function assertDataCleared(extension, keys) {
const storedData = await extensionStorageSync.get(
extension,
keys,
context
);
deepEqual(storedData, {}, `${extension.id} should have lost the data`);
}
server.installCollection("storage-sync-crypto");
server.etag = 1000;
let newKeys = await extensionStorageSync.ensureCanSync([extensionId]);
let newKeys = await extensionStorageSync.ensureCanSync([
extensionId,
extensionId2,
]);
ok(
newKeys.hasKeysFor([extensionId]),
`key isn't present for ${extensionId}`
);
ok(
newKeys.hasKeysFor([extensionId2]),
`key isn't present for ${extensionId2}`
);
let posts = server.getPosts();
equal(posts.length, 1);
const post = posts[0];
assertPostedNewRecord(post);
assertPostedNewRecord(posts[0]);
// Set data for an extension and sync.
await extensionStorageSync.set(extension, { "my-key": 5 }, context);
let keyValue = await extensionStorageSync.get(
extension,
["my-key"],
context
);
equal(keyValue["my-key"], 5, "should get back the data we set");
await assertSetAndGetData(extension, { "my-key": 1 });
await assertSetAndGetData(extension2, { "my-key": 2 });
// Call cleanup for the first extension, to double check it has
// been wiped out even without an active extension context.
cleanUpForContext(extension, context);
// clear everything.
await extensionStorageSync.clearAll();
keyValue = await extensionStorageSync.get(extension, ["my-key"], context);
deepEqual(keyValue, {}, "should have lost the data");
// Assert that the data is gone for both the extensions.
await assertDataCleared(extension, ["my-key"]);
await assertDataCleared(extension2, ["my-key"]);
// should have been no posts caused by the clear.
posts = server.getPosts();
equal(posts.length, 1);
});
});
await testExtension.unload();
});
add_task(async function ensureCanSync_posts_new_keys() {
@ -1615,6 +1672,86 @@ add_task(async function test_storage_sync_pulls_changes() {
});
});
// Tests that an enabled extension which have been synced before it is going
// to be synced on ExtensionStorageSync.syncAll even if there is no active
// context that is currently using the API.
add_task(async function test_storage_sync_on_no_active_context() {
const extensionId = "sync@mochi.test";
const extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
permissions: ["storage"],
applications: { gecko: { id: extensionId } },
},
files: {
"ext-page.html": `<!DOCTYPE html>
<html>
<head>
<script src="ext-page.js"></script>
</head>
</html>
`,
"ext-page.js": function() {
const { browser } = this;
browser.test.onMessage.addListener(async msg => {
if (msg === "get-sync-data") {
browser.test.sendMessage(
"get-sync-data:done",
await browser.storage.sync.get(["remote-key"])
);
}
});
},
},
});
await extension.startup();
await withServer(async server => {
await withSignedInUser(loggedInUser, async function(
extensionStorageSync,
fxaService
) {
const cryptoCollection = new CryptoCollection(fxaService);
let transformer = new CollectionKeyEncryptionRemoteTransformer(
cryptoCollection,
extensionId
);
server.installCollection("storage-sync-crypto");
await extensionStorageSync.ensureCanSync([extensionId]);
const collectionId = await cryptoCollection.extensionIdToCollectionId(
extensionId
);
await server.encryptAndAddRecord(transformer, {
collectionId,
data: {
id: "key-remote_2D_key",
key: "remote-key",
data: 6,
},
predicate: appearsAt(850),
});
server.etag = 1000;
await extensionStorageSync.syncAll();
});
});
const extPage = await ExtensionTestUtils.loadContentPage(
`moz-extension://${extension.uuid}/ext-page.html`,
{ extension }
);
await extension.sendMessage("get-sync-data");
const res = await extension.awaitMessage("get-sync-data:done");
Assert.deepEqual(res, { "remote-key": 6 }, "Got the expected sync data");
await extPage.close();
await extension.unload();
});
add_task(async function test_storage_sync_pushes_changes() {
// FIXME: This test relies on the fact that previous tests pushed
// keys and salts for the default extension ID