-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Define how localStorage is synchronized between browser tabs/windows #403
Comments
Here is some sample code that demonstrates seeming guarantees about when sync occurs between two processes. I'm using the I'm showing my work so that these statements may be corrected or expanded upon. /cc @inexorabletash @rocallahan @jdm @annevk <!--parent.html-->
<script>
if (!window.localStorage.getItem('id')) {
window.localStorage.setItem('id', '0');
}
</script>
<!--Repeat line below dozens of times:-->
<iframe style="width: 50px; height: 2em;" src="consumer.html"></iframe> <!--consumer.html-->
<script>
function domManipulationTaskSourceCallback(handler) {
const styleElem = document.createElement('style');
styleElem.innerHTML = 'body {}';
styleElem.addEventListener('load', () => {
document.body.removeChild(styleElem);
handler();
}, { once: true });
document.body.appendChild(styleElem);
}
var idMessages = [];
window.addEventListener('storage', (event) => {
if (event.key === 'id') {
idMessages.push(event.newValue);
console.count('id');
}
});
function generateId() {
return new Promise((resolve, reject) => {
const current = window.localStorage.getItem('id');
const next = (parseInt(current, 10) + 1).toString();
window.localStorage.setItem('id', next);
domManipulationTaskSourceCallback(() => {
domManipulationTaskSourceCallback(() => {
if (idMessages.includes(next)) {
resolve(generateId());
} else {
resolve(next);
}
});
});
});
}
window.addEventListener('pageshow', (event) => {
// Register.
generateId().then((result) => {
document.body.innerHTML = result;
});
});
</script> |
Since timers use the "timer task source" and |
@annevk is disputing this portion, emphasis added:
Reference for @annevk's comment. In order to make my test valid, this means that I need to convert my double turn to be triggered by something which is queued in the |
New code which triggers in the DOM Manipulation Task Source (I've also modified the above code in my original comment to use this function instead of function domManipulationTaskSourceCallback(handler) {
const styleElem = document.createElement('style');
styleElem.innerHTML = 'body {}';
styleElem.addEventListener('load', () => {
document.body.removeChild(styleElem);
handler();
}, { once: true });
document.body.appendChild(styleElem);
} With this scenario I'm trivially able to trigger To @foolip's original point,
My research implies to me that we are not roughly interoperable and that implementations do not presently have schedule guarantees about when the |
Thinking a bit more, there may exist a task source (possibly |
I've been unable to make this collide across processes: function generateId() {
return new Promise((resolve, reject) => {
const current = window.localStorage.getItem('id');
const next = (parseInt(current, 10) + 1).toString();
window.localStorage.setItem('id', next);
domManipulationTaskSourceCallback(() => {
setTimeout(() => {
domManipulationTaskSourceCallback(() => {
if (idMessages.includes(next)) {
resolve(generateId());
} else {
resolve(next);
}
});
}, 0);
});
});
} |
@foolip in whatwg/storage#18 I took a stab recently at defining the underpinnings of storage and decided to leave this aspect implementation-defined. If you have thoughts on how to improve upon that they'd be most welcome. Thanks! |
Thanks @annevk! I see you already have reviewers on whatwg/storage#86, and I don't think I have much to add beyond "it would be good if this were defined" (this issue) but great to see it being fixed. |
So to be clear, this issue is not being fixed, but many others are. It does provide a better foundation from which this issue could perhaps be tackled by someone willing to do the work though. |
The lack of clarity has come up in Firefox/Gecko in https://bugzilla.mozilla.org/show_bug.cgi?id=1740144 where the bug reporter provides a test case using BroadcastChannel which uses the DOM manipulation task source along with LocalStorage (which also uses the DOM manipulation task source). As I conveyed in a recent-ish discussion on SessionStorage and events, and similar to what the first comment in the thread mentions, Firefox now uses a snapshot based mechanism that is flushed/committed at the end of the task when control returns to the event loop. This will also trigger "storage" events appropriately in other processes. (There is an optimization that will coalesce mutations, but if there are any "storage" listeners in other processes, mutations will not be coalesced so that we can provide full fidelity "storage" events like the key "foo" transitioning through a sequence of values rather than just receiving the final write value.) We run into trouble in a situation like: localStorage["foo"] = "justBeforeBroadcastChannel";
broadcastChannel.postMessage("message!"); In our implementation, the broadcast channel message is queued/broadcast immediately and logic in the other process can end up processing the message before "foo" takes on its new value of "justBeforeBroadcastChannel" (because the LocalStorage propagation happens at the end of the task which is strictly later). We're tentatively considering that we would defer BroadcastChannel messages once we have a pending LocalStorage snapshot so that, at the end of the task, we would commit the snapshot, then send any BroadcastChannel messages, etc. This allows us to maintain run-to-completion semantics and relative task source ordering while decreasing the chance for content to experience inconsistencies, and without making LocalStorage even more powerful (ex: if it ended up being equivalent to SharedArrayBuffer across agent clusters!). |
From #335 (comment) and later comments it sounds like implementations of
localStorage
are not simply synchronous likedocument.cookie
, but maintains an in-process cache that is synchronized with the real storage when returning to the event loop, or some similar condition.The spec now says that "This specification does not define the interaction with other browsing contexts in a multiprocess user agent, and authors are encourages to assume that there is no locking mechanism."
If the way this is actually implemented is roughly interoperable, we should spec that. Needs thorough investigation.
The text was updated successfully, but these errors were encountered: