commit 25cebeac46f86fae49437fda3a206a67630f287e
parent 66485ded5df6a9540c7b63bb411fa099432778f8
Author: Keith Cirkel <keithamus@users.noreply.github.com>
Date: Fri, 3 Oct 2025 12:25:52 +0000
Bug 1955947 - Part 1: Implement Document::HasBeenScrolledSince, replacing Document::HasBeenScrolled r=farre,layout-reviewers,emilio,hiro
This change replaces the `Document::HasBeenScrolled` method, introducing
`Document::HasBeenScrolledSince(lastGeneration)`. This method delegates to
PresContext, which keeps a record of the last scroll "generation" - an
integer counter incremented on each scroll. This is sufficient to determine
whether we should adjust the scroll during a navigation intercept: if the root
scroll generation has been incremented between the time the navigate event
fired and the time the intercept settles, it's likely that was intentional and
we shouldn't try to perform a scroll.
This change is required because Navigations that cause scrolling should
(such as prior .scroll() calls, or navigating to an anchor and
navigating back) should properly restore scroll position, but should not
do so when there is an explicit intent to scroll during intercept.
Differential Revision: https://phabricator.services.mozilla.com/D266430
Diffstat:
6 files changed, 44 insertions(+), 14 deletions(-)
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
@@ -13435,13 +13435,17 @@ bool Document::IsActive() const {
!GetBrowsingContext()->IsInBFCache();
}
-bool Document::HasBeenScrolled() const {
- nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(GetInnerWindow());
- if (!window) {
- return false;
+uint32_t Document::LastScrollGeneration() const {
+ if (nsPresContext* pc = GetPresContext()) {
+ pc->LastScrollGeneration();
}
- if (ScrollContainerFrame* frame = window->GetScrollContainerFrame()) {
- return frame->HasBeenScrolled();
+
+ return 0;
+}
+
+bool Document::HasBeenScrolledSince(const uint32_t& aLastScrollGeneration) const {
+ if (nsPresContext* pc = GetPresContext()) {
+ pc->HasBeenScrolledSince(aLastScrollGeneration);
}
return false;
diff --git a/dom/base/Document.h b/dom/base/Document.h
@@ -2724,12 +2724,13 @@ class Document : public nsINode,
}
/*
- * Return if this document ever has been scrolled.
- * We'd like this to be
- * https://html.spec.whatwg.org/#has-been-scrolled-by-the-user, but better to
- * check for any scroll than no scroll.
+ * Return if this document has been scrolled since the given last scroll
+ * generation.
+ *
+ * Similar to: https://html.spec.whatwg.org/#has-been-scrolled-by-the-user.
*/
- bool HasBeenScrolled() const;
+ bool HasBeenScrolledSince(const uint32_t& aLastScrollGeneration) const;
+ uint32_t LastScrollGeneration() const;
/**
* Returns whether this document should perform image loads.
diff --git a/dom/events/NavigateEvent.cpp b/dom/events/NavigateEvent.cpp
@@ -230,6 +230,9 @@ void NavigateEvent::InitNavigateEvent(const NavigateEventInit& aEventInitDict) {
mInfo = aEventInitDict.mInfo;
mHasUAVisualTransition = aEventInitDict.mHasUAVisualTransition;
mSourceElement = aEventInitDict.mSourceElement;
+ if (RefPtr document = GetDocument()) {
+ mLastScrollGeneration = document->LastScrollGeneration();
+ }
}
void NavigateEvent::SetCanIntercept(bool aCanIntercept) {
@@ -412,8 +415,12 @@ static void ScrollToBeginningOfDocument(Document& aDocument) {
}
// https://html.spec.whatwg.org/#restore-scroll-position-data
-static void RestoreScrollPositionData(Document* aDocument) {
- if (!aDocument || aDocument->HasBeenScrolled()) {
+static void RestoreScrollPositionData(Document* aDocument,
+ const uint32_t& aLastScrollGeneration) {
+ // 1. Let document be entry's document.
+ // 2. If document's has been scrolled by the user is true, then the user agent
+ // should return.
+ if (!aDocument || aDocument->HasBeenScrolledSince(aLastScrollGeneration)) {
return;
}
@@ -433,7 +440,7 @@ void NavigateEvent::ProcessScrollBehavior() {
if (mNavigationType == NavigationType::Traverse ||
mNavigationType == NavigationType::Reload) {
RefPtr<Document> document = GetDocument();
- RestoreScrollPositionData(document);
+ RestoreScrollPositionData(document, mLastScrollGeneration);
return;
}
diff --git a/dom/events/NavigateEvent.h b/dom/events/NavigateEvent.h
@@ -129,6 +129,7 @@ class NavigateEvent final : public Event {
JS::Heap<JS::Value> mInfo;
bool mHasUAVisualTransition = false;
RefPtr<Element> mSourceElement;
+ uint32_t mLastScrollGeneration;
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-navigateevent-interface:navigateevent-2
enum InterceptionState mInterceptionState = InterceptionState::None;
diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h
@@ -470,6 +470,12 @@ class nsPresContext : public nsISupports,
*/
bool HasPaginatedScrolling() const { return mCanPaginatedScroll; }
+ uint32_t LastScrollGeneration() { return mLastScrollGeneration; }
+ void UpdateLastScrollGeneration() { mLastScrollGeneration += 1; }
+ bool HasBeenScrolledSince(const uint32_t& mPreviousScrollGeneration) const {
+ return mPreviousScrollGeneration < mLastScrollGeneration;
+ }
+
/**
* Get/set the size of a page
*/
@@ -1287,6 +1293,11 @@ class nsPresContext : public nsISupports,
mozilla::TimeStamp mFirstMouseMoveTime;
mozilla::TimeStamp mFirstScrollTime;
+ // incremented each time the root scroller scrolls, helpful to determine if
+ // it has scrolled between the start and end of some deferred work (such as a
+ // navigation intercept).
+ uint32_t mLastScrollGeneration;
+
// last time we did a full style flush
mozilla::TimeStamp mLastStyleUpdateForAllAnimations;
diff --git a/layout/generic/ScrollContainerFrame.cpp b/layout/generic/ScrollContainerFrame.cpp
@@ -2688,6 +2688,12 @@ void ScrollContainerFrame::MarkEverScrolled() {
// will return true from now on and MarkNotRecentlyScrolled() won't
// have any effect.
mHasBeenScrolled = true;
+
+ // PresContext should update the last scroll generation, for
+ // NavigateEvents to determine if the root scroller has moved.
+ if (mIsRoot) {
+ PresContext()->UpdateLastScrollGeneration();
+ }
}
void ScrollContainerFrame::MarkNotRecentlyScrolled() {