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

Add initial scroll position hook to HTML spec #10759

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

DavMila
Copy link

@DavMila DavMila commented Nov 12, 2024

scroll-start-target is a CSS property which affects an element's initial scroll position. A scroll container's initial scroll position determines what scroll offset that scroll container should be at before any "explicit" scrolling (e.g. user gesture, programmatic scroll API) occurs on it. This initial scroll position is affected by CSS properties such as scroll-start-target and might change as a document is loaded.

This patch adds a step in the "Updating the document" section of the HTML spec to direct user agents on when to re-evaluate initial scroll positions and adjust accordingly.

(See WHATWG Working Mode: Changes for more details.)


/acknowledgements.html ( diff )
/browsing-the-web.html ( diff )
/infrastructure.html ( diff )

scroll-start-target is a CSS property which affects an element's
initial scroll position. A scroll container's initial scroll position
determines what scroll offset that scroll container should be at before
any "explicit" scrolling (e.g. user gesture, programmatic scroll API)
occurs on it. This initial scroll position is affected by CSS
properties such as scroll-start-target and might change as a document
is loaded.

This patch adds a step in the "Updating the  document" section of the
HTML spec to direct user agents on when to re-evaluate initial scroll
positions and adjust accordingly.
Copy link
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

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

@dbaron would you be willing to review this as well?

source Outdated
Comment on lines 103885 to 103887
<p>To <dfn>update the initial scroll positions for scroll containers</dfn> in a
<code>Document</code> <var>document</var>,
perform the following steps:</p>
Copy link
Member

Choose a reason for hiding this comment

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

Indentation here doesn't follow the contribution guidelines. (This also applies elsewhere in this PR it seems.) You can also leave out "perform the following steps" here.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks, I believe I've now fixed the indentation.

source Show resolved Hide resolved
Copy link
Member

@domenic domenic left a comment

Choose a reason for hiding this comment

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

I worry about where this integrates into the page lifecycle. It's in "update document for history step application", and occurs in both documentIsNew = true and false cases.

This means:

  • It will run for bfcache traversals. This is probably not correct; bfcache traversals should probably just leave the document as it was, instead of performing extra scroll adjustments.

  • For new documents, it will run before most of the document content is loaded. This is probably not correct; documents will generally have no scroll containers at this early stage of the process.

Can I ask how you are implementing this in Chromium, and in particular, at what point in the document lifecycle? Do you do so immediately (like this spec), and just ignore the fact that there probably will be no scrollers at this early point? Do you wait for the load or DOMContentLoaded events? Do you do periodic polling and retrying, like the spec's current "try to scroll to the fragment"?

Step should happen within "If documentIsNew" block like
fragment-scroll.
@DavMila
Copy link
Author

DavMila commented Nov 15, 2024

  • It will run for bfcache traversals. This is probably not correct; bfcache traversals should probably just leave the document as it was, instead of performing extra scroll adjustments.
  • For new documents, it will run before most of the document content is loaded. This is probably not correct; documents will generally have no scroll containers at this early stage of the process.

Can I ask how you are implementing this in Chromium, and in particular, at what point in the document lifecycle? Do you do so immediately (like this spec), and just ignore the fact that there probably will be no scrollers at this early point? Do you wait for the load or DOMContentLoaded events? Do you do periodic polling and retrying, like the spec's current "try to scroll to the fragment"?

I think you're right that the placement of the step is not correct. I ought to have placed the step within the "If documentIsNew is true" block, and I've done so now. The intent is for it to have similar timing to (but lower priority than) "try to scroll to the fragment" so that user agents treat late-arriving scroll containers and/or their scroll-start-target elements the same way as late-arriving fragments which I believe would still be scrolled to.

@domenic
Copy link
Member

domenic commented Nov 18, 2024

The issue is that "try to scroll to the fragment" does a periodic polling operation, which your spec does not. Your spec only tries once, at initial document load, at which point no scrollers will exist.

Again, can I ask how you implemented this in Chromium?

@DavMila
Copy link
Author

DavMila commented Nov 20, 2024

The issue is that "try to scroll to the fragment" does a periodic polling operation, which your spec does not. Your spec only tries once, at initial document load, at which point no scrollers will exist.

Ah thanks, that's clear to me now.

Again, can I ask how you implemented this in Chromium?

The implementation in Chromium does periodically poll like you asked. I guess the hook I'm adding should follow a similar pattern to "try to scroll to the fragment"?
Perhaps I should:

  • keep the placement of the new step ("update the initial scroll positions for scroll containers") in "If documentIsNew is true", and
  • modify the new step to periodically poll each scroll container until the user agent believes the user is no longer interested in the initial scroll position.

Let me know if this sounds reasonable or there's a totally different better approach, thanks!

@domenic
Copy link
Member

domenic commented Nov 21, 2024

That strategy does sound reasonable, although I wonder if you should merge the algorithm with "try to scroll to the fragment" so that they're not competing with each other. Again, I wonder what you do in Chromium; do you have two separate periodic polling algorithms running, one for fragment and one for scroll containers? Or just one?

@DavMila
Copy link
Author

DavMila commented Nov 25, 2024

