browser_localStorage_fis.js (20528B)
1 const HELPER_PAGE_URL = 2 "https://example.com/browser/dom/tests/browser/page_localstorage.html"; 3 const HELPER_PAGE_COOP_COEP_URL = 4 "https://example.com/browser/dom/tests/browser/page_localstorage_coop+coep.html"; 5 const HELPER_PAGE_ORIGIN = "https://example.com/"; 6 7 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); 8 Services.scriptloader.loadSubScript(testDir + "/helper_localStorage.js", this); 9 10 /* import-globals-from helper_localStorage.js */ 11 12 // We spin up a ton of child processes. 13 requestLongerTimeout(4); 14 15 /** 16 * Verify the basics of our multi-e10s localStorage support with fission. 17 * We are focused on whitebox testing two things. 18 * When this is being written, broadcast filtering is not in place, but the test 19 * is intended to attempt to verify that its implementation does not break things. 20 * 21 * 1) That pages see the same localStorage state in a timely fashion when 22 * engaging in non-conflicting operations. We are not testing races or 23 * conflict resolution; the spec does not cover that. 24 * 25 * 2) That there are no edge-cases related to when the Storage instance is 26 * created for the page or the StorageCache for the origin. (StorageCache is 27 * what actually backs the Storage binding exposed to the page.) This 28 * matters because the following reasons can exist for them to be created: 29 * - Preload, on the basis of knowing the origin uses localStorage. The 30 * interesting edge case is when we have the same origin open in different 31 * processes and the origin starts using localStorage when it did not 32 * before. Preload will not have instantiated bindings, which could impact 33 * correctness. 34 * - The page accessing localStorage for read or write purposes. This is the 35 * obvious, boring one. 36 * - The page adding a "storage" listener. This is less obvious and 37 * interacts with the preload edge-case mentioned above. The page needs to 38 * hear "storage" events even if the page has not touched localStorage 39 * itself and its origin had nothing stored in localStorage when the page 40 * was created. 41 * 42 * According to current fission implementation, same origin pages will be loaded 43 * by the same process, which process type is webIsolated=. And thanks to 44 * Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers support, 45 * it is possible to load the same origin page by a special process, which type 46 * is webCOOP+COEP=. These are the only two processes can be used to test 47 * localStroage consistency between tabs in different tabs. 48 * 49 * We use the two child pages for testing, page_localstorage.html and 50 * page_localstorage_coop+coep.html. Their content are the same, but 51 * page_localstorage_coop+coep.html will be loaded with its ^headers^ file. 52 * These pages provide followings 53 * - can be instructed to listen for and record "storage" events 54 * - can be instructed to issue a series of localStorage writes 55 * - can be instructed to return the current entire localStorage contents 56 * 57 * To test localStorage consistency, four subtests are used. 58 * Test case 1: one writer tab and one reader tab 59 * The writer tab issues a series of write operations, then verify the 60 * localStorage contents from the reader tab. 61 * 62 * Test case 2: one writer tab and one listener tab 63 * The writer tab issues a series of write operations, then verify the recorded 64 * storage events from the listener tab. 65 * 66 * Test case 3: one writeThenRead tab and one readThenWrite tab 67 * The writeThenRead first issues a series write of operations, and then verify 68 * the recorded storage events and localStorage contents from readThenWrite 69 * tab. After that readThenWrite tab issues a series of write operations, then 70 * verify the results from writeThenRead tab. 71 * 72 * Test case 4: one writer tab and one lateOpenSeesPreload tab 73 * The writer tab issues a series write of operations. Then open the 74 * lateOpenSeesPreload tab to make sure preloads exists. 75 */ 76 77 /** 78 * Shared constants for test cases 79 */ 80 const noSentinelCheck = null; 81 const initialSentinel = "initial"; 82 const initialWriteMutations = [ 83 // [key (null=clear), newValue (null=delete), oldValue (verification)] 84 ["getsCleared", "1", null], 85 ["alsoGetsCleared", "2", null], 86 [null, null, null], 87 ["stays", "3", null], 88 ["clobbered", "pre", null], 89 ["getsDeletedLater", "4", null], 90 ["getsDeletedImmediately", "5", null], 91 ["getsDeletedImmediately", null, "5"], 92 ["alsoStays", "6", null], 93 ["getsDeletedLater", null, "4"], 94 ["clobbered", "post", "pre"], 95 ]; 96 const initialWriteState = { 97 stays: "3", 98 clobbered: "post", 99 alsoStays: "6", 100 }; 101 102 const lastWriteSentinel = "lastWrite"; 103 const lastWriteMutations = [ 104 ["lastStays", "20", null], 105 ["lastDeleted", "21", null], 106 ["lastClobbered", "lastPre", null], 107 ["lastClobbered", "lastPost", "lastPre"], 108 ["lastDeleted", null, "21"], 109 ]; 110 const lastWriteState = Object.assign({}, initialWriteState, { 111 lastStays: "20", 112 lastClobbered: "lastPost", 113 }); 114 115 /** 116 * Test case 1: one writer tab and one reader tab 117 * Test steps 118 * 1. Clear origin storage to make sure no data and preloads. 119 * 2. Open the writer and reader tabs and verify preloads do not exist. 120 * Open writer tab in webIsolated= process 121 * Open reader tab in webCOOP+COEP= process 122 * 3. Issue a series write operations in the writer tab, and then verify the 123 * storage state on the tab. 124 * 4. Verify the storage state on the reader tab. 125 * 5. Issue another series write operations in the writer tab, and then verify 126 * the storage state on the tab. 127 * 6. Verify the storage state on the reader tab. 128 * 7. Close tabs and clear origin storage. 129 */ 130 add_task(async function () { 131 if (!Services.domStorageManager.nextGenLocalStorageEnabled) { 132 ok(true, "Test ignored when the next gen local storage is not enabled."); 133 return; 134 } 135 136 await SpecialPowers.pushPrefEnv({ 137 set: [ 138 // Stop the preallocated process manager from speculatively creating 139 // processes. Our test explicitly asserts on whether preload happened or 140 // not for each tab's process. This information is loaded and latched by 141 // the StorageDBParent constructor which the child process's 142 // LocalStorageManager() constructor causes to be created via a call to 143 // LocalStorageCache::StartDatabase(). Although the service is lazily 144 // created and should not have been created prior to our opening the tab, 145 // it's safest to ensure the process simply didn't exist before we ask for 146 // it. 147 // 148 // This is done in conjunction with our use of forceNewProcess when 149 // opening tabs. There would be no point if we weren't also requesting a 150 // new process. 151 ["dom.ipc.processPrelaunch.enabled", false], 152 // Enable LocalStorage's testing API so we can explicitly trigger a flush 153 // when needed. 154 ["dom.storage.testing", true], 155 ], 156 }); 157 158 // Ensure that there is no localstorage data or potential false positives for 159 // localstorage preloads by forcing the origin to be cleared prior to the 160 // start of our test. 161 await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 162 163 // Make sure mOriginsHavingData gets updated. 164 await triggerAndWaitForLocalStorageFlush(); 165 166 // - Open tabs. Don't configure any of them yet. 167 const knownTabs = new KnownTabs(); 168 const writerTab = await openTestTab( 169 HELPER_PAGE_URL, 170 "writer", 171 knownTabs, 172 true 173 ); 174 const readerTab = await openTestTab( 175 HELPER_PAGE_COOP_COEP_URL, 176 "reader", 177 knownTabs, 178 true 179 ); 180 // Sanity check that preloading did not occur in the tabs. 181 await verifyTabPreload(writerTab, false, HELPER_PAGE_ORIGIN); 182 await verifyTabPreload(readerTab, false, HELPER_PAGE_ORIGIN); 183 184 // - Issue the initial batch of writes and verify. 185 info("initial writes"); 186 await mutateTabStorage(writerTab, initialWriteMutations, initialSentinel); 187 188 // We expect the writer tab to have the correct state because it just did the 189 // writes. We do not perform a sentinel-check because the writes should be 190 // locally available and consistent. 191 await verifyTabStorageState(writerTab, initialWriteState, noSentinelCheck); 192 // We expect the reader tab to retrieve the current localStorage state from 193 // the database. 194 await verifyTabStorageState(readerTab, initialWriteState, initialSentinel); 195 196 // - Issue last set of writes from writerTab. 197 info("last set of writes"); 198 await mutateTabStorage(writerTab, lastWriteMutations, lastWriteSentinel); 199 200 // The writer performed the writes, no need to wait for the sentinel. 201 await verifyTabStorageState(writerTab, lastWriteState, noSentinelCheck); 202 // We need to wait for the sentinel to show up for the reader. 203 await verifyTabStorageState(readerTab, lastWriteState, lastWriteSentinel); 204 205 // - Clean up. 206 await cleanupTabs(knownTabs); 207 208 clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 209 }); 210 211 /** 212 * Test case 2: one writer tab and one linsener tab 213 * Test steps 214 * 1. Clear origin storage to make sure no data and preloads. 215 * 2. Open the writer and listener tabs and verify preloads do not exist. 216 * Open writer tab in webIsolated= process 217 * Open listener tab in webCOOP+COEP= process 218 * 3. Ask the listener tab to listen and record storage events. 219 * 4. Issue a series write operations in the writer tab, and then verify the 220 * storage state on the tab. 221 * 5. Verify the storage events record from the listener tab is as expected. 222 * 6. Verify the storage state on the listener tab. 223 * 7. Ask the listener tab to listen and record storage events. 224 * 8. Issue another series write operations in the writer tab, and then verify 225 * the storage state on the tab. 226 * 9. Verify the storage events record from the listener tab is as expected. 227 * 10. Verify the storage state on the listener tab. 228 * 11. Close tabs and clear origin storage. 229 */ 230 add_task(async function () { 231 if (!Services.domStorageManager.nextGenLocalStorageEnabled) { 232 ok(true, "Test ignored when the next gen local storage is not enabled."); 233 return; 234 } 235 236 await SpecialPowers.pushPrefEnv({ 237 set: [ 238 ["dom.ipc.processPrelaunch.enabled", false], 239 ["dom.storage.testing", true], 240 ], 241 }); 242 243 // Ensure that there is no localstorage data or potential false positives for 244 // localstorage preloads by forcing the origin to be cleared prior to the 245 // start of our test. 246 await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 247 248 // Make sure mOriginsHavingData gets updated. 249 await triggerAndWaitForLocalStorageFlush(); 250 251 // - Open tabs. Don't configure any of them yet. 252 const knownTabs = new KnownTabs(); 253 const writerTab = await openTestTab( 254 HELPER_PAGE_URL, 255 "writer", 256 knownTabs, 257 true 258 ); 259 const listenerTab = await openTestTab( 260 HELPER_PAGE_COOP_COEP_URL, 261 "listener", 262 knownTabs, 263 true 264 ); 265 // Sanity check that preloading did not occur in the tabs. 266 await verifyTabPreload(writerTab, false, HELPER_PAGE_ORIGIN); 267 await verifyTabPreload(listenerTab, false, HELPER_PAGE_ORIGIN); 268 269 // - Ask the listener tab to listen and record the storage events.. 270 await recordTabStorageEvents(listenerTab, initialSentinel); 271 272 // - Issue the initial batch of writes and verify. 273 info("initial writes"); 274 await mutateTabStorage(writerTab, initialWriteMutations, initialSentinel); 275 276 // We expect the writer tab to have the correct state because it just did the 277 // writes. We do not perform a sentinel-check because the writes should be 278 // locally available and consistent. 279 await verifyTabStorageState(writerTab, initialWriteState, noSentinelCheck); 280 // We expect the listener tab to have heard all events despite preload not 281 // having occurred and despite not issuing any reads or writes itself. We 282 // intentionally check the events before the state because we're most 283 // interested in adding the listener having had a side-effect of subscribing 284 // to changes for the process. 285 // 286 // We ensure it had a chance to hear all of the events because we told 287 // recordTabStorageEvents to listen for the given sentinel. The state check 288 // then does not need to do a sentinel check. 289 await verifyTabStorageEvents( 290 listenerTab, 291 initialWriteMutations, 292 initialSentinel 293 ); 294 await verifyTabStorageState(listenerTab, initialWriteState, noSentinelCheck); 295 296 // - Ask the listener tab to listen and record the storage events. 297 await recordTabStorageEvents(listenerTab, lastWriteSentinel); 298 299 // - Issue last set of writes from writerTab. 300 info("last set of writes"); 301 await mutateTabStorage(writerTab, lastWriteMutations, lastWriteSentinel); 302 303 // The writer performed the writes, no need to wait for the sentinel. 304 await verifyTabStorageState(writerTab, lastWriteState, noSentinelCheck); 305 // Wait for the sentinel event to be received, then check. 306 await verifyTabStorageEvents( 307 listenerTab, 308 lastWriteMutations, 309 lastWriteSentinel 310 ); 311 await verifyTabStorageState(listenerTab, lastWriteState, noSentinelCheck); 312 313 // - Clean up. 314 await cleanupTabs(knownTabs); 315 316 clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 317 }); 318 319 /** 320 * Test case 3: one writeThenRead tab and one readThenWrite tab 321 * Test steps 322 * 1. Clear origin storage to make sure no data and preloads. 323 * 2. Open the writeThenRead and readThenWrite tabs and verify preloads do not 324 * exist. 325 * Open writeThenRead tab in webIsolated= process 326 * Open readThenWrite tab in webCOOP+COEP= process 327 * 3. Ask the readThenWrite tab to listen and record storage events. 328 * 4. Issue a series write operations in the writeThenRead tab, and then verify 329 * the storage state on the tab. 330 * 5. Verify the storage events record from the readThenWrite tab is as 331 * expected. 332 * 6. Verify the storage state on the readThenWrite tab. 333 * 7. Ask the writeThenRead tab to listen and record storage events. 334 * 8. Issue another series write operations in the readThenWrite tab, and then 335 * verify the storage state on the tab. 336 * 9. Verify the storage events record from the writeThenRead tab is as 337 * expected. 338 * 10. Verify the storage state on the writeThenRead tab. 339 * 11. Close tabs and clear origin storage. 340 */ 341 add_task(async function () { 342 if (!Services.domStorageManager.nextGenLocalStorageEnabled) { 343 ok(true, "Test ignored when the next gen local storage is not enabled."); 344 return; 345 } 346 347 await SpecialPowers.pushPrefEnv({ 348 set: [ 349 ["dom.ipc.processPrelaunch.enabled", false], 350 ["dom.storage.testing", true], 351 ], 352 }); 353 354 // Ensure that there is no localstorage data or potential false positives for 355 // localstorage preloads by forcing the origin to be cleared prior to the 356 // start of our test. 357 await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 358 359 // Make sure mOriginsHavingData gets updated. 360 await triggerAndWaitForLocalStorageFlush(); 361 362 // - Open tabs. Don't configure any of them yet. 363 const knownTabs = new KnownTabs(); 364 const writeThenReadTab = await openTestTab( 365 HELPER_PAGE_URL, 366 "writerthenread", 367 knownTabs, 368 true 369 ); 370 const readThenWriteTab = await openTestTab( 371 HELPER_PAGE_COOP_COEP_URL, 372 "readthenwrite", 373 knownTabs, 374 true 375 ); 376 // Sanity check that preloading did not occur in the tabs. 377 await verifyTabPreload(writeThenReadTab, false, HELPER_PAGE_ORIGIN); 378 await verifyTabPreload(readThenWriteTab, false, HELPER_PAGE_ORIGIN); 379 380 // - Ask readThenWrite tab to listen and record storageEvents. 381 await recordTabStorageEvents(readThenWriteTab, initialSentinel); 382 383 // - Issue the initial batch of writes and verify. 384 info("initial writes"); 385 await mutateTabStorage( 386 writeThenReadTab, 387 initialWriteMutations, 388 initialSentinel 389 ); 390 391 // We expect the writer tab to have the correct state because it just did the 392 // writes. We do not perform a sentinel-check because the writes should be 393 // locally available and consistent. 394 await verifyTabStorageState( 395 writeThenReadTab, 396 initialWriteState, 397 noSentinelCheck 398 ); 399 400 // We expect the listener tab to have heard all events despite preload not 401 // having occurred and despite not issuing any reads or writes itself. We 402 // intentionally check the events before the state because we're most 403 // interested in adding the listener having had a side-effect of subscribing 404 // to changes for the process. 405 // 406 // We ensure it had a chance to hear all of the events because we told 407 // recordTabStorageEvents to listen for the given sentinel. The state check 408 // then does not need to do a sentinel check. 409 await verifyTabStorageEvents( 410 readThenWriteTab, 411 initialWriteMutations, 412 initialSentinel 413 ); 414 await verifyTabStorageState( 415 readThenWriteTab, 416 initialWriteState, 417 noSentinelCheck 418 ); 419 420 // - Issue last set of writes from writerTab. 421 info("last set of writes"); 422 await recordTabStorageEvents(writeThenReadTab, lastWriteSentinel); 423 424 await mutateTabStorage( 425 readThenWriteTab, 426 lastWriteMutations, 427 lastWriteSentinel 428 ); 429 430 // The writer performed the writes, no need to wait for the sentinel. 431 await verifyTabStorageState( 432 readThenWriteTab, 433 lastWriteState, 434 noSentinelCheck 435 ); 436 // Wait for the sentinel event to be received, then check. 437 await verifyTabStorageEvents( 438 writeThenReadTab, 439 lastWriteMutations, 440 lastWriteSentinel 441 ); 442 await verifyTabStorageState( 443 writeThenReadTab, 444 lastWriteState, 445 noSentinelCheck 446 ); 447 448 // - Clean up. 449 await cleanupTabs(knownTabs); 450 451 clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 452 }); 453 454 /** 455 * Test case 4: one writerRead tab and one lateOpenSeesPreload tab 456 * Test steps 457 * 1. Clear origin storage to make sure no data and preloads. 458 * 2. Open the writer tab and verify preloads do not exist. 459 * Open writer tab in webIsolated= process 460 * 3. Issue a series write operations in the writer tab, and then verify the 461 * storage state on the tab. 462 * 4. Issue another series write operations in the writer tab, and then verify 463 * the storage state on the tab. 464 * 5. Open lateOpenSeesPreload tab in webCOOP+COEP process 465 * 6. Verify the preloads on the lateOpenSeesPreload tab 466 * 7. Close tabs and clear origin storage. 467 */ 468 add_task(async function () { 469 await SpecialPowers.pushPrefEnv({ 470 set: [ 471 ["dom.ipc.processPrelaunch.enabled", false], 472 ["dom.storage.testing", true], 473 ], 474 }); 475 476 // Ensure that there is no localstorage data or potential false positives for 477 // localstorage preloads by forcing the origin to be cleared prior to the 478 // start of our test. 479 await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 480 481 // Make sure mOriginsHavingData gets updated. 482 await triggerAndWaitForLocalStorageFlush(); 483 484 // - Open tabs. Don't configure any of them yet. 485 const knownTabs = new KnownTabs(); 486 const writerTab = await openTestTab( 487 HELPER_PAGE_URL, 488 "writer", 489 knownTabs, 490 true 491 ); 492 // Sanity check that preloading did not occur in the tabs. 493 await verifyTabPreload(writerTab, false, HELPER_PAGE_ORIGIN); 494 495 // - Configure the tabs. 496 497 // - Issue the initial batch of writes and verify. 498 info("initial writes"); 499 await mutateTabStorage(writerTab, initialWriteMutations, initialSentinel); 500 501 // We expect the writer tab to have the correct state because it just did the 502 // writes. We do not perform a sentinel-check because the writes should be 503 // locally available and consistent. 504 await verifyTabStorageState(writerTab, initialWriteState, noSentinelCheck); 505 506 // - Force a LocalStorage DB flush so mOriginsHavingData is updated. 507 // mOriginsHavingData is only updated when the storage thread runs its 508 // accumulated operations during the flush. If we don't initiate and ensure 509 // that a flush has occurred before moving on to the next step, 510 // mOriginsHavingData may not include our origin when it's sent down to the 511 // child process. 512 info("flush to make preload check work"); 513 await triggerAndWaitForLocalStorageFlush(); 514 515 // - Open a fresh tab and make sure it sees the precache/preload 516 info("late open preload check"); 517 const lateOpenSeesPreload = await openTestTab( 518 HELPER_PAGE_COOP_COEP_URL, 519 "lateOpenSeesPreload", 520 knownTabs, 521 true 522 ); 523 await verifyTabPreload(lateOpenSeesPreload, true, HELPER_PAGE_ORIGIN); 524 525 // - Clean up. 526 await cleanupTabs(knownTabs); 527 528 clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 529 });