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

Clarify some joint session history relations #29

Merged
merged 5 commits into from
Feb 12, 2021
Merged

Conversation

domenic
Copy link
Collaborator

@domenic domenic commented Feb 5, 2021

Closes #23. Closes #24.

Note that the failed-back detection might change per #16.

/cc @jakearchibald @natechapin

README.md Outdated
@@ -193,7 +195,7 @@ _TODO: realistic example of when you'd use this._

Unlike the existing history API's `history.go()` method, which navigates by offset, navigating by key allows the application to not care about intermediate history entries; it just specifies its desired destination entry.

There are also convenience methods, `appHistory.back()` and `appHistory.forward()`.
There are also convenience methods, `appHistory.back()` and `appHistory.forward()`. Note that these only navigate through the _app history_ list, not the _joint session history_; that is, they do not navigate frames, and they cannot go back or forward to a cross-origin destination.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that possible? What if:

  1. PageA contains an iframe to PageB.
  2. appHistory.pushNewEntry(…) in top level page.
  3. Navigate iframe PageB to PageC.

Now we have a joint history with 3 items, and we're currently on the third of those items.

What does appHistory.entries look like? What happens if appHistory.back() is called? And assuming the first entry in appHistory.entries points to step 1 above, what happens if it's traversed to? Does it also navigate the iframe?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, OK, I worked through this and it looks like you can navigate child frames. I don't think child frames can use app history to navigate parent frames...

I'll push a commit showing what I ended up with.

@domenic
Copy link
Collaborator Author

domenic commented Feb 9, 2021

Hmm, no, not ready to make a call here. Let's pull the discussion at https:/WICG/app-history/pull/29/files#r571439026 out to top level. Here's what that scenario looks like in the current HTML spec:

// Outer frame session history
O1. https://example.com/page-a
O2. https://example.com/page-a-pushed

// Inner frame session history
I1. https://example.com/page-b
I2. https://example.com/page-c

// Joint session history: O1, I1, I2 / currently on I2

// App history in top-level
1. https://example.com/page-a         (associated to O1)
2. https://example.com/page-a-pushed  (associated to O2)

// App history in frame
1. https://example.com/page-b         (associated to I1)
2. https://example.com/page-c         (associated to I2)

This shows you what appHistory.entries looks like in each case.

What happens if appHistory.back() is called?

If it's called from the top level, it updates the "current" pointer for the outer frame's session history to O1. Thus, we navigate the top level back to page-a, but leaves the iframe on page-c. That matches the behavior of https://boom-bath.glitch.me/joint-sh-1.html if you click "pushState(), Navigate frame, history.go(-2)", at least in Chrome and Firefox.

The resulting joint session history is O1, O2, I1 / currently on O1.

If appHistory.back() is called from inside the frame, then similarly, we change the "current" pointer for the inner frame's session history to I1. That matches the behavior of https://boom-bath.glitch.me/joint-sh-1.html if you click "pushState(), Navigate frame, history.back()", at least in Chrome and Firefox.

The resulting joint session history is O1, I1, I2 / currently on I1.

Can all this be made to work with the model at whatwg/html#5767 ?

@jakearchibald
Copy link

I'm assuming:

// Joint session history: O1, I1, I2 / currently on I2

…Is supposed to be

// Joint session history: O1, O2, I1, I2 / currently on I2

If (appHistory.back is) called from the top level, it updates the "current" pointer for the outer frame's session history to O1. Thus, we navigate the top level back to page-a, but leaves the iframe on page-c. That matches the behavior of https://boom-bath.glitch.me/joint-sh-1.html if you click "pushState(), Navigate frame, history.go(-2)", at least in Chrome and Firefox.

This happens due to a bug (in Chrome at least, but I don't think it's intentional in other browsers either). I'd rather we fixed the bug than have to spec the bug.

https://iframe-session-history.glitch.me/ - if you navigate one iframe, then another, go(-2) will navigate both iframes back. The "if you navigate the parent then don't navigate the children at the same time" bug is super weird. I guess navigating back more than one entry at once is rare, so folks haven't really encountered it much, but appHistory will cause it to happen more often.

whatwg/html#1191 (comment) - there's a possibility that the reason Chrome kept the bug is no longer an issue, but I'm not sure.

Oh, here's a better reason why we shouldn't spec that bug. Imagine a page with an iframe, and the following history:

  1. Page at #, iframe at #
  2. Iframe to #foo
  3. Top page to #bar
  4. Iframe to #hello - current step

appHistory.back() in the iframe will take us back to step 2, but due to the bug it will navigate the top page back to # and leave the iframe at #hello, which seems baaaaaaad. I'm pretty certain the correct behaviour is to take the page back to # and the iframe to #foo.

Another case to think about:

  1. Page at #, iframe at #
  2. Iframe to #foo
  3. Top page to #bar - current step

What happens if the iframe calls appHistory.navigateTo(appHistory.currentEntry)? This could either no-op, because it's already on the current entry, or it could move to step 2 of the joint session history, which would navigate the parent page back to #.

@domenic
Copy link
Collaborator Author

domenic commented Feb 10, 2021

…Is supposed to be

I don't think so? We remove all current session history entries except the current entry of the joint session history, so we remove O2.

This happens due to a bug (in Chrome at least, but I don't think it's intentional in other browsers either). I'd rather we fixed the bug than have to spec the bug.

Oh, I didn't realize this case was the one we were discussing in the other threads.

Changing this seems really unrealistic to me, given the compat implications surrounding the Gecko and Chrome code, and the fact that in general moving from interop to a new state is a hard sell. Plus, this behavior does what I want, and what I think authors generally want---it gives the ability to control your frame's history independent of any subframes or parent frames!

Oh, here's a better reason why we shouldn't spec that bug. Imagine a page with an iframe, and the following history:

[...]

appHistory.back() in the iframe will take us back to step 2, but due to the bug...

Well... I'm hoping we can avoid thinking of appHistory as moving through the joint list, and instead just talk about it navigating the individual frame. So, what it does, is somehow waves hands moves the iframe back to #foo, while leaving the top page untouched.

Do you think there's any realistic model of history that is browser-compatible while allowing operations like that? In particular, app history would really benefit from a local this-browsing-context-only session history, like the current spec has.

Another case to think about:

[...]

What happens if the iframe calls appHistory.navigateTo(appHistory.currentEntry)?

It should be a no-op.

@domenic
Copy link
Collaborator Author

domenic commented Feb 10, 2021

Re-reading your response and my response to it, I think we may have over-focused on the particular example and its related bug. My larger point and question was the following:

  • The current spec has a model, where there are independent per-browsing context session histories, each with their own "current" pointer.

  • This model would be extremely useful for meeting the current design vision of app history.

  • We have some evidence that cases which you wouldn't expect in a pure joint SH model, but would only be possible with an independent per-BC session histories, can occur. (My demo/the bug.) So a per-BC model is probably not un-implementable, and might even match implementations?

  • That said, we really need a stable and sensible foundation for session history, browsing sessions, and the like, before we introduce anything new like app history. Your current work is our best hope at that. So the question is, do you see a path forward for getting this kind of per-BC behavior, while still rationalizing session history according to your plan (or some slight tweak to it)? Or, is your work purely based on a joint session history model that would clash badly with allowing independently varying per-BC current pointers?

@domenic
Copy link
Collaborator Author

domenic commented Feb 11, 2021

After some offline discussions, we're going to proceed with a joint session history model for now. This means app history can impact other frames (although it doesn't give a view into them).

One major reason of this is that, if appHistory can move a frame through its frame-specific session history independently, the developer can put the user into a state they've never seen before. This then raises weird questions like, what does the back button do? What does the forward button do? And, of course, there's the potential implementation and spec complexity.

So I'll update this PR in that direction. Separately, @jakearchibald seems to be working on a sort of library of crazy nested iframe cases. When that's available, we'll want to analyze each of them in terms of how appHistory operations and views behave.

Closes #23. Closes #24.

Note that the failed-back detection might change per #16.
@domenic
Copy link
Collaborator Author

domenic commented Feb 11, 2021

Alright, this has been updated. I'd love @natechapin and @jakearchibald to take another look before merging. Also, if he has time, @creis's perspective would be much appreciated.

@jakearchibald
Copy link

Separately, @jakearchibald seems to be working on a sort of library of crazy nested iframe cases. When that's available, we'll want to analyze each of them in terms of how appHistory operations and views behave.

haha err it's more of a little presentation on what I'm trying to do with the session history spec. It does cover a few history cases the current spec doesn't handle, but I'm not sure how useful that'll be here.

Copy link

@jakearchibald jakearchibald left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of corrections & questions, but happy for this to land and the questions broken out into separate issues. Enjoying where this is heading!

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved

- If code in the inner frame calls `appHistory.back()`, this will take us back to I1, and take the joint session history back to C. (This does not impact the outer frame.)

- If code in either the inner frame or the outer frame calls `history.back()`, this will take the joint session history back to C, and thus update the inner frame's current app history entry from I2 to I1. (There is no impact on the outer frame.)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting outcome, because C isn't an entry in the iframe, it belongs to the parent.

I'm not saying it's wrong, but it's something that might need further documentation.

You could say: To traverse to a specific appHistory entry, joint history should traverse the least number of steps to make that history entry active.

That means you'd end up on a different item of joint session history depending on the direction of travel. I can't decide if that's intuitive or not.

The alternative is to always travel to the step in joint history where that entry was created. That means it's consistent going back & forward, but may result in changing other frames more than necessary. In this example it would navigate the parent frame (and of course hit that fun bug).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was going for least number of steps... hmm. I'll try to capture this and maybe open a tracking issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I assume your comment is meant to apply to the second bullet, not the third. The third uses history.back(), not appHistory.back().

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes sorry

@domenic domenic merged commit bc2d605 into main Feb 12, 2021
@domenic domenic deleted the forward-clearing branch February 12, 2021 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants