Skip to content
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

Nested contexts and unload prompts #6446

Closed
jakearchibald opened this issue Mar 4, 2021 · 5 comments · Fixed by #6315
Closed

Nested contexts and unload prompts #6446

jakearchibald opened this issue Mar 4, 2021 · 5 comments · Fixed by #6315

Comments

@jakearchibald
Copy link
Collaborator

jakearchibald commented Mar 4, 2021

This came up as I was working through the session history rewrite. How should navigations behave when a page, an iframe, or multiple iframes request to show an unload prompt?

Here's what I've noticed by poking browsers:

Firefox

For top-level navigations, Firefox shows unload prompts, and if the user cancels, they stay on the same session history entry.

Firefox ignores requests to show unload prompts for iframes unless the user has interacted with the iframe (eg clicked it). Otherwise:

For iframes, if going back 1 position, Firefox shows unload prompts, and if the user cancels, they stay on the same session history entry.

If going back more than 1 position, things get weird. Firefox shows unload prompts, and if the user cancels, the cancelled frame doesn't navigate, but the position in session history is changed, which is weird.

If going back 2 positions, involving 2 iframes each with an unload prompt, you get two prompts and if you cancel both you stay on the same session history entry.

Chrome

For top-level navigations, Chrome shows unload prompts, and if the user cancels, they stay on the same session history entry.

For iframes, if going back 1 position, it does the same. Chrome shows unload prompts, and if the user cancels, they stay on the same session history entry.

If going back more than 1 position, things get weird. Chrome shows unload prompts, and if the user cancels, the cancelled frame doesn't navigate, but the position in session history is changed, which is weird.

If going back 2 positions, involving 2 iframes each with an unload prompt, you get two prompts but even if you cancel both Chrome will navigate one of the iframes anyway 🙃. It also changes the position in session history.

Safari

For top-level same-origin navigations, Safari shows unload prompts, and if the user cancels, they stay on the same session history entry.

For top-level cross-origin, Safari does not show a prompt.

Safari allows unload prompts for iframes. It changes the position in session history but doesn't navigate the cancelled frames. Again this is weird, but it seems to do it more consistently in Chrome. Not sure if this is a good thing.

Due to moving the session history position, but not navigating the page, Safari can be 'tricked' into displaying the wrong URL in the URL bar. However, this only happens for same-origin so it isn't a security issue.

The 'correct' behaviour

If we want to allow unload prompts for iframes, I like to spec it so: If any unloading document requests an unload prompt, show one prompt. If cancelled, abort the navigation/traversal. However, I'm not sure UAs are in a good position to 'synchronise' navigations in a way where unloading happens across all documents before moving to the next steps.

When I first saw Firefox's behaviour, I thought we could maybe ignore all unload prompts from iframes, but given it allows them after interaction, maybe not.

I'm less sure about Safari's behaviour of ignoring unload prompts for cross-origin navigations. Feels like that would miss a lot of legitimate use-cases.

The tests

Test: Same origin top level navigation

  1. Go to https://iframe-session-history.glitch.me/page.html?one
  2. Go to https://iframe-session-history.glitch.me/
  3. Add unload prompt
  4. Go back, cancel prompt

Firefox: Shows prompt. Cancelling keeps you on the same session history entry.

Chrome: Shows prompt. Cancelling keeps you on the same session history entry.

Safari: Shows prompt. Cancelling keeps you on the same session history entry.

Behaviour here seems to make sense.

Test: Cross origin top level navigation

  1. Go to https://example.com
  2. Go to https://iframe-session-history.glitch.me/
  3. Add unload prompt
  4. Go back, cancel prompt

Firefox: Shows prompt. Cancelling keeps you on the same session history entry.

Chrome: Shows prompt. Cancelling keeps you on the same session history entry.

Safari: No prompt. Navigates.

Interesting that Safari is different.

Test: Same origin top level navigation, go(-2)

  1. Go to https://iframe-session-history.glitch.me/page.html?one
  2. Go to https://iframe-session-history.glitch.me/
  3. iframe-1, navigate to 'one'
  4. Add unload prompt
  5. Go back two entries at once (using history.go(-2) or long-press back button), cancel prompt

Firefox: Shows prompt. Cancelling keeps you on the same session history entry.

Chrome: Shows prompt. Cancelling keeps you on the same session history entry.

Safari: Prompt shown. Cancelling keeps you on the same session history entry, however, the URL bar changes to https://iframe-session-history.glitch.me/page.html?one (thankfully location.href doesn't change).

Safari's behaviour here is a bug, surely.

Test: Traverse single nested context with prompt

