diff options
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_storage.js')
-rw-r--r-- | toolkit/components/extensions/test/xpcshell/test_ext_storage.js | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_storage.js b/toolkit/components/extensions/test/xpcshell/test_ext_storage.js new file mode 100644 index 000000000..df46dfb63 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage.js @@ -0,0 +1,334 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const STORAGE_SYNC_PREF = "webextensions.storage.sync.enabled"; +Cu.import("resource://gre/modules/Preferences.jsm"); + +/** + * Utility function to ensure that all supported APIs for getting are + * tested. + * + * @param {string} areaName + * either "local" or "sync" according to what we want to test + * @param {string} prop + * "key" to look up using the storage API + * @param {Object} value + * "value" to compare against + */ +async function checkGetImpl(areaName, prop, value) { + let storage = browser.storage[areaName]; + + let data = await storage.get(null); + browser.test.assertEq(value, data[prop], `null getter worked for ${prop} in ${areaName}`); + + data = await storage.get(prop); + browser.test.assertEq(value, data[prop], `string getter worked for ${prop} in ${areaName}`); + + data = await storage.get([prop]); + browser.test.assertEq(value, data[prop], `array getter worked for ${prop} in ${areaName}`); + + data = await storage.get({[prop]: undefined}); + browser.test.assertEq(value, data[prop], `object getter worked for ${prop} in ${areaName}`); +} + +add_task(function* test_local_cache_invalidation() { + function background(checkGet) { + browser.test.onMessage.addListener(async msg => { + if (msg === "set-initial") { + await browser.storage.local.set({"test-prop1": "value1", "test-prop2": "value2"}); + browser.test.sendMessage("set-initial-done"); + } else if (msg === "check") { + await checkGet("local", "test-prop1", "value1"); + await checkGet("local", "test-prop2", "value2"); + browser.test.sendMessage("check-done"); + } + }); + + browser.test.sendMessage("ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["storage"], + }, + background: `(${background})(${checkGetImpl})`, + }); + + yield extension.startup(); + yield extension.awaitMessage("ready"); + + extension.sendMessage("set-initial"); + yield extension.awaitMessage("set-initial-done"); + + Services.obs.notifyObservers(null, "extension-invalidate-storage-cache", ""); + + extension.sendMessage("check"); + yield extension.awaitMessage("check-done"); + + yield extension.unload(); +}); + +add_task(function* test_config_flag_needed() { + function background() { + let promises = []; + let apiTests = [ + {method: "get", args: ["foo"]}, + {method: "set", args: [{foo: "bar"}]}, + {method: "remove", args: ["foo"]}, + {method: "clear", args: []}, + ]; + apiTests.forEach(testDef => { + promises.push(browser.test.assertRejects( + browser.storage.sync[testDef.method](...testDef.args), + "Please set webextensions.storage.sync.enabled to true in about:config", + `storage.sync.${testDef.method} is behind a flag`)); + }); + + Promise.all(promises).then(() => browser.test.notifyPass("flag needed")); + } + + ok(!Preferences.get(STORAGE_SYNC_PREF)); + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["storage"], + }, + background: `(${background})(${checkGetImpl})`, + }); + + yield extension.startup(); + yield extension.awaitFinish("flag needed"); + yield extension.unload(); +}); + +add_task(function* test_reloading_extensions_works() { + // Just some random extension ID that we can re-use + const extensionId = "my-extension-id@1"; + + function loadExtension() { + function background() { + browser.storage.sync.set({"a": "b"}).then(() => { + browser.test.notifyPass("set-works"); + }); + } + + return ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["storage"], + }, + background: `(${background})()`, + }, extensionId); + } + + Preferences.set(STORAGE_SYNC_PREF, true); + + let extension1 = loadExtension(); + + yield extension1.startup(); + yield extension1.awaitFinish("set-works"); + yield extension1.unload(); + + let extension2 = loadExtension(); + + yield extension2.startup(); + yield extension2.awaitFinish("set-works"); + yield extension2.unload(); + + Preferences.reset(STORAGE_SYNC_PREF); +}); + +do_register_cleanup(() => { + Preferences.reset(STORAGE_SYNC_PREF); +}); + +add_task(function* test_backgroundScript() { + async function backgroundScript(checkGet) { + let globalChanges, gResolve; + function clearGlobalChanges() { + globalChanges = new Promise(resolve => { gResolve = resolve; }); + } + clearGlobalChanges(); + let expectedAreaName; + + browser.storage.onChanged.addListener((changes, areaName) => { + browser.test.assertEq(expectedAreaName, areaName, + "Expected area name received by listener"); + gResolve(changes); + }); + + async function checkChanges(areaName, changes, message) { + function checkSub(obj1, obj2) { + for (let prop in obj1) { + browser.test.assertTrue(obj1[prop] !== undefined, + `checkChanges ${areaName} ${prop} is missing (${message})`); + browser.test.assertTrue(obj2[prop] !== undefined, + `checkChanges ${areaName} ${prop} is missing (${message})`); + browser.test.assertEq(obj1[prop].oldValue, obj2[prop].oldValue, + `checkChanges ${areaName} ${prop} old (${message})`); + browser.test.assertEq(obj1[prop].newValue, obj2[prop].newValue, + `checkChanges ${areaName} ${prop} new (${message})`); + } + } + + const recentChanges = await globalChanges; + checkSub(changes, recentChanges); + checkSub(recentChanges, changes); + clearGlobalChanges(); + } + + /* eslint-disable dot-notation */ + async function runTests(areaName) { + expectedAreaName = areaName; + let storage = browser.storage[areaName]; + // Set some data and then test getters. + try { + await storage.set({"test-prop1": "value1", "test-prop2": "value2"}); + await checkChanges(areaName, + {"test-prop1": {newValue: "value1"}, "test-prop2": {newValue: "value2"}}, + "set (a)"); + + await checkGet(areaName, "test-prop1", "value1"); + await checkGet(areaName, "test-prop2", "value2"); + + let data = await storage.get({"test-prop1": undefined, "test-prop2": undefined, "other": "default"}); + browser.test.assertEq("value1", data["test-prop1"], "prop1 correct (a)"); + browser.test.assertEq("value2", data["test-prop2"], "prop2 correct (a)"); + browser.test.assertEq("default", data["other"], "other correct"); + + data = await storage.get(["test-prop1", "test-prop2", "other"]); + browser.test.assertEq("value1", data["test-prop1"], "prop1 correct (b)"); + browser.test.assertEq("value2", data["test-prop2"], "prop2 correct (b)"); + browser.test.assertFalse("other" in data, "other correct"); + + // Remove data in various ways. + await storage.remove("test-prop1"); + await checkChanges(areaName, {"test-prop1": {oldValue: "value1"}}, "remove string"); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse("test-prop1" in data, "prop1 absent (remove string)"); + browser.test.assertTrue("test-prop2" in data, "prop2 present (remove string)"); + + await storage.set({"test-prop1": "value1"}); + await checkChanges(areaName, {"test-prop1": {newValue: "value1"}}, "set (c)"); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertEq(data["test-prop1"], "value1", "prop1 correct (c)"); + browser.test.assertEq(data["test-prop2"], "value2", "prop2 correct (c)"); + + await storage.remove(["test-prop1", "test-prop2"]); + await checkChanges(areaName, + {"test-prop1": {oldValue: "value1"}, "test-prop2": {oldValue: "value2"}}, + "remove array"); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse("test-prop1" in data, "prop1 absent (remove array)"); + browser.test.assertFalse("test-prop2" in data, "prop2 absent (remove array)"); + + // test storage.clear + await storage.set({"test-prop1": "value1", "test-prop2": "value2"}); + // Make sure that set() handler happened before we clear the + // promise again. + await globalChanges; + + clearGlobalChanges(); + await storage.clear(); + + await checkChanges(areaName, + {"test-prop1": {oldValue: "value1"}, "test-prop2": {oldValue: "value2"}}, + "clear"); + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse("test-prop1" in data, "prop1 absent (clear)"); + browser.test.assertFalse("test-prop2" in data, "prop2 absent (clear)"); + + // Make sure we can store complex JSON data. + // known previous values + await storage.set({"test-prop1": "value1", "test-prop2": "value2"}); + + // Make sure the set() handler landed. + await globalChanges; + + clearGlobalChanges(); + await storage.set({ + "test-prop1": { + str: "hello", + bool: true, + null: null, + undef: undefined, + obj: {}, + arr: [1, 2], + date: new Date(0), + regexp: /regexp/, + func: function func() {}, + window, + }, + }); + + await storage.set({"test-prop2": function func() {}}); + const recentChanges = await globalChanges; + + browser.test.assertEq("value1", recentChanges["test-prop1"].oldValue, "oldValue correct"); + browser.test.assertEq("object", typeof(recentChanges["test-prop1"].newValue), "newValue is obj"); + clearGlobalChanges(); + + data = await storage.get({"test-prop1": undefined, "test-prop2": undefined}); + let obj = data["test-prop1"]; + + browser.test.assertEq("hello", obj.str, "string part correct"); + browser.test.assertEq(true, obj.bool, "bool part correct"); + browser.test.assertEq(null, obj.null, "null part correct"); + browser.test.assertEq(undefined, obj.undef, "undefined part correct"); + browser.test.assertEq(undefined, obj.func, "function part correct"); + browser.test.assertEq(undefined, obj.window, "window part correct"); + browser.test.assertEq("1970-01-01T00:00:00.000Z", obj.date, "date part correct"); + browser.test.assertEq("/regexp/", obj.regexp, "regexp part correct"); + browser.test.assertEq("object", typeof(obj.obj), "object part correct"); + browser.test.assertTrue(Array.isArray(obj.arr), "array part present"); + browser.test.assertEq(1, obj.arr[0], "arr[0] part correct"); + browser.test.assertEq(2, obj.arr[1], "arr[1] part correct"); + browser.test.assertEq(2, obj.arr.length, "arr.length part correct"); + + obj = data["test-prop2"]; + + browser.test.assertEq("[object Object]", {}.toString.call(obj), "function serialized as a plain object"); + browser.test.assertEq(0, Object.keys(obj).length, "function serialized as an empty object"); + } catch (e) { + browser.test.fail(`Error: ${e} :: ${e.stack}`); + browser.test.notifyFail("storage"); + } + } + + browser.test.onMessage.addListener(msg => { + let promise; + if (msg === "test-local") { + promise = runTests("local"); + } else if (msg === "test-sync") { + promise = runTests("sync"); + } + promise.then(() => browser.test.sendMessage("test-finished")); + }); + + browser.test.sendMessage("ready"); + } + + let extensionData = { + background: `(${backgroundScript})(${checkGetImpl})`, + manifest: { + permissions: ["storage"], + }, + }; + + Preferences.set(STORAGE_SYNC_PREF, true); + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + yield extension.awaitMessage("ready"); + + extension.sendMessage("test-local"); + yield extension.awaitMessage("test-finished"); + + extension.sendMessage("test-sync"); + yield extension.awaitMessage("test-finished"); + + Preferences.reset(STORAGE_SYNC_PREF); + yield extension.unload(); +}); |