summaryrefslogtreecommitdiffstats
path: root/dom/tests/browser/browser_localStorage_fis.js
blob: 5db8ec5eb5af1a751cdcdf79321dbce2451d8b18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
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);
});