summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js')
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js274
1 files changed, 274 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js b/toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js
new file mode 100644
index 0000000000..ef55ed37e8
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js
@@ -0,0 +1,274 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const NS_ERROR_DOM_QUOTA_EXCEEDED_ERR = 0x80530016;
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "StorageSyncService",
+ "@mozilla.org/extensions/storage/sync;1",
+ "nsIInterfaceRequestor"
+);
+
+function promisify(func, ...params) {
+ return new Promise((resolve, reject) => {
+ let changes = [];
+ func(...params, {
+ QueryInterface: ChromeUtils.generateQI([
+ "mozIExtensionStorageListener",
+ "mozIExtensionStorageCallback",
+ "mozIBridgedSyncEngineCallback",
+ "mozIBridgedSyncEngineApplyCallback",
+ ]),
+ onChanged(extId, json) {
+ changes.push({ extId, changes: JSON.parse(json) });
+ },
+ handleSuccess(value) {
+ resolve({
+ changes,
+ value: typeof value == "string" ? JSON.parse(value) : value,
+ });
+ },
+ handleError(code, message) {
+ reject(Components.Exception(message, code));
+ },
+ });
+ });
+}
+
+add_task(async function setup_storage_sync() {
+ // So that we can write to the profile directory.
+ do_get_profile();
+});
+
+add_task(async function test_storage_sync_service() {
+ const service = StorageSyncService.getInterface(Ci.mozIExtensionStorageArea);
+ {
+ let { changes, value } = await promisify(
+ service.set,
+ "ext-1",
+ JSON.stringify({
+ hi: "hello! 💖",
+ bye: "adiós",
+ })
+ );
+ deepEqual(
+ changes,
+ [
+ {
+ extId: "ext-1",
+ changes: {
+ hi: {
+ newValue: "hello! 💖",
+ },
+ bye: {
+ newValue: "adiós",
+ },
+ },
+ },
+ ],
+ "`set` should notify listeners about changes"
+ );
+ ok(!value, "`set` should not return a value");
+ }
+
+ {
+ let { changes, value } = await promisify(
+ service.get,
+ "ext-1",
+ JSON.stringify(["hi"])
+ );
+ deepEqual(changes, [], "`get` should not notify listeners");
+ deepEqual(
+ value,
+ {
+ hi: "hello! 💖",
+ },
+ "`get` with key should return value"
+ );
+
+ let { value: allValues } = await promisify(service.get, "ext-1", "null");
+ deepEqual(
+ allValues,
+ {
+ hi: "hello! 💖",
+ bye: "adiós",
+ },
+ "`get` without a key should return all values"
+ );
+ }
+
+ {
+ await promisify(
+ service.set,
+ "ext-2",
+ JSON.stringify({
+ hi: "hola! 👋",
+ })
+ );
+ await promisify(service.clear, "ext-1");
+ let { value: allValues } = await promisify(service.get, "ext-1", "null");
+ deepEqual(allValues, {}, "clear removed ext-1");
+
+ let { value: allValues2 } = await promisify(service.get, "ext-2", "null");
+ deepEqual(allValues2, { hi: "hola! 👋" }, "clear didn't remove ext-2");
+ // We need to clear data for ext-2 too, so later tests don't fail due to
+ // this data.
+ await promisify(service.clear, "ext-2");
+ }
+});
+
+add_task(async function test_storage_sync_bridged_engine() {
+ const area = StorageSyncService.getInterface(Ci.mozIExtensionStorageArea);
+ const engine = StorageSyncService.getInterface(Ci.mozIBridgedSyncEngine);
+
+ info("Add some local items");
+ await promisify(area.set, "ext-1", JSON.stringify({ a: "abc" }));
+ await promisify(area.set, "ext-2", JSON.stringify({ b: "xyz" }));
+
+ info("Start a sync");
+ await promisify(engine.syncStarted);
+
+ info("Store some incoming synced items");
+ let incomingEnvelopesAsJSON = [
+ {
+ id: "guidAAA",
+ modified: 0.1,
+ payload: JSON.stringify({
+ extId: "ext-2",
+ data: JSON.stringify({
+ c: 1234,
+ }),
+ }),
+ },
+ {
+ id: "guidBBB",
+ modified: 0.1,
+ payload: JSON.stringify({
+ extId: "ext-3",
+ data: JSON.stringify({
+ d: "new! ✨",
+ }),
+ }),
+ },
+ ].map(e => JSON.stringify(e));
+ await promisify(area.storeIncoming, incomingEnvelopesAsJSON);
+
+ info("Merge");
+ // Three levels of JSON wrapping: each outgoing envelope, the cleartext in
+ // each envelope, and the extension storage data in each cleartext payload.
+ let { value: outgoingEnvelopesAsJSON } = await promisify(area.apply);
+ let outgoingEnvelopes = outgoingEnvelopesAsJSON.map(json => JSON.parse(json));
+ let parsedCleartexts = outgoingEnvelopes.map(e => JSON.parse(e.payload));
+ let parsedData = parsedCleartexts.map(c => JSON.parse(c.data));
+
+ let { changes } = await promisify(
+ area.QueryInterface(Ci.mozISyncedExtensionStorageArea)
+ .fetchPendingSyncChanges
+ );
+ deepEqual(
+ changes,
+ [
+ {
+ extId: "ext-2",
+ changes: {
+ c: { newValue: 1234 },
+ },
+ },
+ {
+ extId: "ext-3",
+ changes: {
+ d: { newValue: "new! ✨" },
+ },
+ },
+ ],
+ "Should return pending synced changes for observers"
+ );
+
+ // ext-1 doesn't exist remotely yet, so the Rust sync layer will generate
+ // a GUID for it. We don't know what it is, so we find it by the extension
+ // ID.
+ let ext1Index = parsedCleartexts.findIndex(c => c.extId == "ext-1");
+ greater(ext1Index, -1, "Should find envelope for ext-1");
+ let ext1Guid = outgoingEnvelopes[ext1Index].id;
+
+ // ext-2 has a remote GUID that we set in the test above.
+ let ext2Index = outgoingEnvelopes.findIndex(c => c.id == "guidAAA");
+ greater(ext2Index, -1, "Should find envelope for ext-2");
+
+ equal(outgoingEnvelopes.length, 2, "Should upload ext-1 and ext-2");
+ deepEqual(
+ parsedData[ext1Index],
+ {
+ a: "abc",
+ },
+ "Should upload new data for ext-1"
+ );
+ deepEqual(
+ parsedData[ext2Index],
+ {
+ b: "xyz",
+ c: 1234,
+ },
+ "Should merge local and remote data for ext-2"
+ );
+
+ info("Mark all extensions as uploaded");
+ await promisify(engine.setUploaded, 0, [ext1Guid, "guidAAA"]);
+
+ info("Finish sync");
+ await promisify(engine.syncFinished);
+
+ // Try fetching values for the remote-only extension we just synced.
+ let { value: ext3Value } = await promisify(area.get, "ext-3", "null");
+ deepEqual(
+ ext3Value,
+ {
+ d: "new! ✨",
+ },
+ "Should return new keys for ext-3"
+ );
+
+ info("Try applying a second time");
+ let secondApply = await promisify(area.apply);
+ deepEqual(secondApply.value, {}, "Shouldn't merge anything on second apply");
+
+ info("Wipe all items");
+ await promisify(engine.wipe);
+
+ for (let extId of ["ext-1", "ext-2", "ext-3"]) {
+ // `get` always returns an object, even if there are no keys for the
+ // extension ID.
+ let { value } = await promisify(area.get, extId, "null");
+ deepEqual(value, {}, `Wipe should remove all values for ${extId}`);
+ }
+});
+
+add_task(async function test_storage_sync_quota() {
+ const service = StorageSyncService.getInterface(Ci.mozIExtensionStorageArea);
+ const engine = StorageSyncService.getInterface(Ci.mozIBridgedSyncEngine);
+ await promisify(engine.wipe);
+ await promisify(service.set, "ext-1", JSON.stringify({ x: "hi" }));
+ await promisify(service.set, "ext-1", JSON.stringify({ longer: "value" }));
+
+ let { value: v1 } = await promisify(service.getBytesInUse, "ext-1", '"x"');
+ Assert.equal(v1, 5); // key len without quotes, value len with quotes.
+ let { value: v2 } = await promisify(service.getBytesInUse, "ext-1", "null");
+ // 5 from 'x', plus 'longer' (6 for key, 7 for value = 13) = 18.
+ Assert.equal(v2, 18);
+
+ // Now set something greater than our quota.
+ await Assert.rejects(
+ promisify(
+ service.set,
+ "ext-1",
+ JSON.stringify({
+ big: "x".repeat(Ci.mozIExtensionStorageArea.SYNC_QUOTA_BYTES),
+ })
+ ),
+ ex => ex.result == NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
+ "should reject with NS_ERROR_DOM_QUOTA_EXCEEDED_ERR"
+ );
+});