From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/tests/browser/browser_localStorage_e10s.js | 284 +++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 dom/tests/browser/browser_localStorage_e10s.js (limited to 'dom/tests/browser/browser_localStorage_e10s.js') diff --git a/dom/tests/browser/browser_localStorage_e10s.js b/dom/tests/browser/browser_localStorage_e10s.js new file mode 100644 index 0000000000..0ecc49fe49 --- /dev/null +++ b/dom/tests/browser/browser_localStorage_e10s.js @@ -0,0 +1,284 @@ +const HELPER_PAGE_URL = + "http://example.com/browser/dom/tests/browser/page_localstorage.html"; +const HELPER_PAGE_ORIGIN = "http://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. 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. + * + * We use the same simple child page in all tabs that: + * - 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 + * + * We open the 5 following tabs: + * - Open a "writer" tab that does not listen for "storage" events and will + * issue only writes. + * - Open a "listener" tab instructed to listen for "storage" events + * immediately. We expect it to capture all events. + * - Open an "reader" tab that does not listen for "storage" events and will + * only issue reads when instructed. + * - Open a "lateWriteThenListen" tab that initially does nothing. We will + * later tell it to issue a write and then listen for events to make sure it + * captures the later events. + * - Open "lateOpenSeesPreload" tab after we've done everything and ensure that + * it preloads/precaches the data without us having touched localStorage or + * added an event listener. + */ +add_task(async function () { + 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 listenerTab = await openTestTab( + HELPER_PAGE_URL, + "listener", + knownTabs, + true + ); + const readerTab = await openTestTab( + HELPER_PAGE_URL, + "reader", + knownTabs, + true + ); + const lateWriteThenListenTab = await openTestTab( + HELPER_PAGE_URL, + "lateWriteThenListen", + 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); + await verifyTabPreload(readerTab, false, HELPER_PAGE_ORIGIN); + + // - Configure the tabs. + const initialSentinel = "initial"; + const noSentinelCheck = null; + await recordTabStorageEvents(listenerTab, initialSentinel); + + // - Issue the initial batch of writes and verify. + info("initial writes"); + 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", + }; + + 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); + // We expect the reader tab to retrieve the current localStorage state from + // the database. Because of the above checks, we are confident that the + // writes have hit PBackground and therefore that the (synchronous) state + // retrieval contains all the data we need. No sentinel-check is required. + await verifyTabStorageState(readerTab, initialWriteState, noSentinelCheck); + + // - Issue second set of writes from lateWriteThenListen + // This tests that our new tab that begins by issuing only writes is building + // on top of the existing state (although we don't verify that until after the + // next set of mutations). We also verify that the initial "writerTab" that + // was our first tab and started with only writes sees the writes, even though + // it did not add an event listener. + + info("late writes"); + const lateWriteSentinel = "lateWrite"; + const lateWriteMutations = [ + ["lateStays", "10", null], + ["lateClobbered", "latePre", null], + ["lateDeleted", "11", null], + ["lateClobbered", "lastPost", "latePre"], + ["lateDeleted", null, "11"], + ]; + const lateWriteState = Object.assign({}, initialWriteState, { + lateStays: "10", + lateClobbered: "lastPost", + }); + + await recordTabStorageEvents(listenerTab, lateWriteSentinel); + + await mutateTabStorage( + lateWriteThenListenTab, + lateWriteMutations, + lateWriteSentinel + ); + + // Verify the writer tab saw the writes. It has to wait for the sentinel to + // appear before checking. + await verifyTabStorageState(writerTab, lateWriteState, lateWriteSentinel); + // Wait for the sentinel event before checking the events and then the state. + await verifyTabStorageEvents( + listenerTab, + lateWriteMutations, + lateWriteSentinel + ); + await verifyTabStorageState(listenerTab, lateWriteState, noSentinelCheck); + // We need to wait for the sentinel to show up for the reader. + await verifyTabStorageState(readerTab, lateWriteState, lateWriteSentinel); + + // - Issue last set of writes from writerTab. + info("last set of writes"); + const lastWriteSentinel = "lastWrite"; + const lastWriteMutations = [ + ["lastStays", "20", null], + ["lastDeleted", "21", null], + ["lastClobbered", "lastPre", null], + ["lastClobbered", "lastPost", "lastPre"], + ["lastDeleted", null, "21"], + ]; + const lastWriteState = Object.assign({}, lateWriteState, { + lastStays: "20", + lastClobbered: "lastPost", + }); + + await recordTabStorageEvents(listenerTab, lastWriteSentinel); + await recordTabStorageEvents(lateWriteThenListenTab, lastWriteSentinel); + + 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); + // We need to wait for the sentinel to show up for the reader. + await verifyTabStorageState(readerTab, lastWriteState, lastWriteSentinel); + // Wait for the sentinel event to be received, then check. + await verifyTabStorageEvents( + lateWriteThenListenTab, + lastWriteMutations, + lastWriteSentinel + ); + await verifyTabStorageState( + lateWriteThenListenTab, + lastWriteState, + 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_URL, + "lateOpenSeesPreload", + knownTabs, + true + ); + await verifyTabPreload(lateOpenSeesPreload, true, HELPER_PAGE_ORIGIN); + + // - Clean up. + await cleanupTabs(knownTabs); + + clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); +}); -- cgit v1.2.3