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

Group- or stack-based navigation history entry patterns #226

Open
k-egor-smirnov opened this issue Apr 15, 2022 · 8 comments
Open

Group- or stack-based navigation history entry patterns #226

k-egor-smirnov opened this issue Apr 15, 2022 · 8 comments
Labels
addition A proposed addition which could be added later without impacting the rest of the API

Comments

@k-egor-smirnov
Copy link

k-egor-smirnov commented Apr 15, 2022

I've tried Navigation API on Chrome Canary and have some thoughts about a navigation tree restoration. I would like to have an ability to traverse to entry that have been overridden.
For example, I have navigation structure like this:

graph TB
    A["Init"]-->second_a["first page"];
    A-->first_a["first page"];
    subgraph second["Tab 2"];
    second_a-->second_b["second page"];
    end;
    subgraph first["Tab 1"];
    first_a-->first_b["second page"];
    end;
  
Loading

And a navigation flow: Init page -> Tab 1 first page -> Tab 1 second page -> Init page (traverse back) -> Tab 2 first page.

When I've tried to save the navigation entry key of the Tab 1 second page and restore it when I'm on the Tab 2 first page, it will throw error that key doesn't exist cause the navigation tree was replaced with the new. Is it possible to have several navigation trees? It would be great to have this ability cause our web app has tab structure and having several versions of navigation tree will bring us closer to native-like behavior!
image

@frehner
Copy link

frehner commented Apr 15, 2022

I think #9 and #20 have discussions of this use case? I could be wrong though.

@domenic
Copy link
Collaborator

domenic commented Apr 18, 2022

Thanks for trying the API! And for making a clear diagram to illustrate your point.

It's unfortunately not really possible to change the browser's model of history into something tree-based instead of list-based. Fundamentally we always need to be able to answer the question "what does the back button do" and "what does the forward button do" and "what happens if the user holds down the back button and picks an entry two entries further back in the list" and so on. Which forces us into the list-based model with a single current-entry pointer.

However, it should be possible to generate a more tree- or stack-like experience on the web, with sufficient web developer work. It might need new capabilities like those mentioned in #9. I admit we haven't thought this through fully. Is that something you'd be able to help us think through? Like, if you had the ability to delete or add or rearrange history entries within the linear list, how would you use that to implement the tree-like experience that you want? It might give some weird experiences for the forward button or for holding down back and going back multiple entries; is that acceptable to you? Questions like that would be great to have someone dig into.

Another thing to consider here is that adding new entries has the potential for abuse, so it's not as easy to implement. But I think we can figure out ways to make it work, if it's necessary for the types of experiences web developers want to create.

@k-egor-smirnov
Copy link
Author

@frehner thank you for mentioning #9 issue. This is almost what I want, but I want to propose some changes to the original request.

As @domenic said, we have to answer 3 questions:

  • What does the back button do
  • What does the forward button do
  • What happens if the user holds down the back button and picks an entry two entries further back in the list

My idea is about creating something like a "Navigation Group". It should start with navigation.beginNavigationGroup() and return a unique id. All the new navigation entries should be added to this group simultaneously with the global navigation history and the group should save a pointer to the last navigation entry. A developer may end this group with navigation.endNavigationGroup(), so all next entries would add without assigning to it.
When it's needed to "replace tab in UI", it could be simply performed with navigation.traverseToGroup(<group id>).
navigation.traverseToGroup would replace the current navigation group with the one that was requested and with the history entry that was saved in this group.

In other words, it's something like history.replace and can be partially fallback-ed with history.back many times to the start of navigation group, fill all of it entries to history with history.pushState and history.back again to the saved pointer. Of course, it's a very bad example, but it possibly working :)

Backing to @domenic questions:

  • Both user and developer always know what the back and forward button would do. Developer may create UI that represent this navigation flow. User can always navigate back and know where it's going.
  • Browser history remains to be linear. We can simply have some navigation groups in shadow.

Sure, we can't rearrange the order of history entries or delete old one, but should we even have these features? I can't imagine use cases for now.

Remaining questions I still have no idea:

  • How to display these entries in browser history (e.g chrome://history/)
  • How to limit these groups back history length
  • Should we have the ability to create sub-groups

@domenic
Copy link
Collaborator

domenic commented Apr 20, 2022

@k-egor-smirnov I'd like to caution you away from jumping to quickly to a proposed solution and API (your "navigation groups" idea). Let's first try to get the problem statement clear, and also be clear on what is possible with current or planned extensions.

The way I suggest doing that is to outline use cases (you already did this, in your OP!) and then fully think through the consequences of them. So let's say we take your example from the OP. After the flow Init page -> Tab 1 first page -> Tab 1 second page -> Init page (traverse back) -> Tab 2 first page, what should the linear history list look like, to give the experience you want for the back button/back-2 button/etc.?

Let's use shorter abbreviations for your scenario: we'll call your "init page" A, "tab 1" X, and "tab 2" Y. Let's use [] to denote the current entry. So the user journey looks like:

  • [A]
  • A, [X1]
  • A, X1, [X2]
  • [A], X1, X2 (is this right?)
  • ??? I'm unclear on what it should look like at this step, besides having [Y1] somewhere. What should be before [Y1]? Should anything be after [Y1]?

@k-egor-smirnov
Copy link
Author

k-egor-smirnov commented Apr 20, 2022

@domenic sure, it's not about real API proposal, just to explain the flow. I think the journey should looks like this, but let's also assign Z for non-grouped entries:
[A]
A, [X1]
A, X1, [X2]
A, X1, X2, [X3]
A, X1, [X2], X3
--- user jump to Y ---
A, [Y1]
A, Y1, [Y2]
--- Y group ends ---
A, Y1, Y2, [Z1]
A, Y1, Y2, Z1, [Z2]
--- user jump back to X; Z entries are dropped; group X history was restored ---
A, X1, [X2], X3

@domenic
Copy link
Collaborator

domenic commented Apr 20, 2022

Perfect, that is super-helpful! I understand the problem space a lot more now. It also makes it clear why you jumped to a stash-then-restore approach.

The kind of primitives in #9 would technically allow what you have above:

  • On the user jump to Y, you would go back to A, delete forward entries, then navigate to Y1.
  • On the user jump back to X, you would either:
    • go back to A, delete forward entries, navigate to X1, navigate to X2, navigate to X3, and then traverse back to X2; or
    • replace Y1 with X1, Y2 with X2, and Z1 with X3; go back to X2; and delete Z2.

However I can see the disadvantages pretty clearly:

  • A navigation-based flow would likely end up with multiple navigations in a row without user interaction. These would likely be skipped by the browser's back button, which is totally not what you want.
  • A replacement-based flow would not work if you were replacing a smaller group with a larger group; it would have to be supplemented with navigations.
  • And, of course, all this requires a lot of work from developers to track and restore things. It'd be nicer and probably more seamless if the platform could do it for you.

To some extent app developer (or router developer) work is going to be unavoidable here, to deal with exactly what the stash and restore steps mean for your application. (Do you keep stuff off-screen, but hidden? Do you re-render everything from scratch? Etc.) I think the most the browser will do is set up the history entry list to contain the right entries, in particular with the right URL and navigation API states. But that seems like a reasonable thing to ask.

Another aspect worth considering, especially from a browser-implementer point of view, is how this interacts with abuse protections (i.e., not allowing you to stuff tons of entries in to prevent the back button from escaping your site). As whatwg/html#7832 mentions, browsers generally stamp some entries as legitimate, and others as spammy, so that the back button will skip the spammy entries.

I think your framing of stash-then-restore gives a good answer here. Basically, you are "borrowing" certified-legitimate entries for later use. This is better than the manual version I outlined above, involving deletions and then navigations; there, deleting the entry loses the signal that it was legitimate.

A consequence of this is that we'd need to make sure you couldn't restore a group twice, thus doubling your count of legitimate entries. But that seems like a reasonable constraint.

At this point I'm tempted to jump back into API design myself, but I'll hold off for a day or two to let discussions on the problem space continue...

@Yay295
Copy link

Yay295 commented Apr 20, 2022

Using an iframe for each tab would probably accomplish this. The top level history list would look like this at the end:
A, [X], Y
the X iframe entries would be:
X1, [X2], X3
and the Y iframe entries would be:
Y1, [Y2]

@domenic domenic changed the title Traverse to entry from navigation tree that have been replaced Group- or stack-based navigation history entry patterns Apr 25, 2022
@domenic domenic added the addition A proposed addition which could be added later without impacting the rest of the API label May 4, 2022
@tbondwilkinson
Copy link
Contributor

This use case came up again in discussions with @sebmarkbage, so I do think it's a further signal that it would be useful for the ability to stash-then-restore history entries. I think most cases involve these flows developing naturally so there's not concern about user activation.

I think the best way to approach this is a new history operation mode, that acts like a replace on a specific history entry but disconnects the current and forward entries so that they can be later restored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition A proposed addition which could be added later without impacting the rest of the API
Projects
None yet
Development

No branches or pull requests

5 participants