Bug 1958965 - Change scroll event setup to match the spec better. r=smaug

This matches the spec (plus resolution at
https://github.com/w3c/csswg-drafts/issues/11164) better, by running the
events per document.

Same comments as D244655 regarding the delayed event stuff that's going
away.

Differential Revision: https://phabricator.services.mozilla.com/D244666
This commit is contained in:
Emilio Cobos Álvarez 2025-04-10 01:07:15 +00:00
parent e0f0653a49
commit abf15a9165
9 changed files with 57 additions and 106 deletions

View file

@ -1424,7 +1424,6 @@ Document::Document(const char* aContentType)
mParserAborted(false),
mReportedDocumentUseCounters(false),
mHasReportedShadowDOMUsage(false),
mHasDelayedRefreshEvent(false),
mLoadEventFiring(false),
mSkipLoadEventAfterClose(false),
mDisableCookieAccess(false),
@ -13219,19 +13218,6 @@ void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
for (net::ChannelEventQueue* queue : queues) {
queue->Resume();
}
// If there have been any events driven by the refresh driver which were
// delayed due to events being suppressed in this document, make sure
// there is a refresh scheduled soon so the events will run.
if (doc->mHasDelayedRefreshEvent) {
doc->mHasDelayedRefreshEvent = false;
if (doc->mPresShell) {
nsRefreshDriver* rd =
doc->mPresShell->GetPresContext()->RefreshDriver();
rd->RunDelayedEventsSoon();
}
}
}
}

View file

@ -2810,8 +2810,6 @@ class Document : public nsINode,
*/
void FireOrClearPostMessageEvents(bool aFireEvents);
void SetHasDelayedRefreshEvent() { mHasDelayedRefreshEvent = true; }
/**
* Flag whether we're about to fire the window's load event for this document.
*/
@ -4920,10 +4918,6 @@ class Document : public nsINode,
bool mHasReportedShadowDOMUsage : 1;
// Whether an event triggered by the refresh driver was delayed because this
// document has suppressed events.
bool mHasDelayedRefreshEvent : 1;
// The HTML spec has a "iframe load in progress" flag, but that doesn't seem
// to have the right semantics. See
// <https://github.com/whatwg/html/issues/4292>. What we have instead is a

View file

