browser_localStorage_e10s.js (11460B)
1 const HELPER_PAGE_URL = 2 "http://example.com/browser/dom/tests/browser/page_localstorage.html"; 3 const HELPER_PAGE_ORIGIN = "http://example.com/"; 4 5 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); 6 Services.scriptloader.loadSubScript(testDir + "/helper_localStorage.js", this); 7 8 /* import-globals-from helper_localStorage.js */ 9 10 // We spin up a ton of child processes. 11 requestLongerTimeout(4); 12 13 /** 14 * Verify the basics of our multi-e10s localStorage support. We are focused on 15 * whitebox testing two things. When this is being written, broadcast filtering 16 * is not in place, but the test is intended to attempt to verify that its 17 * implementation does not break things. 18 * 19 * 1) That pages see the same localStorage state in a timely fashion when 20 * engaging in non-conflicting operations. We are not testing races or 21 * conflict resolution; the spec does not cover that. 22 * 23 * 2) That there are no edge-cases related to when the Storage instance is 24 * created for the page or the StorageCache for the origin. (StorageCache is 25 * what actually backs the Storage binding exposed to the page.) This 26 * matters because the following reasons can exist for them to be created: 27 * - Preload, on the basis of knowing the origin uses localStorage. The 28 * interesting edge case is when we have the same origin open in different 29 * processes and the origin starts using localStorage when it did not 30 * before. Preload will not have instantiated bindings, which could impact 31 * correctness. 32 * - The page accessing localStorage for read or write purposes. This is the 33 * obvious, boring one. 34 * - The page adding a "storage" listener. This is less obvious and 35 * interacts with the preload edge-case mentioned above. The page needs to 36 * hear "storage" events even if the page has not touched localStorage 37 * itself and its origin had nothing stored in localStorage when the page 38 * was created. 39 * 40 * We use the same simple child page in all tabs that: 41 * - can be instructed to listen for and record "storage" events 42 * - can be instructed to issue a series of localStorage writes 43 * - can be instructed to return the current entire localStorage contents 44 * 45 * We open the 5 following tabs: 46 * - Open a "writer" tab that does not listen for "storage" events and will 47 * issue only writes. 48 * - Open a "listener" tab instructed to listen for "storage" events 49 * immediately. We expect it to capture all events. 50 * - Open an "reader" tab that does not listen for "storage" events and will 51 * only issue reads when instructed. 52 * - Open a "lateWriteThenListen" tab that initially does nothing. We will 53 * later tell it to issue a write and then listen for events to make sure it 54 * captures the later events. 55 * - Open "lateOpenSeesPreload" tab after we've done everything and ensure that 56 * it preloads/precaches the data without us having touched localStorage or 57 * added an event listener. 58 */ 59 add_task(async function () { 60 await SpecialPowers.pushPrefEnv({ 61 set: [ 62 // Stop the preallocated process manager from speculatively creating 63 // processes. Our test explicitly asserts on whether preload happened or 64 // not for each tab's process. This information is loaded and latched by 65 // the StorageDBParent constructor which the child process's 66 // LocalStorageManager() constructor causes to be created via a call to 67 // LocalStorageCache::StartDatabase(). Although the service is lazily 68 // created and should not have been created prior to our opening the tab, 69 // it's safest to ensure the process simply didn't exist before we ask for 70 // it. 71 // 72 // This is done in conjunction with our use of forceNewProcess when 73 // opening tabs. There would be no point if we weren't also requesting a 74 // new process. 75 ["dom.ipc.processPrelaunch.enabled", false], 76 // Enable LocalStorage's testing API so we can explicitly trigger a flush 77 // when needed. 78 ["dom.storage.testing", true], 79 ], 80 }); 81 82 // Ensure that there is no localstorage data or potential false positives for 83 // localstorage preloads by forcing the origin to be cleared prior to the 84 // start of our test. 85 await clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 86 87 // Make sure mOriginsHavingData gets updated. 88 await triggerAndWaitForLocalStorageFlush(); 89 90 // - Open tabs. Don't configure any of them yet. 91 const knownTabs = new KnownTabs(); 92 const writerTab = await openTestTab( 93 HELPER_PAGE_URL, 94 "writer", 95 knownTabs, 96 true 97 ); 98 const listenerTab = await openTestTab( 99 HELPER_PAGE_URL, 100 "listener", 101 knownTabs, 102 true 103 ); 104 const readerTab = await openTestTab( 105 HELPER_PAGE_URL, 106 "reader", 107 knownTabs, 108 true 109 ); 110 const lateWriteThenListenTab = await openTestTab( 111 HELPER_PAGE_URL, 112 "lateWriteThenListen", 113 knownTabs, 114 true 115 ); 116 117 // Sanity check that preloading did not occur in the tabs. 118 await verifyTabPreload(writerTab, false, HELPER_PAGE_ORIGIN); 119 await verifyTabPreload(listenerTab, false, HELPER_PAGE_ORIGIN); 120 await verifyTabPreload(readerTab, false, HELPER_PAGE_ORIGIN); 121 122 // - Configure the tabs. 123 const initialSentinel = "initial"; 124 const noSentinelCheck = null; 125 await recordTabStorageEvents(listenerTab, initialSentinel); 126 127 // - Issue the initial batch of writes and verify. 128 info("initial writes"); 129 const initialWriteMutations = [ 130 // [key (null=clear), newValue (null=delete), oldValue (verification)] 131 ["getsCleared", "1", null], 132 ["alsoGetsCleared", "2", null], 133 [null, null, null], 134 ["stays", "3", null], 135 ["clobbered", "pre", null], 136 ["getsDeletedLater", "4", null], 137 ["getsDeletedImmediately", "5", null], 138 ["getsDeletedImmediately", null, "5"], 139 ["alsoStays", "6", null], 140 ["getsDeletedLater", null, "4"], 141 ["clobbered", "post", "pre"], 142 ]; 143 const initialWriteState = { 144 stays: "3", 145 clobbered: "post", 146 alsoStays: "6", 147 }; 148 149 await mutateTabStorage(writerTab, initialWriteMutations, initialSentinel); 150 151 // We expect the writer tab to have the correct state because it just did the 152 // writes. We do not perform a sentinel-check because the writes should be 153 // locally available and consistent. 154 await verifyTabStorageState(writerTab, initialWriteState, noSentinelCheck); 155 // We expect the listener tab to have heard all events despite preload not 156 // having occurred and despite not issuing any reads or writes itself. We 157 // intentionally check the events before the state because we're most 158 // interested in adding the listener having had a side-effect of subscribing 159 // to changes for the process. 160 // 161 // We ensure it had a chance to hear all of the events because we told 162 // recordTabStorageEvents to listen for the given sentinel. The state check 163 // then does not need to do a sentinel check. 164 await verifyTabStorageEvents( 165 listenerTab, 166 initialWriteMutations, 167 initialSentinel 168 ); 169 await verifyTabStorageState(listenerTab, initialWriteState, noSentinelCheck); 170 // We expect the reader tab to retrieve the current localStorage state from 171 // the database. Because of the above checks, we are confident that the 172 // writes have hit PBackground and therefore that the (synchronous) state 173 // retrieval contains all the data we need. No sentinel-check is required. 174 await verifyTabStorageState(readerTab, initialWriteState, noSentinelCheck); 175 176 // - Issue second set of writes from lateWriteThenListen 177 // This tests that our new tab that begins by issuing only writes is building 178 // on top of the existing state (although we don't verify that until after the 179 // next set of mutations). We also verify that the initial "writerTab" that 180 // was our first tab and started with only writes sees the writes, even though 181 // it did not add an event listener. 182 183 info("late writes"); 184 const lateWriteSentinel = "lateWrite"; 185 const lateWriteMutations = [ 186 ["lateStays", "10", null], 187 ["lateClobbered", "latePre", null], 188 ["lateDeleted", "11", null], 189 ["lateClobbered", "lastPost", "latePre"], 190 ["lateDeleted", null, "11"], 191 ]; 192 const lateWriteState = Object.assign({}, initialWriteState, { 193 lateStays: "10", 194 lateClobbered: "lastPost", 195 }); 196 197 await recordTabStorageEvents(listenerTab, lateWriteSentinel); 198 199 await mutateTabStorage( 200 lateWriteThenListenTab, 201 lateWriteMutations, 202 lateWriteSentinel 203 ); 204 205 // Verify the writer tab saw the writes. It has to wait for the sentinel to 206 // appear before checking. 207 await verifyTabStorageState(writerTab, lateWriteState, lateWriteSentinel); 208 // Wait for the sentinel event before checking the events and then the state. 209 await verifyTabStorageEvents( 210 listenerTab, 211 lateWriteMutations, 212 lateWriteSentinel 213 ); 214 await verifyTabStorageState(listenerTab, lateWriteState, noSentinelCheck); 215 // We need to wait for the sentinel to show up for the reader. 216 await verifyTabStorageState(readerTab, lateWriteState, lateWriteSentinel); 217 218 // - Issue last set of writes from writerTab. 219 info("last set of writes"); 220 const lastWriteSentinel = "lastWrite"; 221 const lastWriteMutations = [ 222 ["lastStays", "20", null], 223 ["lastDeleted", "21", null], 224 ["lastClobbered", "lastPre", null], 225 ["lastClobbered", "lastPost", "lastPre"], 226 ["lastDeleted", null, "21"], 227 ]; 228 const lastWriteState = Object.assign({}, lateWriteState, { 229 lastStays: "20", 230 lastClobbered: "lastPost", 231 }); 232 233 await recordTabStorageEvents(listenerTab, lastWriteSentinel); 234 await recordTabStorageEvents(lateWriteThenListenTab, lastWriteSentinel); 235 236 await mutateTabStorage(writerTab, lastWriteMutations, lastWriteSentinel); 237 238 // The writer performed the writes, no need to wait for the sentinel. 239 await verifyTabStorageState(writerTab, lastWriteState, noSentinelCheck); 240 // Wait for the sentinel event to be received, then check. 241 await verifyTabStorageEvents( 242 listenerTab, 243 lastWriteMutations, 244 lastWriteSentinel 245 ); 246 await verifyTabStorageState(listenerTab, lastWriteState, noSentinelCheck); 247 // We need to wait for the sentinel to show up for the reader. 248 await verifyTabStorageState(readerTab, lastWriteState, lastWriteSentinel); 249 // Wait for the sentinel event to be received, then check. 250 await verifyTabStorageEvents( 251 lateWriteThenListenTab, 252 lastWriteMutations, 253 lastWriteSentinel 254 ); 255 await verifyTabStorageState( 256 lateWriteThenListenTab, 257 lastWriteState, 258 noSentinelCheck 259 ); 260 261 // - Force a LocalStorage DB flush so mOriginsHavingData is updated. 262 // mOriginsHavingData is only updated when the storage thread runs its 263 // accumulated operations during the flush. If we don't initiate and ensure 264 // that a flush has occurred before moving on to the next step, 265 // mOriginsHavingData may not include our origin when it's sent down to the 266 // child process. 267 info("flush to make preload check work"); 268 await triggerAndWaitForLocalStorageFlush(); 269 270 // - Open a fresh tab and make sure it sees the precache/preload 271 info("late open preload check"); 272 const lateOpenSeesPreload = await openTestTab( 273 HELPER_PAGE_URL, 274 "lateOpenSeesPreload", 275 knownTabs, 276 true 277 ); 278 await verifyTabPreload(lateOpenSeesPreload, true, HELPER_PAGE_ORIGIN); 279 280 // - Clean up. 281 await cleanupTabs(knownTabs); 282 283 clearOriginStorageEnsuringNoPreload(HELPER_PAGE_ORIGIN); 284 });