Skip to content

Session storage and changing browsing contexts #119

Open

Description

@jakearchibald

I'm trying to figure out how session storage should behave in a few similar cases.

I think the possible options for each of these are:

a) The new page has fresh, empty session storage
b) The new page has the same session storage
c) The new page has a clone of session storage

…where b is generally what happens while navigating around in the same tab, and c happens when opening a popup.

References to "isolated" here mean Cross-Origin-Opener-Policy: same-origin.

1. User navigates from non-isolated pageA to isolated pageB in the same tab. My gut feeling is that "b" is ok here. Only visible data would be able to pass from pageA to pageB. This may be the first time a single unit of session storage has been shared between a (process, origin) pair, although it's pretty common with localstorage.

2. pageA performs a <link rel="prerender"> of pageB. User navigates from pageA to pageB in the same tab and the prerender is used. A prerender will exist in a new browsing context, so this is similar to the previous case, and imo have the same result, "b". The difference is, in the same-origin case, it's more likely that each process will be accessing session storage at the same time.

3. User navigates from pageA to pageB by changing the URL in the URL bar. This case feels different to me. Since the navigation hasn't been caused by web content within the session, "a" feels more appropriate. However, if the user clicks back, the previous session storage should be present.

4. Non-isolated pageA opens a popup to isolated pageB. Again, it seems ok to continue with the current behaviour, which is "c". That seems to be what Chrome does now.

@annevk @rakina does the above make sense?

If so, I think I'll change what I'm currently calling "browsing session" to "top level navigable" (making a special kind of navigable), since it's supposed to represent a window/tab. Then I'll give browsing contexts a session bucket/ID. Multiple browsing contexts can share the same session bucket/ID, and a top level navigable may contain history entries that have browsing sessions with different session bucket/IDs.

This would also allow us to add logic that prevents history.back() traversing to a history entry with a different session than the current entry, although the browser back button would not have this restriction.