That strategy does sound reasonable, although I wonder if you should merge the algorithm with "try to scroll to the fragment" so that they're not competing with each other. Again, I wonder what you do in Chromium; do you have two separate periodic polling algorithms running, one for fragment and one for scroll containers? Or just one?

In Chromium we poll separately for scrolling to the fragment and scroll containers. And we treat scrolling to the fragment as an "explicit scroll" (like a scroll gesture) as a way of giving it precedence over initial-scroll-position-affecting properties like scroll-start-target.
So a scroll to a fragment would be a reason why the user agent "believes the user is no longer interested in the initial scroll position" (like with a gesture scroll).

There might be no scroll containers the first time the algorithm runs,
so we need to keep polling until the user agent believes the user is no
longer interested in the initial scroll position.
@DavMila DavMila requested a review from domenic December 2, 2024 15:48
<li><p><span data-x="perform a scroll">Perform a scroll</span> of <var>scroller</var>'s <span
data-x="scrolling box">scrolling box</span> to <var>position</var>.</p></li>

<li><p><span>Update the initial scroll position</span> for <var>scroller</var>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

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

This always calls itself recursively. That doesn't seem right... it seems like the first time you succeed, you should stop looping.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for calling this out. The idea is for user agents to keep scrollers scrolled to their initial scroll position until some user action indicates otherwise. Since style properties can change the initial scroll position, we need to keep re-evaluating the initial scroll position until such a user action has taken place. I think that, in practice, this re-evaluating of the initial scroll position will simply lead to a noop most of the time.
Also, this is pretty much what user agents should already do with properties like align-content. You can see in this demo (which, unfortunately, only works in Safari at the moment) that as messages are loaded, the scroll position needs to be updated to keep the first message scrolled to, which is what align-content: end is used for here. As soon as the user scrolls, the scroll container is no longer kept at the initial scroll position.

Copy link
Member

Choose a reason for hiding this comment

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

I see. So this is fairly different from the "try to scroll to a fragment" case, where we continue until the fragment is loaded; instead, we need to actively continue, possibly forever. Very interesting!

Let's summarize this in a <p class="note"> after "update the initial scroll position". Something like:

Although [try to scroll to the fragment] and [update the initial scroll position for scroll containers] take place at the same point, and both use a polling system to periodically re-attempt the scrolling, their goals are different. [Try to scroll to the fragment] polls repeatedly until the [indicated part] of the document is found, since it may take a while for the element to show up during the document load. [Update the initial scroll positions for scroll containers] does not have such a finish condition; it can run indefinitely, as long as the user does not indicate disinterest. This is necessary to achieve the desired behavior of keeping a [scroll container] "snapped" to the position indicated by certain CSS properties.

Please feel free to improve my wording; that's just my best understanding so far!

Copy link
Author

Choose a reason for hiding this comment

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

Thanks, I think your note summarizes it quite well, Done.

source Show resolved Hide resolved
source Show resolved Hide resolved
source Show resolved Hide resolved
source Show resolved Hide resolved
source Show resolved Hide resolved
source Show resolved Hide resolved
source Show resolved Hide resolved
@DavMila DavMila requested a review from domenic December 6, 2024 19:16
source Show resolved Hide resolved
<li><p><span data-x="perform a scroll">Perform a scroll</span> of <var>scroller</var>'s <span
data-x="scrolling box">scrolling box</span> to <var>position</var>.</p></li>

<li><p><span>Update the initial scroll position</span> for <var>scroller</var>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

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

I see. So this is fairly different from the "try to scroll to a fragment" case, where we continue until the fragment is loaded; instead, we need to actively continue, possibly forever. Very interesting!

Let's summarize this in a <p class="note"> after "update the initial scroll position". Something like:

Although [try to scroll to the fragment] and [update the initial scroll position for scroll containers] take place at the same point, and both use a polling system to periodically re-attempt the scrolling, their goals are different. [Try to scroll to the fragment] polls repeatedly until the [indicated part] of the document is found, since it may take a while for the element to show up during the document load. [Update the initial scroll positions for scroll containers] does not have such a finish condition; it can run indefinitely, as long as the user does not indicate disinterest. This is necessary to achieve the desired behavior of keeping a [scroll container] "snapped" to the position indicated by certain CSS properties.

Please feel free to improve my wording; that's just my best understanding so far!

David Awogbemila added 2 commits December 9, 2024 09:20
The search for scroll containers includes shadow trees, so the list
traversal order should account for shadow trees.

Adds a note to clarify the recursive nature of "update initial scroll
positions for scroll containers" as it differs from "try to scroll to
the fragment."
@DavMila DavMila requested a review from domenic December 9, 2024 14:34
Copy link
Member

@domenic domenic left a comment

Choose a reason for hiding this comment

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

LGTM. We can merge after all the checkboxes in the OP are filled out.

Can you be sure that the following are tested in the web platform tests?

  • That scrollers inside shadow trees are included
  • That the order is shadow-including tree order, not e.g. flat tree order

@domenic domenic added addition/proposal New features or enhancements topic: navigation integration Better coordination across standards needed labels Dec 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements integration Better coordination across standards needed topic: navigation
Development

Successfully merging this pull request may close these issues.

3 participants