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 --- .../browser/browser_localStorage_snapshotting.js | 774 +++++++++++++++++++++ 1 file changed, 774 insertions(+) create mode 100644 dom/tests/browser/browser_localStorage_snapshotting.js (limited to 'dom/tests/browser/browser_localStorage_snapshotting.js') diff --git a/dom/tests/browser/browser_localStorage_snapshotting.js b/dom/tests/browser/browser_localStorage_snapshotting.js new file mode 100644 index 0000000000..f03fa187b1 --- /dev/null +++ b/dom/tests/browser/browser_localStorage_snapshotting.js @@ -0,0 +1,774 @@ +const HELPER_PAGE_URL = + "http://example.com/browser/dom/tests/browser/page_localstorage_snapshotting.html"; +const HELPER_PAGE_ORIGIN = "http://example.com/"; + +/* import-globals-from helper_localStorage.js */ + +let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); +Services.scriptloader.loadSubScript(testDir + "/helper_localStorage.js", this); + +function clearOrigin() { + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + HELPER_PAGE_ORIGIN + ); + let request = Services.qms.clearStoragesForPrincipal( + principal, + "default", + "ls" + ); + let promise = new Promise(resolve => { + request.callback = () => { + resolve(); + }; + }); + return promise; +} + +async function applyMutations(knownTab, mutations) { + await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [mutations], + function (mutations) { + return content.wrappedJSObject.applyMutations( + Cu.cloneInto(mutations, content) + ); + } + ); +} + +async function verifyState(knownTab, expectedState) { + let actualState = await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [], + function () { + return content.wrappedJSObject.getState(); + } + ); + + for (let [expectedKey, expectedValue] of Object.entries(expectedState)) { + ok(actualState.hasOwnProperty(expectedKey), "key present: " + expectedKey); + is(actualState[expectedKey], expectedValue, "value correct"); + } + for (let actualKey of Object.keys(actualState)) { + if (!expectedState.hasOwnProperty(actualKey)) { + ok(false, "actual state has key it shouldn't have: " + actualKey); + } + } +} + +async function getKeys(knownTab) { + let keys = await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [], + function () { + return content.wrappedJSObject.getKeys(); + } + ); + return keys; +} + +async function beginExplicitSnapshot(knownTab) { + await SpecialPowers.spawn(knownTab.tab.linkedBrowser, [], function () { + return content.wrappedJSObject.beginExplicitSnapshot(); + }); +} + +async function checkpointExplicitSnapshot(knownTab) { + await SpecialPowers.spawn(knownTab.tab.linkedBrowser, [], function () { + return content.wrappedJSObject.checkpointExplicitSnapshot(); + }); +} + +async function endExplicitSnapshot(knownTab) { + await SpecialPowers.spawn(knownTab.tab.linkedBrowser, [], function () { + return content.wrappedJSObject.endExplicitSnapshot(); + }); +} + +async function verifyHasSnapshot(knownTab, expectedHasSnapshot) { + let hasSnapshot = await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [], + function () { + return content.wrappedJSObject.getHasSnapshot(); + } + ); + is(hasSnapshot, expectedHasSnapshot, "Correct has snapshot"); +} + +async function verifySnapshotUsage(knownTab, expectedSnapshotUsage) { + let snapshotUsage = await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [], + function () { + return content.wrappedJSObject.getSnapshotUsage(); + } + ); + is(snapshotUsage, expectedSnapshotUsage, "Correct snapshot usage"); +} + +async function verifyParentState(expectedState) { + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + HELPER_PAGE_ORIGIN + ); + + let actualState = await Services.domStorageManager.getState(principal); + + for (let [expectedKey, expectedValue] of Object.entries(expectedState)) { + ok(actualState.hasOwnProperty(expectedKey), "key present: " + expectedKey); + is(actualState[expectedKey], expectedValue, "value correct"); + } + for (let actualKey of Object.keys(actualState)) { + if (!expectedState.hasOwnProperty(actualKey)) { + ok(false, "actual state has key it shouldn't have: " + actualKey); + } + } +} + +// We spin up a ton of child processes. +requestLongerTimeout(4); + +/** + * Verify snapshotting of our localStorage implementation in multi-e10s setup. + */ +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: [ + // Enable LocalStorage's testing API so we can explicitly create + // snapshots when needed. + ["dom.storage.testing", true], + // Force multiple web and webIsolated content processes so that the + // multi-e10s logic works correctly. + ["dom.ipc.processCount", 8], + ["dom.ipc.processCount.webIsolated", 4], + ], + }); + + // Ensure that there is no localstorage data by forcing the origin to be + // cleared prior to the start of our test.. + await clearOrigin(); + + // - Open tabs. Don't configure any of them yet. + const knownTabs = new KnownTabs(); + const writerTab1 = await openTestTab( + HELPER_PAGE_URL, + "writer1", + knownTabs, + true + ); + const writerTab2 = await openTestTab( + HELPER_PAGE_URL, + "writer2", + knownTabs, + true + ); + const readerTab1 = await openTestTab( + HELPER_PAGE_URL, + "reader1", + knownTabs, + true + ); + const readerTab2 = await openTestTab( + HELPER_PAGE_URL, + "reader2", + knownTabs, + true + ); + + const initialMutations = [ + [null, null], + ["key1", "initial1"], + ["key2", "initial2"], + ["key3", "initial3"], + ["key5", "initial5"], + ["key6", "initial6"], + ["key7", "initial7"], + ["key8", "initial8"], + ]; + + const initialState = { + key1: "initial1", + key2: "initial2", + key3: "initial3", + key5: "initial5", + key6: "initial6", + key7: "initial7", + key8: "initial8", + }; + + let sizeOfOneKey; + let sizeOfOneValue; + let sizeOfOneItem; + let sizeOfKeys = 0; + let sizeOfItems = 0; + + let entries = Object.entries(initialState); + for (let i = 0; i < entries.length; i++) { + let entry = entries[i]; + let sizeOfKey = entry[0].length; + let sizeOfValue = entry[1].length; + let sizeOfItem = sizeOfKey + sizeOfValue; + if (i == 0) { + sizeOfOneKey = sizeOfKey; + sizeOfOneValue = sizeOfValue; + sizeOfOneItem = sizeOfItem; + } + sizeOfKeys += sizeOfKey; + sizeOfItems += sizeOfItem; + } + + info("Size of one key is " + sizeOfOneKey); + info("Size of one value is " + sizeOfOneValue); + info("Size of one item is " + sizeOfOneItem); + info("Size of keys is " + sizeOfKeys); + info("Size of items is " + sizeOfItems); + + const prefillValues = [ + // Zero prefill (prefill disabled) + 0, + // Less than one key length prefill + sizeOfOneKey - 1, + // Greater than one key length and less than one item length prefill + sizeOfOneKey + 1, + // Precisely one item length prefill + sizeOfOneItem, + // Precisely two times one item length prefill + 2 * sizeOfOneItem, + // Precisely size of keys prefill + sizeOfKeys, + // Less than size of keys plus one value length prefill + sizeOfKeys + sizeOfOneValue - 1, + // Precisely size of keys plus one value length prefill + sizeOfKeys + sizeOfOneValue, + // Greater than size of keys plus one value length and less than size of + // keys plus two times one value length prefill + sizeOfKeys + sizeOfOneValue + 1, + // Precisely size of keys plus two times one value length prefill + sizeOfKeys + 2 * sizeOfOneValue, + // Precisely size of keys plus three times one value length prefill + sizeOfKeys + 3 * sizeOfOneValue, + // Precisely size of keys plus four times one value length prefill + sizeOfKeys + 4 * sizeOfOneValue, + // Precisely size of keys plus five times one value length prefill + sizeOfKeys + 5 * sizeOfOneValue, + // Precisely size of keys plus six times one value length prefill + sizeOfKeys + 6 * sizeOfOneValue, + // Precisely size of items prefill + sizeOfItems, + // Unlimited prefill + -1, + ]; + + for (let prefillValue of prefillValues) { + info("Setting prefill value to " + prefillValue); + + await SpecialPowers.pushPrefEnv({ + set: [["dom.storage.snapshot_prefill", prefillValue]], + }); + + const gradualPrefillValues = [ + // Zero gradual prefill + 0, + // Less than one key length gradual prefill + sizeOfOneKey - 1, + // Greater than one key length and less than one item length gradual + // prefill + sizeOfOneKey + 1, + // Precisely one item length gradual prefill + sizeOfOneItem, + // Precisely two times one item length gradual prefill + 2 * sizeOfOneItem, + // Precisely three times one item length gradual prefill + 3 * sizeOfOneItem, + // Precisely four times one item length gradual prefill + 4 * sizeOfOneItem, + // Precisely five times one item length gradual prefill + 5 * sizeOfOneItem, + // Precisely six times one item length gradual prefill + 6 * sizeOfOneItem, + // Precisely size of items prefill + sizeOfItems, + // Unlimited gradual prefill + -1, + ]; + + for (let gradualPrefillValue of gradualPrefillValues) { + info("Setting gradual prefill value to " + gradualPrefillValue); + + await SpecialPowers.pushPrefEnv({ + set: [["dom.storage.snapshot_gradual_prefill", gradualPrefillValue]], + }); + + info("Stage 1"); + + const setRemoveMutations1 = [ + ["key0", "setRemove10"], + ["key1", "setRemove11"], + ["key2", null], + ["key3", "setRemove13"], + ["key4", "setRemove14"], + ["key5", "setRemove15"], + ["key6", "setRemove16"], + ["key7", "setRemove17"], + ["key8", null], + ["key9", "setRemove19"], + ]; + + const setRemoveState1 = { + key0: "setRemove10", + key1: "setRemove11", + key3: "setRemove13", + key4: "setRemove14", + key5: "setRemove15", + key6: "setRemove16", + key7: "setRemove17", + key9: "setRemove19", + }; + + const setRemoveMutations2 = [ + ["key0", "setRemove20"], + ["key1", null], + ["key2", "setRemove22"], + ["key3", "setRemove23"], + ["key4", "setRemove24"], + ["key5", "setRemove25"], + ["key6", "setRemove26"], + ["key7", null], + ["key8", "setRemove28"], + ["key9", "setRemove29"], + ]; + + const setRemoveState2 = { + key0: "setRemove20", + key2: "setRemove22", + key3: "setRemove23", + key4: "setRemove24", + key5: "setRemove25", + key6: "setRemove26", + key8: "setRemove28", + key9: "setRemove29", + }; + + // Apply initial mutations using an explicit snapshot. The explicit + // snapshot here ensures that the parent process have received the + // changes. + await beginExplicitSnapshot(writerTab1); + await applyMutations(writerTab1, initialMutations); + await endExplicitSnapshot(writerTab1); + + // Begin explicit snapshots in all tabs except readerTab2. All these tabs + // should see the initial state regardless what other tabs are doing. + await beginExplicitSnapshot(writerTab1); + await beginExplicitSnapshot(writerTab2); + await beginExplicitSnapshot(readerTab1); + + // Apply first array of set/remove mutations in writerTab1 and end the + // explicit snapshot. This will trigger saving of values in other active + // snapshots. + await applyMutations(writerTab1, setRemoveMutations1); + await endExplicitSnapshot(writerTab1); + + // Begin an explicit snapshot in readerTab2. writerTab1 already ended its + // explicit snapshot, so readerTab2 should see mutations done by + // writerTab1. + await beginExplicitSnapshot(readerTab2); + + // Apply second array of set/remove mutations in writerTab2 and end the + // explicit snapshot. This will trigger saving of values in other active + // snapshots, but only if they haven't been saved already. + await applyMutations(writerTab2, setRemoveMutations2); + await endExplicitSnapshot(writerTab2); + + // Verify state in readerTab1, it should match the initial state. + await verifyState(readerTab1, initialState); + await endExplicitSnapshot(readerTab1); + + // Verify state in readerTab2, it should match the state after the first + // array of set/remove mutatations have been applied and "commited". + await verifyState(readerTab2, setRemoveState1); + await endExplicitSnapshot(readerTab2); + + // Verify final state, it should match the state after the second array of + // set/remove mutation have been applied and "commited". An explicit + // snapshot is used. + await beginExplicitSnapshot(readerTab1); + await verifyState(readerTab1, setRemoveState2); + await endExplicitSnapshot(readerTab1); + + info("Stage 2"); + + const setRemoveClearMutations1 = [ + ["key0", "setRemoveClear10"], + ["key1", null], + [null, null], + ]; + + const setRemoveClearState1 = {}; + + const setRemoveClearMutations2 = [ + ["key8", null], + ["key9", "setRemoveClear29"], + [null, null], + ]; + + const setRemoveClearState2 = {}; + + // This is very similar to previous stage except that in addition to + // set/remove, the clear operation is involved too. + await beginExplicitSnapshot(writerTab1); + await applyMutations(writerTab1, initialMutations); + await endExplicitSnapshot(writerTab1); + + await beginExplicitSnapshot(writerTab1); + await beginExplicitSnapshot(writerTab2); + await beginExplicitSnapshot(readerTab1); + + await applyMutations(writerTab1, setRemoveClearMutations1); + await endExplicitSnapshot(writerTab1); + + await beginExplicitSnapshot(readerTab2); + + await applyMutations(writerTab2, setRemoveClearMutations2); + await endExplicitSnapshot(writerTab2); + + await verifyState(readerTab1, initialState); + await endExplicitSnapshot(readerTab1); + + await verifyState(readerTab2, setRemoveClearState1); + await endExplicitSnapshot(readerTab2); + + await beginExplicitSnapshot(readerTab1); + await verifyState(readerTab1, setRemoveClearState2); + await endExplicitSnapshot(readerTab1); + + info("Stage 3"); + + const changeOrderMutations = [ + ["key1", null], + ["key2", null], + ["key3", null], + ["key5", null], + ["key6", null], + ["key7", null], + ["key8", null], + ["key8", "initial8"], + ["key7", "initial7"], + ["key6", "initial6"], + ["key5", "initial5"], + ["key3", "initial3"], + ["key2", "initial2"], + ["key1", "initial1"], + ]; + + // Apply initial mutations using an explicit snapshot. The explicit + // snapshot here ensures that the parent process have received the + // changes. + await beginExplicitSnapshot(writerTab1); + await applyMutations(writerTab1, initialMutations); + await endExplicitSnapshot(writerTab1); + + // Begin explicit snapshots in all tabs except writerTab2 which is not + // used in this stage. All these tabs should see the initial order + // regardless what other tabs are doing. + await beginExplicitSnapshot(readerTab1); + await beginExplicitSnapshot(writerTab1); + await beginExplicitSnapshot(readerTab2); + + // Get all keys in readerTab1 and end the explicit snapshot. No mutations + // have been applied yet. + let tab1Keys = await getKeys(readerTab1); + await endExplicitSnapshot(readerTab1); + + // Apply mutations that change the order of keys and end the explicit + // snapshot. The state is unchanged. This will trigger saving of key order + // in other active snapshots, but only if the order hasn't been saved + // already. + await applyMutations(writerTab1, changeOrderMutations); + await endExplicitSnapshot(writerTab1); + + // Get all keys in readerTab2 and end the explicit snapshot. Change order + // mutations have been applied, but the order should stay unchanged. + let tab2Keys = await getKeys(readerTab2); + await endExplicitSnapshot(readerTab2); + + // Verify the key order is the same. + is(tab2Keys.length, tab1Keys.length, "Correct keys length"); + for (let i = 0; i < tab2Keys.length; i++) { + is(tab2Keys[i], tab1Keys[i], "Correct key"); + } + + // Verify final state, it should match the initial state since applied + // mutations only changed the key order. An explicit snapshot is used. + await beginExplicitSnapshot(readerTab1); + await verifyState(readerTab1, initialState); + await endExplicitSnapshot(readerTab1); + } + } + + // - Clean up. + await cleanupTabs(knownTabs); + + clearOrigin(); +}); + +/** + * Verify that snapshots are able to work with negative usage. This can happen + * when there's an item stored in localStorage with given size and then two + * snaphots (created at the same time) mutate the item. The first one replases + * it with something bigger and the other one removes it. + */ +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: [ + // Force multiple web and webIsolated content processes so that the + // multi-e10s logic works correctly. + ["dom.ipc.processCount", 4], + ["dom.ipc.processCount.webIsolated", 2], + // Disable snapshot peak usage pre-incrementation to make the testing + // easier. + ["dom.storage.snapshot_peak_usage.initial_preincrement", 0], + ["dom.storage.snapshot_peak_usage.reduced_initial_preincrement", 0], + ["dom.storage.snapshot_peak_usage.gradual_preincrement", 0], + ["dom.storage.snapshot_peak_usage.reuced_gradual_preincrement", 0], + // Enable LocalStorage's testing API so we can explicitly create + // snapshots when needed. + ["dom.storage.testing", true], + ], + }); + + // Ensure that there is no localstorage data by forcing the origin to be + // cleared prior to the start of our test. + await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); + + // Open tabs. Don't configure any of them yet. + const knownTabs = new KnownTabs(); + const writerTab1 = await openTestTab( + HELPER_PAGE_URL, + "writer1", + knownTabs, + true + ); + const writerTab2 = await openTestTab( + HELPER_PAGE_URL, + "writer2", + knownTabs, + true + ); + + // Apply the initial mutation using an explicit snapshot. The explicit + // snapshot here ensures that the parent process have received the changes. + await beginExplicitSnapshot(writerTab1); + await applyMutations(writerTab1, [["key", "something"]]); + await endExplicitSnapshot(writerTab1); + + // Begin explicit snapshots in both tabs. Both tabs should see the initial + // state. + await beginExplicitSnapshot(writerTab1); + await beginExplicitSnapshot(writerTab2); + + // Apply the first mutation in writerTab1 and end the explicit snapshot. + await applyMutations(writerTab1, [["key", "somethingBigger"]]); + await endExplicitSnapshot(writerTab1); + + // Apply the second mutation in writerTab2 and end the explicit snapshot. + await applyMutations(writerTab2, [["key", null]]); + await endExplicitSnapshot(writerTab2); + + // Verify the final state, it should match the state after the second + // mutation has been applied and "commited". An explicit snapshot is used. + await beginExplicitSnapshot(writerTab1); + await verifyState(writerTab1, {}); + await endExplicitSnapshot(writerTab1); + + // Clean up. + await cleanupTabs(knownTabs); + + clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); +}); + +/** + * Verify that snapshot usage is correctly updated after each operation. + */ +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: [ + // Force multiple web and webIsolated content processes so that the + // multi-e10s logic works correctly. + ["dom.ipc.processCount", 4], + ["dom.ipc.processCount.webIsolated", 2], + // Disable snapshot peak usage pre-incrementation to make the testing + // easier. + ["dom.storage.snapshot_peak_usage.initial_preincrement", 0], + ["dom.storage.snapshot_peak_usage.reduced_initial_preincrement", 0], + ["dom.storage.snapshot_peak_usage.gradual_preincrement", 0], + ["dom.storage.snapshot_peak_usage.reuced_gradual_preincrement", 0], + // Enable LocalStorage's testing API so we can explicitly create + // snapshots when needed. + ["dom.storage.testing", true], + ], + }); + + // Ensure that there is no localstorage data by forcing the origin to be + // cleared prior to the start of our test. + await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); + + // Open tabs. Don't configure any of them yet. + const knownTabs = new KnownTabs(); + const writerTab1 = await openTestTab( + HELPER_PAGE_URL, + "writer1", + knownTabs, + true + ); + const writerTab2 = await openTestTab( + HELPER_PAGE_URL, + "writer2", + knownTabs, + true + ); + + // Apply the initial mutation using an explicit snapshot. The explicit + // snapshot here ensures that the parent process have received the changes. + await beginExplicitSnapshot(writerTab1); + await verifySnapshotUsage(writerTab1, 0); + await applyMutations(writerTab1, [["key", "something"]]); + await verifySnapshotUsage(writerTab1, 12); + await endExplicitSnapshot(writerTab1); + await verifyHasSnapshot(writerTab1, false); + + // Begin an explicit snapshot in writerTab1 and apply the first mutatation + // in it. + await beginExplicitSnapshot(writerTab1); + await verifySnapshotUsage(writerTab1, 12); + await applyMutations(writerTab1, [["key", "somethingBigger"]]); + await verifySnapshotUsage(writerTab1, 18); + + // Begin an explicit snapshot in writerTab2 and apply the second mutatation + // in it. + await beginExplicitSnapshot(writerTab2); + await verifySnapshotUsage(writerTab2, 18); + await applyMutations(writerTab2, [[null, null]]); + await verifySnapshotUsage(writerTab2, 6); + + // End explicit snapshots in both tabs. + await endExplicitSnapshot(writerTab1); + await verifyHasSnapshot(writerTab1, false); + await endExplicitSnapshot(writerTab2); + await verifyHasSnapshot(writerTab2, false); + + // Verify the final state, it should match the state after the second + // mutation has been applied and "commited". An explicit snapshot is used. + await beginExplicitSnapshot(writerTab1); + await verifySnapshotUsage(writerTab1, 0); + await verifyState(writerTab1, {}); + await endExplicitSnapshot(writerTab1); + await verifyHasSnapshot(writerTab1, false); + + // Clean up. + await cleanupTabs(knownTabs); + + clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); +}); + +/** + * Verify that datastore in the parent is correctly updated after a checkpoint. + */ +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: [ + // Force multiple web and webIsolated content processes so that the + // multi-e10s logic works correctly. + ["dom.ipc.processCount", 4], + ["dom.ipc.processCount.webIsolated", 2], + // Enable LocalStorage's testing API so we can explicitly create + // snapshots when needed. + ["dom.storage.testing", true], + ], + }); + + // Ensure that there is no localstorage data by forcing the origin to be + // cleared prior to the start of our test. + await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); + + // Open tabs. Don't configure any of them yet. + const knownTabs = new KnownTabs(); + const writerTab1 = await openTestTab( + HELPER_PAGE_URL, + "writer1", + knownTabs, + true + ); + + await verifyParentState({}); + + // Apply the initial mutation using an explicit snapshot. The explicit + // snapshot here ensures that the parent process have received the changes. + await beginExplicitSnapshot(writerTab1); + await verifyParentState({}); + await applyMutations(writerTab1, [["key", "something"]]); + await verifyParentState({}); + await endExplicitSnapshot(writerTab1); + + await verifyParentState({ key: "something" }); + + // Begin an explicit snapshot in writerTab1, apply the first mutation in + // writerTab1 and checkpoint the explicit snapshot. + await beginExplicitSnapshot(writerTab1); + await verifyParentState({ key: "something" }); + await applyMutations(writerTab1, [["key", "somethingBigger"]]); + await verifyParentState({ key: "something" }); + await checkpointExplicitSnapshot(writerTab1); + + await verifyParentState({ key: "somethingBigger" }); + + // Apply the second mutation in writerTab1 and checkpoint the explicit + // snapshot. + await applyMutations(writerTab1, [["key", null]]); + await verifyParentState({ key: "somethingBigger" }); + await checkpointExplicitSnapshot(writerTab1); + + await verifyParentState({}); + + // Apply the third mutation in writerTab1 and end the explicit snapshot. + await applyMutations(writerTab1, [["otherKey", "something"]]); + await verifyParentState({}); + await endExplicitSnapshot(writerTab1); + + await verifyParentState({ otherKey: "something" }); + + // Verify the final state, it should match the state after the third mutation + // has been applied and "commited". An explicit snapshot is used. + await beginExplicitSnapshot(writerTab1); + await verifyParentState({ otherKey: "something" }); + await verifyState(writerTab1, { otherKey: "something" }); + await endExplicitSnapshot(writerTab1); + + await verifyParentState({ otherKey: "something" }); + + // Clean up. + await cleanupTabs(knownTabs); + + clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); +}); -- cgit v1.2.3