Activity

  1. annevk commented on Jan 28, 2021

    @annevk
    Member

    I tend to agree with your assessment, but I would love to hear from @asutherland and maybe @janvarga about sharing sessionStorage across the process boundary. If sessionStorage has a different storage architecture from localStorage I wonder if it's worth salvaging or whether sessions should be reintroduced as part of buckets (and not have the problematic aspects there, such as cloning).

  2. jakearchibald commented on Jan 28, 2021

    @jakearchibald
    ContributorAuthor

    I think there'd be a lot of value in something like session-idb, and yeah, it wouldn't clone when opening auxiliary browsing instances (although the "duplicate tab" feature might).

  3. mkruisselbrink commented on Jan 28, 2021

    @mkruisselbrink

    The chrome implementation of session storage today does rely in a number of ways on there always being at most one renderer process accessing a particular session storage. It's been a while since I looked at/touched that code, but I think this made it much easier to ensure we always dispatch the right events even in the presence of copy-on-write behavior. So while supporting sharing one session storage across multiple renderer processes is probably possible, it would certainly be a non-trivial effort.

  4. mkruisselbrink commented on Jan 28, 2021

    @mkruisselbrink

    Having said that, it doesn't sound like case 1) would actually involve two processes accessing the same storage at the same time, so it would still be okay. 2) might be more problematic if that does actually let two processes access the same storage at the same time. My understanding was that at some point portals was considering doing the same, but ended up deciding to block storage before activation instead (partially to solve this problem).

  5. asutherland commented on Jan 29, 2021

    @asutherland
    Collaborator

    Firefox's SessionStorage implementation currently assumes only a single content process (akin to renderer) will actively be using the storage for a given (Top level browsing context (session) id, origin) tuple. It's expected and handled that as history is traversed that this will result in this notional active process changing. In the event the concurrent access assumption is violated, each content process will effectively fork the state on first SessionStorage access and live in their own universes, never to perceive the canonical state that a new content process would see.

    If needed, this could instead be made to converge with the current LocalStorage NextGen implementation wherein multiple processes can access the same data with a consistent snapshot established for the duration of a task the first time LS is accessed within an task, with atomic write-back of any deltas occurring at the end of the task. In the event of a race, mutation batches will stack and all processes will perceive the canonical state during the next task. Sync-ish IPC is sadly involved.

    In general it would be nice if the single content/renderer process invariant can be maintained. Which maybe is to say that a SessionStorage storage bottle can only be accessed from one agent cluster at a time? In cases where that would be violated, we must clone/copy/stick-in-the-replicator the bottle or create a new empty bottle. It sounds like that's what's being proposed here?

  6. annevk commented on Jan 30, 2021

    @annevk
    Member

    I think we're actually fine and I slightly misunderstood the implications of something Jake said. When you navigate from origin A without COOP to origin A with COOP there is a process swap, but it's not the case that both would have access to the same sessionStorage simultaneously.

  7. jakearchibald commented on Jan 30, 2021

    @jakearchibald
    ContributorAuthor

    Yeah it's the prerender case where access can happen simultaneously.

  8. jakearchibald commented on Feb 23, 2021

    @jakearchibald
    ContributorAuthor

    Would a "clone & swap" system like this be easier than truly shared storage @mkruisselbrink @asutherland?

    1. A top-level page contain a same-origin prerender. The same-origin prerendered page receives a clone of session storage.
    2. Wait until the prerendered page activates.
    3. Switch the session storage of the now-activated prerendered page from its clone to the session storage used by the old top-level page.
    4. Fire "storage" events for every difference between the old cloned storage and the new storage.

    This means that clicking back would take you to the previous page without changing session storage.

  9. asutherland commented on Feb 23, 2021

    @asutherland
    Collaborator

    Are the modifications performed by the pre-rendering page to sessionStorage discarded by the bottle transplant? Or is step 4 more than bringing the pre-render up-to-speed with the changes in the old top-level-page and implies also applying the changes made by the pre-render?

  10. jakearchibald commented on Feb 24, 2021

    @jakearchibald
    ContributorAuthor

    Ignore the specifics of step 4 for now, I'm getting ahead of myself.

    Are the modifications performed by the pre-rendering page to sessionStorage discarded by the bottle transplant?

    Yes, discarded, although some event may provide insight into which keys changed as a result of the transplant, and provide their old value.

    So:

    1. Main page has session storage { foo: 'bar', hello: 'world' }.
    2. Prerender created, which has a clone of session storage, so { foo: 'bar', hello: 'world' }.
    3. Main page sets 'username' to 'sarah' in session storage, so { foo: 'bar', hello: 'world', username: 'sarah' }.
    4. Prerender sets 'foo' to 'yo', and '123' to '456', so { foo: 'yo', hello: 'world', '123': '456' }.

    If prerender becomes the main page, it's session storage will be replaced with the main page's storage, and (maybe) it gets an event that tells it:

    • 'foo' changed, was 'yo'
    • 'username' changed, was undefined
    • '123' changed, was '456'

    I don't know if this is a good idea yet, but I'm trying to get a sense for how complex it is vs making session storage 'work' across processes.

  11. jakearchibald commented on Feb 24, 2021

    @jakearchibald
    ContributorAuthor

    I asked folks on Twitter how they were using session storage, here's an analysis of how prerendering could disrupt that, and how I landed on the above idea https://docs.google.com/document/d/1cCTBZR6nWsVC2TlQ8PBse7eBb4ro0rtPJxX0zCou1lw.

  12. asutherland commented on Feb 24, 2021

    @asutherland
    Collaborator

    Thank you for the clarification and excellent example and summary! Also, thank you very much for creating and linking the amazing document in which you've gathered use-cases and analyzed the impacts of the different solutions on the use-cases!

    I think you make a great case for "clone & swap". In particular, I think it:

    • Maximizes web-compat by being deterministic instead of allowing races that will fundamentally depend on browser implementation details.
      • As a direct consequence, this should also simplify future browser implementation. That said, the fact that LocalStorage potentially involves races means that browsers might have to deal with implementing "shared" anyways.
    • Maximizes the ability of web developers to reason about what's going on by being deterministic and not involving races.
      • As per the above, LocalStorage will still be a problem here for developers (because races will continue be inherent), but their experience with StorageEvents across LocalStorage and SessionStorage should help their logic act consistently in both cases.
      • I could see developers expecting that more of a "merge" would happen rather than than the swap/replacement, but it seems appropriate for the window with the user interaction to "win"; SessionStorage should be reflecting user actions and intent and any mutations the pre-rendered window would perform would by definition not be as a result of direct user action (in the page).
    • Avoids the potential need for synchronous IPC in the implementation.
    • Could be relaxed to a "shared" model in the future if desired without regressing sites that properly handle storage events.
  13. jakearchibald commented on Feb 24, 2021

    @jakearchibald
    ContributorAuthor

    Cheers! There might still be a race between what the script in the main page sets in session storage before it's cloned for the prerender (edit: although that will be resolved at swap time). The "empty" solution has fewer races, but means the prerender is more likely to be 'correct', and fetch meaningful data.

  14. jakearchibald commented on Feb 25, 2021

    @jakearchibald
    ContributorAuthor

    I've been thinking about the event a bit more, and I don't think we need it. In fact, if "clone & swap" ends up being a stopgap before a shared model, we don't really want a bespoke event that might be useless in future.

    There are a few ways for a page to become active 'later':

    • Prerender
    • Portals
    • Bfcache

    And in all cases, you need to look at session storage if you have state there. Firefox and Safari do not queue storage events for bfcached pages (Chrome's experimental implementation does, but is what what we want @altimin?).

    Maybe the best rule for developers is to check session storage for changes after 'pageshow'? For prerender, we can make sure the storage is swapped before that event.

    The only sharp edge is that the 'pageshow' event fires after 'load' for some reason. We'd need to make sure there's an easy way to ignore the 'pageshow' event for a page that wasn't 'hidden'.

  15. asutherland commented on Feb 25, 2021

    @asutherland
    Collaborator

    Firefox's storage implementation originally intended to queue storage events but the naive implementation logic for that never actually worked, just temporarily consumed memory as it buffered the events without coalescing, so we removed it in a cleanup.

    Defining "pageshow" to mean "welcome to the future! everything has changed! reconsider everything!" is very appealing as an implementer, but if you're going to expose SessionStorage to a pre-rendered page at all, maybe it's better to instead generate synthetic StorageEvent events with the constraint that oldValue will not be present. So for pages that are frozen/etc., they'd just accumulate a list of dirty keys and when they become unfrozen, we fire a bunch of synthetic storage events at them. This seems friendlier to content and with reasonable memory costs since we're just storing a set of keys.

  16. 37 remaining items

  17. hiroshige-g commented on May 2, 2022

    @hiroshige-g

    Friendly ping; any opinion about whether we should queue storage events during BFCache or not?

  18. annevk commented on May 3, 2022

    @annevk
    Member

    Above @asutherland argued for dispatching a sequence of events for "dirty keys" (with oldValue being null). I think I personally would somewhat prefer not firing events at all and use this renewed interest in bfcache to teach web developers that this can have changed. It seems it has the potential to get rather noisy otherwise.

  19. asutherland commented on May 3, 2022

    @asutherland
    Collaborator

    I defer to @annevk in all things; my key concern in general would be that anything in BFCache should not accumulate an unbounded list of changes that could result in unbounded memory usage and @annevk's proposal does cover that.

  20. rakina commented on May 4, 2022

    @rakina
    Member

    As an outsider, @annevk's proposal in #119 (comment) makes sense to me to balance between supporting current usage of the API and not being too much of a burden on the implementation side. Sites can always proactively check for things on "pageshow" too. @mkruisselbrink @jakearchibald please shout if there are problems with this approach!

  21. hiroshige-g commented on May 24, 2022

    @hiroshige-g

    OK, the concensus here is #119 (comment).
    I updated the test to expect no events are fired for storage updates while the page is in BFCache, and filed a bug against Chromium (https://crbug.com/1328939).

  22. fergald commented on Mar 26, 2026

    @fergald

    I'm concerned by this proposal. Basically what it is saying is that storage events are not reliable and you cannot write code that expects to observe all storage events. APIs that are reliable without caveats are valuable. This is a compat-breaking change for Chrome, where these events are currently reliable. I suspect that it's a source of brokenness in any browser that doesn't deliver these events reliably. Is the BFCaching worth it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions