forked from mirrors/gecko-dev
		
	 80f9d1564b
			
		
	
	
		80f9d1564b
		
	
	
	
	
		
			
			This patch reuses the infrastructure of browser_localStorage_e10s.js to make a localStorage consistency testing for fission. Since only two processes can be used for the same-origin pages when fission on, the test of browser_localStroage_e10s.js is separated into four subtests. 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 of write operations, then verify the recorded storage events and localStorage contents from the 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 exist. To load the same origin pages in different processes in fission world, page_localstorage_coop+coep.html is created. page_localstorage_coop+coep.html has the same content as page_localstorage.html, but it is loaded with its header file. Since the test infrastructure is reused, the following modifications are applied on browser_localStorage_e10s.js # Move page_localstorage_e10s.html to page_localstorage.html. Such that this test page can be reused both in fission and non-fission tests # Move help functions defined in page_localstorage_e10s.html to page_localstorage.js. Such that these help functions can be reused in page_localstorage_coop+coep.html # Rename help_localStorage_e10s.js to help_localStorage.js and move help functions defined in browser_localStorage_e10s.html to help_localStorage.js Such that these help functions can be reused in fission and non-fission tests browser_localStorage_fis.js is only for fission on testcase. Differential Revision: https://phabricator.services.mozilla.com/D110939
		
			
				
	
	
		
			304 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| // Simple tab wrapper abstracting our messaging mechanism;
 | |
