529 lines
20 KiB
JavaScript
529 lines
20 KiB
JavaScript
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);
|
|
});
|