tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_localStorage_snapshotting.js (25633B)


      1 const HELPER_PAGE_URL =
      2  "https://example.com/browser/dom/tests/browser/page_localstorage_snapshotting.html";
      3 const HELPER_PAGE_ORIGIN = "https://example.com/";
      4 
      5 /* import-globals-from helper_localStorage.js */
      6 
      7 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
      8 Services.scriptloader.loadSubScript(testDir + "/helper_localStorage.js", this);
      9 
     10 function clearOrigin() {
     11  let principal =
     12    Services.scriptSecurityManager.createContentPrincipalFromOrigin(
     13      HELPER_PAGE_ORIGIN
     14    );
     15  let request = Services.qms.clearStoragesForClient(principal, "ls", "default");
     16  let promise = new Promise(resolve => {
     17    request.callback = () => {
     18      resolve();
     19    };
     20  });
     21  return promise;
     22 }
     23 
     24 async function applyMutations(knownTab, mutations) {
     25  await SpecialPowers.spawn(
     26    knownTab.tab.linkedBrowser,
     27    [mutations],
     28    function (mutations) {
     29      return content.wrappedJSObject.applyMutations(
     30        Cu.cloneInto(mutations, content)
     31      );
     32    }
     33  );
     34 }
     35 
     36 async function verifyState(knownTab, expectedState) {
     37  let actualState = await SpecialPowers.spawn(
     38    knownTab.tab.linkedBrowser,
     39    [],
     40    function () {
     41      return content.wrappedJSObject.getState();
     42    }
     43  );
     44 
     45  for (let [expectedKey, expectedValue] of Object.entries(expectedState)) {
     46    ok(actualState.hasOwnProperty(expectedKey), "key present: " + expectedKey);
     47    is(actualState[expectedKey], expectedValue, "value correct");
     48  }
     49  for (let actualKey of Object.keys(actualState)) {
     50    if (!expectedState.hasOwnProperty(actualKey)) {
     51      ok(false, "actual state has key it shouldn't have: " + actualKey);
     52    }
     53  }
     54 }
     55 
     56 async function getKeys(knownTab) {
     57  let keys = await SpecialPowers.spawn(
     58    knownTab.tab.linkedBrowser,
     59    [],
     60    function () {
     61      return content.wrappedJSObject.getKeys();
     62    }
     63  );
     64  return keys;
     65 }
     66 
     67 async function beginExplicitSnapshot(knownTab) {
     68  await SpecialPowers.spawn(knownTab.tab.linkedBrowser, [], function () {
     69    return content.wrappedJSObject.beginExplicitSnapshot();
     70  });
     71 }
     72 
     73 async function checkpointExplicitSnapshot(knownTab) {
     74  await SpecialPowers.spawn(knownTab.tab.linkedBrowser, [], function () {
     75    return content.wrappedJSObject.checkpointExplicitSnapshot();
     76  });
     77 }
     78 
     79 async function endExplicitSnapshot(knownTab) {
     80  await SpecialPowers.spawn(knownTab.tab.linkedBrowser, [], function () {
     81    return content.wrappedJSObject.endExplicitSnapshot();
     82  });
     83 }
     84 
     85 async function verifyHasSnapshot(knownTab, expectedHasSnapshot) {
     86  let hasSnapshot = await SpecialPowers.spawn(
     87    knownTab.tab.linkedBrowser,
     88    [],
     89    function () {
     90      return content.wrappedJSObject.getHasSnapshot();
     91    }
     92  );
     93  is(hasSnapshot, expectedHasSnapshot, "Correct has snapshot");
     94 }
     95 
     96 async function verifySnapshotUsage(knownTab, expectedSnapshotUsage) {
     97  let snapshotUsage = await SpecialPowers.spawn(
     98    knownTab.tab.linkedBrowser,
     99    [],
    100    function () {
    101      return content.wrappedJSObject.getSnapshotUsage();
    102    }
    103  );
    104  is(snapshotUsage, expectedSnapshotUsage, "Correct snapshot usage");
    105 }
    106 
    107 async function verifyParentState(expectedState) {
    108  let principal =
    109    Services.scriptSecurityManager.createContentPrincipalFromOrigin(
    110      HELPER_PAGE_ORIGIN
    111    );
    112 
    113  let actualState = await Services.domStorageManager.getState(principal);
    114 
    115  for (let [expectedKey, expectedValue] of Object.entries(expectedState)) {
    116    ok(actualState.hasOwnProperty(expectedKey), "key present: " + expectedKey);
    117    is(actualState[expectedKey], expectedValue, "value correct");
    118  }
    119  for (let actualKey of Object.keys(actualState)) {
    120    if (!expectedState.hasOwnProperty(actualKey)) {
    121      ok(false, "actual state has key it shouldn't have: " + actualKey);
    122    }
    123  }
    124 }
    125 
    126 // We spin up a ton of child processes.
    127 requestLongerTimeout(6);
    128 
    129 /**
    130 * Verify snapshotting of our localStorage implementation in multi-e10s setup.
    131 */
    132 add_task(async function () {
    133  if (!Services.domStorageManager.nextGenLocalStorageEnabled) {
    134    ok(true, "Test ignored when the next gen local storage is not enabled.");
    135    return;
    136  }
    137 
    138  await SpecialPowers.pushPrefEnv({
    139    set: [
    140      // Enable LocalStorage's testing API so we can explicitly create
    141      // snapshots when needed.
    142      ["dom.storage.testing", true],
    143      // Force multiple web and webIsolated content processes so that the
    144      // multi-e10s logic works correctly.
    145      ["dom.ipc.processCount", 8],
    146      ["dom.ipc.processCount.webIsolated", 4],
    147    ],
    148  });
    149 
    150  // Ensure that there is no localstorage data by forcing the origin to be
    151  // cleared prior to the start of our test..
    152  await clearOrigin();
    153 
    154  // - Open tabs.  Don't configure any of them yet.
    155  const knownTabs = new KnownTabs();
    156  const writerTab1 = await openTestTab(
    157    HELPER_PAGE_URL,
    158    "writer1",
    159    knownTabs,
    160    true
    161  );
    162  const writerTab2 = await openTestTab(
    163    HELPER_PAGE_URL,
    164    "writer2",
    165    knownTabs,
    166    true
    167  );
    168  const readerTab1 = await openTestTab(
    169    HELPER_PAGE_URL,
    170    "reader1",
    171    knownTabs,
    172    true
    173  );
    174  const readerTab2 = await openTestTab(
    175    HELPER_PAGE_URL,
    176    "reader2",
    177    knownTabs,
    178    true
    179  );
    180 
    181  const initialMutations = [
    182    [null, null],
    183    ["key1", "initial1"],
    184    ["key2", "initial2"],
    185    ["key3", "initial3"],
    186    ["key5", "initial5"],
    187    ["key6", "initial6"],
    188    ["key7", "initial7"],
    189    ["key8", "initial8"],
    190  ];
    191 
    192  const initialState = {
    193    key1: "initial1",
    194    key2: "initial2",
    195    key3: "initial3",
    196    key5: "initial5",
    197    key6: "initial6",
    198    key7: "initial7",
    199    key8: "initial8",
    200  };
    201 
    202  let sizeOfOneKey;
    203  let sizeOfOneValue;
    204  let sizeOfOneItem;
    205  let sizeOfKeys = 0;
    206  let sizeOfItems = 0;
    207 
    208  let entries = Object.entries(initialState);
    209  for (let i = 0; i < entries.length; i++) {
    210    let entry = entries[i];
    211    let sizeOfKey = entry[0].length;
    212    let sizeOfValue = entry[1].length;
    213    let sizeOfItem = sizeOfKey + sizeOfValue;
    214    if (i == 0) {
    215      sizeOfOneKey = sizeOfKey;
    216      sizeOfOneValue = sizeOfValue;
    217      sizeOfOneItem = sizeOfItem;
    218    }
    219    sizeOfKeys += sizeOfKey;
    220    sizeOfItems += sizeOfItem;
    221  }
    222 
    223  info("Size of one key is " + sizeOfOneKey);
    224  info("Size of one value is " + sizeOfOneValue);
    225  info("Size of one item is " + sizeOfOneItem);
    226  info("Size of keys is " + sizeOfKeys);
    227  info("Size of items is " + sizeOfItems);
    228 
    229  const prefillValues = [
    230    // Zero prefill (prefill disabled)
    231    0,
    232    // Less than one key length prefill
    233    sizeOfOneKey - 1,
    234    // Greater than one key length and less than one item length prefill
    235    sizeOfOneKey + 1,
    236    // Precisely one item length prefill
    237    sizeOfOneItem,
    238    // Precisely two times one item length prefill
    239    2 * sizeOfOneItem,
    240    // Precisely size of keys prefill
    241    sizeOfKeys,
    242    // Less than size of keys plus one value length prefill
    243    sizeOfKeys + sizeOfOneValue - 1,
    244    // Precisely size of keys plus one value length prefill
    245    sizeOfKeys + sizeOfOneValue,
    246    // Greater than size of keys plus one value length and less than size of
    247    // keys plus two times one value length prefill
    248    sizeOfKeys + sizeOfOneValue + 1,
    249    // Precisely size of keys plus two times one value length prefill
    250    sizeOfKeys + 2 * sizeOfOneValue,
    251    // Precisely size of keys plus three times one value length prefill
    252    sizeOfKeys + 3 * sizeOfOneValue,
    253    // Precisely size of keys plus four times one value length prefill
    254    sizeOfKeys + 4 * sizeOfOneValue,
    255    // Precisely size of keys plus five times one value length prefill
    256    sizeOfKeys + 5 * sizeOfOneValue,
    257    // Precisely size of keys plus six times one value length prefill
    258    sizeOfKeys + 6 * sizeOfOneValue,
    259    // Precisely size of items prefill
    260    sizeOfItems,
    261    // Unlimited prefill
    262    -1,
    263  ];
    264 
    265  for (let prefillValue of prefillValues) {
    266    info("Setting prefill value to " + prefillValue);
    267 
    268    await SpecialPowers.pushPrefEnv({
    269      set: [["dom.storage.snapshot_prefill", prefillValue]],
    270    });
    271 
    272    const gradualPrefillValues = [
    273      // Zero gradual prefill
    274      0,
    275      // Less than one key length gradual prefill
    276      sizeOfOneKey - 1,
    277      // Greater than one key length and less than one item length gradual
    278      // prefill
    279      sizeOfOneKey + 1,
    280      // Precisely one item length gradual prefill
    281      sizeOfOneItem,
    282      // Precisely two times one item length gradual prefill
    283      2 * sizeOfOneItem,
    284      // Precisely three times one item length gradual prefill
    285      3 * sizeOfOneItem,
    286      // Precisely four times one item length gradual prefill
    287      4 * sizeOfOneItem,
    288      // Precisely five times one item length gradual prefill
    289      5 * sizeOfOneItem,
    290      // Precisely six times one item length gradual prefill
    291      6 * sizeOfOneItem,
    292      // Precisely size of items prefill
    293      sizeOfItems,
    294      // Unlimited gradual prefill
    295      -1,
    296    ];
    297 
    298    for (let gradualPrefillValue of gradualPrefillValues) {
    299      info("Setting gradual prefill value to " + gradualPrefillValue);
    300 
    301      await SpecialPowers.pushPrefEnv({
    302        set: [["dom.storage.snapshot_gradual_prefill", gradualPrefillValue]],
    303      });
    304 
    305      info("Stage 1");
    306 
    307      const setRemoveMutations1 = [
    308        ["key0", "setRemove10"],
    309        ["key1", "setRemove11"],
    310        ["key2", null],
    311        ["key3", "setRemove13"],
    312        ["key4", "setRemove14"],
    313        ["key5", "setRemove15"],
    314        ["key6", "setRemove16"],
    315        ["key7", "setRemove17"],
    316        ["key8", null],
    317        ["key9", "setRemove19"],
    318      ];
    319 
    320      const setRemoveState1 = {
    321        key0: "setRemove10",
    322        key1: "setRemove11",
    323        key3: "setRemove13",
    324        key4: "setRemove14",
    325        key5: "setRemove15",
    326        key6: "setRemove16",
    327        key7: "setRemove17",
    328        key9: "setRemove19",
    329      };
    330 
    331      const setRemoveMutations2 = [
    332        ["key0", "setRemove20"],
    333        ["key1", null],
    334        ["key2", "setRemove22"],
    335        ["key3", "setRemove23"],
    336        ["key4", "setRemove24"],
    337        ["key5", "setRemove25"],
    338        ["key6", "setRemove26"],
    339        ["key7", null],
    340        ["key8", "setRemove28"],
    341        ["key9", "setRemove29"],
    342      ];
    343 
    344      const setRemoveState2 = {
    345        key0: "setRemove20",
    346        key2: "setRemove22",
    347        key3: "setRemove23",
    348        key4: "setRemove24",
    349        key5: "setRemove25",
    350        key6: "setRemove26",
    351        key8: "setRemove28",
    352        key9: "setRemove29",
    353      };
    354 
    355      // Apply initial mutations using an explicit snapshot. The explicit
    356      // snapshot here ensures that the parent process have received the
    357      // changes.
    358      await beginExplicitSnapshot(writerTab1);
    359      await applyMutations(writerTab1, initialMutations);
    360      await endExplicitSnapshot(writerTab1);
    361 
    362      // Begin explicit snapshots in all tabs except readerTab2. All these tabs
    363      // should see the initial state regardless what other tabs are doing.
    364      await beginExplicitSnapshot(writerTab1);
    365      await beginExplicitSnapshot(writerTab2);
    366      await beginExplicitSnapshot(readerTab1);
    367 
    368      // Apply first array of set/remove mutations in writerTab1 and end the
    369      // explicit snapshot. This will trigger saving of values in other active
    370      // snapshots.
    371      await applyMutations(writerTab1, setRemoveMutations1);
    372      await endExplicitSnapshot(writerTab1);
    373 
    374      // Begin an explicit snapshot in readerTab2. writerTab1 already ended its
    375      // explicit snapshot, so readerTab2 should see mutations done by
    376      // writerTab1.
    377      await beginExplicitSnapshot(readerTab2);
    378 
    379      // Apply second array of set/remove mutations in writerTab2 and end the
    380      // explicit snapshot. This will trigger saving of values in other active
    381      // snapshots, but only if they haven't been saved already.
    382      await applyMutations(writerTab2, setRemoveMutations2);
    383      await endExplicitSnapshot(writerTab2);
    384 
    385      // Verify state in readerTab1, it should match the initial state.
    386      await verifyState(readerTab1, initialState);
    387      await endExplicitSnapshot(readerTab1);
    388 
    389      // Verify state in readerTab2, it should match the state after the first
    390      // array of set/remove mutatations have been applied and "commited".
    391      await verifyState(readerTab2, setRemoveState1);
    392      await endExplicitSnapshot(readerTab2);
    393 
    394      // Verify final state, it should match the state after the second array of
    395      // set/remove mutation have been applied and "commited". An explicit
    396      // snapshot is used.
    397      await beginExplicitSnapshot(readerTab1);
    398      await verifyState(readerTab1, setRemoveState2);
    399      await endExplicitSnapshot(readerTab1);
    400 
    401      info("Stage 2");
    402 
    403      const setRemoveClearMutations1 = [
    404        ["key0", "setRemoveClear10"],
    405        ["key1", null],
    406        [null, null],
    407      ];
    408 
    409      const setRemoveClearState1 = {};
    410 
    411      const setRemoveClearMutations2 = [
    412        ["key8", null],
    413        ["key9", "setRemoveClear29"],
    414        [null, null],
    415      ];
    416 
    417      const setRemoveClearState2 = {};
    418 
    419      // This is very similar to previous stage except that in addition to
    420      // set/remove, the clear operation is involved too.
    421      await beginExplicitSnapshot(writerTab1);
    422      await applyMutations(writerTab1, initialMutations);
    423      await endExplicitSnapshot(writerTab1);
    424 
    425      await beginExplicitSnapshot(writerTab1);
    426      await beginExplicitSnapshot(writerTab2);
    427      await beginExplicitSnapshot(readerTab1);
    428 
    429      await applyMutations(writerTab1, setRemoveClearMutations1);
    430      await endExplicitSnapshot(writerTab1);
    431 
    432      await beginExplicitSnapshot(readerTab2);
    433 
    434      await applyMutations(writerTab2, setRemoveClearMutations2);
    435      await endExplicitSnapshot(writerTab2);
    436 
    437      await verifyState(readerTab1, initialState);
    438      await endExplicitSnapshot(readerTab1);
    439 
    440      await verifyState(readerTab2, setRemoveClearState1);
    441      await endExplicitSnapshot(readerTab2);
    442 
    443      await beginExplicitSnapshot(readerTab1);
    444      await verifyState(readerTab1, setRemoveClearState2);
    445      await endExplicitSnapshot(readerTab1);
    446 
    447      info("Stage 3");
    448 
    449      const changeOrderMutations = [
    450        ["key1", null],
    451        ["key2", null],
    452        ["key3", null],
    453        ["key5", null],
    454        ["key6", null],
    455        ["key7", null],
    456        ["key8", null],
    457        ["key8", "initial8"],
    458        ["key7", "initial7"],
    459        ["key6", "initial6"],
    460        ["key5", "initial5"],
    461        ["key3", "initial3"],
    462        ["key2", "initial2"],
    463        ["key1", "initial1"],
    464      ];
    465 
    466      // Apply initial mutations using an explicit snapshot. The explicit
    467      // snapshot here ensures that the parent process have received the
    468      // changes.
    469      await beginExplicitSnapshot(writerTab1);
    470      await applyMutations(writerTab1, initialMutations);
    471      await endExplicitSnapshot(writerTab1);
    472 
    473      // Begin explicit snapshots in all tabs except writerTab2 which is not
    474      // used in this stage. All these tabs should see the initial order
    475      // regardless what other tabs are doing.
    476      await beginExplicitSnapshot(readerTab1);
    477      await beginExplicitSnapshot(writerTab1);
    478      await beginExplicitSnapshot(readerTab2);
    479 
    480      // Get all keys in readerTab1 and end the explicit snapshot. No mutations
    481      // have been applied yet.
    482      let tab1Keys = await getKeys(readerTab1);
    483      await endExplicitSnapshot(readerTab1);
    484 
    485      // Apply mutations that change the order of keys and end the explicit
    486      // snapshot. The state is unchanged. This will trigger saving of key order
    487      // in other active snapshots, but only if the order hasn't been saved
    488      // already.
    489      await applyMutations(writerTab1, changeOrderMutations);
    490      await endExplicitSnapshot(writerTab1);
    491 
    492      // Get all keys in readerTab2 and end the explicit snapshot. Change order
    493      // mutations have been applied, but the order should stay unchanged.
    494      let tab2Keys = await getKeys(readerTab2);
    495      await endExplicitSnapshot(readerTab2);
    496 
    497      // Verify the key order is the same.
    498      is(tab2Keys.length, tab1Keys.length, "Correct keys length");
    499      for (let i = 0; i < tab2Keys.length; i++) {
    500        is(tab2Keys[i], tab1Keys[i], "Correct key");
    501      }
    502 
    503      // Verify final state, it should match the initial state since applied
    504      // mutations only changed the key order. An explicit snapshot is used.
    505      await beginExplicitSnapshot(readerTab1);
    506      await verifyState(readerTab1, initialState);
    507      await endExplicitSnapshot(readerTab1);
    508    }
    509  }
    510 
    511  // - Clean up.
    512  await cleanupTabs(knownTabs);
    513 
    514  clearOrigin();
    515 });
    516 
    517 /**
    518 * Verify that snapshots are able to work with negative usage. This can happen
    519 * when there's an item stored in localStorage with given size and then two
    520 * snaphots (created at the same time) mutate the item. The first one replases
    521 * it with something bigger and the other one removes it.
    522 */
    523 add_task(async function () {
    524  if (!Services.domStorageManager.nextGenLocalStorageEnabled) {
    525    ok(true, "Test ignored when the next gen local storage is not enabled.");
    526    return;
    527  }
    528 
    529  await SpecialPowers.pushPrefEnv({
    530    set: [
    531      // Force multiple web and webIsolated content processes so that the
    532      // multi-e10s logic works correctly.
    533      ["dom.ipc.processCount", 4],
    534      ["dom.ipc.processCount.webIsolated", 2],
    535      // Disable snapshot peak usage pre-incrementation to make the testing
    536      // easier.
    537      ["dom.storage.snapshot_peak_usage.initial_preincrement", 0],
    538      ["dom.storage.snapshot_peak_usage.reduced_initial_preincrement", 0],
    539      ["dom.storage.snapshot_peak_usage.gradual_preincrement", 0],
    540      ["dom.storage.snapshot_peak_usage.reuced_gradual_preincrement", 0],
    541      // Enable LocalStorage's testing API so we can explicitly create
    542      // snapshots when needed.
    543      ["dom.storage.testing", true],
    544    ],
    545  });
    546 
    547  // Ensure that there is no localstorage data by forcing the origin to be
    548  // cleared prior to the start of our test.
    549  await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
    550 
    551  // Open tabs.  Don't configure any of them yet.
    552  const knownTabs = new KnownTabs();
    553  const writerTab1 = await openTestTab(
    554    HELPER_PAGE_URL,
    555    "writer1",
    556    knownTabs,
    557    true
    558  );
    559  const writerTab2 = await openTestTab(
    560    HELPER_PAGE_URL,
    561    "writer2",
    562    knownTabs,
    563    true
    564  );
    565 
    566  // Apply the initial mutation using an explicit snapshot. The explicit
    567  // snapshot here ensures that the parent process have received the changes.
    568  await beginExplicitSnapshot(writerTab1);
    569  await applyMutations(writerTab1, [["key", "something"]]);
    570  await endExplicitSnapshot(writerTab1);
    571 
    572  // Begin explicit snapshots in both tabs. Both tabs should see the initial
    573  // state.
    574  await beginExplicitSnapshot(writerTab1);
    575  await beginExplicitSnapshot(writerTab2);
    576 
    577  // Apply the first mutation in writerTab1 and end the explicit snapshot.
    578  await applyMutations(writerTab1, [["key", "somethingBigger"]]);
    579  await endExplicitSnapshot(writerTab1);
    580 
    581  // Apply the second mutation in writerTab2 and end the explicit snapshot.
    582  await applyMutations(writerTab2, [["key", null]]);
    583  await endExplicitSnapshot(writerTab2);
    584 
    585  // Verify the final state, it should match the state after the second
    586  // mutation has been applied and "commited". An explicit snapshot is used.
    587  await beginExplicitSnapshot(writerTab1);
    588  await verifyState(writerTab1, {});
    589  await endExplicitSnapshot(writerTab1);
    590 
    591  // Clean up.
    592  await cleanupTabs(knownTabs);
    593 
    594  clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
    595 });
    596 
    597 /**
    598 * Verify that snapshot usage is correctly updated after each operation.
    599 */
    600 add_task(async function () {
    601  if (!Services.domStorageManager.nextGenLocalStorageEnabled) {
    602    ok(true, "Test ignored when the next gen local storage is not enabled.");
    603    return;
    604  }
    605 
    606  await SpecialPowers.pushPrefEnv({
    607    set: [
    608      // Force multiple web and webIsolated content processes so that the
    609      // multi-e10s logic works correctly.
    610      ["dom.ipc.processCount", 4],
    611      ["dom.ipc.processCount.webIsolated", 2],
    612      // Disable snapshot peak usage pre-incrementation to make the testing
    613      // easier.
    614      ["dom.storage.snapshot_peak_usage.initial_preincrement", 0],
    615      ["dom.storage.snapshot_peak_usage.reduced_initial_preincrement", 0],
    616      ["dom.storage.snapshot_peak_usage.gradual_preincrement", 0],
    617      ["dom.storage.snapshot_peak_usage.reuced_gradual_preincrement", 0],
    618      // Enable LocalStorage's testing API so we can explicitly create
    619      // snapshots when needed.
    620      ["dom.storage.testing", true],
    621    ],
    622  });
    623 
    624  // Ensure that there is no localstorage data by forcing the origin to be
    625  // cleared prior to the start of our test.
    626  await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
    627 
    628  // Open tabs.  Don't configure any of them yet.
    629  const knownTabs = new KnownTabs();
    630  const writerTab1 = await openTestTab(
    631    HELPER_PAGE_URL,
    632    "writer1",
    633    knownTabs,
    634    true
    635  );
    636  const writerTab2 = await openTestTab(
    637    HELPER_PAGE_URL,
    638    "writer2",
    639    knownTabs,
    640    true
    641  );
    642 
    643  // Apply the initial mutation using an explicit snapshot. The explicit
    644  // snapshot here ensures that the parent process have received the changes.
    645  await beginExplicitSnapshot(writerTab1);
    646  await verifySnapshotUsage(writerTab1, 0);
    647  await applyMutations(writerTab1, [["key", "something"]]);
    648  await verifySnapshotUsage(writerTab1, 12);
    649  await endExplicitSnapshot(writerTab1);
    650  await verifyHasSnapshot(writerTab1, false);
    651 
    652  // Begin an explicit snapshot in writerTab1 and apply the first mutatation
    653  // in it.
    654  await beginExplicitSnapshot(writerTab1);
    655  await verifySnapshotUsage(writerTab1, 12);
    656  await applyMutations(writerTab1, [["key", "somethingBigger"]]);
    657  await verifySnapshotUsage(writerTab1, 18);
    658 
    659  // Begin an explicit snapshot in writerTab2 and apply the second mutatation
    660  // in it.
    661  await beginExplicitSnapshot(writerTab2);
    662  await verifySnapshotUsage(writerTab2, 18);
    663  await applyMutations(writerTab2, [[null, null]]);
    664  await verifySnapshotUsage(writerTab2, 6);
    665 
    666  // End explicit snapshots in both tabs.
    667  await endExplicitSnapshot(writerTab1);
    668  await verifyHasSnapshot(writerTab1, false);
    669  await endExplicitSnapshot(writerTab2);
    670  await verifyHasSnapshot(writerTab2, false);
    671 
    672  // Verify the final state, it should match the state after the second
    673  // mutation has been applied and "commited". An explicit snapshot is used.
    674  await beginExplicitSnapshot(writerTab1);
    675  await verifySnapshotUsage(writerTab1, 0);
    676  await verifyState(writerTab1, {});
    677  await endExplicitSnapshot(writerTab1);
    678  await verifyHasSnapshot(writerTab1, false);
    679 
    680  // Clean up.
    681  await cleanupTabs(knownTabs);
    682 
    683  clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
    684 });
    685 
    686 /**
    687 * Verify that datastore in the parent is correctly updated after a checkpoint.
    688 */
    689 add_task(async function () {
    690  if (!Services.domStorageManager.nextGenLocalStorageEnabled) {
    691    ok(true, "Test ignored when the next gen local storage is not enabled.");
    692    return;
    693  }
    694 
    695  await SpecialPowers.pushPrefEnv({
    696    set: [
    697      // Force multiple web and webIsolated content processes so that the
    698      // multi-e10s logic works correctly.
    699      ["dom.ipc.processCount", 4],
    700      ["dom.ipc.processCount.webIsolated", 2],
    701      // Enable LocalStorage's testing API so we can explicitly create
    702      // snapshots when needed.
    703      ["dom.storage.testing", true],
    704    ],
    705  });
    706 
    707  // Ensure that there is no localstorage data by forcing the origin to be
    708  // cleared prior to the start of our test.
    709  await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
    710 
    711  // Open tabs.  Don't configure any of them yet.
    712  const knownTabs = new KnownTabs();
    713  const writerTab1 = await openTestTab(
    714    HELPER_PAGE_URL,
    715    "writer1",
    716    knownTabs,
    717    true
    718  );
    719 
    720  await verifyParentState({});
    721 
    722  // Apply the initial mutation using an explicit snapshot. The explicit
    723  // snapshot here ensures that the parent process have received the changes.
    724  await beginExplicitSnapshot(writerTab1);
    725  await verifyParentState({});
    726  await applyMutations(writerTab1, [["key", "something"]]);
    727  await verifyParentState({});
    728  await endExplicitSnapshot(writerTab1);
    729 
    730  await verifyParentState({ key: "something" });
    731 
    732  // Begin an explicit snapshot in writerTab1, apply the first mutation in
    733  // writerTab1 and checkpoint the explicit snapshot.
    734  await beginExplicitSnapshot(writerTab1);
    735  await verifyParentState({ key: "something" });
    736  await applyMutations(writerTab1, [["key", "somethingBigger"]]);
    737  await verifyParentState({ key: "something" });
    738  await checkpointExplicitSnapshot(writerTab1);
    739 
    740  await verifyParentState({ key: "somethingBigger" });
    741 
    742  // Apply the second mutation in writerTab1 and checkpoint the explicit
    743  // snapshot.
    744  await applyMutations(writerTab1, [["key", null]]);
    745  await verifyParentState({ key: "somethingBigger" });
    746  await checkpointExplicitSnapshot(writerTab1);
    747 
    748  await verifyParentState({});
    749 
    750  // Apply the third mutation in writerTab1 and end the explicit snapshot.
    751  await applyMutations(writerTab1, [["otherKey", "something"]]);
    752  await verifyParentState({});
    753  await endExplicitSnapshot(writerTab1);
    754 
    755  await verifyParentState({ otherKey: "something" });
    756 
    757  // Verify the final state, it should match the state after the third mutation
    758  // has been applied and "commited". An explicit snapshot is used.
    759  await beginExplicitSnapshot(writerTab1);
    760  await verifyParentState({ otherKey: "something" });
    761  await verifyState(writerTab1, { otherKey: "something" });
    762  await endExplicitSnapshot(writerTab1);
    763 
    764  await verifyParentState({ otherKey: "something" });
    765 
    766  // Clean up.
    767  await cleanupTabs(knownTabs);
    768 
    769  clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
    770 });