| class KnownTab {
 | |
|   constructor(name, tab) {
 | |
|     this.name = name;
 | |
|     this.tab = tab;
 | |
|   }
 | |
| 
 | |
|   cleanup() {
 | |
|     this.tab = null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Simple data structure class to help us track opened tabs and their pids.
 | |
| class KnownTabs {
 | |
|   constructor() {
 | |
|     this.byPid = new Map();
 | |
|     this.byName = new Map();
 | |
|   }
 | |
| 
 | |
|   cleanup() {
 | |
|     for (let key of this.byPid.keys()) {
 | |
|       this.byPid[key] = null;
 | |
|     }
 | |
|     this.byPid = null;
 | |
|     this.byName = null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Open our helper page in a tab in its own content process, asserting that it
 | |
|  * really is in its own process.  We initially load and wait for about:blank to
 | |
|  * load, and only then loadURI to our actual page.  This is to ensure that
 | |
|  * LocalStorageManager has had an opportunity to be created and populate
 | |
|  * mOriginsHavingData.
 | |
|  *
 | |
|  * (nsGlobalWindow will reliably create LocalStorageManager as a side-effect of
 | |
|  * the unconditional call to nsGlobalWindow::PreloadLocalStorage.  This will
 | |
|  * reliably create the StorageDBChild instance, and its corresponding
 | |
|  * StorageDBParent will send the set of origins when it is constructed.)
 | |
|  */
 | |
| async function openTestTab(
 | |
|   helperPageUrl,
 | |
|   name,
 | |
|   knownTabs,
 | |
|   shouldLoadInNewProcess
 | |
| ) {
 | |
|   let realUrl = helperPageUrl + "?" + encodeURIComponent(name);
 | |
|   // Load and wait for about:blank.
 | |
|   let tab = await BrowserTestUtils.openNewForegroundTab({
 | |
|     gBrowser,
 | |
|     opening: "about:blank",
 | |
|     forceNewProcess: true,
 | |
|   });
 | |
|   ok(!knownTabs.byName.has(name), "tab needs its own name: " + name);
 | |
| 
 | |
|   let knownTab = new KnownTab(name, tab);
 | |
|   knownTabs.byName.set(name, knownTab);
 | |
| 
 | |
|   // Now trigger the actual load of our page.
 | |
|   BrowserTestUtils.loadURI(tab.linkedBrowser, realUrl);
 | |
|   await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 | |
| 
 | |
|   let pid = tab.linkedBrowser.frameLoader.remoteTab.osPid;
 | |
|   if (shouldLoadInNewProcess) {
 | |
|     ok(
 | |
|       !knownTabs.byPid.has(pid),
 | |
|       "tab should be loaded in new process, pid: " + pid
 | |
|     );
 | |
|   } else {
 | |
|     ok(
 | |
|       knownTabs.byPid.has(pid),
 | |
|       "tab should be loaded in the same process, new pid: " + pid
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   if (knownTabs.byPid.has(pid)) {
 | |
|     knownTabs.byPid.get(pid).set(name, knownTab);
 | |
|   } else {
 | |
|     let pidMap = new Map();
 | |
|     pidMap.set(name, knownTab);
 | |
|     knownTabs.byPid.set(pid, pidMap);
 | |
|   }
 | |
| 
 | |
|   return knownTab;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Close all the tabs we opened.
 | |
|  */
 | |
| async function cleanupTabs(knownTabs) {
 | |
|   for (let knownTab of knownTabs.byName.values()) {
 | |
|     BrowserTestUtils.removeTab(knownTab.tab);
 | |
|     knownTab.cleanup();
 | |
|   }
 | |
|   knownTabs.cleanup();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Wait for a LocalStorage flush to occur.  This notification can occur as a
 | |
|  * result of any of:
 | |
|  * - The normal, hardcoded 5-second flush timer.
 | |
|  * - InsertDBOp seeing a preload op for an origin with outstanding changes.
 | |
|  * - Us generating a "domstorage-test-flush-force" observer notification.
 | |
|  */
 | |
| function waitForLocalStorageFlush() {
 | |
|   if (Services.domStorageManager.nextGenLocalStorageEnabled) {
 | |
|     return new Promise(resolve => executeSoon(resolve));
 | |
|   }
 | |
| 
 | |
|   return new Promise(function(resolve) {
 | |
|     let observer = {
 | |
|       observe() {
 | |
|         SpecialPowers.removeObserver(observer, "domstorage-test-flushed");
 | |
|         resolve();
 | |
|       },
 | |
|     };
 | |
|     SpecialPowers.addObserver(observer, "domstorage-test-flushed");
 | |
|   });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Trigger and wait for a flush.  This is only necessary for forcing
 | |
|  * mOriginsHavingData to be updated.  Normal operations exposed to content know
 | |
|  * to automatically flush when necessary for correctness.
 | |
|  *
 | |
|  * The notification we're waiting for to verify flushing is fundamentally
 | |
|  * ambiguous (see waitForLocalStorageFlush), so we actually trigger the flush
 | |
|  * twice and wait twice.  In the event there was a race, there will be 3 flush
 | |
|  * notifications, but correctness is guaranteed after the second notification.
 | |
|  */
 | |
| function triggerAndWaitForLocalStorageFlush() {
 | |
|   if (Services.domStorageManager.nextGenLocalStorageEnabled) {
 | |
|     return new Promise(resolve => executeSoon(resolve));
 | |
|   }
 | |
| 
 | |
|   SpecialPowers.notifyObservers(null, "domstorage-test-flush-force");
 | |
|   // This first wait is ambiguous...
 | |
|   return waitForLocalStorageFlush().then(function() {
 | |
|     // So issue a second flush and wait for that.
 | |
|     SpecialPowers.notifyObservers(null, "domstorage-test-flush-force");
 | |
|     return waitForLocalStorageFlush();
 | |
|   });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Clear the origin's storage so that "OriginsHavingData" will return false for
 | |
|  * our origin.  Note that this is only the case for AsyncClear() which is
 | |
|  * explicitly issued against a cache, or AsyncClearAll() which we can trigger
 | |
|  * by wiping all storage.  However, the more targeted domain clearings that
 | |
|  * we can trigger via observer, AsyncClearMatchingOrigin and
 | |
|  * AsyncClearMatchingOriginAttributes will not clear the hashtable entry for
 | |
|  * the origin.
 | |
|  *
 | |
|  * So we explicitly access the cache here in the parent for the origin and issue
 | |
|  * an explicit clear.  Clearing all storage might be a little easier but seems
 | |
|  * like asking for intermittent failures.
 | |
|  */
 | |
| function clearOriginStorageEnsuringNoPreload(origin) {
 | |
|   let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
 | |
|     origin
 | |
|   );
 | |
| 
 | |
|   if (Services.domStorageManager.nextGenLocalStorageEnabled) {
 | |
|     let request = Services.qms.clearStoragesForPrincipal(
 | |
|       principal,
 | |
|       "default",
 | |
|       "ls"
 | |
|     );
 | |
|     let promise = new Promise(resolve => {
 | |
|       request.callback = () => {
 | |
|         resolve();
 | |
|       };
 | |
|     });
 | |
|     return promise;
 | |
|   }
 | |
| 
 | |
|   // We want to use createStorage to force the cache to be created so we can
 | |
|   // issue the clear.  It's possible for getStorage to return false but for the
 | |
|   // origin preload hash to still have our origin in it.
 | |
|   let storage = Services.domStorageManager.createStorage(
 | |
|     null,
 | |
|     principal,
 | |
|     principal,
 | |
|     ""
 | |
|   );
 | |
|   storage.clear();
 | |
| 
 | |
|   // We also need to trigger a flush os that mOriginsHavingData gets updated.
 | |
|   // The inherent flush race is fine here because
 | |
|   return triggerAndWaitForLocalStorageFlush();
 | |
| }
 | |
| 
 | |
| async function verifyTabPreload(knownTab, expectStorageExists, origin) {
 | |
|   let storageExists = await SpecialPowers.spawn(
 | |
|     knownTab.tab.linkedBrowser,
 | |
|     [origin],
 | |
|     function(origin) {
 | |
|       let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
 | |
|         origin
 | |
|       );
 | |
|       if (Services.domStorageManager.nextGenLocalStorageEnabled) {
 | |
|         return Services.domStorageManager.isPreloaded(principal);
 | |
|       }
 | |
|       return !!Services.domStorageManager.getStorage(
 | |
|         null,
 | |
|         principal,
 | |
|         principal
 | |
|       );
 | |
|     }
 | |
|   );
 | |
|   is(storageExists, expectStorageExists, "Storage existence === preload");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Instruct the given tab to execute the given series of mutations.  For
 | |
|  * simplicity, the mutations representation matches the expected events rep.
 | |
|  */
 | |
| async function mutateTabStorage(knownTab, mutations, sentinelValue) {
 | |
|   await SpecialPowers.spawn(
 | |
|     knownTab.tab.linkedBrowser,
 | |
|     [{ mutations, sentinelValue }],
 | |
|     function(args) {
 | |
|       return content.wrappedJSObject.mutateStorage(Cu.cloneInto(args, content));
 | |
|     }
 | |
|   );
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Instruct the given tab to add a "storage" event listener and record all
 | |
|  * received events.  verifyTabStorageEvents is the corresponding method to
 | |
|  * check and assert the recorded events.
 | |
|  */
 | |
| async function recordTabStorageEvents(knownTab, sentinelValue) {
 | |
|   await SpecialPowers.spawn(
 | |
|     knownTab.tab.linkedBrowser,
 | |
|     [sentinelValue],
 | |
|     function(sentinelValue) {
 | |
|       return content.wrappedJSObject.listenForStorageEvents(sentinelValue);
 | |
|     }
 | |
|   );
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Retrieve the current localStorage contents perceived by the tab and assert
 | |
|  * that they match the provided expected state.
 | |
|  *
 | |
|  * If maybeSentinel is non-null, it's assumed to be a string that identifies the
 | |
|  * value we should be waiting for the sentinel key to take on.  This is
 | |
|  * necessary because we cannot make any assumptions about when state will be
 | |
|  * propagated to the given process.  See the comments in
 | |
|  * page_localstorage_e10s.js for more context.  In general, a sentinel value is
 | |
|  * required for correctness unless the process in question is the one where the
 | |
|  * writes were performed or verifyTabStorageEvents was used.
 | |
|  */
 | |
| async function verifyTabStorageState(knownTab, expectedState, maybeSentinel) {
 | |
|   let actualState = await SpecialPowers.spawn(
 | |
|     knownTab.tab.linkedBrowser,
 | |
|     [maybeSentinel],
 | |
|     function(maybeSentinel) {
 | |
|       return content.wrappedJSObject.getStorageState(maybeSentinel);
 | |
|     }
 | |
|   );
 | |
| 
 | |
|   for (let [expectedKey, expectedValue] of Object.entries(expectedState)) {
 | |
|     ok(actualState.hasOwnProperty(expectedKey), "key present: " + expectedKey);
 | |
|     is(actualState[expectedKey], expectedValue, "value correct");
 | |
|   }
 | |
|   for (let actualKey of Object.keys(actualState)) {
 | |
|     if (!expectedState.hasOwnProperty(actualKey)) {
 | |
|       ok(false, "actual state has key it shouldn't have: " + actualKey);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Retrieve and clear the storage events recorded by the tab and assert that
 | |
|  * they match the provided expected events.  For simplicity, the expected events
 | |
|  * representation is the same as that used by mutateTabStorage.
 | |
|  *
 | |
|  * Note that by convention for test readability we are passed a 3rd argument of
 | |
|  * the sentinel value, but we don't actually care what it is.
 | |
|  */
 | |
| async function verifyTabStorageEvents(knownTab, expectedEvents) {
 | |
|   let actualEvents = await SpecialPowers.spawn(
 | |
|     knownTab.tab.linkedBrowser,
 | |
|     [],
 | |
|     function() {
 | |
|       return content.wrappedJSObject.returnAndClearStorageEvents();
 | |
|     }
 | |
|   );
 | |
| 
 | |
|   is(actualEvents.length, expectedEvents.length, "right number of events");
 | |
|   for (let i = 0; i < actualEvents.length; i++) {
 | |
|     let [actualKey, actualNewValue, actualOldValue] = actualEvents[i];
 | |
|     let [expectedKey, expectedNewValue, expectedOldValue] = expectedEvents[i];
 | |
|     is(actualKey, expectedKey, "keys match");
 | |
|     is(actualNewValue, expectedNewValue, "new values match");
 | |
|     is(actualOldValue, expectedOldValue, "old values match");
 | |
|   }
 | |
| }
 |