зеркало из https://github.com/mozilla/gecko-dev.git
138 строки
5.1 KiB
HTML
138 строки
5.1 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<script>
|
|
/**
|
|
* Helper page used by browser_localStorage_e10s.js.
|
|
*
|
|
* We expose methods to be invoked by ContentTask.spawn() calls.
|
|
* ContentTask.spawn() uses the message manager and is PContent-based. When
|
|
* LocalStorage was PContent-managed, ordering was inherently ensured so we
|
|
* could assume each page had already received all relevant events. Now some
|
|
* explicit type of coordination is required.
|
|
*
|
|
* This gets complicated because:
|
|
* - LocalStorage is an ugly API that gives us almost unlimited implementation
|
|
* flexibility in the face of multiple processes. It's also an API that sites
|
|
* may misuse which may encourage us to leverage that flexibility in the
|
|
* future to improve performance at the expense of propagation latency, and
|
|
* possibly involving content-observable coalescing of events.
|
|
* - The Quantum DOM effort and its event labeling and separate task queues and
|
|
* green threading and current LocalStorage implementation mean that using
|
|
* other PBackground-based APIs such as BroadcastChannel may not provide
|
|
* reliable ordering guarantees. Specifically, it's hard to guarantee that
|
|
* a BroadcastChannel postMessage() issued after a series of LocalStorage
|
|
* writes won't be received by the target window before the writes are
|
|
* perceived. At least not without constraining the implementations of both
|
|
* APIs.
|
|
* - Some of our tests explicitly want to verify LocalStorage behavior without
|
|
* having a "storage" listener, so we can't add a storage listener if the test
|
|
* didn't already want one.
|
|
*
|
|
* We use 2 approaches for coordination:
|
|
* 1. If we're already listening for events, we listen for the sentinel value to
|
|
* be written. This is efficient and appropriate in this case.
|
|
* 2. If we're not listening for events, we use setTimeout(0) to poll the
|
|
* localStorage key and value until it changes to our expected value.
|
|
* setTimeout(0) eventually clamps to setTimeout(4), so in the event we are
|
|
* experiencing delays, we have reasonable, non-CPU-consuming back-off in
|
|
* place that leaves the CPU free to time out and fail our test if something
|
|
* broke. This is ugly but makes us less brittle.
|
|
*
|
|
* Both of these involve mutateStorage writing the sentinel value at the end of
|
|
* the batch. All of our result-returning methods accordingly filter out the
|
|
* sentinel key/value pair.
|
|
**/
|
|
var pageName = document.location.search.substring(1);
|
|
window.addEventListener(
|
|
"load",
|
|
() => { document.getElementById("pageNameH").textContent = pageName; });
|
|
|
|
// Key that conveys the end of a write batch. Filtered out from state and
|
|
// events.
|
|
const SENTINEL_KEY = 'WRITE_BATCH_SENTINEL';
|
|
|
|
var storageEventsPromise = null;
|
|
function listenForStorageEvents(sentinelValue) {
|
|
const recordedEvents = [];
|
|
storageEventsPromise = new Promise(function(resolve, reject) {
|
|
window.addEventListener(
|
|
"storage",
|
|
function thisHandler(event) {
|
|
if (event.key === SENTINEL_KEY) {
|
|
// There should be no way for this to have the wrong value, but reject
|
|
// if it is wrong.
|
|
if (event.newValue === sentinelValue) {
|
|
window.removeEventListener("storage", thisHandler);
|
|
resolve(recordedEvents);
|
|
} else {
|
|
reject(event.newValue);
|
|
}
|
|
} else {
|
|
recordedEvents.push([event.key, event.newValue, event.oldValue]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function mutateStorage({ mutations, sentinelValue }) {
|
|
mutations.forEach(function([key, value]) {
|
|
if (key !== null) {
|
|
if (value === null) {
|
|
localStorage.removeItem(key);
|
|
} else {
|
|
localStorage.setItem(key, value);
|
|
}
|
|
} else {
|
|
localStorage.clear();
|
|
}
|
|
});
|
|
localStorage.setItem(SENTINEL_KEY, sentinelValue);
|
|
}
|
|
|
|
// Returns a promise that is resolve when the sentinel key has taken on the
|
|
// sentinel value. Oddly structured to make sure promises don't let us
|
|
// accidentally side-step the timeout clamping logic.
|
|
function waitForSentinelValue(sentinelValue) {
|
|
return new Promise(function(resolve) {
|
|
function checkFunc() {
|
|
if (localStorage.getItem(SENTINEL_KEY) === sentinelValue) {
|
|
resolve();
|
|
} else {
|
|
// I believe linters will only yell at us if we use a non-zero constant.
|
|
// Other forms of back-off were considered, including attempting to
|
|
// issue a round-trip through PBackground, but that still potentially
|
|
// runs afoul of labeling while also making us dependent on unrelated
|
|
// APIs.
|
|
setTimeout(checkFunc, 0);
|
|
}
|
|
}
|
|
checkFunc();
|
|
});
|
|
}
|
|
|
|
async function getStorageState(maybeSentinel) {
|
|
if (maybeSentinel) {
|
|
await waitForSentinelValue(maybeSentinel);
|
|
}
|
|
|
|
let numKeys = localStorage.length;
|
|
let state = {};
|
|
for (var iKey = 0; iKey < numKeys; iKey++) {
|
|
let key = localStorage.key(iKey);
|
|
if (key !== SENTINEL_KEY) {
|
|
state[key] = localStorage.getItem(key);
|
|
}
|
|
}
|
|
return state;
|
|
}
|
|
|
|
function returnAndClearStorageEvents() {
|
|
return storageEventsPromise;
|
|
}
|
|
</script>
|
|
</head>
|
|
<body><h2 id="pageNameH"></h2></body>
|
|
</html>
|