summaryrefslogtreecommitdiffstats
path: root/dom/tests/browser/browser_localStorage_e10s.js
blob: 0ecc49fe49a7c65ee7ea7a31dd4145f1471dc079 (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
const HELPER_PAGE_URL =
  "http://example.com/browser/dom/tests/browser/page_localstorage.html";
const HELPER_PAGE_ORIGIN = "http://example.com/";

let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
Services.scriptloader.loadSubScript(testDir + "/helper_localStorage.js", this);

/* import-globals-from helper_localStorage.js */

// We spin up a ton of child processes.
requestLongerTimeout(4);

/**
 * Verify the basics of our multi-e10s localStorage support.  We are focused on
 * whitebox testing two things.  When this is being written, broadcast filtering
 * is not in place, but the test is intended to attempt to verify that its
 * implementation does not break things.
 *
 * 1) That pages see the same localStorage state in a timely fashion when
 *    engaging in non-conflicting operations.  We are not testing races or
 *    conflict resolution; the spec does not cover that.
 *
 * 2) That there are no edge-cases related to when the Storage instance is
 *    created for the page or the StorageCache for the origin.  (StorageCache is
 *    what actually backs the Storage binding exposed to the page.)  This
 *    matters because the following reasons can exist for them to be created:
 *    - Preload, on the basis of knowing the origin uses localStorage.  The
 *      interesting edge case is when we have the same origin open in different
 *      processes and the origin starts using localStorage when it did not
 *      before.  Preload will not have instantiated bindings, which could impact
 *      correctness.
 *    - The page accessing localStorage for read or write purposes.  This is the
 *      obvious, boring one.
 *    - The page adding a "storage" listener.  This is less obvious and
 *      interacts with the preload edge-case mentioned above.  The page needs to
 *      hear "storage" events even if the page has not touched localStorage
 *      itself and its origin had nothing stored in localStorage when the page
 *      was created.
 *
 * We use the same simple child page in all tabs that:
 * - can be instructed to listen for and record "storage" events
 * - can be instructed to issue a series of localStorage writes
 * - can be instructed to return the current entire localStorage contents
 *
 * We open the 5 following tabs:
 * - Open a "writer" tab that does not listen for "storage" events and will
 *   issue only writes.
 * - Open a "listener" tab instructed to listen for "storage" events
 *   immediately.  We expect it to capture all events.
 * - Open an "reader" tab that does not listen for "storage" events and will
 *   only issue reads when instructed.
 * - Open a "lateWriteThenListen" tab that initially does nothing.  We will
 *   later tell it to issue a write and then listen for events to make sure it
 *   captures the later events.
 * - Open "lateOpenSeesPreload" tab after we've done everything and ensure that
 *   it preloads/precaches the data without us having touched localStorage or
 *   added an event listener.
 */
add_task(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      // Stop the preallocated process manager from speculatively creating
      // processes.  Our test explicitly asserts on whether preload happened or
      // not for each tab's process.  This information is loaded and latched by
      // the StorageDBParent constructor which the child process's
      // LocalStorageManager() constructor causes to be created via a call to
      // LocalStorageCache::StartDatabase().  Although the service is lazily
      // created and should not have been created prior to our opening the tab,
      // it's safest to ensure the process simply didn't exist before we ask for
      // it.
      //
      // This is done in conjunction with our use of forceNewProcess when
      // opening tabs.  There would be no point if we weren't also requesting a
      // new process.
      ["dom.ipc.processPrelaunch.enabled", false],
      // Enable LocalStorage's testing API so we can explicitly trigger a flush
      // when needed.
      ["dom.storage.testing", true],
    ],
  });

  // Ensure that there is no localstorage data or potential false positives for
  // localstorage preloads by forcing the origin to be cleared prior to the
  // start of our test.
  await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);

  // Make sure mOriginsHavingData gets updated.
  await triggerAndWaitForLocalStorageFlush();

  // - Open tabs.  Don't configure any of them yet.
  const knownTabs = new KnownTabs();
  const writerTab = await openTestTab(
    HELPER_PAGE_URL,
    "writer",
    knownTabs,
    true
  );
  const listenerTab = await openTestTab(
    HELPER_PAGE_URL,
    "listener",
    knownTabs,
    true
  );
  const readerTab = await openTestTab(
    HELPER_PAGE_URL,
    "reader",
    knownTabs,
    true
  );
  const lateWriteThenListenTab = await openTestTab(
    HELPER_PAGE_URL,
    "lateWriteThenListen",
    knownTabs,
    true
  );

  // Sanity check that preloading did not occur in the tabs.
  await verifyTabPreload(writerTab, false, HELPER_PAGE_ORIGIN);
  await verifyTabPreload(listenerTab, false, HELPER_PAGE_ORIGIN);
  await verifyTabPreload(readerTab, false, HELPER_PAGE_ORIGIN);

  // - Configure the tabs.
  const initialSentinel = "initial";
  const noSentinelCheck = null;
  await recordTabStorageEvents(listenerTab, initialSentinel);

  // - Issue the initial batch of writes and verify.
  info("initial writes");
  const initialWriteMutations = [
    // [key (null=clear), newValue (null=delete), oldValue (verification)]
    ["getsCleared", "1", null],
    ["alsoGetsCleared", "2", null],
    [null, null, null],
    ["stays", "3", null],
    ["clobbered", "pre", null],
    ["getsDeletedLater", "4", null],
    ["getsDeletedImmediately", "5", null],
    ["getsDeletedImmediately", null, "5"],
    ["alsoStays", "6", null],
    ["getsDeletedLater", null, "4"],
    ["clobbered", "post", "pre"],
  ];
  const initialWriteState = {
    stays: "3",
    clobbered: "post",
    alsoStays: "6",
  };

  await mutateTabStorage(writerTab, initialWriteMutations, initialSentinel);

  // We expect the writer tab to have the correct state because it just did the
  // writes.  We do not perform a sentinel-check because the writes should be
  // locally available and consistent.
  await verifyTabStorageState(writerTab, initialWriteState, noSentinelCheck);
  // We expect the listener tab to have heard all events despite preload not
  // having occurred and despite not issuing any reads or writes itself.  We
  // intentionally check the events before the state because we're most
  // interested in adding the listener having had a side-effect of subscribing
  // to changes for the process.
  //
  // We ensure it had a chance to hear all of the events because we told
  // recordTabStorageEvents to listen for the given sentinel.  The state check
  // then does not need to do a sentinel check.
  await verifyTabStorageEvents(
    listenerTab,
    initialWriteMutations,
    initialSentinel
  );
  await verifyTabStorageState(listenerTab, initialWriteState, noSentinelCheck);
  // We expect the reader tab to retrieve the current localStorage state from
  // the database.  Because of the above checks, we are confident that the
  // writes have hit PBackground and therefore that the (synchronous) state
  // retrieval contains all the data we need.  No sentinel-check is required.
  await verifyTabStorageState(readerTab, initialWriteState, noSentinelCheck);

  // - Issue second set of writes from lateWriteThenListen
  // This tests that our new tab that begins by issuing only writes is building
  // on top of the existing state (although we don't verify that until after the
  // next set of mutations).  We also verify that the initial "writerTab" that
  // was our first tab and started with only writes sees the writes, even though
  // it did not add an event listener.

  info("late writes");
  const lateWriteSentinel = "lateWrite";
  const lateWriteMutations = [
    ["lateStays", "10", null],
    ["lateClobbered", "latePre", null],
    ["lateDeleted", "11", null],
    ["lateClobbered", "lastPost", "latePre"],
    ["lateDeleted", null, "11"],
  ];
  const lateWriteState = Object.assign({}, initialWriteState, {
    lateStays: "10",
    lateClobbered: "lastPost",
  });

  await recordTabStorageEvents(listenerTab, lateWriteSentinel);

  await mutateTabStorage(
    lateWriteThenListenTab,
    lateWriteMutations,
    lateWriteSentinel
  );

  // Verify the writer tab saw the writes.  It has to wait for the sentinel to
  // appear before checking.
  await verifyTabStorageState(writerTab, lateWriteState, lateWriteSentinel);
  // Wait for the sentinel event before checking the events and then the state.
  await verifyTabStorageEvents(
    listenerTab,
    lateWriteMutations,
    lateWriteSentinel
  );
  await verifyTabStorageState(listenerTab, lateWriteState, noSentinelCheck);
  // We need to wait for the sentinel to show up for the reader.
  await verifyTabStorageState(readerTab, lateWriteState, lateWriteSentinel);

  // - Issue last set of writes from writerTab.
  info("last set of writes");
  const lastWriteSentinel = "lastWrite";
  const lastWriteMutations = [
    ["lastStays", "20", null],
    ["lastDeleted", "21", null],
    ["lastClobbered", "lastPre", null],
    ["lastClobbered", "lastPost", "lastPre"],
    ["lastDeleted", null, "21"],
  ];
  const lastWriteState = Object.assign({}, lateWriteState, {
    lastStays: "20",
    lastClobbered: "lastPost",
  });

  await recordTabStorageEvents(listenerTab, lastWriteSentinel);
  await recordTabStorageEvents(lateWriteThenListenTab, lastWriteSentinel);

  await mutateTabStorage(writerTab, lastWriteMutations, lastWriteSentinel);

  // The writer performed the writes, no need to wait for the sentinel.
  await verifyTabStorageState(writerTab, lastWriteState, noSentinelCheck);
  // Wait for the sentinel event to be received, then check.
  await verifyTabStorageEvents(
    listenerTab,
    lastWriteMutations,
    lastWriteSentinel
  );
  await verifyTabStorageState(listenerTab, lastWriteState, noSentinelCheck);
  // We need to wait for the sentinel to show up for the reader.
  await verifyTabStorageState(readerTab, lastWriteState, lastWriteSentinel);
  // Wait for the sentinel event to be received, then check.
  await verifyTabStorageEvents(
    lateWriteThenListenTab,
    lastWriteMutations,
    lastWriteSentinel
  );
  await verifyTabStorageState(
    lateWriteThenListenTab,
    lastWriteState,
    noSentinelCheck
  );

  // - Force a LocalStorage DB flush so mOriginsHavingData is updated.
  // mOriginsHavingData is only updated when the storage thread runs its
  // accumulated operations during the flush.  If we don't initiate and ensure
  // that a flush has occurred before moving on to the next step,
  // mOriginsHavingData may not include our origin when it's sent down to the
  // child process.
  info("flush to make preload check work");
  await triggerAndWaitForLocalStorageFlush();

  // - Open a fresh tab and make sure it sees the precache/preload
  info("late open preload check");
  const lateOpenSeesPreload = await openTestTab(
    HELPER_PAGE_URL,
    "lateOpenSeesPreload",
    knownTabs,
    true
  );
  await verifyTabPreload(lateOpenSeesPreload, true, HELPER_PAGE_ORIGIN);

  // - Clean up.
  await cleanupTabs(knownTabs);

  clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN);
});