summaryrefslogtreecommitdiffstats
path: root/dom/tests/browser/browser_localStorage_fis.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/tests/browser/browser_localStorage_fis.js')
-rw-r--r--dom/tests/browser/browser_localStorage_fis.js529
1 files changed, 529 insertions, 0 deletions
diff --git a/dom/tests/browser/browser_localStorage_fis.js b/dom/tests/browser/browser_localStorage_fis.js
new file mode 100644
index 0000000000..5db8ec5eb5
--- /dev/null
+++ b/dom/tests/browser/browser_localStorage_fis.js
@@ -0,0 +1,529 @@
+const HELPER_PAGE_URL =
+ "https://example.com/browser/dom/tests/browser/page_localstorage.html";
+const HELPER_PAGE_COOP_COEP_URL =
+ "https://example.com/browser/dom/tests/browser/page_localstorage_coop+coep.html";
+const HELPER_PAGE_ORIGIN = "https://example.com/";
+
+let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+Services.scriptloader.loadSubScript(testDir + "/helper_localStorage.js", this);
+
+/* import-globals-from helper_localStorage.js */
+
+// We spin up a ton of child processes.
+requestLongerTimeout(4);
+
+/**
+ * Verify the basics of our multi-e10s localStorage support with fission.
+ * We are focused on whitebox testing two things.
+ * When this is being written, broadcast filtering is not in place, but the test
+ * is intended to attempt to verify that its implementation does not break things.
+ *
+ * 1) That pages see the same localStorage state in a timely fashion when
+ * engaging in non-conflicting operations. We are not testing races or
+ * conflict resolution; the spec does not cover that.
+ *
+ * 2) That there are no edge-cases related to when the Storage instance is
+ * created for the page or the StorageCache for the origin. (StorageCache is
+ * what actually backs the Storage binding exposed to the page.) This
+ * matters because the following reasons can exist for them to be created:
+ * - Preload, on the basis of knowing the origin uses localStorage. The
+ * interesting edge case is when we have the same origin open in different
+ * processes and the origin starts using localStorage when it did not
+ * before. Preload will not have instantiated bindings, which could impact
+ * correctness.
+ * - The page accessing localStorage for read or write purposes. This is the
+ * obvious, boring one.
+ * - The page adding a "storage" listener. This is less obvious and
+ * interacts with the preload edge-case mentioned above. The page needs to
+ * hear "storage" events even if the page has not touched localStorage
+ * itself and its origin had nothing stored in localStorage when the page
+ * was created.
+ *
+ * According to current fission implementation, same origin pages will be loaded
+ * by the same process, which process type is webIsolated=. And thanks to
+ * Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers support,
+ * it is possible to load the same origin page by a special process, which type
+ * is webCOOP+COEP=. These are the only two processes can be used to test
+ * localStroage consistency between tabs in different tabs.
+ *
+ * We use the two child pages for testing, page_localstorage.html and
+ * page_localstorage_coop+coep.html. Their content are the same, but
+ * page_localstorage_coop+coep.html will be loaded with its ^headers^ file.
+ * These pages provide followings
+ * - can be instructed to listen for and record "storage" events
+ * - can be instructed to issue a series of localStorage writes
+ * - can be instructed to return the current entire localStorage contents
+ *
+ * To test localStorage consistency, four subtests are used.
+ * Test case 1: one writer tab and one reader tab
+ * The writer tab issues a series of write operations, then verify the
+ * localStorage contents from the reader tab.
+ *
+ * Test case 2: one writer tab and one listener tab
+ * The writer tab issues a series of write operations, then verify the recorded
+ * storage events from the listener tab.
+ *
+ * Test case 3: one writeThenRead tab and one readThenWrite tab
+ * The writeThenRead first issues a series write of operations, and then verify
+ * the recorded storage events and localStorage contents from readThenWrite
+ * tab. After that readThenWrite tab issues a series of write operations, then
+ * verify the results from writeThenRead tab.
+ *
+ * Test case 4: one writer tab and one lateOpenSeesPreload tab
+ * The writer tab issues a series write of operations. Then open the
+ * lateOpenSeesPreload tab to make sure preloads exists.
+ */
+
+/**
+ * Shared constants for test cases
+ */
+const noSentinelCheck = null;
+const initialSentinel = "initial";
+const initialWriteMutations = [
+ // [key (null=clear), newValue (null=delete), oldValue (verification)]
+ ["getsCleared", "1", null],
+ ["alsoGetsCleared", "2", null],
+ [null, null, null],
+ ["stays", "3", null],
+ ["clobbered", "pre", null],
+ ["getsDeletedLater", "4", null],
+ ["getsDeletedImmediately", "5", null],
+ ["getsDeletedImmediately", null, "5"],
+ ["alsoStays", "6", null],
+ ["getsDeletedLater", null, "4"],
+ ["clobbered", "post", "pre"],
+];
+const initialWriteState = {
+ stays: "3",
+ clobbered: "post",
+ alsoStays: "6",
+};
+
+const lastWriteSentinel = "lastWrite";
+const lastWriteMutations = [
+ ["lastStays", "20", null],
+ ["lastDeleted", "21", null],
+ ["lastClobbered", "lastPre", null],
+ ["lastClobbered", "lastPost", "lastPre"],
+ ["lastDeleted", null, "21"],
+];
+const lastWriteState = Object.assign({}, initialWriteState, {
+ lastStays: "20",
+ lastClobbered: "lastPost",
+});
+
+/**
+ * Test case 1: one writer tab and one reader tab
+ * Test steps
+ * 1. Clear origin storage to make sure no data and preloads.
+ * 2. Open the writer and reader tabs and verify preloads do not exist.
+ * Open writer tab in webIsolated= process
+ * Open reader tab in webCOOP+COEP= process
+ * 3. Issue a series write operations in the writer tab, and then verify the
+ * storage state on the tab.
+ * 4. Verify the storage state on the reader tab.
+ * 5. Issue another series write operations in the writer tab, and then verify
+ * the storage state on the tab.
+ * 6. Verify the storage state on the reader tab.
+ * 7. Close tabs and clear origin storage.
+ */
+add_task(async function () {
+ if (!Services.domStorageManager.nextGenLocalStorageEnabled) {
+ ok(true, "Test ignored when the next gen local storage is not enabled.");
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Stop the preallocated process manager from speculatively creating
+ // processes. Our test explicitly asserts on whether preload happened or
+ // not for each tab's process. This information is loaded and latched by
+ // the StorageDBParent constructor which the child process's
+ // LocalStorageManager() constructor causes to be created via a call to
+ // LocalStorageCache::StartDatabase(). Although the service is lazily
+ // created and should not have been created prior to our opening the tab,
+ // it's safest to ensure the process simply didn't exist before we ask for
+ // it.
+ //
+ // This is done in conjunction with our use of forceNewProcess when
+ // opening tabs. There would be no point if we weren't also requesting a
+ // new process.
+ ["dom.ipc.processPrelaunch.enabled", false],
+ // Enable LocalStorage's testing API so we can explicitly trigger a flush
+ // when needed.
+ ["dom.storage.testing", true],
+ ],
+ });
+
+ // Ensure that there is no localstorage data or potential false positives for
+ // localstorage preloads by forcing the origin to be cleared prior to the
+ // start of our test.
+ await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
+
+ // Make sure mOriginsHavingData gets updated.
+ await triggerAndWaitForLocalStorageFlush();
+
+ // - Open tabs. Don't configure any of them yet.
+ const knownTabs = new KnownTabs();
+ const writerTab = await openTestTab(
+ HELPER_PAGE_URL,
+ "writer",
+ knownTabs,
+ true
+ );
+ const readerTab = await openTestTab(
+ HELPER_PAGE_COOP_COEP_URL,
+ "reader",
+ knownTabs,
+ true
+ );
+ // Sanity check that preloading did not occur in the tabs.
+ await verifyTabPreload(writerTab, false, HELPER_PAGE_ORIGIN);
+ await verifyTabPreload(readerTab, false, HELPER_PAGE_ORIGIN);
+
+ // - Issue the initial batch of writes and verify.
+ info("initial writes");
+ await mutateTabStorage(writerTab, initialWriteMutations, initialSentinel);
+
+ // We expect the writer tab to have the correct state because it just did the
+ // writes. We do not perform a sentinel-check because the writes should be
+ // locally available and consistent.
+ await verifyTabStorageState(writerTab, initialWriteState, noSentinelCheck);
+ // We expect the reader tab to retrieve the current localStorage state from
+ // the database.
+ await verifyTabStorageState(readerTab, initialWriteState, initialSentinel);
+
+ // - Issue last set of writes from writerTab.
+ info("last set of writes");
+ await mutateTabStorage(writerTab, lastWriteMutations, lastWriteSentinel);
+
+ // The writer performed the writes, no need to wait for the sentinel.
+ await verifyTabStorageState(writerTab, lastWriteState, noSentinelCheck);
+ // We need to wait for the sentinel to show up for the reader.
+ await verifyTabStorageState(readerTab, lastWriteState, lastWriteSentinel);
+
+ // - Clean up.
+ await cleanupTabs(knownTabs);
+
+ clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
+});
+
+/**
+ * Test case 2: one writer tab and one linsener tab
+ * Test steps
+ * 1. Clear origin storage to make sure no data and preloads.
+ * 2. Open the writer and listener tabs and verify preloads do not exist.
+ * Open writer tab in webIsolated= process
+ * Open listener tab in webCOOP+COEP= process
+ * 3. Ask the listener tab to listen and record storage events.
+ * 4. Issue a series write operations in the writer tab, and then verify the
+ * storage state on the tab.
+ * 5. Verify the storage events record from the listener tab is as expected.
+ * 6. Verify the storage state on the listener tab.
+ * 7. Ask the listener tab to listen and record storage events.
+ * 8. Issue another series write operations in the writer tab, and then verify
+ * the storage state on the tab.
+ * 9. Verify the storage events record from the listener tab is as expected.
+ * 10. Verify the storage state on the listener tab.
+ * 11. Close tabs and clear origin storage.
+ */
+add_task(async function () {
+ if (!Services.domStorageManager.nextGenLocalStorageEnabled) {
+ ok(true, "Test ignored when the next gen local storage is not enabled.");
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processPrelaunch.enabled", false],
+ ["dom.storage.testing", true],
+ ],
+ });
+
+ // Ensure that there is no localstorage data or potential false positives for
+ // localstorage preloads by forcing the origin to be cleared prior to the
+ // start of our test.
+ await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
+
+ // Make sure mOriginsHavingData gets updated.
+ await triggerAndWaitForLocalStorageFlush();
+
+ // - Open tabs. Don't configure any of them yet.
+ const knownTabs = new KnownTabs();
+ const writerTab = await openTestTab(
+ HELPER_PAGE_URL,
+ "writer",
+ knownTabs,
+ true
+ );
+ const listenerTab = await openTestTab(
+ HELPER_PAGE_COOP_COEP_URL,
+ "listener",
+ knownTabs,
+ true
+ );
+ // Sanity check that preloading did not occur in the tabs.
+ await verifyTabPreload(writerTab, false, HELPER_PAGE_ORIGIN);
+ await verifyTabPreload(listenerTab, false, HELPER_PAGE_ORIGIN);
+
+ // - Ask the listener tab to listen and record the storage events..
+ await recordTabStorageEvents(listenerTab, initialSentinel);
+
+ // - Issue the initial batch of writes and verify.
+ info("initial writes");
+ await mutateTabStorage(writerTab, initialWriteMutations, initialSentinel);
+
+ // We expect the writer tab to have the correct state because it just did the
+ // writes. We do not perform a sentinel-check because the writes should be
+ // locally available and consistent.
+ await verifyTabStorageState(writerTab, initialWriteState, noSentinelCheck);
+ // We expect the listener tab to have heard all events despite preload not
+ // having occurred and despite not issuing any reads or writes itself. We
+ // intentionally check the events before the state because we're most
+ // interested in adding the listener having had a side-effect of subscribing
+ // to changes for the process.
+ //
+ // We ensure it had a chance to hear all of the events because we told
+ // recordTabStorageEvents to listen for the given sentinel. The state check
+ // then does not need to do a sentinel check.
+ await verifyTabStorageEvents(
+ listenerTab,
+ initialWriteMutations,
+ initialSentinel
+ );
+ await verifyTabStorageState(listenerTab, initialWriteState, noSentinelCheck);
+
+ // - Ask the listener tab to listen and record the storage events.
+ await recordTabStorageEvents(listenerTab, lastWriteSentinel);
+
+ // - Issue last set of writes from writerTab.
+ info("last set of writes");
+ await mutateTabStorage(writerTab, lastWriteMutations, lastWriteSentinel);
+
+ // The writer performed the writes, no need to wait for the sentinel.
+ await verifyTabStorageState(writerTab, lastWriteState, noSentinelCheck);
+ // Wait for the sentinel event to be received, then check.
+ await verifyTabStorageEvents(
+ listenerTab,
+ lastWriteMutations,
+ lastWriteSentinel
+ );
+ await verifyTabStorageState(listenerTab, lastWriteState, noSentinelCheck);
+
+ // - Clean up.
+ await cleanupTabs(knownTabs);
+
+ clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
+});
+
+/**
+ * Test case 3: one writeThenRead tab and one readThenWrite tab
+ * Test steps
+ * 1. Clear origin storage to make sure no data and preloads.
+ * 2. Open the writeThenRead and readThenWrite tabs and verify preloads do not
+ * exist.
+ * Open writeThenRead tab in webIsolated= process
+ * Open readThenWrite tab in webCOOP+COEP= process
+ * 3. Ask the readThenWrite tab to listen and record storage events.
+ * 4. Issue a series write operations in the writeThenRead tab, and then verify
+ * the storage state on the tab.
+ * 5. Verify the storage events record from the readThenWrite tab is as
+ * expected.
+ * 6. Verify the storage state on the readThenWrite tab.
+ * 7. Ask the writeThenRead tab to listen and record storage events.
+ * 8. Issue another series write operations in the readThenWrite tab, and then
+ * verify the storage state on the tab.
+ * 9. Verify the storage events record from the writeThenRead tab is as
+ * expected.
+ * 10. Verify the storage state on the writeThenRead tab.
+ * 11. Close tabs and clear origin storage.
+ **/
+add_task(async function () {
+ if (!Services.domStorageManager.nextGenLocalStorageEnabled) {
+ ok(true, "Test ignored when the next gen local storage is not enabled.");
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processPrelaunch.enabled", false],
+ ["dom.storage.testing", true],
+ ],
+ });
+
+ // Ensure that there is no localstorage data or potential false positives for
+ // localstorage preloads by forcing the origin to be cleared prior to the
+ // start of our test.
+ await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
+
+ // Make sure mOriginsHavingData gets updated.
+ await triggerAndWaitForLocalStorageFlush();
+
+ // - Open tabs. Don't configure any of them yet.
+ const knownTabs = new KnownTabs();
+ const writeThenReadTab = await openTestTab(
+ HELPER_PAGE_URL,
+ "writerthenread",
+ knownTabs,
+ true
+ );
+ const readThenWriteTab = await openTestTab(
+ HELPER_PAGE_COOP_COEP_URL,
+ "readthenwrite",
+ knownTabs,
+ true
+ );
+ // Sanity check that preloading did not occur in the tabs.
+ await verifyTabPreload(writeThenReadTab, false, HELPER_PAGE_ORIGIN);
+ await verifyTabPreload(readThenWriteTab, false, HELPER_PAGE_ORIGIN);
+
+ // - Ask readThenWrite tab to listen and record storageEvents.
+ await recordTabStorageEvents(readThenWriteTab, initialSentinel);
+
+ // - Issue the initial batch of writes and verify.
+ info("initial writes");
+ await mutateTabStorage(
+ writeThenReadTab,
+ initialWriteMutations,
+ initialSentinel
+ );
+
+ // We expect the writer tab to have the correct state because it just did the
+ // writes. We do not perform a sentinel-check because the writes should be
+ // locally available and consistent.
+ await verifyTabStorageState(
+ writeThenReadTab,
+ initialWriteState,
+ noSentinelCheck
+ );
+
+ // We expect the listener tab to have heard all events despite preload not
+ // having occurred and despite not issuing any reads or writes itself. We
+ // intentionally check the events before the state because we're most
+ // interested in adding the listener having had a side-effect of subscribing
+ // to changes for the process.
+ //
+ // We ensure it had a chance to hear all of the events because we told
+ // recordTabStorageEvents to listen for the given sentinel. The state check
+ // then does not need to do a sentinel check.
+ await verifyTabStorageEvents(
+ readThenWriteTab,
+ initialWriteMutations,
+ initialSentinel
+ );
+ await verifyTabStorageState(
+ readThenWriteTab,
+ initialWriteState,
+ noSentinelCheck
+ );
+
+ // - Issue last set of writes from writerTab.
+ info("last set of writes");
+ await recordTabStorageEvents(writeThenReadTab, lastWriteSentinel);
+
+ await mutateTabStorage(
+ readThenWriteTab,
+ lastWriteMutations,
+ lastWriteSentinel
+ );
+
+ // The writer performed the writes, no need to wait for the sentinel.
+ await verifyTabStorageState(
+ readThenWriteTab,
+ lastWriteState,
+ noSentinelCheck
+ );
+ // Wait for the sentinel event to be received, then check.
+ await verifyTabStorageEvents(
+ writeThenReadTab,
+ lastWriteMutations,
+ lastWriteSentinel
+ );
+ await verifyTabStorageState(
+ writeThenReadTab,
+ lastWriteState,
+ noSentinelCheck
+ );
+
+ // - Clean up.
+ await cleanupTabs(knownTabs);
+
+ clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
+});
+
+/**
+ * Test case 4: one writerRead tab and one lateOpenSeesPreload tab
+ * Test steps
+ * 1. Clear origin storage to make sure no data and preloads.
+ * 2. Open the writer tab and verify preloads do not exist.
+ * Open writer tab in webIsolated= process
+ * 3. Issue a series write operations in the writer tab, and then verify the
+ * storage state on the tab.
+ * 4. Issue another series write operations in the writer tab, and then verify
+ * the storage state on the tab.
+ * 5. Open lateOpenSeesPreload tab in webCOOP+COEP process
+ * 6. Verify the preloads on the lateOpenSeesPreload tab
+ * 7. Close tabs and clear origin storage.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processPrelaunch.enabled", false],
+ ["dom.storage.testing", true],
+ ],
+ });
+
+ // Ensure that there is no localstorage data or potential false positives for
+ // localstorage preloads by forcing the origin to be cleared prior to the
+ // start of our test.
+ await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
+
+ // Make sure mOriginsHavingData gets updated.
+ await triggerAndWaitForLocalStorageFlush();
+
+ // - Open tabs. Don't configure any of them yet.
+ const knownTabs = new KnownTabs();
+ const writerTab = await openTestTab(
+ HELPER_PAGE_URL,
+ "writer",
+ knownTabs,
+ true
+ );
+ // Sanity check that preloading did not occur in the tabs.
+ await verifyTabPreload(writerTab, false, HELPER_PAGE_ORIGIN);
+
+ // - Configure the tabs.
+
+ // - Issue the initial batch of writes and verify.
+ info("initial writes");
+ await mutateTabStorage(writerTab, initialWriteMutations, initialSentinel);
+
+ // We expect the writer tab to have the correct state because it just did the
+ // writes. We do not perform a sentinel-check because the writes should be
+ // locally available and consistent.
+ await verifyTabStorageState(writerTab, initialWriteState, noSentinelCheck);
+
+ // - Force a LocalStorage DB flush so mOriginsHavingData is updated.
+ // mOriginsHavingData is only updated when the storage thread runs its
+ // accumulated operations during the flush. If we don't initiate and ensure
+ // that a flush has occurred before moving on to the next step,
+ // mOriginsHavingData may not include our origin when it's sent down to the
+ // child process.
+ info("flush to make preload check work");
+ await triggerAndWaitForLocalStorageFlush();
+
+ // - Open a fresh tab and make sure it sees the precache/preload
+ info("late open preload check");
+ const lateOpenSeesPreload = await openTestTab(
+ HELPER_PAGE_COOP_COEP_URL,
+ "lateOpenSeesPreload",
+ knownTabs,
+ true
+ );
+ await verifyTabPreload(lateOpenSeesPreload, true, HELPER_PAGE_ORIGIN);
+
+ // - Clean up.
+ await cleanupTabs(knownTabs);
+
+ clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
+});