https://iframe-session-history.glitch.me/

  1. iframe-1, navigate to 'one'
  2. iframe-2, navigate to 'one'
  3. iframe-2, add unload prompt
  4. Go back, cancel prompt.

Firefox: No prompt shown unless the iframe has been interacted with (eg clicked). Otherwise, prompt shown. Cancelling keeps you in the same position in session history.

Chrome: Prompt shown. Cancelling keeps you in the same position in session history.

Safari: Prompt shown. Cancelling moves you to the previous position in session history, but the iframe doesn't navigate. Going back again doesn't reshow the prompt (it navigates iframe-1 as expected). Going forward twice (to the point where iframe-2 was navigated) shows the prompt again, and reloads the iframe page unless cancelled.

Safari's behaviour seems broken here. Chrome/Firefox behaviour is better.

Test: Traverse two nested contexts at once, one with prompt

https://iframe-session-history.glitch.me/

  1. iframe-1, navigate to 'one'
  2. iframe-2, navigate to 'one'
  3. iframe-1, add unload prompt
  4. Go back two entries at once (using history.go(-2) or long-press back button), cancel prompt

Firefox: No prompt shown unless the iframe has been interacted with (eg clicked). Otherwise, cancelling moves you back two places in session history. ifame-2 navigates, iframe-1 does not. Going forward shows the prompt again.

Chrome: Prompt shown. Cancelling moves you back two places in session history. ifame-2 navigates, iframe-1 does not. Going forward shows the prompt again.

Safari: Prompt shown. Cancelling moves you back two places in session history. ifame-2 navigates, iframe-1 does not. Same behaviour as previous test in terms of navigating back & forth.

Safari's behaviour is weird but consistent with the last test. The way Chrome/Firefox seems to have changed behaviour with this example is weird.

The results are the same if iframe-2 has the prompt instead.

Test: Traverse two nested contexts at once, both with prompt

  1. iframe-1, navigate to 'one'
  2. iframe-2, navigate to 'one'
  3. iframe-1, add unload prompt
  4. iframe-2, add unload prompt
  5. Go back two entries at once (using history.go(-2) or long-press back button), cancel prompts.

Firefox: No prompt shown unless the iframe has been interacted with (eg clicked). Otherwise, two prompts are shown. Cancelling both keeps you on the same session history entry.

Chrome: Two prompts shown. Cancelling both moves you back two places in session history. However, iframe-2 is still navigated.

Safari: Two prompts show. Cancelling both moves you back two places in session history. It prevents the navigation of both frames. Going forward doesn't show a prompt, but going forward again shows a prompt for iframe-2.

Safari's behaviour is kinda consistent with previous behaviours, although the lack of prompt when going forward seems unusual compared to previous results. Chrome navigating iframe-2 is just a bug, surely.

@annevk
Copy link
Member

annevk commented Mar 4, 2021

I'm not aware of anything and I agree it sounds great. I have asked some other people to weigh in as needed. (Gijs provided https://bugzilla.mozilla.org/show_bug.cgi?id=1131187 and https://bugzilla.mozilla.org/show_bug.cgi?id=1655866, and suggested the frame in the tests in OP might not be interacted with by the user as the cause.)

@jakearchibald
Copy link
Collaborator Author

Aww, I was hoping we could just ignore unload prompts from frames. I guess not. I've updated the results and recommendation.

@jakearchibald
Copy link
Collaborator Author

jakearchibald commented Mar 13, 2021

The "weird" behaviour of moving history position without updating the active document came up in #6483 too, but it seems reasonable there, so I might end up specing that anyway.

If the top level shows a prompt, and the user cancels, the traversal should cancel.

Ideally, for iframes, we should cancel traversal if any iframe shows a prompt and the user cancels (and limit to one prompt). But if that isn't implementable, we should allow the traversal to go ahead but avoid changing the active documents for frames that show a prompt (and it's cancelled).

We should avoid the mix of iframe behaviours we currently see in Chrome and Firefox.

@jakearchibald
Copy link
Collaborator Author

There's also the case of navigations that fail the allowed to navigate test. Again, it feels like we should cancel the whole traversal if any part of it is not allowed.

@jakearchibald
Copy link
Collaborator Author

jakearchibald commented Apr 8, 2021

If the page has two iframes, both with unload listeners, and the top-level page is navigated, all browsers ignore the second prompt (although the event still runs). I haven't tested this with cross-origin iframes (is it racey?), but it seems like browsers are trying not to show multiple prompts, but just forgot (or don't care about) the go(-2) case.

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

Successfully merging a pull request may close this issue.

2 participants