@ -216,7 +216,7 @@ VisualViewport::VisualViewportScrollEvent::VisualViewportScrollEvent(
mPrevLayoutOffset(aPrevLayoutOffset) {
VVP_LOG("%p: Registering PostScroll on %p %p\n", aViewport, aPresContext,
aPresContext->RefreshDriver());
aPresContext->RefreshDriver()->PostScrollEvent(this);
aPresContext->PresShell()->PostScrollEvent(this);
}
bool VisualViewport::VisualViewportScrollEvent::HasPresContext(

View file

@ -1997,6 +1997,16 @@ bool PresShell::CanHandleUserInputEvents(WidgetGUIEvent* aGUIEvent) {
return true;
}
void PresShell::PostScrollEvent(Runnable* aEvent) {
MOZ_ASSERT(aEvent);
const bool hadEvents = !mPendingScrollEvents.IsEmpty();
mPendingScrollEvents.AppendElement(aEvent);
if (!hadEvents) {
mPresContext->RefreshDriver()->ScheduleRenderingPhase(
RenderingPhase::ScrollSteps);
}
}
void PresShell::ScheduleResizeEventIfNeeded(ResizeEventKind aKind) {
if (mIsDestroying) {
return;
@ -2011,7 +2021,7 @@ void PresShell::ScheduleResizeEventIfNeeded(ResizeEventKind aKind) {
mVisualViewportResizeEventPending = true;
}
mPresContext->RefreshDriver()->ScheduleRenderingPhase(
mozilla::RenderingPhase::ResizeSteps);
RenderingPhase::ResizeSteps);
}
bool PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight,
@ -2165,6 +2175,19 @@ void PresShell::RunResizeSteps() {
}
}
// https://drafts.csswg.org/cssom-view/#document-run-the-scroll-steps
// But note: https://github.com/w3c/csswg-drafts/issues/11164
void PresShell::RunScrollSteps() {
// Scroll events are one-shot, so after running them we can drop them.
// However, dispatching a scroll event can potentially cause more scroll
// events to be posted, so we move the initial set into a temporary array
// first. (Newly posted scroll events will be dispatched on the next tick.)
auto events = std::move(mPendingScrollEvents);
for (auto& event : events) {
event->Run();
}
}
static nsIContent* GetNativeAnonymousSubtreeRoot(nsIContent* aContent) {
if (!aContent) {
return nullptr;

View file

@ -361,6 +361,8 @@ class PresShell final : public nsStubDocumentObserver,
enum class ResizeEventKind : uint8_t { Regular, Visual };
void ScheduleResizeEventIfNeeded(ResizeEventKind = ResizeEventKind::Regular);
void PostScrollEvent(mozilla::Runnable*);
/**
* Returns true if the document hosted by this presShell is in a devtools
* Responsive Design Mode browsing context.
@ -1175,6 +1177,7 @@ class PresShell final : public nsStubDocumentObserver,
bool HasHandledUserInput() const { return mHasHandledUserInput; }
MOZ_CAN_RUN_SCRIPT void RunResizeSteps();
MOZ_CAN_RUN_SCRIPT void RunScrollSteps();
void NativeAnonymousContentWillBeRemoved(nsIContent* aAnonContent);
@ -3101,6 +3104,8 @@ class PresShell final : public nsStubDocumentObserver,
nsTHashSet<ScrollContainerFrame*> mPendingScrollAnchorSelection;
nsTHashSet<ScrollContainerFrame*> mPendingScrollAnchorAdjustment;
nsTHashSet<ScrollContainerFrame*> mPendingScrollResnap;
// Pending list of scroll/scrollend/etc events.
nsTArray<RefPtr<Runnable>> mPendingScrollEvents;
nsTHashSet<nsIContent*> mHiddenContentInForcedLayout;

View file

@ -1556,27 +1556,6 @@ bool nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
return true;
}
void nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent,
bool aDelayed) {
if (aDelayed) {
mDelayedScrollEvents.AppendElement(aScrollEvent);
} else {
mScrollEvents.AppendElement(aScrollEvent);
ScheduleRenderingPhase(RenderingPhase::ScrollSteps);
}
}
void nsRefreshDriver::DispatchScrollEvents() {
// Scroll events are one-shot, so after running them we can drop them.
// However, dispatching a scroll event can potentially cause more scroll
// events to be posted, so we move the initial set into a temporary array
// first. (Newly posted scroll events will be dispatched on the next tick.)
ScrollEventArray events = std::move(mScrollEvents);
for (auto& event : events) {
event->Run();
}
}
void nsRefreshDriver::AddPostRefreshObserver(
nsAPostRefreshObserver* aObserver) {
MOZ_ASSERT(!mPostRefreshObservers.Contains(aObserver));
@ -1654,17 +1633,6 @@ void nsRefreshDriver::FlushForceNotifyContentfulPaintPresContext() {
}
}
void nsRefreshDriver::RunDelayedEventsSoon() {
// Place entries for delayed events into their corresponding normal list,
// and schedule a refresh. When these delayed events run, if their document
// still has events suppressed then they will be readded to the delayed
// events list.
mScrollEvents.AppendElements(mDelayedScrollEvents);
mDelayedScrollEvents.Clear();
ScheduleRenderingPhase(RenderingPhase::ScrollSteps);
}
bool nsRefreshDriver::CanDoCatchUpTick() {
if (mTestControllingRefreshes || !mActiveTimer) {
return false;
@ -2584,9 +2552,12 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
});
// Step 9. For each doc of docs, run the scroll steps for doc.
RunRenderingPhaseLegacy(
RenderingPhase::ScrollSteps,
[&]() MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { DispatchScrollEvents(); });
RunRenderingPhase(RenderingPhase::ScrollSteps,
[](Document& aDoc) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
if (RefPtr<PresShell> ps = aDoc.GetPresShell()) {
ps->RunScrollSteps();
}
});
// Step 10. For each doc of docs, evaluate media queries and report changes
// for doc.

View file

@ -26,7 +26,6 @@
#include "nsThreadUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/dom/VisualViewport.h"
#include "mozilla/layers/TransactionIdAllocator.h"
#include "LayersTypes.h"
@ -46,14 +45,17 @@ class PresShell;
class RefreshDriverTimer;
class Runnable;
class Task;
namespace dom {
class Document;
}
} // namespace mozilla
class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
public nsARefreshObserver {
using Document = mozilla::dom::Document;
using TransactionId = mozilla::layers::TransactionId;
using VVPScrollEvent =
mozilla::dom::VisualViewport::VisualViewportScrollEvent;
using LogPresShellObserver = mozilla::LogPresShellObserver;
public:
@ -105,8 +107,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
mozilla::FlushType aFlushType);
void PostScrollEvent(mozilla::Runnable* aScrollEvent, bool aDelayed = false);
void DispatchScrollEvents();
MOZ_CAN_RUN_SCRIPT void FlushLayoutOnPendingDocsAndFixUpFocus();
/**
@ -341,9 +341,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
static void DispatchIdleTaskAfterTickUnlessExists(mozilla::Task* aTask);
static void CancelIdleTask(mozilla::Task* aTask);
// Schedule a refresh so that any delayed events will run soon.
void RunDelayedEventsSoon();
void InitializeTimer() {
MOZ_ASSERT(!mActiveTimer);
EnsureTimerStarted();
@ -420,7 +417,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
void FinishedVsyncTick() { mAttemptedExtraTickSinceLastVsync = false; }
private:
using ScrollEventArray = nsTArray<RefPtr<mozilla::Runnable>>;
using RequestTable = nsTHashSet<RefPtr<imgIRequest>>;
struct ImageStartData {
ImageStartData() = default;
@ -450,10 +446,10 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
MOZ_CAN_RUN_SCRIPT
void RunVideoAndFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
MOZ_CAN_RUN_SCRIPT
void RunVideoFrameCallbacks(const nsTArray<RefPtr<mozilla::dom::Document>>&,
void RunVideoFrameCallbacks(const nsTArray<RefPtr<Document>>&,
mozilla::TimeStamp aNowTime);
MOZ_CAN_RUN_SCRIPT
void RunFrameRequestCallbacks(const nsTArray<RefPtr<mozilla::dom::Document>>&,
void RunFrameRequestCallbacks(const nsTArray<RefPtr<Document>>&,
mozilla::TimeStamp aNowTime);
void UpdateRemoteFrameEffects();
void UpdateRelevancyOfContentVisibilityAutoFrames();
@ -645,9 +641,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
RequestTable mRequests;
ImageStartTable mStartTable;
AutoTArray<nsCOMPtr<nsIRunnable>, 16> mEarlyRunners;
ScrollEventArray mScrollEvents;
// Scroll events on documents that might have events suppressed.
ScrollEventArray mDelayedScrollEvents;
AutoTArray<mozilla::PresShell*, 16> mStyleFlushObservers;
nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers;
nsTArray<mozilla::UniquePtr<mozilla::PendingFullscreenEvent>>

View file

@ -159,7 +159,7 @@ static ScrollDirections GetOverflowChange(const nsRect& aCurScrolledRect,
class ScrollContainerFrame::ScrollEvent : public Runnable {
public:
NS_DECL_NSIRUNNABLE
explicit ScrollEvent(ScrollContainerFrame* aHelper, bool aDelayed);
explicit ScrollEvent(ScrollContainerFrame* aHelper);
void Revoke() { mHelper = nullptr; }
private:
@ -169,7 +169,7 @@ class ScrollContainerFrame::ScrollEvent : public Runnable {
class ScrollContainerFrame::ScrollEndEvent : public Runnable {
public:
NS_DECL_NSIRUNNABLE
explicit ScrollEndEvent(ScrollContainerFrame* aHelper, bool aDelayed);
explicit ScrollEndEvent(ScrollContainerFrame* aHelper);
void Revoke() { mHelper = nullptr; }
private:
@ -5382,30 +5382,20 @@ nsresult ScrollContainerFrame::FireScrollPortEvent() {
return EventDispatcher::Dispatch(content, presContext, &event);
}
void ScrollContainerFrame::PostScrollEndEvent(bool aDelayed) {
void ScrollContainerFrame::PostScrollEndEvent() {
if (mScrollEndEvent) {
return;
}
// The ScrollEndEvent constructor registers itself with the refresh driver.
mScrollEndEvent = new ScrollEndEvent(this, aDelayed);
// The ScrollEndEvent constructor registers itself.
mScrollEndEvent = new ScrollEndEvent(this);
}
void ScrollContainerFrame::FireScrollEndEvent() {
RefPtr<nsIContent> content = GetContent();
MOZ_ASSERT(content);
MOZ_ASSERT(mScrollEndEvent);
mScrollEndEvent->Revoke();
mScrollEndEvent = nullptr;
if (content->GetComposedDoc() &&
content->GetComposedDoc()->EventHandlingSuppressed()) {
content->GetComposedDoc()->SetHasDelayedRefreshEvent();
PostScrollEndEvent(/* aDelayed = */ true);
return;
}
RefPtr<nsPresContext> presContext = PresContext();
nsEventStatus status = nsEventStatus_eIgnore;
WidgetGUIEvent event(true, eScrollend, nullptr);
@ -5825,10 +5815,9 @@ void ScrollContainerFrame::CurPosAttributeChangedInternal(nsIContent* aContent,
/* ============= Scroll events ========== */
ScrollContainerFrame::ScrollEvent::ScrollEvent(ScrollContainerFrame* aHelper,
bool aDelayed)
ScrollContainerFrame::ScrollEvent::ScrollEvent(ScrollContainerFrame* aHelper)
: Runnable("ScrollContainerFrame::ScrollEvent"), mHelper(aHelper) {
mHelper->PresContext()->RefreshDriver()->PostScrollEvent(this, aDelayed);
mHelper->PresShell()->PostScrollEvent(this);
}
// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
@ -5841,9 +5830,9 @@ ScrollContainerFrame::ScrollEvent::Run() {
}
ScrollContainerFrame::ScrollEndEvent::ScrollEndEvent(
ScrollContainerFrame* aHelper, bool aDelayed)
ScrollContainerFrame* aHelper)
: Runnable("ScrollContainerFrame::ScrollEndEvent"), mHelper(aHelper) {
mHelper->PresContext()->RefreshDriver()->PostScrollEvent(this, aDelayed);
mHelper->PresShell()->PostScrollEvent(this);
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
@ -5864,16 +5853,6 @@ void ScrollContainerFrame::FireScrollEvent() {
mScrollEvent->Revoke();
mScrollEvent = nullptr;
// If event handling is suppressed, keep posting the scroll event to the
// refresh driver until it is unsuppressed. The event is marked as delayed so
// that the refresh driver does not continue ticking.
if (content->GetComposedDoc() &&
content->GetComposedDoc()->EventHandlingSuppressed()) {
content->GetComposedDoc()->SetHasDelayedRefreshEvent();
PostScrollEvent(/* aDelayed = */ true);
return;
}
bool oldProcessing = mProcessingScrollEvent;
AutoWeakFrame weakFrame(this);
auto RestoreProcessingScrollEvent = mozilla::MakeScopeExit([&] {
@ -5903,13 +5882,13 @@ void ScrollContainerFrame::FireScrollEvent() {
}
}
void ScrollContainerFrame::PostScrollEvent(bool aDelayed) {
void ScrollContainerFrame::PostScrollEvent() {
if (mScrollEvent) {
return;
}
// The ScrollEvent constructor registers itself with the refresh driver.
mScrollEvent = new ScrollEvent(this, aDelayed);
// The ScrollEvent constructor registers itself.
mScrollEvent = new ScrollEvent(this);
}
// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)

View file

@ -1083,7 +1083,7 @@ class ScrollContainerFrame : public nsContainerFrame,
PhysicalAxes GetOverflowAxes() const;
MOZ_CAN_RUN_SCRIPT nsresult FireScrollPortEvent();
void PostScrollEndEvent(bool aDelayed = false);
void PostScrollEndEvent();
MOZ_CAN_RUN_SCRIPT void FireScrollEndEvent();
void PostOverflowEvent();
@ -1111,7 +1111,7 @@ class ScrollContainerFrame : public nsContainerFrame,
*/
void CurPosAttributeChangedInternal(nsIContent*, bool aDoScroll = true);
void PostScrollEvent(bool aDelayed = false);
void PostScrollEvent();
MOZ_CAN_RUN_SCRIPT void FireScrollEvent();
void PostScrolledAreaEvent();
MOZ_CAN_RUN_SCRIPT void FireScrolledAreaEvent();