Backed out changeset 332b088b3141 (bug 1774315) for causing scroll related failures...
[gecko.git] / layout / generic / nsGfxScrollFrame.cpp
blob3c3feb75198c49a10b1a1f61b6e03848133b42f3
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* rendering object to wrap rendering objects that should be scrollable */
9 #include "nsGfxScrollFrame.h"
11 #include "ScrollPositionUpdate.h"
12 #include "mozilla/layers/LayersTypes.h"
13 #include "nsIXULRuntime.h"
14 #include "base/compiler_specific.h"
15 #include "DisplayItemClip.h"
16 #include "nsCOMPtr.h"
17 #include "nsIContentViewer.h"
18 #include "nsPresContext.h"
19 #include "nsView.h"
20 #include "nsViewportInfo.h"
21 #include "nsContainerFrame.h"
22 #include "nsGkAtoms.h"
23 #include "nsNameSpaceManager.h"
24 #include "mozilla/intl/BidiEmbeddingLevel.h"
25 #include "mozilla/dom/DocumentInlines.h"
26 #include "mozilla/gfx/gfxVars.h"
27 #include "nsFontMetrics.h"
28 #include "mozilla/dom/NodeInfo.h"
29 #include "nsScrollbarFrame.h"
30 #include "nsINode.h"
31 #include "nsIScrollbarMediator.h"
32 #include "nsITextControlFrame.h"
33 #include "nsILayoutHistoryState.h"
34 #include "nsNodeInfoManager.h"
35 #include "nsContentCreatorFunctions.h"
36 #include "nsStyleTransformMatrix.h"
37 #include "mozilla/PresState.h"
38 #include "nsContentUtils.h"
39 #include "nsDisplayList.h"
40 #include "nsHTMLDocument.h"
41 #include "nsLayoutUtils.h"
42 #include "nsBidiPresUtils.h"
43 #include "nsBidiUtils.h"
44 #include "nsDocShell.h"
45 #include "mozilla/ContentEvents.h"
46 #include "mozilla/DisplayPortUtils.h"
47 #include "mozilla/EventDispatcher.h"
48 #include "mozilla/Preferences.h"
49 #include "mozilla/PresShell.h"
50 #include "mozilla/ScopeExit.h"
51 #include "mozilla/ScrollbarPreferences.h"
52 #include "mozilla/ScrollingMetrics.h"
53 #include "mozilla/StaticPrefs_browser.h"
54 #include "mozilla/StaticPrefs_toolkit.h"
55 #include "mozilla/StaticPtr.h"
56 #include "mozilla/SVGOuterSVGFrame.h"
57 #include "mozilla/ViewportUtils.h"
58 #include "mozilla/LookAndFeel.h"
59 #include "mozilla/dom/Element.h"
60 #include "mozilla/dom/Event.h"
61 #include "mozilla/dom/HTMLMarqueeElement.h"
62 #include "mozilla/dom/ScrollTimeline.h"
63 #include <stdint.h>
64 #include "mozilla/MathAlgorithms.h"
65 #include "mozilla/Telemetry.h"
66 #include "nsSubDocumentFrame.h"
67 #include "mozilla/Attributes.h"
68 #include "ScrollbarActivity.h"
69 #include "nsRefreshDriver.h"
70 #include "nsStyleConsts.h"
71 #include "nsIScrollPositionListener.h"
72 #include "StickyScrollContainer.h"
73 #include "nsIFrameInlines.h"
74 #include "gfxPlatform.h"
75 #include "mozilla/StaticPrefs_apz.h"
76 #include "mozilla/StaticPrefs_general.h"
77 #include "mozilla/StaticPrefs_layers.h"
78 #include "mozilla/StaticPrefs_layout.h"
79 #include "mozilla/StaticPrefs_mousewheel.h"
80 #include "mozilla/ToString.h"
81 #include "ScrollAnimationPhysics.h"
82 #include "ScrollAnimationBezierPhysics.h"
83 #include "ScrollAnimationMSDPhysics.h"
84 #include "ScrollSnap.h"
85 #include "UnitTransforms.h"
86 #include "nsSliderFrame.h"
87 #include "ViewportFrame.h"
88 #include "mozilla/gfx/gfxVars.h"
89 #include "mozilla/layers/APZCCallbackHelper.h"
90 #include "mozilla/layers/APZPublicUtils.h"
91 #include "mozilla/layers/AxisPhysicsModel.h"
92 #include "mozilla/layers/AxisPhysicsMSDModel.h"
93 #include "mozilla/layers/ScrollingInteractionContext.h"
94 #include "mozilla/layers/ScrollLinkedEffectDetector.h"
95 #include "mozilla/Unused.h"
96 #include "MobileViewportManager.h"
97 #include "VisualViewport.h"
98 #include "WindowRenderer.h"
99 #include <algorithm>
100 #include <cstdlib> // for std::abs(int/long)
101 #include <cmath> // for std::abs(float/double)
102 #include <tuple> // for std::tie
104 static mozilla::LazyLogModule sApzPaintSkipLog("apz.paintskip");
105 #define PAINT_SKIP_LOG(...) \
106 MOZ_LOG(sApzPaintSkipLog, LogLevel::Debug, (__VA_ARGS__))
107 static mozilla::LazyLogModule sScrollRestoreLog("scrollrestore");
108 #define SCROLLRESTORE_LOG(...) \
109 MOZ_LOG(sScrollRestoreLog, LogLevel::Debug, (__VA_ARGS__))
110 static mozilla::LazyLogModule sRootScrollbarsLog("rootscrollbars");
111 #define ROOT_SCROLLBAR_LOG(...) \
112 if (mIsRoot) { \
113 MOZ_LOG(sRootScrollbarsLog, LogLevel::Debug, (__VA_ARGS__)); \
115 static mozilla::LazyLogModule sDisplayportLog("apz.displayport");
117 using namespace mozilla;
118 using namespace mozilla::dom;
119 using namespace mozilla::gfx;
120 using namespace mozilla::layers;
121 using namespace mozilla::layout;
122 using nsStyleTransformMatrix::TransformReferenceBox;
124 static ScrollDirections GetOverflowChange(const nsRect& aCurScrolledRect,
125 const nsRect& aPrevScrolledRect) {
126 ScrollDirections result;
127 if (aPrevScrolledRect.x != aCurScrolledRect.x ||
128 aPrevScrolledRect.width != aCurScrolledRect.width) {
129 result += ScrollDirection::eHorizontal;
131 if (aPrevScrolledRect.y != aCurScrolledRect.y ||
132 aPrevScrolledRect.height != aCurScrolledRect.height) {
133 result += ScrollDirection::eVertical;
135 return result;
139 * This class handles the dispatching of scroll events to content.
141 * Scroll events are posted to the refresh driver via
142 * nsRefreshDriver::PostScrollEvent(), and they are fired during a refresh
143 * driver tick, after running requestAnimationFrame callbacks but before
144 * the style flush. This allows rAF callbacks to perform scrolling and have
145 * that scrolling be reflected on the same refresh driver tick, while at
146 * the same time allowing scroll event listeners to make style changes and
147 * have those style changes be reflected on the same refresh driver tick.
149 * ScrollEvents cannot be refresh observers, because none of the existing
150 * categories of refresh observers (FlushType::Style, FlushType::Layout,
151 * and FlushType::Display) are run at the desired time in a refresh driver
152 * tick. They behave similarly to refresh observers in that their presence
153 * causes the refresh driver to tick.
155 * ScrollEvents are one-shot runnables; the refresh driver drops them after
156 * running them.
158 class nsHTMLScrollFrame::ScrollEvent : public Runnable {
159 public:
160 NS_DECL_NSIRUNNABLE
161 explicit ScrollEvent(nsHTMLScrollFrame* aHelper, bool aDelayed);
162 void Revoke() { mHelper = nullptr; }
164 private:
165 nsHTMLScrollFrame* mHelper;
168 class nsHTMLScrollFrame::ScrollEndEvent : public Runnable {
169 public:
170 NS_DECL_NSIRUNNABLE
171 explicit ScrollEndEvent(nsHTMLScrollFrame* aHelper);
172 void Revoke() { mHelper = nullptr; }
174 private:
175 nsHTMLScrollFrame* mHelper;
178 class nsHTMLScrollFrame::AsyncScrollPortEvent : public Runnable {
179 public:
180 NS_DECL_NSIRUNNABLE
181 explicit AsyncScrollPortEvent(nsHTMLScrollFrame* helper)
182 : Runnable("nsHTMLScrollFrame::AsyncScrollPortEvent"), mHelper(helper) {}
183 void Revoke() { mHelper = nullptr; }
185 private:
186 nsHTMLScrollFrame* mHelper;
189 class nsHTMLScrollFrame::ScrolledAreaEvent : public Runnable {
190 public:
191 NS_DECL_NSIRUNNABLE
192 explicit ScrolledAreaEvent(nsHTMLScrollFrame* helper)
193 : Runnable("nsHTMLScrollFrame::ScrolledAreaEvent"), mHelper(helper) {}
194 void Revoke() { mHelper = nullptr; }
196 private:
197 nsHTMLScrollFrame* mHelper;
200 //----------------------------------------------------------------------
202 //----------nsHTMLScrollFrame-------------------------------------------
204 class ScrollFrameActivityTracker final
205 : public nsExpirationTracker<nsHTMLScrollFrame, 4> {
206 public:
207 // Wait for 3-4s between scrolls before we remove our layers.
208 // That's 4 generations of 1s each.
209 enum { TIMEOUT_MS = 1000 };
210 explicit ScrollFrameActivityTracker(nsIEventTarget* aEventTarget)
211 : nsExpirationTracker<nsHTMLScrollFrame, 4>(
212 TIMEOUT_MS, "ScrollFrameActivityTracker", aEventTarget) {}
213 ~ScrollFrameActivityTracker() { AgeAllGenerations(); }
215 virtual void NotifyExpired(nsHTMLScrollFrame* aObject) override {
216 RemoveObject(aObject);
217 aObject->MarkNotRecentlyScrolled();
220 static StaticAutoPtr<ScrollFrameActivityTracker> gScrollFrameActivityTracker;
222 nsHTMLScrollFrame* NS_NewHTMLScrollFrame(PresShell* aPresShell,
223 ComputedStyle* aStyle, bool aIsRoot) {
224 return new (aPresShell)
225 nsHTMLScrollFrame(aStyle, aPresShell->GetPresContext(), aIsRoot);
228 NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)
230 nsHTMLScrollFrame::nsHTMLScrollFrame(ComputedStyle* aStyle,
231 nsPresContext* aPresContext,
232 nsIFrame::ClassID aID, bool aIsRoot)
233 : nsContainerFrame(aStyle, aPresContext, aID),
234 mHScrollbarBox(nullptr),
235 mVScrollbarBox(nullptr),
236 mScrolledFrame(nullptr),
237 mScrollCornerBox(nullptr),
238 mResizerBox(nullptr),
239 mReferenceFrameDuringPainting(nullptr),
240 mAsyncScroll(nullptr),
241 mAsyncSmoothMSDScroll(nullptr),
242 mLastScrollOrigin(ScrollOrigin::None),
243 mDestination(0, 0),
244 mRestorePos(-1, -1),
245 mLastPos(-1, -1),
246 mApzScrollPos(0, 0),
247 mLastUpdateFramesPos(-1, -1),
248 mDisplayPortAtLastFrameUpdate(),
249 mScrollParentID(mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID),
250 mAnchor(this),
251 mCurrentAPZScrollAnimationType(APZScrollAnimationType::No),
252 mIsFirstScrollableFrameSequenceNumber(Nothing()),
253 mInScrollingGesture(InScrollingGesture::No),
254 mAllowScrollOriginDowngrade(false),
255 mHadDisplayPortAtLastFrameUpdate(false),
256 mHasVerticalScrollbar(false),
257 mHasHorizontalScrollbar(false),
258 mOnlyNeedVScrollbarToScrollVVInsideLV(false),
259 mOnlyNeedHScrollbarToScrollVVInsideLV(false),
260 mFrameIsUpdatingScrollbar(false),
261 mDidHistoryRestore(false),
262 mIsRoot(aIsRoot),
263 mSuppressScrollbarUpdate(false),
264 mSkippedScrollbarLayout(false),
265 mHadNonInitialReflow(false),
266 mFirstReflow(true),
267 mHorizontalOverflow(false),
268 mVerticalOverflow(false),
269 mPostedReflowCallback(false),
270 mMayHaveDirtyFixedChildren(false),
271 mUpdateScrollbarAttributes(false),
272 mHasBeenScrolledRecently(false),
273 mWillBuildScrollableLayer(false),
274 mIsParentToActiveScrollFrames(false),
275 mHasBeenScrolled(false),
276 mIgnoreMomentumScroll(false),
277 mTransformingByAPZ(false),
278 mScrollableByAPZ(false),
279 mZoomableByAPZ(false),
280 mHasOutOfFlowContentInsideFilter(false),
281 mSuppressScrollbarRepaints(false),
282 mIsUsingMinimumScaleSize(false),
283 mMinimumScaleSizeChanged(false),
284 mProcessingScrollEvent(false),
285 mApzAnimationRequested(false),
286 mApzAnimationTriggeredByScriptRequested(false),
287 mReclampVVOffsetInReflowFinished(false),
288 mMayScheduleScrollAnimations(false),
289 #ifdef MOZ_WIDGET_ANDROID
290 mHasVerticalOverflowForDynamicToolbar(false),
291 #endif
292 mVelocityQueue(PresContext()) {
293 AppendScrollUpdate(ScrollPositionUpdate::NewScrollframe(nsPoint()));
295 if (UsesOverlayScrollbars()) {
296 mScrollbarActivity = new ScrollbarActivity(this);
299 if (mIsRoot) {
300 mZoomableByAPZ = PresShell()->GetZoomableByAPZ();
304 nsHTMLScrollFrame::~nsHTMLScrollFrame() = default;
306 void nsHTMLScrollFrame::ScrollbarActivityStarted() const {
307 if (mScrollbarActivity) {
308 mScrollbarActivity->ActivityStarted();
312 void nsHTMLScrollFrame::ScrollbarActivityStopped() const {
313 if (mScrollbarActivity) {
314 mScrollbarActivity->ActivityStopped();
318 void nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot,
319 PostDestroyData& aPostDestroyData) {
320 DestroyAbsoluteFrames(aDestructRoot, aPostDestroyData);
321 if (mIsRoot) {
322 PresShell()->ResetVisualViewportOffset();
325 mAnchor.Destroy();
327 if (mScrollbarActivity) {
328 mScrollbarActivity->Destroy();
329 mScrollbarActivity = nullptr;
332 // Unbind the content created in CreateAnonymousContent later...
333 aPostDestroyData.AddAnonymousContent(mHScrollbarContent.forget());
334 aPostDestroyData.AddAnonymousContent(mVScrollbarContent.forget());
335 aPostDestroyData.AddAnonymousContent(mScrollCornerContent.forget());
336 aPostDestroyData.AddAnonymousContent(mResizerContent.forget());
338 if (mPostedReflowCallback) {
339 PresShell()->CancelReflowCallback(this);
340 mPostedReflowCallback = false;
343 if (mDisplayPortExpiryTimer) {
344 mDisplayPortExpiryTimer->Cancel();
345 mDisplayPortExpiryTimer = nullptr;
347 if (mActivityExpirationState.IsTracked()) {
348 gScrollFrameActivityTracker->RemoveObject(this);
350 if (gScrollFrameActivityTracker && gScrollFrameActivityTracker->IsEmpty()) {
351 gScrollFrameActivityTracker = nullptr;
354 if (mScrollActivityTimer) {
355 mScrollActivityTimer->Cancel();
356 mScrollActivityTimer = nullptr;
358 RemoveObservers();
359 if (mScrollEvent) {
360 mScrollEvent->Revoke();
362 if (mScrollEndEvent) {
363 mScrollEndEvent->Revoke();
365 nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
368 void nsHTMLScrollFrame::SetInitialChildList(ChildListID aListID,
369 nsFrameList&& aChildList) {
370 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
371 ReloadChildFrames();
374 void nsHTMLScrollFrame::AppendFrames(ChildListID aListID,
375 nsFrameList&& aFrameList) {
376 NS_ASSERTION(aListID == FrameChildListID::Principal,
377 "Only main list supported");
378 mFrames.AppendFrames(nullptr, std::move(aFrameList));
379 ReloadChildFrames();
382 void nsHTMLScrollFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
383 const nsLineList::iterator* aPrevFrameLine,
384 nsFrameList&& aFrameList) {
385 NS_ASSERTION(aListID == FrameChildListID::Principal,
386 "Only main list supported");
387 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
388 "inserting after sibling frame with different parent");
389 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
390 ReloadChildFrames();
393 void nsHTMLScrollFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
394 NS_ASSERTION(aListID == FrameChildListID::Principal,
395 "Only main list supported");
396 mFrames.DestroyFrame(aOldFrame);
397 ReloadChildFrames();
401 HTML scrolling implementation
403 All other things being equal, we prefer layouts with fewer scrollbars showing.
406 namespace mozilla {
408 enum class ShowScrollbar : uint8_t {
409 Auto,
410 Always,
411 // Never is a misnomer. We can still get a scrollbar if we need to scroll the
412 // visual viewport inside the layout viewport. Thus this enum is best thought
413 // of as value used by layout, which does not know about the visual viewport.
414 // The visual viewport does not affect any layout sizes, so this is sound.
415 Never,
418 static ShowScrollbar ShouldShowScrollbar(StyleOverflow aOverflow) {
419 switch (aOverflow) {
420 case StyleOverflow::Scroll:
421 return ShowScrollbar::Always;
422 case StyleOverflow::Hidden:
423 return ShowScrollbar::Never;
424 default:
425 case StyleOverflow::Auto:
426 return ShowScrollbar::Auto;
430 struct MOZ_STACK_CLASS ScrollReflowInput {
431 // === Filled in by the constructor. Members in this section shouldn't change
432 // their values after the constructor. ===
433 const ReflowInput& mReflowInput;
434 ShowScrollbar mHScrollbar;
435 // If the horizontal scrollbar is allowed (even if mHScrollbar ==
436 // ShowScrollbar::Never) provided that it is for scrolling the visual viewport
437 // inside the layout viewport only.
438 bool mHScrollbarAllowedForScrollingVVInsideLV = true;
439 ShowScrollbar mVScrollbar;
440 // If the vertical scrollbar is allowed (even if mVScrollbar ==
441 // ShowScrollbar::Never) provided that it is for scrolling the visual viewport
442 // inside the layout viewport only.
443 bool mVScrollbarAllowedForScrollingVVInsideLV = true;
444 nsMargin mComputedBorder;
446 // === Filled in by ReflowScrolledFrame ===
447 OverflowAreas mContentsOverflowAreas;
448 // The scrollbar gutter sizes used in the most recent reflow of
449 // mScrolledFrame. The writing-mode is the same as the scroll
450 // container.
451 LogicalMargin mScrollbarGutterFromLastReflow;
452 // True if the most recent reflow of mScrolledFrame is with the
453 // horizontal scrollbar.
454 bool mReflowedContentsWithHScrollbar = false;
455 // True if the most recent reflow of mScrolledFrame is with the
456 // vertical scrollbar.
457 bool mReflowedContentsWithVScrollbar = false;
459 // === Filled in when TryLayout succeeds ===
460 // The size of the inside-border area
461 nsSize mInsideBorderSize;
462 // Whether we decided to show the horizontal scrollbar in the most recent
463 // TryLayout.
464 bool mShowHScrollbar = false;
465 // Whether we decided to show the vertical scrollbar in the most recent
466 // TryLayout.
467 bool mShowVScrollbar = false;
468 // If mShow(H|V)Scrollbar is true then
469 // mOnlyNeed(V|H)ScrollbarToScrollVVInsideLV indicates if the only reason we
470 // need that scrollbar is to scroll the visual viewport inside the layout
471 // viewport. These scrollbars are special in that even if they are layout
472 // scrollbars they do not take up any layout space.
473 bool mOnlyNeedHScrollbarToScrollVVInsideLV = false;
474 bool mOnlyNeedVScrollbarToScrollVVInsideLV = false;
476 ScrollReflowInput(nsHTMLScrollFrame* aFrame, const ReflowInput& aReflowInput);
478 nscoord VScrollbarMinHeight() const { return mVScrollbarPrefSize.height; }
479 nscoord VScrollbarPrefWidth() const { return mVScrollbarPrefSize.width; }
480 nscoord HScrollbarMinWidth() const { return mHScrollbarPrefSize.width; }
481 nscoord HScrollbarPrefHeight() const { return mHScrollbarPrefSize.height; }
483 // Returns the sizes occupied by the scrollbar gutters. If aShowVScroll or
484 // aShowHScroll is true, the sizes occupied by the scrollbars are also
485 // included.
486 nsMargin ScrollbarGutter(bool aShowVScrollbar, bool aShowHScrollbar,
487 bool aScrollbarOnRight) const {
488 if (mOverlayScrollbars) {
489 return mScrollbarGutter;
491 nsMargin gutter = mScrollbarGutter;
492 if (aShowVScrollbar && gutter.right == 0 && gutter.left == 0) {
493 const nscoord w = VScrollbarPrefWidth();
494 if (aScrollbarOnRight) {
495 gutter.right = w;
496 } else {
497 gutter.left = w;
500 if (aShowHScrollbar && gutter.bottom == 0) {
501 // The horizontal scrollbar is always at the bottom side.
502 gutter.bottom = HScrollbarPrefHeight();
504 return gutter;
507 bool OverlayScrollbars() const { return mOverlayScrollbars; }
509 private:
510 // Filled in by the constructor. Put variables here to keep them unchanged
511 // after initializing them in the constructor.
512 nsSize mVScrollbarPrefSize;
513 nsSize mHScrollbarPrefSize;
514 bool mOverlayScrollbars = false;
515 // The scrollbar gutter sizes resolved from the scrollbar-gutter and
516 // scrollbar-width property.
517 nsMargin mScrollbarGutter;
520 ScrollReflowInput::ScrollReflowInput(nsHTMLScrollFrame* aFrame,
521 const ReflowInput& aReflowInput)
522 : mReflowInput(aReflowInput),
523 mComputedBorder(aReflowInput.ComputedPhysicalBorderPadding() -
524 aReflowInput.ComputedPhysicalPadding()),
525 mScrollbarGutterFromLastReflow(aFrame->GetWritingMode()) {
526 ScrollStyles styles = aFrame->GetScrollStyles();
527 mHScrollbar = ShouldShowScrollbar(styles.mHorizontal);
528 mVScrollbar = ShouldShowScrollbar(styles.mVertical);
529 mOverlayScrollbars = aFrame->UsesOverlayScrollbars();
531 if (nsScrollbarFrame* scrollbar = aFrame->GetScrollbarBox(false)) {
532 scrollbar->SetScrollbarMediatorContent(mReflowInput.mFrame->GetContent());
533 mHScrollbarPrefSize = scrollbar->ScrollbarMinSize();
534 // A zero minimum size is a bug with non-overlay scrollbars. That means
535 // we'll always try to place the scrollbar, even if it will ultimately not
536 // fit, see bug 1809630. XUL collapsing is the exception because the
537 // front-end uses it.
538 MOZ_ASSERT(mHScrollbarPrefSize.width && mHScrollbarPrefSize.height,
539 "Shouldn't have a zero horizontal scrollbar-size");
540 } else {
541 mHScrollbar = ShowScrollbar::Never;
542 mHScrollbarAllowedForScrollingVVInsideLV = false;
544 if (nsScrollbarFrame* scrollbar = aFrame->GetScrollbarBox(true)) {
545 scrollbar->SetScrollbarMediatorContent(mReflowInput.mFrame->GetContent());
546 mVScrollbarPrefSize = scrollbar->ScrollbarMinSize();
547 // See above.
548 MOZ_ASSERT(mVScrollbarPrefSize.width && mVScrollbarPrefSize.height,
549 "Shouldn't have a zero vertical scrollbar-size");
550 } else {
551 mVScrollbar = ShowScrollbar::Never;
552 mVScrollbarAllowedForScrollingVVInsideLV = false;
555 const auto* scrollbarStyle =
556 nsLayoutUtils::StyleForScrollbar(mReflowInput.mFrame);
557 // Hide the scrollbar when the scrollbar-width is set to none.
559 // Note: In some cases this is unnecessary, because scrollbar-width:none
560 // makes us suppress scrollbars in CreateAnonymousContent. But if this frame
561 // initially had a non-'none' scrollbar-width and dynamically changed to
562 // 'none', then we'll need to handle it here.
563 if (scrollbarStyle->StyleUIReset()->ScrollbarWidth() ==
564 StyleScrollbarWidth::None) {
565 mHScrollbar = ShowScrollbar::Never;
566 mHScrollbarAllowedForScrollingVVInsideLV = false;
567 mVScrollbar = ShowScrollbar::Never;
568 mVScrollbarAllowedForScrollingVVInsideLV = false;
569 } else if (const auto& scrollbarGutterStyle =
570 scrollbarStyle->StyleDisplay()->mScrollbarGutter;
571 scrollbarGutterStyle && !mOverlayScrollbars) {
572 const auto stable =
573 bool(scrollbarGutterStyle & StyleScrollbarGutter::STABLE);
574 const auto bothEdges =
575 bool(scrollbarGutterStyle & StyleScrollbarGutter::BOTH_EDGES);
577 if (mReflowInput.GetWritingMode().IsVertical()) {
578 const nscoord h = HScrollbarPrefHeight();
579 if (bothEdges) {
580 mScrollbarGutter.top = mScrollbarGutter.bottom = h;
581 } else if (stable) {
582 // The horizontal scrollbar gutter is always at the bottom side.
583 mScrollbarGutter.bottom = h;
585 } else {
586 const nscoord w = VScrollbarPrefWidth();
587 if (bothEdges) {
588 mScrollbarGutter.left = mScrollbarGutter.right = w;
589 } else if (stable) {
590 if (aFrame->IsScrollbarOnRight()) {
591 mScrollbarGutter.right = w;
592 } else {
593 mScrollbarGutter.left = w;
600 } // namespace mozilla
602 // XXXldb Can this go away?
603 static nsSize ComputeInsideBorderSize(const ScrollReflowInput& aState,
604 const nsSize& aDesiredInsideBorderSize) {
605 // aDesiredInsideBorderSize is the frame size; i.e., it includes
606 // borders and padding (but the scrolled child doesn't have
607 // borders). The scrolled child has the same padding as us.
608 nscoord contentWidth = aState.mReflowInput.ComputedWidth();
609 if (contentWidth == NS_UNCONSTRAINEDSIZE) {
610 contentWidth = aDesiredInsideBorderSize.width -
611 aState.mReflowInput.ComputedPhysicalPadding().LeftRight();
613 nscoord contentHeight = aState.mReflowInput.ComputedHeight();
614 if (contentHeight == NS_UNCONSTRAINEDSIZE) {
615 contentHeight = aDesiredInsideBorderSize.height -
616 aState.mReflowInput.ComputedPhysicalPadding().TopBottom();
619 contentWidth = aState.mReflowInput.ApplyMinMaxWidth(contentWidth);
620 contentHeight = aState.mReflowInput.ApplyMinMaxHeight(contentHeight);
621 return nsSize(
622 contentWidth + aState.mReflowInput.ComputedPhysicalPadding().LeftRight(),
623 contentHeight +
624 aState.mReflowInput.ComputedPhysicalPadding().TopBottom());
628 * Assuming that we know the metrics for our wrapped frame and
629 * whether the horizontal and/or vertical scrollbars are present,
630 * compute the resulting layout and return true if the layout is
631 * consistent. If the layout is consistent then we fill in the
632 * computed fields of the ScrollReflowInput.
634 * The layout is consistent when both scrollbars are showing if and only
635 * if they should be showing. A horizontal scrollbar should be showing if all
636 * following conditions are met:
637 * 1) the style is not HIDDEN
638 * 2) our inside-border height is at least the scrollbar height (i.e., the
639 * scrollbar fits vertically)
640 * 3) the style is SCROLL, or the kid's overflow-area XMost is
641 * greater than the scrollport width
643 * @param aForce if true, then we just assume the layout is consistent.
645 bool nsHTMLScrollFrame::TryLayout(ScrollReflowInput& aState,
646 ReflowOutput* aKidMetrics,
647 bool aAssumeHScroll, bool aAssumeVScroll,
648 bool aForce) {
649 if ((aState.mVScrollbar == ShowScrollbar::Never && aAssumeVScroll) ||
650 (aState.mHScrollbar == ShowScrollbar::Never && aAssumeHScroll)) {
651 NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
652 return false;
655 const auto wm = GetWritingMode();
656 const nsMargin scrollbarGutter = aState.ScrollbarGutter(
657 aAssumeVScroll, aAssumeHScroll, IsScrollbarOnRight());
658 const LogicalMargin logicalScrollbarGutter(wm, scrollbarGutter);
660 const bool inlineEndsGutterChanged =
661 aState.mScrollbarGutterFromLastReflow.IStartEnd(wm) !=
662 logicalScrollbarGutter.IStartEnd(wm);
663 const bool blockEndsGutterChanged =
664 aState.mScrollbarGutterFromLastReflow.BStartEnd(wm) !=
665 logicalScrollbarGutter.BStartEnd(wm);
666 const bool shouldReflowScrolledFrame =
667 inlineEndsGutterChanged ||
668 (blockEndsGutterChanged && ScrolledContentDependsOnBSize(aState));
670 if (shouldReflowScrolledFrame) {
671 if (blockEndsGutterChanged) {
672 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(mScrolledFrame);
674 aKidMetrics->mOverflowAreas.Clear();
675 ROOT_SCROLLBAR_LOG(
676 "TryLayout reflowing scrolled frame with scrollbars h=%d, v=%d\n",
677 aAssumeHScroll, aAssumeVScroll);
678 ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll, aKidMetrics);
681 const nsSize scrollbarGutterSize(scrollbarGutter.LeftRight(),
682 scrollbarGutter.TopBottom());
684 // First, compute our inside-border size and scrollport size
685 // XXXldb Can we depend more on ComputeSize here?
686 nsSize kidSize = GetContainSizeAxes().ContainSize(
687 aKidMetrics->PhysicalSize(), *aState.mReflowInput.mFrame);
688 const nsSize desiredInsideBorderSize = kidSize + scrollbarGutterSize;
689 aState.mInsideBorderSize =
690 ComputeInsideBorderSize(aState, desiredInsideBorderSize);
692 nsSize layoutSize =
693 mIsUsingMinimumScaleSize ? mMinimumScaleSize : aState.mInsideBorderSize;
695 const nsSize scrollPortSize =
696 Max(nsSize(0, 0), layoutSize - scrollbarGutterSize);
697 if (mIsUsingMinimumScaleSize) {
698 mICBSize =
699 Max(nsSize(0, 0), aState.mInsideBorderSize - scrollbarGutterSize);
702 nsSize visualViewportSize = scrollPortSize;
703 ROOT_SCROLLBAR_LOG("TryLayout with VV %s\n",
704 ToString(visualViewportSize).c_str());
705 mozilla::PresShell* presShell = PresShell();
706 // Note: we check for a non-null MobileViepwortManager here, but ideally we
707 // should be able to drop that clause as well. It's just that in some cases
708 // with extension popups the composition size comes back as stale, because
709 // the content viewer is only resized after the popup contents are reflowed.
710 // That case also happens to have no APZ and no MVM, so we use that as a
711 // way to detect the scenario. Bug 1648669 tracks removing this clause.
712 if (mIsRoot && presShell->GetMobileViewportManager()) {
713 visualViewportSize = nsLayoutUtils::CalculateCompositionSizeForFrame(
714 this, false, &layoutSize);
715 visualViewportSize =
716 Max(nsSize(0, 0), visualViewportSize - scrollbarGutterSize);
718 float resolution = presShell->GetResolution();
719 visualViewportSize.width /= resolution;
720 visualViewportSize.height /= resolution;
721 ROOT_SCROLLBAR_LOG("TryLayout now with VV %s\n",
722 ToString(visualViewportSize).c_str());
725 nsRect overflowRect = aState.mContentsOverflowAreas.ScrollableOverflow();
726 // If the content height expanded by the minimum-scale will be taller than
727 // the scrollable overflow area, we need to expand the area here to tell
728 // properly whether we need to render the overlay vertical scrollbar.
729 // NOTE: This expanded size should NOT be used for non-overley scrollbars
730 // cases since putting the vertical non-overlay scrollbar will make the
731 // content width narrow a little bit, which in turn the minimum scale value
732 // becomes a bit bigger than before, then the vertical scrollbar is no longer
733 // needed, which means the content width becomes the original width, then the
734 // minimum-scale is changed to the original one, and so forth.
735 if (UsesOverlayScrollbars() && mIsUsingMinimumScaleSize &&
736 mMinimumScaleSize.height > overflowRect.YMost()) {
737 overflowRect.height += mMinimumScaleSize.height - overflowRect.YMost();
739 nsRect scrolledRect =
740 GetUnsnappedScrolledRectInternal(overflowRect, scrollPortSize);
741 ROOT_SCROLLBAR_LOG(
742 "TryLayout scrolledRect:%s overflowRect:%s scrollportSize:%s\n",
743 ToString(scrolledRect).c_str(), ToString(overflowRect).c_str(),
744 ToString(scrollPortSize).c_str());
745 nscoord oneDevPixel = PresContext()->DevPixelsToAppUnits(1);
747 bool showHScrollbar = aAssumeHScroll;
748 bool showVScrollbar = aAssumeVScroll;
749 if (!aForce) {
750 nsSize sizeToCompare = visualViewportSize;
751 if (gfxPlatform::UseDesktopZoomingScrollbars()) {
752 sizeToCompare = scrollPortSize;
755 // No need to compute showHScrollbar if we got ShowScrollbar::Never.
756 if (aState.mHScrollbar != ShowScrollbar::Never) {
757 showHScrollbar =
758 aState.mHScrollbar == ShowScrollbar::Always ||
759 scrolledRect.XMost() >= sizeToCompare.width + oneDevPixel ||
760 scrolledRect.x <= -oneDevPixel;
761 // TODO(emilio): This should probably check this scrollbar's minimum size
762 // in both axes, for consistency?
763 if (aState.mHScrollbar == ShowScrollbar::Auto &&
764 scrollPortSize.width < aState.HScrollbarMinWidth()) {
765 showHScrollbar = false;
767 ROOT_SCROLLBAR_LOG("TryLayout wants H Scrollbar: %d =? %d\n",
768 showHScrollbar, aAssumeHScroll);
771 // No need to compute showVScrollbar if we got ShowScrollbar::Never.
772 if (aState.mVScrollbar != ShowScrollbar::Never) {
773 showVScrollbar =
774 aState.mVScrollbar == ShowScrollbar::Always ||
775 scrolledRect.YMost() >= sizeToCompare.height + oneDevPixel ||
776 scrolledRect.y <= -oneDevPixel;
777 // TODO(emilio): This should probably check this scrollbar's minimum size
778 // in both axes, for consistency?
779 if (aState.mVScrollbar == ShowScrollbar::Auto &&
780 scrollPortSize.height < aState.VScrollbarMinHeight()) {
781 showVScrollbar = false;
783 ROOT_SCROLLBAR_LOG("TryLayout wants V Scrollbar: %d =? %d\n",
784 showVScrollbar, aAssumeVScroll);
787 if (showHScrollbar != aAssumeHScroll || showVScrollbar != aAssumeVScroll) {
788 const nsMargin wantedScrollbarGutter = aState.ScrollbarGutter(
789 showVScrollbar, showHScrollbar, IsScrollbarOnRight());
790 // We report an inconsistent layout only when the desired visibility of
791 // the scrollbars can change the size of the scrollbar gutters.
792 if (scrollbarGutter != wantedScrollbarGutter) {
793 return false;
798 // If we reach here, the layout is consistent. Record the desired visibility
799 // of the scrollbars.
800 aState.mShowHScrollbar = showHScrollbar;
801 aState.mShowVScrollbar = showVScrollbar;
802 const nsPoint scrollPortOrigin(
803 aState.mComputedBorder.left + scrollbarGutter.left,
804 aState.mComputedBorder.top + scrollbarGutter.top);
805 SetScrollPort(nsRect(scrollPortOrigin, scrollPortSize));
807 if (mIsRoot && gfxPlatform::UseDesktopZoomingScrollbars()) {
808 bool vvChanged = true;
809 const bool overlay = aState.OverlayScrollbars();
810 // This loop can run at most twice since we can only add a scrollbar once.
811 // At this point we've already decided that this layout is consistent so we
812 // will return true. Scrollbars added here never take up layout space even
813 // if they are layout scrollbars so any changes made here will not make us
814 // return false.
815 while (vvChanged) {
816 vvChanged = false;
817 if (!aState.mShowHScrollbar &&
818 aState.mHScrollbarAllowedForScrollingVVInsideLV) {
819 if (ScrollPort().width >= visualViewportSize.width + oneDevPixel &&
820 (overlay ||
821 visualViewportSize.width >= aState.HScrollbarMinWidth())) {
822 vvChanged = true;
823 if (!overlay) {
824 visualViewportSize.height -= aState.HScrollbarPrefHeight();
826 aState.mShowHScrollbar = true;
827 aState.mOnlyNeedHScrollbarToScrollVVInsideLV = true;
828 ROOT_SCROLLBAR_LOG("TryLayout added H scrollbar for VV, VV now %s\n",
829 ToString(visualViewportSize).c_str());
833 if (!aState.mShowVScrollbar &&
834 aState.mVScrollbarAllowedForScrollingVVInsideLV) {
835 if (ScrollPort().height >= visualViewportSize.height + oneDevPixel &&
836 (overlay ||
837 visualViewportSize.height >= aState.VScrollbarMinHeight())) {
838 vvChanged = true;
839 if (!overlay) {
840 visualViewportSize.width -= aState.VScrollbarPrefWidth();
842 aState.mShowVScrollbar = true;
843 aState.mOnlyNeedVScrollbarToScrollVVInsideLV = true;
844 ROOT_SCROLLBAR_LOG("TryLayout added V scrollbar for VV, VV now %s\n",
845 ToString(visualViewportSize).c_str());
851 return true;
854 bool nsHTMLScrollFrame::ScrolledContentDependsOnBSize(
855 const ScrollReflowInput& aState) const {
856 return mScrolledFrame->HasAnyStateBits(
857 NS_FRAME_CONTAINS_RELATIVE_BSIZE |
858 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE) ||
859 aState.mReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
860 aState.mReflowInput.ComputedMinBSize() > 0 ||
861 aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE;
864 void nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowInput& aState,
865 bool aAssumeHScroll,
866 bool aAssumeVScroll,
867 ReflowOutput* aMetrics) {
868 const WritingMode wm = GetWritingMode();
870 // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should
871 // be OK
872 LogicalMargin padding = aState.mReflowInput.ComputedLogicalPadding(wm);
873 nscoord availISize =
874 aState.mReflowInput.ComputedISize() + padding.IStartEnd(wm);
876 nscoord computedBSize = aState.mReflowInput.ComputedBSize();
877 nscoord computedMinBSize = aState.mReflowInput.ComputedMinBSize();
878 nscoord computedMaxBSize = aState.mReflowInput.ComputedMaxBSize();
879 if (!ShouldPropagateComputedBSizeToScrolledContent()) {
880 computedBSize = NS_UNCONSTRAINEDSIZE;
881 computedMinBSize = 0;
882 computedMaxBSize = NS_UNCONSTRAINEDSIZE;
885 const LogicalMargin scrollbarGutter(
886 wm, aState.ScrollbarGutter(aAssumeVScroll, aAssumeHScroll,
887 IsScrollbarOnRight()));
888 if (const nscoord inlineEndsGutter = scrollbarGutter.IStartEnd(wm);
889 inlineEndsGutter > 0) {
890 availISize = std::max(0, availISize - inlineEndsGutter);
892 if (const nscoord blockEndsGutter = scrollbarGutter.BStartEnd(wm);
893 blockEndsGutter > 0) {
894 if (computedBSize != NS_UNCONSTRAINEDSIZE) {
895 computedBSize = std::max(0, computedBSize - blockEndsGutter);
897 computedMinBSize = std::max(0, computedMinBSize - blockEndsGutter);
898 if (computedMaxBSize != NS_UNCONSTRAINEDSIZE) {
899 computedMaxBSize = std::max(0, computedMaxBSize - blockEndsGutter);
903 nsPresContext* presContext = PresContext();
905 // Pass InitFlags::CallerWillInit so we can pass in the correct padding.
906 ReflowInput kidReflowInput(presContext, aState.mReflowInput, mScrolledFrame,
907 LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE),
908 Nothing(), ReflowInput::InitFlag::CallerWillInit);
909 const WritingMode kidWM = kidReflowInput.GetWritingMode();
910 kidReflowInput.Init(presContext, Nothing(), Nothing(),
911 Some(padding.ConvertTo(kidWM, wm)));
912 kidReflowInput.mFlags.mAssumingHScrollbar = aAssumeHScroll;
913 kidReflowInput.mFlags.mAssumingVScrollbar = aAssumeVScroll;
914 kidReflowInput.mFlags.mTreatBSizeAsIndefinite =
915 aState.mReflowInput.mFlags.mTreatBSizeAsIndefinite;
916 kidReflowInput.SetComputedBSize(computedBSize);
917 kidReflowInput.SetComputedMinBSize(computedMinBSize);
918 kidReflowInput.SetComputedMaxBSize(computedMaxBSize);
919 if (aState.mReflowInput.IsBResizeForWM(kidWM)) {
920 kidReflowInput.SetBResize(true);
922 if (aState.mReflowInput.IsBResizeForPercentagesForWM(kidWM)) {
923 kidReflowInput.mFlags.mIsBResizeForPercentages = true;
926 // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to
927 // reflect our assumptions while we reflow the child.
928 bool didHaveHorizontalScrollbar = mHasHorizontalScrollbar;
929 bool didHaveVerticalScrollbar = mHasVerticalScrollbar;
930 mHasHorizontalScrollbar = aAssumeHScroll;
931 mHasVerticalScrollbar = aAssumeVScroll;
933 nsReflowStatus status;
934 // No need to pass a true container-size to ReflowChild or
935 // FinishReflowChild, because it's only used there when positioning
936 // the frame (i.e. if ReflowChildFlags::NoMoveFrame isn't set)
937 const nsSize dummyContainerSize;
938 ReflowChild(mScrolledFrame, presContext, *aMetrics, kidReflowInput, wm,
939 LogicalPoint(wm), dummyContainerSize,
940 ReflowChildFlags::NoMoveFrame, status);
942 mHasHorizontalScrollbar = didHaveHorizontalScrollbar;
943 mHasVerticalScrollbar = didHaveVerticalScrollbar;
945 // Don't resize or position the view (if any) because we're going to resize
946 // it to the correct size anyway in PlaceScrollArea. Allowing it to
947 // resize here would size it to the natural height of the frame,
948 // which will usually be different from the scrollport height;
949 // invalidating the difference will cause unnecessary repainting.
950 FinishReflowChild(
951 mScrolledFrame, presContext, *aMetrics, &kidReflowInput, wm,
952 LogicalPoint(wm), dummyContainerSize,
953 ReflowChildFlags::NoMoveFrame | ReflowChildFlags::NoSizeView);
955 if (mScrolledFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
956 // Propagate NS_FRAME_CONTAINS_RELATIVE_BSIZE from our inner scrolled frame
957 // to ourselves so that our containing block is aware of it.
959 // Note: If the scrolled frame has any child whose block-size depends on the
960 // containing block's block-size, the NS_FRAME_CONTAINS_RELATIVE_BSIZE bit
961 // is set on the scrolled frame when initializing the child's ReflowInput in
962 // ReflowInput::InitResizeFlags(). Therefore, we propagate the bit here
963 // after we reflowed the scrolled frame.
964 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
967 // XXX Some frames (e.g. nsFrameFrame, nsTextFrame) don't
968 // bother setting their mOverflowArea. This is wrong because every frame
969 // should always set mOverflowArea. In fact nsFrameFrame doesn't
970 // support the 'outline' property because of this. Rather than fix the
971 // world right now, just fix up the overflow area if necessary. Note that we
972 // don't check HasOverflowRect() because it could be set even though the
973 // overflow area doesn't include the frame bounds.
974 aMetrics->UnionOverflowAreasWithDesiredBounds();
976 auto* disp = StyleDisplay();
977 if (MOZ_UNLIKELY(disp->mOverflowClipBoxInline ==
978 StyleOverflowClipBox::ContentBox)) {
979 // The scrolled frame is scrollable in the inline axis with
980 // `overflow-clip-box:content-box`. To prevent its content from being
981 // clipped at the scroll container's padding edges, we inflate its
982 // children's scrollable overflow area with its inline padding, and union
983 // its scrollable overflow area with its children's inflated scrollable
984 // overflow area.
985 OverflowAreas childOverflow;
986 mScrolledFrame->UnionChildOverflow(childOverflow);
987 nsRect childScrollableOverflow = childOverflow.ScrollableOverflow();
989 const LogicalMargin inlinePadding =
990 padding.ApplySkipSides(LogicalSides(wm, eLogicalSideBitsBBoth));
991 childScrollableOverflow.Inflate(inlinePadding.GetPhysicalMargin(wm));
993 nsRect& so = aMetrics->ScrollableOverflow();
994 so = so.UnionEdges(childScrollableOverflow);
997 aState.mContentsOverflowAreas = aMetrics->mOverflowAreas;
998 aState.mScrollbarGutterFromLastReflow = scrollbarGutter;
999 aState.mReflowedContentsWithHScrollbar = aAssumeHScroll;
1000 aState.mReflowedContentsWithVScrollbar = aAssumeVScroll;
1003 bool nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowInput& aState) {
1004 if (aState.mHScrollbar != ShowScrollbar::Auto) {
1005 // no guessing required
1006 return aState.mHScrollbar == ShowScrollbar::Always;
1008 // We only care about scrollbars that might take up space when trying to guess
1009 // if we need a scrollbar, so we ignore scrollbars only created to scroll the
1010 // visual viewport inside the layout viewport because they take up no layout
1011 // space.
1012 return mHasHorizontalScrollbar && !mOnlyNeedHScrollbarToScrollVVInsideLV;
1015 bool nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowInput& aState) {
1016 if (aState.mVScrollbar != ShowScrollbar::Auto) {
1017 // no guessing required
1018 return aState.mVScrollbar == ShowScrollbar::Always;
1021 // If we've had at least one non-initial reflow, then just assume
1022 // the state of the vertical scrollbar will be what we determined
1023 // last time.
1024 if (mHadNonInitialReflow) {
1025 // We only care about scrollbars that might take up space when trying to
1026 // guess if we need a scrollbar, so we ignore scrollbars only created to
1027 // scroll the visual viewport inside the layout viewport because they take
1028 // up no layout space.
1029 return mHasVerticalScrollbar && !mOnlyNeedVScrollbarToScrollVVInsideLV;
1032 // If this is the initial reflow, guess false because usually
1033 // we have very little content by then.
1034 if (InInitialReflow()) return false;
1036 if (mIsRoot) {
1037 nsIFrame* f = mScrolledFrame->PrincipalChildList().FirstChild();
1038 if (f && f->IsSVGOuterSVGFrame() &&
1039 static_cast<SVGOuterSVGFrame*>(f)->VerticalScrollbarNotNeeded()) {
1040 // Common SVG case - avoid a bad guess.
1041 return false;
1043 // Assume that there will be a scrollbar; it seems to me
1044 // that 'most pages' do have a scrollbar, and anyway, it's cheaper
1045 // to do an extra reflow for the pages that *don't* need a
1046 // scrollbar (because on average they will have less content).
1047 return true;
1050 // For non-viewports, just guess that we don't need a scrollbar.
1051 // XXX I wonder if statistically this is the right idea; I'm
1052 // basically guessing that there are a lot of overflow:auto DIVs
1053 // that get their intrinsic size and don't overflow
1054 return false;
1057 bool nsHTMLScrollFrame::InInitialReflow() const {
1058 // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a
1059 // root scrollframe. In that case we want to skip this clause altogether.
1060 // The guess here is that there are lots of overflow:auto divs out there that
1061 // end up auto-sizing so they don't overflow, and that the root basically
1062 // always needs a scrollbar if it did last time we loaded this page (good
1063 // assumption, because our initial reflow is no longer synchronous).
1064 return !mIsRoot && HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
1067 void nsHTMLScrollFrame::ReflowContents(ScrollReflowInput& aState,
1068 const ReflowOutput& aDesiredSize) {
1069 const WritingMode desiredWm = aDesiredSize.GetWritingMode();
1070 ReflowOutput kidDesiredSize(desiredWm);
1071 ReflowScrolledFrame(aState, GuessHScrollbarNeeded(aState),
1072 GuessVScrollbarNeeded(aState), &kidDesiredSize);
1074 // There's an important special case ... if the child appears to fit
1075 // in the inside-border rect (but overflows the scrollport), we
1076 // should try laying it out without a vertical scrollbar. It will
1077 // usually fit because making the available-width wider will not
1078 // normally make the child taller. (The only situation I can think
1079 // of is when you have a line containing %-width inline replaced
1080 // elements whose percentages sum to more than 100%, so increasing
1081 // the available width makes the line break where it was fitting
1082 // before.) If we don't treat this case specially, then we will
1083 // decide that showing scrollbars is OK because the content
1084 // overflows when we're showing scrollbars and we won't try to
1085 // remove the vertical scrollbar.
1087 // Detecting when we enter this special case is important for when
1088 // people design layouts that exactly fit the container "most of the
1089 // time".
1091 // XXX Is this check really sufficient to catch all the incremental cases
1092 // where the ideal case doesn't have a scrollbar?
1093 if ((aState.mReflowedContentsWithHScrollbar ||
1094 aState.mReflowedContentsWithVScrollbar) &&
1095 aState.mVScrollbar != ShowScrollbar::Always &&
1096 aState.mHScrollbar != ShowScrollbar::Always) {
1097 nsSize kidSize = GetContainSizeAxes().ContainSize(
1098 kidDesiredSize.PhysicalSize(), *aState.mReflowInput.mFrame);
1099 nsSize insideBorderSize = ComputeInsideBorderSize(aState, kidSize);
1100 nsRect scrolledRect = GetUnsnappedScrolledRectInternal(
1101 kidDesiredSize.ScrollableOverflow(), insideBorderSize);
1102 if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
1103 // Let's pretend we had no scrollbars coming in here
1104 kidDesiredSize.mOverflowAreas.Clear();
1105 ReflowScrolledFrame(aState, false, false, &kidDesiredSize);
1109 if (IsRootScrollFrameOfDocument()) {
1110 UpdateMinimumScaleSize(aState.mContentsOverflowAreas.ScrollableOverflow(),
1111 kidDesiredSize.PhysicalSize());
1114 // Try vertical scrollbar settings that leave the vertical scrollbar
1115 // unchanged. Do this first because changing the vertical scrollbar setting is
1116 // expensive, forcing a reflow always.
1118 // Try leaving the horizontal scrollbar unchanged first. This will be more
1119 // efficient.
1120 ROOT_SCROLLBAR_LOG("Trying layout1 with %d, %d\n",
1121 aState.mReflowedContentsWithHScrollbar,
1122 aState.mReflowedContentsWithVScrollbar);
1123 if (TryLayout(aState, &kidDesiredSize, aState.mReflowedContentsWithHScrollbar,
1124 aState.mReflowedContentsWithVScrollbar, false)) {
1125 return;
1127 ROOT_SCROLLBAR_LOG("Trying layout2 with %d, %d\n",
1128 !aState.mReflowedContentsWithHScrollbar,
1129 aState.mReflowedContentsWithVScrollbar);
1130 if (TryLayout(aState, &kidDesiredSize,
1131 !aState.mReflowedContentsWithHScrollbar,
1132 aState.mReflowedContentsWithVScrollbar, false)) {
1133 return;
1136 // OK, now try toggling the vertical scrollbar. The performance advantage
1137 // of trying the status-quo horizontal scrollbar state
1138 // does not exist here (we'll have to reflow due to the vertical scrollbar
1139 // change), so always try no horizontal scrollbar first.
1140 bool newVScrollbarState = !aState.mReflowedContentsWithVScrollbar;
1141 ROOT_SCROLLBAR_LOG("Trying layout3 with %d, %d\n", false, newVScrollbarState);
1142 if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false)) {
1143 return;
1145 ROOT_SCROLLBAR_LOG("Trying layout4 with %d, %d\n", true, newVScrollbarState);
1146 if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false)) {
1147 return;
1150 // OK, we're out of ideas. Try again enabling whatever scrollbars we can
1151 // enable and force the layout to stick even if it's inconsistent.
1152 // This just happens sometimes.
1153 ROOT_SCROLLBAR_LOG("Giving up, adding both scrollbars...\n");
1154 TryLayout(aState, &kidDesiredSize, aState.mHScrollbar != ShowScrollbar::Never,
1155 aState.mVScrollbar != ShowScrollbar::Never, true);
1158 void nsHTMLScrollFrame::PlaceScrollArea(ScrollReflowInput& aState,
1159 const nsPoint& aScrollPosition) {
1160 nsIFrame* scrolledFrame = mScrolledFrame;
1161 // Set the x,y of the scrolled frame to the correct value
1162 scrolledFrame->SetPosition(ScrollPort().TopLeft() - aScrollPosition);
1164 // Recompute our scrollable overflow, taking perspective children into
1165 // account. Note that this only recomputes the overflow areas stored on the
1166 // helper (which are used to compute scrollable length and scrollbar thumb
1167 // sizes) but not the overflow areas stored on the frame. This seems to work
1168 // for now, but it's possible that we may need to update both in the future.
1169 AdjustForPerspective(aState.mContentsOverflowAreas.ScrollableOverflow());
1171 // Preserve the width or height of empty rects
1172 const nsSize portSize = ScrollPort().Size();
1173 nsRect scrolledRect = GetUnsnappedScrolledRectInternal(
1174 aState.mContentsOverflowAreas.ScrollableOverflow(), portSize);
1175 nsRect scrolledArea =
1176 scrolledRect.UnionEdges(nsRect(nsPoint(0, 0), portSize));
1178 // Store the new overflow area. Note that this changes where an outline
1179 // of the scrolled frame would be painted, but scrolled frames can't have
1180 // outlines (the outline would go on this scrollframe instead).
1181 // Using FinishAndStoreOverflow is needed so the overflow rect gets set
1182 // correctly. It also messes with the overflow rect in the 'clip' case, but
1183 // scrolled frames can't have 'overflow' either.
1184 // This needs to happen before SyncFrameViewAfterReflow so
1185 // HasOverflowRect() will return the correct value.
1186 OverflowAreas overflow(scrolledArea, scrolledArea);
1187 scrolledFrame->FinishAndStoreOverflow(overflow, scrolledFrame->GetSize());
1189 // Note that making the view *exactly* the size of the scrolled area
1190 // is critical, since the view scrolling code uses the size of the
1191 // scrolled view to clamp scroll requests.
1192 // Normally the scrolledFrame won't have a view but in some cases it
1193 // might create its own.
1194 nsContainerFrame::SyncFrameViewAfterReflow(
1195 scrolledFrame->PresContext(), scrolledFrame, scrolledFrame->GetView(),
1196 scrolledArea, ReflowChildFlags::Default);
1199 nscoord nsHTMLScrollFrame::IntrinsicScrollbarGutterSizeAtInlineEdges(
1200 gfxContext* aRenderingContext) {
1201 const bool isVerticalWM = GetWritingMode().IsVertical();
1202 nsScrollbarFrame* inlineEndScrollbarBox =
1203 isVerticalWM ? mHScrollbarBox : mVScrollbarBox;
1204 if (!inlineEndScrollbarBox) {
1205 // No scrollbar box frame means no intrinsic size.
1206 return 0;
1209 if (PresContext()->UseOverlayScrollbars()) {
1210 return 0;
1213 const auto* styleForScrollbar = nsLayoutUtils::StyleForScrollbar(this);
1214 if (styleForScrollbar->StyleUIReset()->ScrollbarWidth() ==
1215 StyleScrollbarWidth::None) {
1216 // Scrollbar shouldn't appear at all with "scrollbar-width: none".
1217 return 0;
1220 const auto& styleScrollbarGutter =
1221 styleForScrollbar->StyleDisplay()->mScrollbarGutter;
1222 ScrollStyles ss = GetScrollStyles();
1223 const StyleOverflow& inlineEndStyleOverflow =
1224 isVerticalWM ? ss.mHorizontal : ss.mVertical;
1226 // Return the scrollbar-gutter size only if we have "overflow:scroll" or
1227 // non-auto "scrollbar-gutter", so early-return here if the conditions aren't
1228 // satisfied.
1229 if (inlineEndStyleOverflow != StyleOverflow::Scroll &&
1230 styleScrollbarGutter == StyleScrollbarGutter::AUTO) {
1231 return 0;
1234 // No need to worry about reflow depth here since it's just for scrollbars.
1235 nsSize scrollbarPrefSize = inlineEndScrollbarBox->ScrollbarMinSize();
1236 const nscoord scrollbarSize =
1237 isVerticalWM ? scrollbarPrefSize.height : scrollbarPrefSize.width;
1238 const auto bothEdges =
1239 bool(styleScrollbarGutter & StyleScrollbarGutter::BOTH_EDGES);
1240 return bothEdges ? scrollbarSize * 2 : scrollbarSize;
1243 // Legacy, this sucks!
1244 static bool IsMarqueeScrollbox(const nsIFrame& aScrollFrame) {
1245 if (!aScrollFrame.GetContent()) {
1246 return false;
1248 if (MOZ_LIKELY(!aScrollFrame.GetContent()->HasBeenInUAWidget())) {
1249 return false;
1251 MOZ_ASSERT(aScrollFrame.GetParent() &&
1252 aScrollFrame.GetParent()->GetContent());
1253 return aScrollFrame.GetParent() &&
1254 HTMLMarqueeElement::FromNodeOrNull(
1255 aScrollFrame.GetParent()->GetContent());
1258 /* virtual */
1259 nscoord nsHTMLScrollFrame::GetMinISize(gfxContext* aRenderingContext) {
1260 nscoord result = [&] {
1261 if (const Maybe<nscoord> containISize = ContainIntrinsicISize()) {
1262 return *containISize;
1264 if (MOZ_UNLIKELY(IsMarqueeScrollbox(*this))) {
1265 return 0;
1267 return mScrolledFrame->GetMinISize(aRenderingContext);
1268 }();
1270 DISPLAY_MIN_INLINE_SIZE(this, result);
1271 return result + IntrinsicScrollbarGutterSizeAtInlineEdges(aRenderingContext);
1274 /* virtual */
1275 nscoord nsHTMLScrollFrame::GetPrefISize(gfxContext* aRenderingContext) {
1276 const Maybe<nscoord> containISize = ContainIntrinsicISize();
1277 nscoord result = containISize
1278 ? *containISize
1279 : mScrolledFrame->GetPrefISize(aRenderingContext);
1280 DISPLAY_PREF_INLINE_SIZE(this, result);
1281 return NSCoordSaturatingAdd(
1282 result, IntrinsicScrollbarGutterSizeAtInlineEdges(aRenderingContext));
1285 // When we have perspective set on the outer scroll frame, and transformed
1286 // children (possibly with preserve-3d) then the effective transform on the
1287 // child depends on the offset to the scroll frame, which changes as we scroll.
1288 // This perspective transform can cause the element to move relative to the
1289 // scrolled inner frame, which would cause the scrollable length changes during
1290 // scrolling if we didn't account for it. Since we don't want scrollHeight/Width
1291 // and the size of scrollbar thumbs to change during scrolling, we compute the
1292 // scrollable overflow by determining the scroll position at which the child
1293 // becomes completely visible within the scrollport rather than using the union
1294 // of the overflow areas at their current position.
1295 static void GetScrollableOverflowForPerspective(
1296 nsIFrame* aScrolledFrame, nsIFrame* aCurrentFrame, const nsRect aScrollPort,
1297 nsPoint aOffset, nsRect& aScrolledFrameOverflowArea) {
1298 // Iterate over all children except pop-ups.
1299 for (const auto& [list, listID] : aCurrentFrame->ChildLists()) {
1300 if (listID == FrameChildListID::Popup) {
1301 continue;
1304 for (nsIFrame* child : list) {
1305 nsPoint offset = aOffset;
1307 // When we reach a direct child of the scroll, then we record the offset
1308 // to convert from that frame's coordinate into the scroll frame's
1309 // coordinates. Preserve-3d descendant frames use the same offset as their
1310 // ancestors, since TransformRect already converts us into the coordinate
1311 // space of the preserve-3d root.
1312 if (aScrolledFrame == aCurrentFrame) {
1313 offset = child->GetPosition();
1316 if (child->Extend3DContext()) {
1317 // If we're a preserve-3d frame, then recurse and include our
1318 // descendants since overflow of preserve-3d frames is only included
1319 // in the post-transform overflow area of the preserve-3d root frame.
1320 GetScrollableOverflowForPerspective(aScrolledFrame, child, aScrollPort,
1321 offset, aScrolledFrameOverflowArea);
1324 // If we're transformed, then we want to consider the possibility that
1325 // this frame might move relative to the scrolled frame when scrolling.
1326 // For preserve-3d, leaf frames have correct overflow rects relative to
1327 // themselves. preserve-3d 'nodes' (intermediate frames and the root) have
1328 // only their untransformed children included in their overflow relative
1329 // to self, which is what we want to include here.
1330 if (child->IsTransformed()) {
1331 // Compute the overflow rect for this leaf transform frame in the
1332 // coordinate space of the scrolled frame.
1333 nsPoint scrollPos = aScrolledFrame->GetPosition();
1334 nsRect preScroll, postScroll;
1336 // TODO: Can we reuse the reference box?
1337 TransformReferenceBox refBox(child);
1338 preScroll = nsDisplayTransform::TransformRect(
1339 child->ScrollableOverflowRectRelativeToSelf(), child, refBox);
1342 // Temporarily override the scroll position of the scrolled frame by
1343 // 10 CSS pixels, and then recompute what the overflow rect would be.
1344 // This scroll position may not be valid, but that shouldn't matter
1345 // for our calculations.
1347 aScrolledFrame->SetPosition(scrollPos + nsPoint(600, 600));
1348 TransformReferenceBox refBox(child);
1349 postScroll = nsDisplayTransform::TransformRect(
1350 child->ScrollableOverflowRectRelativeToSelf(), child, refBox);
1351 aScrolledFrame->SetPosition(scrollPos);
1354 // Compute how many app units the overflow rects moves by when we adjust
1355 // the scroll position by 1 app unit.
1356 double rightDelta =
1357 (postScroll.XMost() - preScroll.XMost() + 600.0) / 600.0;
1358 double bottomDelta =
1359 (postScroll.YMost() - preScroll.YMost() + 600.0) / 600.0;
1361 // We can't ever have negative scrolling.
1362 NS_ASSERTION(rightDelta > 0.0f && bottomDelta > 0.0f,
1363 "Scrolling can't be reversed!");
1365 // Move preScroll into the coordinate space of the scrollport.
1366 preScroll += offset + scrollPos;
1368 // For each of the four edges of preScroll, figure out how far they
1369 // extend beyond the scrollport. Ignore negative values since that means
1370 // that side is already scrolled in to view and we don't need to add
1371 // overflow to account for it.
1372 nsMargin overhang(std::max(0, aScrollPort.Y() - preScroll.Y()),
1373 std::max(0, preScroll.XMost() - aScrollPort.XMost()),
1374 std::max(0, preScroll.YMost() - aScrollPort.YMost()),
1375 std::max(0, aScrollPort.X() - preScroll.X()));
1377 // Scale according to rightDelta/bottomDelta to adjust for the different
1378 // scroll rates.
1379 overhang.top = NSCoordSaturatingMultiply(
1380 overhang.top, static_cast<float>(1 / bottomDelta));
1381 overhang.right = NSCoordSaturatingMultiply(
1382 overhang.right, static_cast<float>(1 / rightDelta));
1383 overhang.bottom = NSCoordSaturatingMultiply(
1384 overhang.bottom, static_cast<float>(1 / bottomDelta));
1385 overhang.left = NSCoordSaturatingMultiply(
1386 overhang.left, static_cast<float>(1 / rightDelta));
1388 // Take the minimum overflow rect that would allow the current scroll
1389 // position, using the size of the scroll port and offset by the
1390 // inverse of the scroll position.
1391 nsRect overflow = aScrollPort - scrollPos;
1393 // Expand it by our margins to get an overflow rect that would allow all
1394 // edges of our transformed content to be scrolled into view.
1395 overflow.Inflate(overhang);
1397 // Merge it with the combined overflow
1398 aScrolledFrameOverflowArea.UnionRect(aScrolledFrameOverflowArea,
1399 overflow);
1400 } else if (aCurrentFrame == aScrolledFrame) {
1401 aScrolledFrameOverflowArea.UnionRect(
1402 aScrolledFrameOverflowArea,
1403 child->ScrollableOverflowRectRelativeToParent());
1409 BaselineSharingGroup nsHTMLScrollFrame::GetDefaultBaselineSharingGroup() const {
1410 return mScrolledFrame->GetDefaultBaselineSharingGroup();
1413 nscoord nsHTMLScrollFrame::SynthesizeFallbackBaseline(
1414 mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
1415 // Marign-end even for central baselines.
1416 if (aWM.IsLineInverted()) {
1417 return -GetLogicalUsedMargin(aWM).BStart(aWM);
1419 return aBaselineGroup == BaselineSharingGroup::First
1420 ? BSize(aWM) + GetLogicalUsedMargin(aWM).BEnd(aWM)
1421 : -GetLogicalUsedMargin(aWM).BEnd(aWM);
1424 Maybe<nscoord> nsHTMLScrollFrame::GetNaturalBaselineBOffset(
1425 WritingMode aWM, BaselineSharingGroup aBaselineGroup,
1426 BaselineExportContext aExportContext) const {
1427 // Block containers that are scrollable always have a last baseline
1428 // that are synthesized from block-end margin edge.
1429 // Note(dshin): This behaviour is really only relevant to `inline-block`
1430 // alignment context. In the context of table/flex/grid alignment, first/last
1431 // baselines are calculated through `GetFirstLineBaseline`, which does
1432 // calculations of its own.
1433 // https://drafts.csswg.org/css-align/#baseline-export
1434 if (aExportContext == BaselineExportContext::LineLayout &&
1435 aBaselineGroup == BaselineSharingGroup::Last &&
1436 mScrolledFrame->IsBlockFrameOrSubclass()) {
1437 return Some(SynthesizeFallbackBaseline(aWM, aBaselineGroup));
1440 if (StyleDisplay()->IsContainLayout()) {
1441 return Nothing{};
1444 // OK, here's where we defer to our scrolled frame.
1445 return mScrolledFrame
1446 ->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext)
1447 .map([this, aWM](nscoord aBaseline) {
1448 // We have to add our border BStart thickness to whatever it returns, to
1449 // produce an offset in our frame-rect's coordinate system. (We don't
1450 // have to add padding, because the scrolled frame handles our padding.)
1451 LogicalMargin border = GetLogicalUsedBorder(aWM);
1452 const auto bSize = GetLogicalSize(aWM).BSize(aWM);
1453 // Clamp the baseline to the border rect. See bug 1791069.
1454 return std::clamp(border.BStart(aWM) + aBaseline, 0, bSize);
1458 void nsHTMLScrollFrame::AdjustForPerspective(nsRect& aScrollableOverflow) {
1459 // If we have perspective that is being applied to our children, then
1460 // the effective transform on the child depends on the relative position
1461 // of the child to us and changes during scrolling.
1462 if (!ChildrenHavePerspective()) {
1463 return;
1465 aScrollableOverflow.SetEmpty();
1466 GetScrollableOverflowForPerspective(mScrolledFrame, mScrolledFrame,
1467 ScrollPort(), nsPoint(),
1468 aScrollableOverflow);
1471 void nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext,
1472 ReflowOutput& aDesiredSize,
1473 const ReflowInput& aReflowInput,
1474 nsReflowStatus& aStatus) {
1475 MarkInReflow();
1476 DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
1477 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
1478 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1480 HandleScrollbarStyleSwitching();
1482 ScrollReflowInput state(this, aReflowInput);
1484 //------------ Handle Incremental Reflow -----------------
1485 bool reflowHScrollbar = true;
1486 bool reflowVScrollbar = true;
1487 bool reflowScrollCorner = true;
1488 if (!aReflowInput.ShouldReflowAllKids()) {
1489 auto NeedsReflow = [](const nsIFrame* aFrame) {
1490 return aFrame && aFrame->IsSubtreeDirty();
1493 reflowHScrollbar = NeedsReflow(mHScrollbarBox);
1494 reflowVScrollbar = NeedsReflow(mVScrollbarBox);
1495 reflowScrollCorner =
1496 NeedsReflow(mScrollCornerBox) || NeedsReflow(mResizerBox);
1499 if (mIsRoot) {
1500 reflowScrollCorner = false;
1503 const nsRect oldScrollPort = ScrollPort();
1504 nsRect oldScrolledAreaBounds =
1505 mScrolledFrame->ScrollableOverflowRectRelativeToParent();
1506 nsPoint oldScrollPosition = GetScrollPosition();
1508 ReflowContents(state, aDesiredSize);
1510 nsSize layoutSize =
1511 mIsUsingMinimumScaleSize ? mMinimumScaleSize : state.mInsideBorderSize;
1512 aDesiredSize.Width() = layoutSize.width + state.mComputedBorder.LeftRight();
1513 aDesiredSize.Height() = layoutSize.height + state.mComputedBorder.TopBottom();
1515 // Set the size of the frame now since computing the perspective-correct
1516 // overflow (within PlaceScrollArea) can rely on it.
1517 SetSize(aDesiredSize.GetWritingMode(),
1518 aDesiredSize.Size(aDesiredSize.GetWritingMode()));
1520 // Restore the old scroll position, for now, even if that's not valid anymore
1521 // because we changed size. We'll fix it up in a post-reflow callback, because
1522 // our current size may only be temporary (e.g. we're compute XUL desired
1523 // sizes).
1524 PlaceScrollArea(state, oldScrollPosition);
1525 if (!mPostedReflowCallback) {
1526 // Make sure we'll try scrolling to restored position
1527 PresShell()->PostReflowCallback(this);
1528 mPostedReflowCallback = true;
1531 bool didOnlyHScrollbar = mOnlyNeedHScrollbarToScrollVVInsideLV;
1532 bool didOnlyVScrollbar = mOnlyNeedVScrollbarToScrollVVInsideLV;
1533 mOnlyNeedHScrollbarToScrollVVInsideLV =
1534 state.mOnlyNeedHScrollbarToScrollVVInsideLV;
1535 mOnlyNeedVScrollbarToScrollVVInsideLV =
1536 state.mOnlyNeedVScrollbarToScrollVVInsideLV;
1538 bool didHaveHScrollbar = mHasHorizontalScrollbar;
1539 bool didHaveVScrollbar = mHasVerticalScrollbar;
1540 mHasHorizontalScrollbar = state.mShowHScrollbar;
1541 mHasVerticalScrollbar = state.mShowVScrollbar;
1542 const nsRect& newScrollPort = ScrollPort();
1543 nsRect newScrolledAreaBounds =
1544 mScrolledFrame->ScrollableOverflowRectRelativeToParent();
1545 if (mSkippedScrollbarLayout || reflowHScrollbar || reflowVScrollbar ||
1546 reflowScrollCorner || HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
1547 didHaveHScrollbar != state.mShowHScrollbar ||
1548 didHaveVScrollbar != state.mShowVScrollbar ||
1549 didOnlyHScrollbar != mOnlyNeedHScrollbarToScrollVVInsideLV ||
1550 didOnlyVScrollbar != mOnlyNeedVScrollbarToScrollVVInsideLV ||
1551 !oldScrollPort.IsEqualEdges(newScrollPort) ||
1552 !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
1553 if (!mSuppressScrollbarUpdate) {
1554 mSkippedScrollbarLayout = false;
1555 nsHTMLScrollFrame::SetScrollbarVisibility(mHScrollbarBox,
1556 state.mShowHScrollbar);
1557 nsHTMLScrollFrame::SetScrollbarVisibility(mVScrollbarBox,
1558 state.mShowVScrollbar);
1559 // place and reflow scrollbars
1560 const nsRect insideBorderArea(
1561 nsPoint(state.mComputedBorder.left, state.mComputedBorder.top),
1562 layoutSize);
1563 LayoutScrollbars(state, insideBorderArea, oldScrollPort);
1564 } else {
1565 mSkippedScrollbarLayout = true;
1568 if (mIsRoot) {
1569 if (RefPtr<MobileViewportManager> manager =
1570 PresShell()->GetMobileViewportManager()) {
1571 // Note that this runs during layout, and when we get here the root
1572 // scrollframe has already been laid out. It may have added or removed
1573 // scrollbars as a result of that layout, so we need to ensure the
1574 // visual viewport is updated to account for that before we read the
1575 // visual viewport size.
1576 manager->UpdateVisualViewportSizeForPotentialScrollbarChange();
1577 } else if (oldScrollPort.Size() != newScrollPort.Size()) {
1578 // We want to make sure to send a visual viewport resize event if the
1579 // scrollport changed sizes for root scroll frames. The
1580 // MobileViewportManager will do that, but if we don't have one (ie we
1581 // aren't a root content document for example) we have to send one
1582 // ourselves.
1583 if (auto* window = nsGlobalWindowInner::Cast(
1584 aPresContext->Document()->GetInnerWindow())) {
1585 window->VisualViewport()->PostResizeEvent();
1590 // Note that we need to do this after the
1591 // UpdateVisualViewportSizeForPotentialScrollbarChange call above because that
1592 // is what updates the visual viewport size and we need it to be up to date.
1593 if (mIsRoot && !state.OverlayScrollbars() &&
1594 (didHaveHScrollbar != state.mShowHScrollbar ||
1595 didHaveVScrollbar != state.mShowVScrollbar ||
1596 didOnlyHScrollbar != mOnlyNeedHScrollbarToScrollVVInsideLV ||
1597 didOnlyVScrollbar != mOnlyNeedVScrollbarToScrollVVInsideLV) &&
1598 PresShell()->IsVisualViewportOffsetSet()) {
1599 // Removing layout/classic scrollbars can make a previously valid vvoffset
1600 // invalid. For example, if we are zoomed in on an overflow hidden document
1601 // and then zoom back out, when apz reaches the initial resolution (ie 1.0)
1602 // it won't know that we can remove the scrollbars, so the vvoffset can
1603 // validly be upto the width/height of the scrollbars. After we reflow and
1604 // remove the scrollbars the only valid vvoffset is (0,0). We could wait
1605 // until we send the new frame metrics to apz and then have it reply with
1606 // the new corrected vvoffset but having an inconsistent vvoffset causes
1607 // problems so trigger the vvoffset to be re-set and re-clamped in
1608 // ReflowFinished.
1609 mReclampVVOffsetInReflowFinished = true;
1612 aDesiredSize.SetOverflowAreasToDesiredBounds();
1614 UpdateSticky();
1615 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput,
1616 aStatus);
1618 if (!InInitialReflow() && !mHadNonInitialReflow) {
1619 mHadNonInitialReflow = true;
1622 if (mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
1623 PostScrolledAreaEvent();
1626 UpdatePrevScrolledRect();
1628 aStatus.Reset(); // This type of frame can't be split.
1629 PostOverflowEvent();
1632 void nsHTMLScrollFrame::DidReflow(nsPresContext* aPresContext,
1633 const ReflowInput* aReflowInput) {
1634 nsContainerFrame::DidReflow(aPresContext, aReflowInput);
1635 if (NeedsResnap()) {
1636 PostPendingResnap();
1637 } else {
1638 PresShell()->PostPendingScrollAnchorAdjustment(Anchor());
1642 ////////////////////////////////////////////////////////////////////////////////
1644 #ifdef DEBUG_FRAME_DUMP
1645 nsresult nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const {
1646 return MakeFrameName(u"HTMLScroll"_ns, aResult);
1648 #endif
1650 #ifdef ACCESSIBILITY
1651 a11y::AccType nsHTMLScrollFrame::AccessibleType() {
1652 if (IsTableCaption()) {
1653 return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
1656 // Create an accessible regardless of focusable state because the state can be
1657 // changed during frame life cycle without any notifications to accessibility.
1658 if (mContent->IsRootOfNativeAnonymousSubtree() ||
1659 GetScrollStyles().IsHiddenInBothDirections()) {
1660 return a11y::eNoType;
1663 return a11y::eHyperTextType;
1665 #endif
1667 NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
1668 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
1669 NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
1670 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
1671 NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
1672 NS_QUERYFRAME_ENTRY(nsHTMLScrollFrame)
1673 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
1675 nsMargin nsHTMLScrollFrame::GetDesiredScrollbarSizes() const {
1676 nsPresContext* pc = PresContext();
1677 if (pc->UseOverlayScrollbars()) {
1678 return {};
1681 const auto& style = *nsLayoutUtils::StyleForScrollbar(this);
1682 const auto scrollbarWidth = style.StyleUIReset()->ScrollbarWidth();
1683 if (scrollbarWidth == StyleScrollbarWidth::None) {
1684 return {};
1687 ScrollStyles styles = GetScrollStyles();
1688 nsMargin result(0, 0, 0, 0);
1690 auto size = pc->DevPixelsToAppUnits(
1691 pc->Theme()->GetScrollbarSize(pc, scrollbarWidth, nsITheme::Overlay::No));
1692 if (styles.mVertical != StyleOverflow::Hidden) {
1693 if (IsScrollbarOnRight())
1694 result.left = size;
1695 else
1696 result.right = size;
1699 if (styles.mHorizontal != StyleOverflow::Hidden) {
1700 // We don't currently support any scripts that would require a scrollbar
1701 // at the top. (Are there any?)
1702 result.bottom = size;
1705 return result;
1708 nscoord nsIScrollableFrame::GetNondisappearingScrollbarWidth(nsPresContext* aPc,
1709 WritingMode aWM) {
1710 // We use this to size the combobox dropdown button. For that, we need to have
1711 // the proper big, non-overlay scrollbar size, regardless of whether we're
1712 // using e.g. scrollbar-width: thin, or overlay scrollbars.
1713 auto size = aPc->Theme()->GetScrollbarSize(aPc, StyleScrollbarWidth::Auto,
1714 nsITheme::Overlay::No);
1715 return aPc->DevPixelsToAppUnits(size);
1718 void nsHTMLScrollFrame::HandleScrollbarStyleSwitching() {
1719 // Check if we switched between scrollbar styles.
1720 if (mScrollbarActivity && !UsesOverlayScrollbars()) {
1721 mScrollbarActivity->Destroy();
1722 mScrollbarActivity = nullptr;
1723 } else if (!mScrollbarActivity && UsesOverlayScrollbars()) {
1724 mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(this));
1728 #if defined(MOZ_WIDGET_ANDROID)
1729 static bool IsFocused(nsIContent* aContent) {
1730 // Some content elements, like the GetContent() of a scroll frame
1731 // for a text input field, are inside anonymous subtrees, but the focus
1732 // manager always reports a non-anonymous element as the focused one, so
1733 // walk up the tree until we reach a non-anonymous element.
1734 while (aContent && aContent->IsInNativeAnonymousSubtree()) {
1735 aContent = aContent->GetParent();
1738 return aContent ? nsContentUtils::IsFocusedContent(aContent) : false;
1740 #endif
1742 void nsHTMLScrollFrame::SetScrollableByAPZ(bool aScrollable) {
1743 mScrollableByAPZ = aScrollable;
1746 void nsHTMLScrollFrame::SetZoomableByAPZ(bool aZoomable) {
1747 if (!nsLayoutUtils::UsesAsyncScrolling(this)) {
1748 // If APZ is disabled on this window, then we're never actually going to
1749 // do any zooming. So we don't need to do any of the setup for it. Note
1750 // that this function gets called from ZoomConstraintsClient even if APZ
1751 // is disabled to indicate the zoomability of content.
1752 aZoomable = false;
1754 if (mZoomableByAPZ != aZoomable) {
1755 // We might be changing the result of DecideScrollableLayer() so schedule a
1756 // paint to make sure we pick up the result of that change.
1757 mZoomableByAPZ = aZoomable;
1758 SchedulePaint();
1762 void nsHTMLScrollFrame::SetHasOutOfFlowContentInsideFilter() {
1763 mHasOutOfFlowContentInsideFilter = true;
1766 bool nsHTMLScrollFrame::WantAsyncScroll() const {
1767 ScrollStyles styles = GetScrollStyles();
1768 nscoord oneDevPixel =
1769 GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
1770 nsRect scrollRange = GetLayoutScrollRange();
1772 // If the page has a visual viewport size that's different from
1773 // the layout viewport size at the current zoom level, we need to be
1774 // able to scroll the visual viewport inside the layout viewport
1775 // even if the page is not zoomable.
1776 if (!GetVisualScrollRange().IsEqualInterior(scrollRange)) {
1777 return true;
1780 bool isVScrollable = (scrollRange.height >= oneDevPixel) &&
1781 (styles.mVertical != StyleOverflow::Hidden);
1782 bool isHScrollable = (scrollRange.width >= oneDevPixel) &&
1783 (styles.mHorizontal != StyleOverflow::Hidden);
1785 #if defined(MOZ_WIDGET_ANDROID)
1786 // Mobile platforms need focus to scroll text inputs.
1787 bool canScrollWithoutScrollbars =
1788 !IsForTextControlWithNoScrollbars() || IsFocused(GetContent());
1789 #else
1790 bool canScrollWithoutScrollbars = true;
1791 #endif
1793 // The check for scroll bars was added in bug 825692 to prevent layerization
1794 // of text inputs for performance reasons.
1795 bool isVAsyncScrollable =
1796 isVScrollable && (mVScrollbarBox || canScrollWithoutScrollbars);
1797 bool isHAsyncScrollable =
1798 isHScrollable && (mHScrollbarBox || canScrollWithoutScrollbars);
1799 return isVAsyncScrollable || isHAsyncScrollable;
1802 static nsRect GetOnePixelRangeAroundPoint(const nsPoint& aPoint,
1803 bool aIsHorizontal) {
1804 nsRect allowedRange(aPoint, nsSize());
1805 nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
1806 if (aIsHorizontal) {
1807 allowedRange.x = aPoint.x - halfPixel;
1808 allowedRange.width = halfPixel * 2 - 1;
1809 } else {
1810 allowedRange.y = aPoint.y - halfPixel;
1811 allowedRange.height = halfPixel * 2 - 1;
1813 return allowedRange;
1816 void nsHTMLScrollFrame::ScrollByPage(nsScrollbarFrame* aScrollbar,
1817 int32_t aDirection,
1818 ScrollSnapFlags aSnapFlags) {
1819 ScrollByUnit(aScrollbar, ScrollMode::Smooth, aDirection, ScrollUnit::PAGES,
1820 aSnapFlags);
1823 void nsHTMLScrollFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar,
1824 int32_t aDirection,
1825 ScrollSnapFlags aSnapFlags) {
1826 ScrollByUnit(aScrollbar, ScrollMode::Instant, aDirection, ScrollUnit::WHOLE,
1827 aSnapFlags);
1830 void nsHTMLScrollFrame::ScrollByLine(nsScrollbarFrame* aScrollbar,
1831 int32_t aDirection,
1832 ScrollSnapFlags aSnapFlags) {
1833 bool isHorizontal = aScrollbar->IsHorizontal();
1834 nsIntPoint delta;
1835 if (isHorizontal) {
1836 const double kScrollMultiplier =
1837 StaticPrefs::toolkit_scrollbox_horizontalScrollDistance();
1838 delta.x = static_cast<int32_t>(aDirection * kScrollMultiplier);
1839 if (GetLineScrollAmount().width * delta.x > GetPageScrollAmount().width) {
1840 // The scroll frame is so small that the delta would be more
1841 // than an entire page. Scroll by one page instead to maintain
1842 // context.
1843 ScrollByPage(aScrollbar, aDirection);
1844 return;
1846 } else {
1847 const double kScrollMultiplier =
1848 StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
1849 delta.y = static_cast<int32_t>(aDirection * kScrollMultiplier);
1850 if (GetLineScrollAmount().height * delta.y > GetPageScrollAmount().height) {
1851 // The scroll frame is so small that the delta would be more
1852 // than an entire page. Scroll by one page instead to maintain
1853 // context.
1854 ScrollByPage(aScrollbar, aDirection);
1855 return;
1859 nsIntPoint overflow;
1860 ScrollBy(delta, ScrollUnit::LINES, ScrollMode::Smooth, &overflow,
1861 ScrollOrigin::Other, nsIScrollableFrame::NOT_MOMENTUM, aSnapFlags);
1864 void nsHTMLScrollFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar) {
1865 aScrollbar->MoveToNewPosition(nsScrollbarFrame::ImplementsScrollByUnit::Yes);
1868 void nsHTMLScrollFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
1869 nscoord aOldPos, nscoord aNewPos) {
1870 MOZ_ASSERT(aScrollbar != nullptr);
1871 bool isHorizontal = aScrollbar->IsHorizontal();
1872 nsPoint current = GetScrollPosition();
1873 nsPoint dest = current;
1874 if (isHorizontal) {
1875 dest.x = IsPhysicalLTR() ? aNewPos : aNewPos - GetLayoutScrollRange().width;
1876 } else {
1877 dest.y = aNewPos;
1879 nsRect allowedRange = GetOnePixelRangeAroundPoint(dest, isHorizontal);
1881 // Don't try to scroll if we're already at an acceptable place.
1882 // Don't call Contains here since Contains returns false when the point is
1883 // on the bottom or right edge of the rectangle.
1884 if (allowedRange.ClampPoint(current) == current) {
1885 return;
1888 ScrollToWithOrigin(
1889 dest, &allowedRange,
1890 ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Other});
1893 void nsHTMLScrollFrame::ScrollbarReleased(nsScrollbarFrame* aScrollbar) {
1894 // Scrollbar scrolling does not result in fling gestures, clear any
1895 // accumulated velocity
1896 mVelocityQueue.Reset();
1898 // Perform scroll snapping, if needed. Scrollbar movement uses the same
1899 // smooth scrolling animation as keyboard scrolling.
1900 ScrollSnap(mDestination, ScrollMode::Smooth);
1903 void nsHTMLScrollFrame::ScrollByUnit(nsScrollbarFrame* aScrollbar,
1904 ScrollMode aMode, int32_t aDirection,
1905 ScrollUnit aUnit,
1906 ScrollSnapFlags aSnapFlags) {
1907 MOZ_ASSERT(aScrollbar != nullptr);
1908 bool isHorizontal = aScrollbar->IsHorizontal();
1909 nsIntPoint delta;
1910 if (isHorizontal) {
1911 delta.x = aDirection;
1912 } else {
1913 delta.y = aDirection;
1915 nsIntPoint overflow;
1916 ScrollBy(delta, aUnit, aMode, &overflow, ScrollOrigin::Other,
1917 nsIScrollableFrame::NOT_MOMENTUM, aSnapFlags);
1920 //-------------------- Helper ----------------------
1922 // AsyncSmoothMSDScroll has ref counting.
1923 class nsHTMLScrollFrame::AsyncSmoothMSDScroll final
1924 : public nsARefreshObserver {
1925 public:
1926 AsyncSmoothMSDScroll(const nsPoint& aInitialPosition,
1927 const nsPoint& aInitialDestination,
1928 const nsSize& aInitialVelocity, const nsRect& aRange,
1929 const mozilla::TimeStamp& aStartTime,
1930 nsPresContext* aPresContext,
1931 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds,
1932 ScrollTriggeredByScript aTriggeredByScript)
1933 : mXAxisModel(aInitialPosition.x, aInitialDestination.x,
1934 aInitialVelocity.width,
1935 StaticPrefs::layout_css_scroll_behavior_spring_constant(),
1936 StaticPrefs::layout_css_scroll_behavior_damping_ratio()),
1937 mYAxisModel(aInitialPosition.y, aInitialDestination.y,
1938 aInitialVelocity.height,
1939 StaticPrefs::layout_css_scroll_behavior_spring_constant(),
1940 StaticPrefs::layout_css_scroll_behavior_damping_ratio()),
1941 mRange(aRange),
1942 mStartPosition(aInitialPosition),
1943 mLastRefreshTime(aStartTime),
1944 mCallee(nullptr),
1945 mOneDevicePixelInAppUnits(aPresContext->DevPixelsToAppUnits(1)),
1946 mSnapTargetIds(std::move(aSnapTargetIds)),
1947 mTriggeredByScript(aTriggeredByScript) {
1948 Telemetry::SetHistogramRecordingEnabled(
1949 Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, true);
1952 NS_INLINE_DECL_REFCOUNTING(AsyncSmoothMSDScroll, override)
1954 nsSize GetVelocity() {
1955 // In nscoords per second
1956 return nsSize(mXAxisModel.GetVelocity(), mYAxisModel.GetVelocity());
1959 nsPoint GetPosition() {
1960 // In nscoords
1961 return nsPoint(NSToCoordRound(mXAxisModel.GetPosition()),
1962 NSToCoordRound(mYAxisModel.GetPosition()));
1965 void SetDestination(const nsPoint& aDestination,
1966 ScrollTriggeredByScript aTriggeredByScript) {
1967 mXAxisModel.SetDestination(static_cast<int32_t>(aDestination.x));
1968 mYAxisModel.SetDestination(static_cast<int32_t>(aDestination.y));
1969 mTriggeredByScript = aTriggeredByScript;
1972 void SetRange(const nsRect& aRange) { mRange = aRange; }
1974 nsRect GetRange() { return mRange; }
1976 nsPoint GetStartPosition() { return mStartPosition; }
1978 void Simulate(const TimeDuration& aDeltaTime) {
1979 mXAxisModel.Simulate(aDeltaTime);
1980 mYAxisModel.Simulate(aDeltaTime);
1982 nsPoint desired = GetPosition();
1983 nsPoint clamped = mRange.ClampPoint(desired);
1984 if (desired.x != clamped.x) {
1985 // The scroll has hit the "wall" at the left or right edge of the allowed
1986 // scroll range.
1987 // Absorb the impact to avoid bounceback effect.
1988 mXAxisModel.SetVelocity(0.0);
1989 mXAxisModel.SetPosition(clamped.x);
1992 if (desired.y != clamped.y) {
1993 // The scroll has hit the "wall" at the left or right edge of the allowed
1994 // scroll range.
1995 // Absorb the impact to avoid bounceback effect.
1996 mYAxisModel.SetVelocity(0.0);
1997 mYAxisModel.SetPosition(clamped.y);
2001 bool IsFinished() {
2002 return mXAxisModel.IsFinished(mOneDevicePixelInAppUnits) &&
2003 mYAxisModel.IsFinished(mOneDevicePixelInAppUnits);
2006 virtual void WillRefresh(mozilla::TimeStamp aTime) override {
2007 mozilla::TimeDuration deltaTime = aTime - mLastRefreshTime;
2008 mLastRefreshTime = aTime;
2010 // The callback may release "this".
2011 // We don't access members after returning, so no need for KungFuDeathGrip.
2012 nsHTMLScrollFrame::AsyncSmoothMSDScrollCallback(mCallee, deltaTime);
2016 * Set a refresh observer for smooth scroll iterations (and start observing).
2017 * Should be used at most once during the lifetime of this object.
2019 void SetRefreshObserver(nsHTMLScrollFrame* aCallee) {
2020 NS_ASSERTION(aCallee && !mCallee,
2021 "AsyncSmoothMSDScroll::SetRefreshObserver - Invalid usage.");
2023 RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style,
2024 "Smooth scroll (MSD) animation");
2025 mCallee = aCallee;
2029 * The mCallee holds a strong ref to us since the refresh driver doesn't.
2030 * Our dtor and mCallee's Destroy() method both call RemoveObserver() -
2031 * whichever comes first removes us from the refresh driver.
2033 void RemoveObserver() {
2034 if (mCallee) {
2035 RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style);
2036 mCallee = nullptr;
2040 UniquePtr<ScrollSnapTargetIds> TakeSnapTargetIds() {
2041 return std::move(mSnapTargetIds);
2044 bool WasTriggeredByScript() const {
2045 return mTriggeredByScript == ScrollTriggeredByScript::Yes;
2048 private:
2049 // Private destructor, to discourage deletion outside of Release():
2050 ~AsyncSmoothMSDScroll() {
2051 RemoveObserver();
2052 Telemetry::SetHistogramRecordingEnabled(
2053 Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, false);
2056 nsRefreshDriver* RefreshDriver(nsHTMLScrollFrame* aCallee) {
2057 return aCallee->PresContext()->RefreshDriver();
2060 mozilla::layers::AxisPhysicsMSDModel mXAxisModel, mYAxisModel;
2061 nsRect mRange;
2062 nsPoint mStartPosition;
2063 mozilla::TimeStamp mLastRefreshTime;
2064 nsHTMLScrollFrame* mCallee;
2065 nscoord mOneDevicePixelInAppUnits;
2066 UniquePtr<ScrollSnapTargetIds> mSnapTargetIds;
2067 ScrollTriggeredByScript mTriggeredByScript;
2070 // AsyncScroll has ref counting.
2071 class nsHTMLScrollFrame::AsyncScroll final : public nsARefreshObserver {
2072 public:
2073 typedef mozilla::TimeStamp TimeStamp;
2074 typedef mozilla::TimeDuration TimeDuration;
2076 explicit AsyncScroll(UniquePtr<ScrollSnapTargetIds> aSnapTargetIds,
2077 ScrollTriggeredByScript aTriggeredByScript)
2078 : mOrigin(ScrollOrigin::NotSpecified),
2079 mCallee(nullptr),
2080 mSnapTargetIds(std::move(aSnapTargetIds)),
2081 mTriggeredByScript(aTriggeredByScript) {
2082 Telemetry::SetHistogramRecordingEnabled(
2083 Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, true);
2086 private:
2087 // Private destructor, to discourage deletion outside of Release():
2088 ~AsyncScroll() {
2089 RemoveObserver();
2090 Telemetry::SetHistogramRecordingEnabled(
2091 Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, false);
2094 public:
2095 void InitSmoothScroll(TimeStamp aTime, nsPoint aInitialPosition,
2096 nsPoint aDestination, ScrollOrigin aOrigin,
2097 const nsRect& aRange, const nsSize& aCurrentVelocity);
2098 void Init(nsPoint aInitialPosition, const nsRect& aRange) {
2099 mAnimationPhysics = nullptr;
2100 mRange = aRange;
2101 mStartPosition = aInitialPosition;
2104 bool IsSmoothScroll() { return mAnimationPhysics != nullptr; }
2106 bool IsFinished(const TimeStamp& aTime) const {
2107 MOZ_RELEASE_ASSERT(mAnimationPhysics);
2108 return mAnimationPhysics->IsFinished(aTime);
2111 nsPoint PositionAt(const TimeStamp& aTime) const {
2112 MOZ_RELEASE_ASSERT(mAnimationPhysics);
2113 return mAnimationPhysics->PositionAt(aTime);
2116 nsSize VelocityAt(const TimeStamp& aTime) const {
2117 MOZ_RELEASE_ASSERT(mAnimationPhysics);
2118 return mAnimationPhysics->VelocityAt(aTime);
2121 nsPoint GetStartPosition() const { return mStartPosition; }
2123 // Most recent scroll origin.
2124 ScrollOrigin mOrigin;
2126 // Allowed destination positions around mDestination
2127 nsRect mRange;
2129 // Initial position where the async scroll was triggered.
2130 nsPoint mStartPosition;
2132 private:
2133 void InitPreferences(TimeStamp aTime, nsAtom* aOrigin);
2135 UniquePtr<ScrollAnimationPhysics> mAnimationPhysics;
2137 // The next section is observer/callback management
2138 // Bodies of WillRefresh and RefreshDriver contain nsHTMLScrollFrame specific
2139 // code.
2140 public:
2141 NS_INLINE_DECL_REFCOUNTING(AsyncScroll, override)
2144 * Set a refresh observer for smooth scroll iterations (and start observing).
2145 * Should be used at most once during the lifetime of this object.
2147 void SetRefreshObserver(nsHTMLScrollFrame* aCallee) {
2148 NS_ASSERTION(aCallee && !mCallee,
2149 "AsyncScroll::SetRefreshObserver - Invalid usage.");
2151 RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style,
2152 "Smooth scroll animation");
2153 mCallee = aCallee;
2154 auto* presShell = mCallee->PresShell();
2155 MOZ_ASSERT(presShell);
2156 presShell->SuppressDisplayport(true);
2159 virtual void WillRefresh(mozilla::TimeStamp aTime) override {
2160 // The callback may release "this".
2161 // We don't access members after returning, so no need for KungFuDeathGrip.
2162 nsHTMLScrollFrame::AsyncScrollCallback(mCallee, aTime);
2166 * The mCallee holds a strong ref to us since the refresh driver doesn't.
2167 * Our dtor and mCallee's Destroy() method both call RemoveObserver() -
2168 * whichever comes first removes us from the refresh driver.
2170 void RemoveObserver() {
2171 if (mCallee) {
2172 RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style);
2173 auto* presShell = mCallee->PresShell();
2174 MOZ_ASSERT(presShell);
2175 presShell->SuppressDisplayport(false);
2176 mCallee = nullptr;
2180 UniquePtr<ScrollSnapTargetIds> TakeSnapTargetIds() {
2181 return std::move(mSnapTargetIds);
2184 bool WasTriggeredByScript() const {
2185 return mTriggeredByScript == ScrollTriggeredByScript::Yes;
2188 private:
2189 nsHTMLScrollFrame* mCallee;
2190 UniquePtr<ScrollSnapTargetIds> mSnapTargetIds;
2191 ScrollTriggeredByScript mTriggeredByScript;
2193 nsRefreshDriver* RefreshDriver(nsHTMLScrollFrame* aCallee) {
2194 return aCallee->PresContext()->RefreshDriver();
2198 void nsHTMLScrollFrame::AsyncScroll::InitSmoothScroll(
2199 TimeStamp aTime, nsPoint aInitialPosition, nsPoint aDestination,
2200 ScrollOrigin aOrigin, const nsRect& aRange,
2201 const nsSize& aCurrentVelocity) {
2202 switch (aOrigin) {
2203 case ScrollOrigin::NotSpecified:
2204 case ScrollOrigin::Restore:
2205 case ScrollOrigin::Relative:
2206 // We don't have special prefs for "restore", just treat it as "other".
2207 // "restore" scrolls are (for now) always instant anyway so unless
2208 // something changes we should never have aOrigin ==
2209 // ScrollOrigin::Restore here.
2210 aOrigin = ScrollOrigin::Other;
2211 break;
2212 case ScrollOrigin::Apz:
2213 // Likewise we should never get APZ-triggered scrolls here, and if that
2214 // changes something is likely broken somewhere.
2215 MOZ_ASSERT(false);
2216 break;
2217 default:
2218 break;
2221 // Read preferences only on first iteration or for a different event origin.
2222 if (!mAnimationPhysics || aOrigin != mOrigin) {
2223 mOrigin = aOrigin;
2224 if (StaticPrefs::general_smoothScroll_msdPhysics_enabled()) {
2225 mAnimationPhysics =
2226 MakeUnique<ScrollAnimationMSDPhysics>(aInitialPosition);
2227 } else {
2228 ScrollAnimationBezierPhysicsSettings settings =
2229 layers::apz::ComputeBezierAnimationSettingsForOrigin(mOrigin);
2230 mAnimationPhysics =
2231 MakeUnique<ScrollAnimationBezierPhysics>(aInitialPosition, settings);
2235 mStartPosition = aInitialPosition;
2236 mRange = aRange;
2238 mAnimationPhysics->Update(aTime, aDestination, aCurrentVelocity);
2242 * Callback function from AsyncSmoothMSDScroll, used in
2243 * nsHTMLScrollFrame::ScrollTo
2245 void nsHTMLScrollFrame::AsyncSmoothMSDScrollCallback(
2246 nsHTMLScrollFrame* aInstance, mozilla::TimeDuration aDeltaTime) {
2247 NS_ASSERTION(aInstance != nullptr, "aInstance must not be null");
2248 NS_ASSERTION(aInstance->mAsyncSmoothMSDScroll,
2249 "Did not expect AsyncSmoothMSDScrollCallback without an active "
2250 "MSD scroll.");
2252 nsRect range = aInstance->mAsyncSmoothMSDScroll->GetRange();
2253 aInstance->mAsyncSmoothMSDScroll->Simulate(aDeltaTime);
2255 if (!aInstance->mAsyncSmoothMSDScroll->IsFinished()) {
2256 nsPoint destination = aInstance->mAsyncSmoothMSDScroll->GetPosition();
2257 // Allow this scroll operation to land on any pixel boundary within the
2258 // allowed scroll range for this frame.
2259 // If the MSD is under-dampened or the destination is changed rapidly,
2260 // it is expected (and desired) that the scrolling may overshoot.
2261 nsRect intermediateRange = nsRect(destination, nsSize()).UnionEdges(range);
2262 aInstance->ScrollToImpl(destination, intermediateRange);
2263 // 'aInstance' might be destroyed here
2264 return;
2267 aInstance->CompleteAsyncScroll(
2268 aInstance->mAsyncSmoothMSDScroll->GetStartPosition(), range,
2269 aInstance->mAsyncSmoothMSDScroll->TakeSnapTargetIds());
2273 * Callback function from AsyncScroll, used in nsHTMLScrollFrame::ScrollTo
2275 void nsHTMLScrollFrame::AsyncScrollCallback(nsHTMLScrollFrame* aInstance,
2276 mozilla::TimeStamp aTime) {
2277 MOZ_ASSERT(aInstance != nullptr, "aInstance must not be null");
2278 MOZ_ASSERT(
2279 aInstance->mAsyncScroll,
2280 "Did not expect AsyncScrollCallback without an active async scroll.");
2282 if (!aInstance || !aInstance->mAsyncScroll) {
2283 return; // XXX wallpaper bug 1107353 for now.
2286 nsRect range = aInstance->mAsyncScroll->mRange;
2287 if (aInstance->mAsyncScroll->IsSmoothScroll()) {
2288 if (!aInstance->mAsyncScroll->IsFinished(aTime)) {
2289 nsPoint destination = aInstance->mAsyncScroll->PositionAt(aTime);
2290 // Allow this scroll operation to land on any pixel boundary between the
2291 // current position and the final allowed range. (We don't want
2292 // intermediate steps to be more constrained than the final step!)
2293 nsRect intermediateRange =
2294 nsRect(aInstance->GetScrollPosition(), nsSize()).UnionEdges(range);
2295 aInstance->ScrollToImpl(destination, intermediateRange);
2296 // 'aInstance' might be destroyed here
2297 return;
2301 aInstance->CompleteAsyncScroll(aInstance->mAsyncScroll->GetStartPosition(),
2302 range,
2303 aInstance->mAsyncScroll->TakeSnapTargetIds());
2306 void nsHTMLScrollFrame::SetTransformingByAPZ(bool aTransforming) {
2307 if (mTransformingByAPZ && !aTransforming) {
2308 PostScrollEndEvent();
2310 mTransformingByAPZ = aTransforming;
2311 if (!mozilla::css::TextOverflow::HasClippedTextOverflow(this) ||
2312 mozilla::css::TextOverflow::HasBlockEllipsis(mScrolledFrame)) {
2313 // If the block has some overflow marker stuff we should kick off a paint
2314 // because we have special behaviour for it when APZ scrolling is active.
2315 SchedulePaint();
2319 void nsHTMLScrollFrame::CompleteAsyncScroll(
2320 const nsPoint& aStartPosition, const nsRect& aRange,
2321 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds, ScrollOrigin aOrigin) {
2322 SetLastSnapTargetIds(std::move(aSnapTargetIds));
2324 bool scrollPositionChanged = mDestination != aStartPosition;
2325 bool isNotHandledByApz =
2326 nsLayoutUtils::CanScrollOriginClobberApz(aOrigin) ||
2327 ScrollAnimationState().contains(AnimationState::MainThread);
2329 // Apply desired destination range since this is the last step of scrolling.
2330 RemoveObservers();
2331 AutoWeakFrame weakFrame(this);
2332 ScrollToImpl(mDestination, aRange, aOrigin);
2333 if (!weakFrame.IsAlive()) {
2334 return;
2336 // We are done scrolling, set our destination to wherever we actually ended
2337 // up scrolling to.
2338 mDestination = GetScrollPosition();
2339 // Post a `scrollend` event for scrolling not handled by APZ, including:
2341 // - programmatic instant scrolls
2342 // - the end of a smooth scroll animation running on the main thread
2344 // For scrolling handled by APZ, the `scrollend` event is posted in
2345 // SetTransformingByAPZ() when the APZC is transitioning from a transforming
2346 // to a non-transforming state (e.g. a transition from PANNING to NOTHING).
2347 // The scrollend event should not be fired for a scroll that does not
2348 // result in a scroll position change.
2349 if (isNotHandledByApz && scrollPositionChanged) {
2350 PostScrollEndEvent();
2354 bool nsHTMLScrollFrame::HasBgAttachmentLocal() const {
2355 const nsStyleBackground* bg = StyleBackground();
2356 return bg->HasLocalBackground();
2359 void nsHTMLScrollFrame::ScrollToInternal(
2360 nsPoint aScrollPosition, ScrollMode aMode, ScrollOrigin aOrigin,
2361 const nsRect* aRange, ScrollSnapFlags aSnapFlags,
2362 ScrollTriggeredByScript aTriggeredByScript) {
2363 if (aOrigin == ScrollOrigin::NotSpecified) {
2364 aOrigin = ScrollOrigin::Other;
2366 ScrollToWithOrigin(
2367 aScrollPosition, aRange,
2368 ScrollOperationParams{aMode, aOrigin, aSnapFlags, aTriggeredByScript});
2371 void nsHTMLScrollFrame::ScrollToCSSPixels(const CSSIntPoint& aScrollPosition,
2372 ScrollMode aMode) {
2373 CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels();
2374 // Transmogrify this scroll to a relative one if there's any on-going
2375 // animation in APZ triggered by __user__.
2376 // Bug 1740164: We will apply it for cases there's no animation in APZ.
2378 auto scrollAnimationState = ScrollAnimationState();
2379 bool isScrollAnimating =
2380 scrollAnimationState.contains(AnimationState::MainThread) ||
2381 scrollAnimationState.contains(AnimationState::APZPending) ||
2382 scrollAnimationState.contains(AnimationState::APZRequested);
2383 if (mCurrentAPZScrollAnimationType ==
2384 APZScrollAnimationType::TriggeredByUserInput &&
2385 !isScrollAnimating) {
2386 CSSIntPoint delta = aScrollPosition - currentCSSPixels;
2387 // This transmogrification need to be an intended end position scroll
2388 // operation.
2389 ScrollByCSSPixelsInternal(delta, aMode,
2390 ScrollSnapFlags::IntendedEndPosition);
2391 return;
2394 nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
2395 nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
2396 nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2 * halfPixel - 1,
2397 2 * halfPixel - 1);
2398 // XXX I don't think the following blocks are needed anymore, now that
2399 // ScrollToImpl simply tries to scroll an integer number of layer
2400 // pixels from the current position
2401 nsPoint current = GetScrollPosition();
2402 if (currentCSSPixels.x == aScrollPosition.x) {
2403 pt.x = current.x;
2404 range.x = pt.x;
2405 range.width = 0;
2407 if (currentCSSPixels.y == aScrollPosition.y) {
2408 pt.y = current.y;
2409 range.y = pt.y;
2410 range.height = 0;
2412 ScrollToWithOrigin(
2413 pt, &range,
2414 ScrollOperationParams{
2415 aMode, ScrollOrigin::Other,
2416 // This ScrollToCSSPixels is used for Element.scrollTo,
2417 // Element.scrollTop, Element.scrollLeft and for Window.scrollTo.
2418 ScrollSnapFlags::IntendedEndPosition, ScrollTriggeredByScript::Yes});
2419 // 'this' might be destroyed here
2422 void nsHTMLScrollFrame::ScrollToCSSPixelsForApz(
2423 const CSSPoint& aScrollPosition, ScrollSnapTargetIds&& aLastSnapTargetIds) {
2424 nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
2425 nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000);
2426 nsRect range(pt.x - halfRange, pt.y - halfRange, 2 * halfRange - 1,
2427 2 * halfRange - 1);
2428 ScrollToWithOrigin(
2429 pt, &range,
2430 ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Apz,
2431 std::move(aLastSnapTargetIds)});
2432 // 'this' might be destroyed here
2435 CSSIntPoint nsHTMLScrollFrame::GetScrollPositionCSSPixels() {
2436 return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition());
2440 * this method wraps calls to ScrollToImpl(), either in one shot or
2441 * incrementally, based on the setting of the smoothness scroll pref
2443 void nsHTMLScrollFrame::ScrollToWithOrigin(nsPoint aScrollPosition,
2444 const nsRect* aRange,
2445 ScrollOperationParams&& aParams) {
2446 // None is never a valid scroll origin to be passed in.
2447 MOZ_ASSERT(aParams.mOrigin != ScrollOrigin::None);
2449 if (aParams.mOrigin != ScrollOrigin::Restore) {
2450 // If we're doing a non-restore scroll, we don't want to later
2451 // override it by restoring our saved scroll position.
2452 SCROLLRESTORE_LOG("%p: Clearing mRestorePos (cur=%s, dst=%s)\n", this,
2453 ToString(GetScrollPosition()).c_str(),
2454 ToString(aScrollPosition).c_str());
2455 mRestorePos.x = mRestorePos.y = -1;
2458 Maybe<SnapTarget> snapTarget;
2459 if (!aParams.IsScrollSnapDisabled()) {
2460 snapTarget = GetSnapPointForDestination(ScrollUnit::DEVICE_PIXELS,
2461 aParams.mSnapFlags, mDestination,
2462 aScrollPosition);
2463 if (snapTarget) {
2464 aScrollPosition = snapTarget->mPosition;
2468 nsRect scrollRange = GetLayoutScrollRange();
2469 mDestination = scrollRange.ClampPoint(aScrollPosition);
2470 if (mDestination != aScrollPosition &&
2471 aParams.mOrigin == ScrollOrigin::Restore &&
2472 GetPageLoadingState() != LoadingState::Loading) {
2473 // If we're doing a restore but the scroll position is clamped, promote
2474 // the origin from one that APZ can clobber to one that it can't clobber.
2475 aParams.mOrigin = ScrollOrigin::Other;
2478 nsRect range = aRange && snapTarget.isNothing()
2479 ? *aRange
2480 : nsRect(aScrollPosition, nsSize(0, 0));
2482 UniquePtr<ScrollSnapTargetIds> snapTargetIds;
2483 if (snapTarget) {
2484 snapTargetIds =
2485 MakeUnique<ScrollSnapTargetIds>(std::move(snapTarget->mTargetIds));
2486 } else {
2487 snapTargetIds =
2488 MakeUnique<ScrollSnapTargetIds>(std::move(aParams.mTargetIds));
2490 if (aParams.IsInstant()) {
2491 // Asynchronous scrolling is not allowed, so we'll kill any existing
2492 // async-scrolling process and do an instant scroll.
2493 CompleteAsyncScroll(GetScrollPosition(), range, std::move(snapTargetIds),
2494 aParams.mOrigin);
2495 mApzSmoothScrollDestination = Nothing();
2496 return;
2499 if (!aParams.IsSmoothMsd()) {
2500 // If we get a non-smooth-scroll, reset the cached APZ scroll destination,
2501 // so that we know to process the next smooth-scroll destined for APZ.
2502 mApzSmoothScrollDestination = Nothing();
2505 nsPresContext* presContext = PresContext();
2506 TimeStamp now =
2507 presContext->RefreshDriver()->IsTestControllingRefreshesEnabled()
2508 ? presContext->RefreshDriver()->MostRecentRefresh()
2509 : TimeStamp::Now();
2511 nsSize currentVelocity(0, 0);
2513 const bool canHandoffToApz =
2514 nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll() &&
2515 CanApzScrollInTheseDirections(
2516 DirectionsInDelta(mDestination - GetScrollPosition()));
2518 if (aParams.IsSmoothMsd()) {
2519 mIgnoreMomentumScroll = true;
2520 if (!mAsyncSmoothMSDScroll) {
2521 nsPoint sv = mVelocityQueue.GetVelocity();
2522 currentVelocity.width = sv.x;
2523 currentVelocity.height = sv.y;
2524 if (mAsyncScroll) {
2525 if (mAsyncScroll->IsSmoothScroll()) {
2526 currentVelocity = mAsyncScroll->VelocityAt(now);
2528 mAsyncScroll = nullptr;
2531 if (canHandoffToApz) {
2532 ApzSmoothScrollTo(mDestination, ScrollMode::SmoothMsd, aParams.mOrigin,
2533 aParams.mTriggeredByScript, std::move(snapTargetIds));
2534 return;
2537 mAsyncSmoothMSDScroll = new AsyncSmoothMSDScroll(
2538 GetScrollPosition(), mDestination, currentVelocity,
2539 GetLayoutScrollRange(), now, presContext, std::move(snapTargetIds),
2540 aParams.mTriggeredByScript);
2542 mAsyncSmoothMSDScroll->SetRefreshObserver(this);
2543 } else {
2544 // A previous smooth MSD scroll is still in progress, so we just need to
2545 // update its range and destination.
2546 mAsyncSmoothMSDScroll->SetRange(GetLayoutScrollRange());
2547 mAsyncSmoothMSDScroll->SetDestination(mDestination,
2548 aParams.mTriggeredByScript);
2551 return;
2554 if (mAsyncSmoothMSDScroll) {
2555 currentVelocity = mAsyncSmoothMSDScroll->GetVelocity();
2556 mAsyncSmoothMSDScroll = nullptr;
2559 const bool isSmoothScroll =
2560 aParams.IsSmooth() && nsLayoutUtils::IsSmoothScrollingEnabled();
2561 if (!mAsyncScroll) {
2562 if (isSmoothScroll && canHandoffToApz) {
2563 ApzSmoothScrollTo(mDestination, ScrollMode::Smooth, aParams.mOrigin,
2564 aParams.mTriggeredByScript, std::move(snapTargetIds));
2565 return;
2568 mAsyncScroll =
2569 new AsyncScroll(std::move(snapTargetIds), aParams.mTriggeredByScript);
2570 mAsyncScroll->SetRefreshObserver(this);
2573 if (isSmoothScroll) {
2574 mAsyncScroll->InitSmoothScroll(now, GetScrollPosition(), mDestination,
2575 aParams.mOrigin, range, currentVelocity);
2576 } else {
2577 mAsyncScroll->Init(GetScrollPosition(), range);
2581 // We can't use nsContainerFrame::PositionChildViews here because
2582 // we don't want to invalidate views that have moved.
2583 static void AdjustViews(nsIFrame* aFrame) {
2584 nsView* view = aFrame->GetView();
2585 if (view) {
2586 nsPoint pt;
2587 aFrame->GetParent()->GetClosestView(&pt);
2588 pt += aFrame->GetPosition();
2589 view->SetPosition(pt.x, pt.y);
2591 return;
2594 if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
2595 return;
2598 // Call AdjustViews recursively for all child frames except the popup list as
2599 // the views for popups are not scrolled.
2600 for (const auto& [list, listID] : aFrame->ChildLists()) {
2601 if (listID == FrameChildListID::Popup) {
2602 continue;
2604 for (nsIFrame* child : list) {
2605 AdjustViews(child);
2610 void nsHTMLScrollFrame::MarkScrollbarsDirtyForReflow() const {
2611 auto* presShell = PresShell();
2612 if (mVScrollbarBox) {
2613 presShell->FrameNeedsReflow(mVScrollbarBox,
2614 IntrinsicDirty::FrameAncestorsAndDescendants,
2615 NS_FRAME_IS_DIRTY);
2617 if (mHScrollbarBox) {
2618 presShell->FrameNeedsReflow(mHScrollbarBox,
2619 IntrinsicDirty::FrameAncestorsAndDescendants,
2620 NS_FRAME_IS_DIRTY);
2624 void nsHTMLScrollFrame::InvalidateScrollbars() const {
2625 if (mHScrollbarBox) {
2626 mHScrollbarBox->InvalidateFrameSubtree();
2628 if (mVScrollbarBox) {
2629 mVScrollbarBox->InvalidateFrameSubtree();
2633 bool nsHTMLScrollFrame::IsAlwaysActive() const {
2634 if (nsDisplayItem::ForceActiveLayers()) {
2635 return true;
2638 // Unless this is the root scrollframe for a non-chrome document
2639 // which is the direct child of a chrome document, we default to not
2640 // being "active".
2641 if (!(mIsRoot && PresContext()->IsRootContentDocumentCrossProcess())) {
2642 return false;
2645 // If we have scrolled before, then we should stay active.
2646 if (mHasBeenScrolled) {
2647 return true;
2650 // If we're overflow:hidden, then start as inactive until
2651 // we get scrolled manually.
2652 ScrollStyles styles = GetScrollStyles();
2653 return (styles.mHorizontal != StyleOverflow::Hidden &&
2654 styles.mVertical != StyleOverflow::Hidden);
2657 static void RemoveDisplayPortCallback(nsITimer* aTimer, void* aClosure) {
2658 nsHTMLScrollFrame* sf = static_cast<nsHTMLScrollFrame*>(aClosure);
2660 // This function only ever gets called from the expiry timer, so it must
2661 // be non-null here. Set it to null here so that we don't keep resetting
2662 // it unnecessarily in MarkRecentlyScrolled().
2663 MOZ_ASSERT(sf->mDisplayPortExpiryTimer);
2664 sf->mDisplayPortExpiryTimer = nullptr;
2666 if (!sf->AllowDisplayPortExpiration() || sf->mIsParentToActiveScrollFrames) {
2667 // If this is a scroll parent for some other scrollable frame, don't
2668 // expire the displayport because it would break scroll handoff. Once the
2669 // descendant scrollframes have their displayports expired, they will
2670 // trigger the displayport expiration on this scrollframe as well, and
2671 // mIsParentToActiveScrollFrames will presumably be false when that kicks
2672 // in.
2673 return;
2676 // Remove the displayport from this scrollframe if it's been a while
2677 // since it's scrolled, except if it needs to be always active. Note that
2678 // there is one scrollframe that doesn't fall under this general rule, and
2679 // that is the one that nsLayoutUtils::MaybeCreateDisplayPort decides to put
2680 // a displayport on (i.e. the first scrollframe that WantAsyncScroll()s).
2681 // If that scrollframe is this one, we remove the displayport anyway, and
2682 // as part of the next paint MaybeCreateDisplayPort will put another
2683 // displayport back on it. Although the displayport will "flicker" off and
2684 // back on, the layer itself should never disappear, because this all
2685 // happens between actual painting. If the displayport is reset to a
2686 // different position that's ok; this scrollframe hasn't been scrolled
2687 // recently and so the reset should be correct.
2689 nsIContent* content = sf->GetContent();
2691 if (nsHTMLScrollFrame::ShouldActivateAllScrollFrames()) {
2692 // If we are activating all scroll frames then we only want to remove the
2693 // regular display port and downgrade to a minimal display port.
2694 MOZ_ASSERT(!content->GetProperty(nsGkAtoms::MinimalDisplayPort));
2695 content->SetProperty(nsGkAtoms::MinimalDisplayPort,
2696 reinterpret_cast<void*>(true));
2697 } else {
2698 content->RemoveProperty(nsGkAtoms::MinimalDisplayPort);
2699 DisplayPortUtils::RemoveDisplayPort(content);
2700 // Be conservative and unflag this this scrollframe as being scrollable by
2701 // APZ. If it is still scrollable this will get flipped back soon enough.
2702 sf->mScrollableByAPZ = false;
2705 DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(sf);
2706 sf->SchedulePaint();
2709 void nsHTMLScrollFrame::MarkEverScrolled() {
2710 // Mark this frame as having been scrolled. If this is the root
2711 // scroll frame of a content document, then IsAlwaysActive()
2712 // will return true from now on and MarkNotRecentlyScrolled() won't
2713 // have any effect.
2714 mHasBeenScrolled = true;
2717 void nsHTMLScrollFrame::MarkNotRecentlyScrolled() {
2718 if (!mHasBeenScrolledRecently) return;
2720 mHasBeenScrolledRecently = false;
2721 SchedulePaint();
2724 void nsHTMLScrollFrame::MarkRecentlyScrolled() {
2725 mHasBeenScrolledRecently = true;
2726 if (IsAlwaysActive()) {
2727 return;
2730 if (mActivityExpirationState.IsTracked()) {
2731 gScrollFrameActivityTracker->MarkUsed(this);
2732 } else {
2733 if (!gScrollFrameActivityTracker) {
2734 gScrollFrameActivityTracker =
2735 new ScrollFrameActivityTracker(GetMainThreadSerialEventTarget());
2737 gScrollFrameActivityTracker->AddObject(this);
2740 // If we just scrolled and there's a displayport expiry timer in place,
2741 // reset the timer.
2742 ResetDisplayPortExpiryTimer();
2745 void nsHTMLScrollFrame::ResetDisplayPortExpiryTimer() {
2746 if (mDisplayPortExpiryTimer) {
2747 mDisplayPortExpiryTimer->InitWithNamedFuncCallback(
2748 RemoveDisplayPortCallback, this,
2749 StaticPrefs::apz_displayport_expiry_ms(), nsITimer::TYPE_ONE_SHOT,
2750 "nsHTMLScrollFrame::ResetDisplayPortExpiryTimer");
2754 bool nsHTMLScrollFrame::AllowDisplayPortExpiration() {
2755 if (IsAlwaysActive()) {
2756 return false;
2759 if (mIsRoot && PresContext()->IsRoot()) {
2760 return false;
2763 // If this was the first scrollable frame found, this displayport should
2764 // not expire.
2765 if (IsFirstScrollableFrameSequenceNumber().isSome()) {
2766 return false;
2769 if (ShouldActivateAllScrollFrames() &&
2770 GetContent()->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
2771 return false;
2773 return true;
2776 void nsHTMLScrollFrame::TriggerDisplayPortExpiration() {
2777 if (!AllowDisplayPortExpiration()) {
2778 return;
2781 if (!StaticPrefs::apz_displayport_expiry_ms()) {
2782 // a zero time disables the expiry
2783 return;
2786 if (!mDisplayPortExpiryTimer) {
2787 mDisplayPortExpiryTimer = NS_NewTimer();
2789 ResetDisplayPortExpiryTimer();
2792 void nsHTMLScrollFrame::ScrollVisual() {
2793 MarkEverScrolled();
2795 AdjustViews(mScrolledFrame);
2796 // We need to call this after fixing up the view positions
2797 // to be consistent with the frame hierarchy.
2798 MarkRecentlyScrolled();
2802 * Clamp desired scroll position aDesired and range [aDestLower, aDestUpper]
2803 * to [aBoundLower, aBoundUpper] and then select the appunit value from among
2804 * aBoundLower, aBoundUpper and those such that (aDesired - aCurrent) *
2805 * aRes/aAppUnitsPerPixel is an integer (or as close as we can get
2806 * modulo rounding to appunits) that is in [aDestLower, aDestUpper] and
2807 * closest to aDesired. If no such value exists, return the nearest in
2808 * [aDestLower, aDestUpper].
2810 static nscoord ClampAndAlignWithPixels(nscoord aDesired, nscoord aBoundLower,
2811 nscoord aBoundUpper, nscoord aDestLower,
2812 nscoord aDestUpper,
2813 nscoord aAppUnitsPerPixel, double aRes,
2814 nscoord aCurrent) {
2815 // Intersect scroll range with allowed range, by clamping the ends
2816 // of aRange to be within bounds
2817 nscoord destLower = clamped(aDestLower, aBoundLower, aBoundUpper);
2818 nscoord destUpper = clamped(aDestUpper, aBoundLower, aBoundUpper);
2820 nscoord desired = clamped(aDesired, destLower, destUpper);
2822 double currentLayerVal = (aRes * aCurrent) / aAppUnitsPerPixel;
2823 double desiredLayerVal = (aRes * desired) / aAppUnitsPerPixel;
2824 double delta = desiredLayerVal - currentLayerVal;
2825 double nearestLayerVal = NS_round(delta) + currentLayerVal;
2827 // Convert back from PaintedLayer space to appunits relative to the top-left
2828 // of the scrolled frame.
2829 nscoord aligned =
2830 aRes == 0.0
2831 ? 0.0
2832 : NSToCoordRoundWithClamp(nearestLayerVal * aAppUnitsPerPixel / aRes);
2834 // Use a bound if it is within the allowed range and closer to desired than
2835 // the nearest pixel-aligned value.
2836 if (aBoundUpper == destUpper &&
2837 static_cast<decltype(Abs(desired))>(aBoundUpper - desired) <
2838 Abs(desired - aligned)) {
2839 return aBoundUpper;
2842 if (aBoundLower == destLower &&
2843 static_cast<decltype(Abs(desired))>(desired - aBoundLower) <
2844 Abs(aligned - desired)) {
2845 return aBoundLower;
2848 // Accept the nearest pixel-aligned value if it is within the allowed range.
2849 if (aligned >= destLower && aligned <= destUpper) {
2850 return aligned;
2853 // Check if opposite pixel boundary fits into allowed range.
2854 double oppositeLayerVal =
2855 nearestLayerVal + ((nearestLayerVal < desiredLayerVal) ? 1.0 : -1.0);
2856 nscoord opposite = aRes == 0.0
2857 ? 0.0
2858 : NSToCoordRoundWithClamp(oppositeLayerVal *
2859 aAppUnitsPerPixel / aRes);
2860 if (opposite >= destLower && opposite <= destUpper) {
2861 return opposite;
2864 // No alignment available.
2865 return desired;
2869 * Clamp desired scroll position aPt to aBounds and then snap
2870 * it to the same layer pixel edges as aCurrent, keeping it within aRange
2871 * during snapping. aCurrent is the current scroll position.
2873 static nsPoint ClampAndAlignWithLayerPixels(const nsPoint& aPt,
2874 const nsRect& aBounds,
2875 const nsRect& aRange,
2876 const nsPoint& aCurrent,
2877 nscoord aAppUnitsPerPixel,
2878 const MatrixScales& aScale) {
2879 return nsPoint(
2880 ClampAndAlignWithPixels(aPt.x, aBounds.x, aBounds.XMost(), aRange.x,
2881 aRange.XMost(), aAppUnitsPerPixel, aScale.xScale,
2882 aCurrent.x),
2883 ClampAndAlignWithPixels(aPt.y, aBounds.y, aBounds.YMost(), aRange.y,
2884 aRange.YMost(), aAppUnitsPerPixel, aScale.yScale,
2885 aCurrent.y));
2888 /* static */
2889 void nsHTMLScrollFrame::ScrollActivityCallback(nsITimer* aTimer,
2890 void* anInstance) {
2891 nsHTMLScrollFrame* self = static_cast<nsHTMLScrollFrame*>(anInstance);
2893 // Fire the synth mouse move.
2894 self->mScrollActivityTimer->Cancel();
2895 self->mScrollActivityTimer = nullptr;
2896 self->PresShell()->SynthesizeMouseMove(true);
2899 void nsHTMLScrollFrame::ScheduleSyntheticMouseMove() {
2900 if (!mScrollActivityTimer) {
2901 mScrollActivityTimer = NS_NewTimer(
2902 PresContext()->Document()->EventTargetFor(TaskCategory::Other));
2903 if (!mScrollActivityTimer) {
2904 return;
2908 mScrollActivityTimer->InitWithNamedFuncCallback(
2909 ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT,
2910 "nsHTMLScrollFrame::ScheduleSyntheticMouseMove");
2913 void nsHTMLScrollFrame::NotifyApproximateFrameVisibilityUpdate(
2914 bool aIgnoreDisplayPort) {
2915 mLastUpdateFramesPos = GetScrollPosition();
2916 if (aIgnoreDisplayPort) {
2917 mHadDisplayPortAtLastFrameUpdate = false;
2918 mDisplayPortAtLastFrameUpdate = nsRect();
2919 } else {
2920 mHadDisplayPortAtLastFrameUpdate = DisplayPortUtils::GetDisplayPort(
2921 GetContent(), &mDisplayPortAtLastFrameUpdate);
2925 bool nsHTMLScrollFrame::GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
2926 nsRect* aDisplayPort) {
2927 if (mHadDisplayPortAtLastFrameUpdate) {
2928 *aDisplayPort = mDisplayPortAtLastFrameUpdate;
2930 return mHadDisplayPortAtLastFrameUpdate;
2933 MatrixScales GetPaintedLayerScaleForFrame(nsIFrame* aFrame) {
2934 MOZ_ASSERT(aFrame, "need a frame");
2936 nsPresContext* presCtx = aFrame->PresContext()->GetRootPresContext();
2938 if (!presCtx) {
2939 presCtx = aFrame->PresContext();
2940 MOZ_ASSERT(presCtx);
2943 ParentLayerToScreenScale2D transformToAncestorScale =
2944 ParentLayerToParentLayerScale(
2945 presCtx->PresShell()->GetCumulativeResolution()) *
2946 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
2947 aFrame);
2949 return transformToAncestorScale.ToUnknownScale();
2952 void nsHTMLScrollFrame::ScrollToImpl(
2953 nsPoint aPt, const nsRect& aRange, ScrollOrigin aOrigin,
2954 ScrollTriggeredByScript aTriggeredByScript) {
2955 // None is never a valid scroll origin to be passed in.
2956 MOZ_ASSERT(aOrigin != ScrollOrigin::None);
2958 // Figure out the effective origin for this scroll request.
2959 if (aOrigin == ScrollOrigin::NotSpecified) {
2960 // If no origin was specified, we still want to set it to something that's
2961 // non-unknown, so that we can use eUnknown to distinguish if the frame was
2962 // scrolled at all. Default it to some generic placeholder.
2963 aOrigin = ScrollOrigin::Other;
2966 // If this scroll is |relative|, but we've already had a user scroll that
2967 // was not relative, promote this origin to |other|. This ensures that we
2968 // may only transmit a relative update to APZ if all scrolls since the last
2969 // transaction or repaint request have been relative.
2970 if (aOrigin == ScrollOrigin::Relative &&
2971 (mLastScrollOrigin != ScrollOrigin::None &&
2972 mLastScrollOrigin != ScrollOrigin::NotSpecified &&
2973 mLastScrollOrigin != ScrollOrigin::Relative &&
2974 mLastScrollOrigin != ScrollOrigin::Apz)) {
2975 aOrigin = ScrollOrigin::Other;
2978 // If the origin is a downgrade, and downgrades are allowed, process the
2979 // downgrade even if we're going to early-exit because we're already at
2980 // the correct scroll position. This ensures that if there wasn't a main-
2981 // thread scroll update pending before a frame reconstruction (as indicated
2982 // by mAllowScrollOriginDowngrade=true), then after the frame reconstruction
2983 // the origin is downgraded to "restore" even if the layout scroll offset to
2984 // be restored is (0,0) (which will take the early-exit below). This is
2985 // important so that restoration of a *visual* scroll offset (which might be
2986 // to something other than (0,0)) isn't clobbered.
2987 bool isScrollOriginDowngrade =
2988 nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) &&
2989 !nsLayoutUtils::CanScrollOriginClobberApz(aOrigin);
2990 bool allowScrollOriginChange =
2991 mAllowScrollOriginDowngrade && isScrollOriginDowngrade;
2993 if (allowScrollOriginChange) {
2994 mLastScrollOrigin = aOrigin;
2995 mAllowScrollOriginDowngrade = false;
2998 nsPresContext* presContext = PresContext();
2999 nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
3000 // 'scale' is our estimate of the scale factor that will be applied
3001 // when rendering the scrolled content to its own PaintedLayer.
3002 MatrixScales scale = GetPaintedLayerScaleForFrame(mScrolledFrame);
3003 nsPoint curPos = GetScrollPosition();
3005 // Try to align aPt with curPos so they have an integer number of layer
3006 // pixels between them. This gives us the best chance of scrolling without
3007 // having to invalidate due to changes in subpixel rendering.
3008 // Note that when we actually draw into a PaintedLayer, the coordinates
3009 // that get mapped onto the layer buffer pixels are from the display list,
3010 // which are relative to the display root frame's top-left increasing down,
3011 // whereas here our coordinates are scroll positions which increase upward
3012 // and are relative to the scrollport top-left. This difference doesn't
3013 // actually matter since all we are about is that there be an integer number
3014 // of layer pixels between pt and curPos.
3015 nsPoint pt = ClampAndAlignWithLayerPixels(aPt, GetLayoutScrollRange(), aRange,
3016 curPos, appUnitsPerDevPixel, scale);
3017 if (pt == curPos) {
3018 // Even if we are bailing out due to no-op main-thread scroll position
3019 // change, we might need to cancel an APZ smooth scroll that we already
3020 // kicked off. It might be reasonable to eventually remove the
3021 // mApzSmoothScrollDestination clause from this if statement, as that
3022 // may simplify this a bit and should be fine from the APZ side.
3023 if (mApzSmoothScrollDestination && aOrigin != ScrollOrigin::Clamp) {
3024 if (aOrigin == ScrollOrigin::Relative) {
3025 AppendScrollUpdate(
3026 ScrollPositionUpdate::NewRelativeScroll(mApzScrollPos, pt));
3027 mApzScrollPos = pt;
3028 } else if (aOrigin != ScrollOrigin::Apz) {
3029 ScrollOrigin origin =
3030 (mAllowScrollOriginDowngrade || !isScrollOriginDowngrade)
3031 ? aOrigin
3032 : mLastScrollOrigin;
3033 AppendScrollUpdate(ScrollPositionUpdate::NewScroll(origin, pt));
3036 return;
3039 // If we are scrolling the RCD-RSF, and a visual scroll update is pending,
3040 // cancel it; otherwise, it will clobber this scroll.
3041 if (IsRootScrollFrameOfDocument() &&
3042 presContext->IsRootContentDocumentCrossProcess()) {
3043 auto* ps = presContext->GetPresShell();
3044 if (const auto& visualScrollUpdate = ps->GetPendingVisualScrollUpdate()) {
3045 if (visualScrollUpdate->mVisualScrollOffset != aPt) {
3046 // Only clobber if the scroll was originated by the main thread.
3047 // Respect the priority of origins (an "eRestore" layout scroll should
3048 // not clobber an "eMainThread" visual scroll.)
3049 bool shouldClobber =
3050 aOrigin == ScrollOrigin::Other ||
3051 (aOrigin == ScrollOrigin::Restore &&
3052 visualScrollUpdate->mUpdateType == FrameMetrics::eRestore);
3053 if (shouldClobber) {
3054 ps->AcknowledgePendingVisualScrollUpdate();
3055 ps->ClearPendingVisualScrollUpdate();
3061 bool needFrameVisibilityUpdate = mLastUpdateFramesPos == nsPoint(-1, -1);
3063 nsPoint dist(std::abs(pt.x - mLastUpdateFramesPos.x),
3064 std::abs(pt.y - mLastUpdateFramesPos.y));
3065 nsSize visualViewportSize = GetVisualViewportSize();
3066 nscoord horzAllowance = std::max(
3067 visualViewportSize.width /
3068 std::max(
3069 StaticPrefs::
3070 layout_framevisibility_amountscrollbeforeupdatehorizontal(),
3072 AppUnitsPerCSSPixel());
3073 nscoord vertAllowance = std::max(
3074 visualViewportSize.height /
3075 std::max(
3076 StaticPrefs::
3077 layout_framevisibility_amountscrollbeforeupdatevertical(),
3079 AppUnitsPerCSSPixel());
3080 if (dist.x >= horzAllowance || dist.y >= vertAllowance) {
3081 needFrameVisibilityUpdate = true;
3084 // notify the listeners.
3085 for (uint32_t i = 0; i < mListeners.Length(); i++) {
3086 mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
3089 nsRect oldDisplayPort;
3090 nsIContent* content = GetContent();
3091 DisplayPortUtils::GetDisplayPort(content, &oldDisplayPort);
3092 oldDisplayPort.MoveBy(-mScrolledFrame->GetPosition());
3094 // Update frame position for scrolling
3095 mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
3097 // If |mLastScrollOrigin| is already set to something that can clobber APZ's
3098 // scroll offset, then we don't want to change it to something that can't.
3099 // If we allowed this, then we could end up in a state where APZ ignores
3100 // legitimate scroll offset updates because the origin has been masked by
3101 // a later change within the same refresh driver tick.
3102 allowScrollOriginChange =
3103 (mAllowScrollOriginDowngrade || !isScrollOriginDowngrade);
3105 if (allowScrollOriginChange) {
3106 mLastScrollOrigin = aOrigin;
3107 mAllowScrollOriginDowngrade = false;
3110 if (aOrigin == ScrollOrigin::Relative) {
3111 MOZ_ASSERT(!isScrollOriginDowngrade);
3112 MOZ_ASSERT(mLastScrollOrigin == ScrollOrigin::Relative);
3113 AppendScrollUpdate(
3114 ScrollPositionUpdate::NewRelativeScroll(mApzScrollPos, pt));
3115 mApzScrollPos = pt;
3116 } else if (aOrigin != ScrollOrigin::Apz) {
3117 AppendScrollUpdate(ScrollPositionUpdate::NewScroll(mLastScrollOrigin, pt));
3120 if (mLastScrollOrigin == ScrollOrigin::Apz) {
3121 mApzScrollPos = GetScrollPosition();
3124 ScrollVisual();
3125 mAnchor.UserScrolled();
3127 // Only report user-triggered scrolling interactions
3128 bool jsOnStack = nsContentUtils::GetCurrentJSContext() != nullptr;
3129 bool scrollingToAnchor = ScrollingInteractionContext::IsScrollingToAnchor();
3130 if (!jsOnStack && !scrollingToAnchor) {
3131 nsPoint distanceScrolled(std::abs(pt.x - curPos.x),
3132 std::abs(pt.y - curPos.y));
3133 ScrollingMetrics::OnScrollingInteraction(
3134 CSSPoint::FromAppUnits(distanceScrolled).Length());
3137 bool schedulePaint = true;
3138 if (nsLayoutUtils::AsyncPanZoomEnabled(this) &&
3139 !nsLayoutUtils::ShouldDisableApzForElement(content) &&
3140 !content->GetProperty(nsGkAtoms::MinimalDisplayPort) &&
3141 StaticPrefs::apz_paint_skipping_enabled()) {
3142 // If APZ is enabled with paint-skipping, there are certain conditions in
3143 // which we can skip paints:
3144 // 1) If APZ triggered this scroll, and the tile-aligned displayport is
3145 // unchanged.
3146 // 2) If non-APZ triggered this scroll, but we can handle it by just asking
3147 // APZ to update the scroll position. Again we make this conditional on
3148 // the tile-aligned displayport being unchanged.
3149 // We do the displayport check first since it's common to all scenarios,
3150 // and then if the displayport is unchanged, we check if APZ triggered,
3151 // or can handle, this scroll. If so, we set schedulePaint to false and
3152 // skip the paint.
3153 // Because of bug 1264297, we also don't do paint-skipping for elements with
3154 // perspective, because the displayport may not have captured everything
3155 // that needs to be painted. So even if the final tile-aligned displayport
3156 // is the same, we force a repaint for these elements. Bug 1254260 tracks
3157 // fixing this properly.
3158 nsRect displayPort;
3159 bool usingDisplayPort =
3160 DisplayPortUtils::GetDisplayPort(content, &displayPort);
3161 displayPort.MoveBy(-mScrolledFrame->GetPosition());
3163 PAINT_SKIP_LOG(
3164 "New scrollpos %s usingDP %d dpEqual %d scrollableByApz "
3165 "%d perspective %d bglocal %d filter %d\n",
3166 ToString(CSSPoint::FromAppUnits(GetScrollPosition())).c_str(),
3167 usingDisplayPort, displayPort.IsEqualEdges(oldDisplayPort),
3168 mScrollableByAPZ, HasPerspective(), HasBgAttachmentLocal(),
3169 mHasOutOfFlowContentInsideFilter);
3170 if (usingDisplayPort && displayPort.IsEqualEdges(oldDisplayPort) &&
3171 !HasPerspective() && !HasBgAttachmentLocal() &&
3172 !mHasOutOfFlowContentInsideFilter) {
3173 bool haveScrollLinkedEffects =
3174 content->GetComposedDoc()->HasScrollLinkedEffect();
3175 bool apzDisabled = haveScrollLinkedEffects &&
3176 StaticPrefs::apz_disable_for_scroll_linked_effects();
3177 if (!apzDisabled) {
3178 if (LastScrollOrigin() == ScrollOrigin::Apz) {
3179 schedulePaint = false;
3180 PAINT_SKIP_LOG("Skipping due to APZ scroll\n");
3181 } else if (mScrollableByAPZ) {
3182 nsIWidget* widget = presContext->GetNearestWidget();
3183 WindowRenderer* renderer =
3184 widget ? widget->GetWindowRenderer() : nullptr;
3185 if (renderer) {
3186 mozilla::layers::ScrollableLayerGuid::ViewID id;
3187 bool success = nsLayoutUtils::FindIDFor(content, &id);
3188 MOZ_ASSERT(success); // we have a displayport, we better have an ID
3190 // Schedule an empty transaction to carry over the scroll offset
3191 // update, instead of a full transaction. This empty transaction
3192 // might still get squashed into a full transaction if something
3193 // happens to trigger one.
3194 MOZ_ASSERT(!mScrollUpdates.IsEmpty());
3195 success = renderer->AddPendingScrollUpdateForNextTransaction(
3196 id, mScrollUpdates.LastElement());
3197 if (success) {
3198 schedulePaint = false;
3199 SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY);
3200 PAINT_SKIP_LOG(
3201 "Skipping due to APZ-forwarded main-thread scroll\n");
3202 } else {
3203 PAINT_SKIP_LOG(
3204 "Failed to set pending scroll update on layer manager\n");
3212 // If the new scroll offset is going to clobber APZ's scroll offset, for
3213 // the RCD-RSF this will have the effect of updating the visual viewport
3214 // offset in a way that keeps the relative offset between the layout and
3215 // visual viewports constant. This will cause APZ to send us a new visual
3216 // viewport offset, but instead of waiting for that, just set the value
3217 // we expect APZ will set ourselves, to minimize the chances of
3218 // inconsistencies from querying a stale value.
3219 if (mIsRoot && nsLayoutUtils::CanScrollOriginClobberApz(aOrigin)) {
3220 AutoWeakFrame weakFrame(this);
3221 AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame,
3222 !schedulePaint);
3224 nsPoint visualViewportOffset = curPos;
3225 if (presContext->PresShell()->IsVisualViewportOffsetSet()) {
3226 visualViewportOffset =
3227 presContext->PresShell()->GetVisualViewportOffset();
3229 nsPoint relativeOffset = visualViewportOffset - curPos;
3231 presContext->PresShell()->SetVisualViewportOffset(pt + relativeOffset,
3232 curPos);
3233 if (!weakFrame.IsAlive()) {
3234 return;
3238 if (schedulePaint) {
3239 SchedulePaint();
3241 if (needFrameVisibilityUpdate) {
3242 presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
3246 if (ChildrenHavePerspective()) {
3247 // The overflow areas of descendants may depend on the scroll position,
3248 // so ensure they get updated.
3250 // First we recompute the overflow areas of the transformed children
3251 // that use the perspective. FinishAndStoreOverflow only calls this
3252 // if the size changes, so we need to do it manually.
3253 RecomputePerspectiveChildrenOverflow(this);
3255 // Update the overflow for the scrolled frame to take any changes from the
3256 // children into account.
3257 mScrolledFrame->UpdateOverflow();
3259 // Update the overflow for the outer so that we recompute scrollbars.
3260 UpdateOverflow();
3263 ScheduleSyntheticMouseMove();
3265 nsAutoScriptBlocker scriptBlocker;
3266 PresShell::AutoAssertNoFlush noFlush(*PresShell());
3268 { // scope the AutoScrollbarRepaintSuppression
3269 AutoWeakFrame weakFrame(this);
3270 AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame,
3271 !schedulePaint);
3272 UpdateScrollbarPosition();
3273 if (!weakFrame.IsAlive()) {
3274 return;
3278 presContext->RecordInteractionTime(
3279 nsPresContext::InteractionType::ScrollInteraction, TimeStamp::Now());
3281 PostScrollEvent();
3282 // If this is a viewport scroll, this could affect the relative offset
3283 // between layout and visual viewport, so we might have to fire a visual
3284 // viewport scroll event as well.
3285 if (mIsRoot) {
3286 if (auto* window = nsGlobalWindowInner::Cast(
3287 PresContext()->Document()->GetInnerWindow())) {
3288 window->VisualViewport()->PostScrollEvent(
3289 presContext->PresShell()->GetVisualViewportOffset(), curPos);
3293 // Schedule the scroll-timelines linked to its scrollable frame.
3294 // if `pt == curPos`, we early return, so the position must be changed at
3295 // this moment. Therefore, we can schedule scroll animations directly.
3296 ScheduleScrollAnimations();
3298 // notify the listeners.
3299 for (uint32_t i = 0; i < mListeners.Length(); i++) {
3300 mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
3303 if (nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell()) {
3304 docShell->NotifyScrollObservers();
3308 // Finds the max z-index of the items in aList that meet the following
3309 // conditions
3310 // 1) have z-index auto or z-index >= 0, and
3311 // 2) aFrame is a proper ancestor of the item's frame.
3312 // Returns Nothing() if there is no such item.
3313 static Maybe<int32_t> MaxZIndexInListOfItemsContainedInFrame(
3314 nsDisplayList* aList, nsIFrame* aFrame) {
3315 Maybe<int32_t> maxZIndex = Nothing();
3316 for (nsDisplayItem* item : *aList) {
3317 int32_t zIndex = item->ZIndex();
3318 if (zIndex < 0 ||
3319 !nsLayoutUtils::IsProperAncestorFrame(aFrame, item->Frame())) {
3320 continue;
3322 if (!maxZIndex) {
3323 maxZIndex = Some(zIndex);
3324 } else {
3325 maxZIndex = Some(std::max(maxZIndex.value(), zIndex));
3328 return maxZIndex;
3331 template <class T>
3332 static void AppendInternalItemToTop(const nsDisplayListSet& aLists, T* aItem,
3333 const Maybe<int32_t>& aZIndex) {
3334 if (aZIndex) {
3335 aItem->SetOverrideZIndex(aZIndex.value());
3336 aLists.PositionedDescendants()->AppendToTop(aItem);
3337 } else {
3338 aLists.Content()->AppendToTop(aItem);
3342 static const uint32_t APPEND_OWN_LAYER = 0x1;
3343 static const uint32_t APPEND_POSITIONED = 0x2;
3344 static const uint32_t APPEND_SCROLLBAR_CONTAINER = 0x4;
3345 static const uint32_t APPEND_OVERLAY = 0x8;
3346 static const uint32_t APPEND_TOP = 0x10;
3348 static void AppendToTop(nsDisplayListBuilder* aBuilder,
3349 const nsDisplayListSet& aLists, nsDisplayList* aSource,
3350 nsIFrame* aSourceFrame, nsIFrame* aScrollFrame,
3351 uint32_t aFlags) {
3352 if (aSource->IsEmpty()) {
3353 return;
3356 nsDisplayWrapList* newItem;
3357 const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
3358 if (aFlags & APPEND_OWN_LAYER) {
3359 ScrollbarData scrollbarData;
3360 if (aFlags & APPEND_SCROLLBAR_CONTAINER) {
3361 scrollbarData = ScrollbarData::CreateForScrollbarContainer(
3362 aBuilder->GetCurrentScrollbarDirection(),
3363 aBuilder->GetCurrentScrollbarTarget());
3364 // Direction should be set
3365 MOZ_ASSERT(scrollbarData.mDirection.isSome());
3368 newItem = MakeDisplayItemWithIndex<nsDisplayOwnLayer>(
3369 aBuilder, aSourceFrame,
3370 /* aIndex = */ nsDisplayOwnLayer::OwnLayerForScrollbar, aSource, asr,
3371 nsDisplayOwnLayerFlags::None, scrollbarData, true, false);
3372 } else {
3373 // Build the wrap list with an index of 1, since the scrollbar frame itself
3374 // might have already built an nsDisplayWrapList.
3375 newItem = MakeDisplayItemWithIndex<nsDisplayWrapper>(
3376 aBuilder, aSourceFrame, 1, aSource, asr, false);
3378 if (!newItem) {
3379 return;
3382 if (aFlags & APPEND_POSITIONED) {
3383 // We want overlay scrollbars to always be on top of the scrolled content,
3384 // but we don't want them to unnecessarily cover overlapping elements from
3385 // outside our scroll frame.
3386 Maybe<int32_t> zIndex = Nothing();
3387 if (aFlags & APPEND_TOP) {
3388 zIndex = Some(INT32_MAX);
3389 } else if (aFlags & APPEND_OVERLAY) {
3390 zIndex = MaxZIndexInListOfItemsContainedInFrame(
3391 aLists.PositionedDescendants(), aScrollFrame);
3392 } else if (aSourceFrame->StylePosition()->mZIndex.IsInteger()) {
3393 zIndex = Some(aSourceFrame->StylePosition()->mZIndex.integer._0);
3395 AppendInternalItemToTop(aLists, newItem, zIndex);
3396 } else {
3397 aLists.BorderBackground()->AppendToTop(newItem);
3401 struct HoveredStateComparator {
3402 static bool Hovered(const nsIFrame* aFrame) {
3403 return aFrame->GetContent()->IsElement() &&
3404 aFrame->GetContent()->AsElement()->HasAttr(nsGkAtoms::hover);
3407 bool Equals(nsIFrame* A, nsIFrame* B) const {
3408 return Hovered(A) == Hovered(B);
3411 bool LessThan(nsIFrame* A, nsIFrame* B) const {
3412 return !Hovered(A) && Hovered(B);
3416 void nsHTMLScrollFrame::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder,
3417 const nsDisplayListSet& aLists,
3418 bool aCreateLayer,
3419 bool aPositioned) {
3420 const bool overlayScrollbars = UsesOverlayScrollbars();
3422 AutoTArray<nsIFrame*, 3> scrollParts;
3423 for (nsIFrame* kid : PrincipalChildList()) {
3424 if (kid == mScrolledFrame ||
3425 (kid->IsAbsPosContainingBlock() || overlayScrollbars) != aPositioned) {
3426 continue;
3429 scrollParts.AppendElement(kid);
3431 if (scrollParts.IsEmpty()) {
3432 return;
3435 // We can't check will-change budget during display list building phase.
3436 // This means that we will build scroll bar layers for out of budget
3437 // will-change: scroll position.
3438 const mozilla::layers::ScrollableLayerGuid::ViewID scrollTargetId =
3439 IsScrollingActive()
3440 ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
3441 : mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
3443 scrollParts.Sort(HoveredStateComparator());
3445 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3446 // Don't let scrollparts extent outside our frame's border-box, if these are
3447 // viewport scrollbars. They would create layerization problems. This wouldn't
3448 // normally be an issue but themes can add overflow areas to scrollbar parts.
3449 if (mIsRoot) {
3450 nsRect scrollPartsClip(aBuilder->ToReferenceFrame(this),
3451 TrueOuterSize(aBuilder));
3452 clipState.ClipContentDescendants(scrollPartsClip);
3455 for (uint32_t i = 0; i < scrollParts.Length(); ++i) {
3456 MOZ_ASSERT(scrollParts[i]);
3457 Maybe<ScrollDirection> scrollDirection;
3458 uint32_t appendToTopFlags = 0;
3459 if (scrollParts[i] == mVScrollbarBox) {
3460 scrollDirection.emplace(ScrollDirection::eVertical);
3461 appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
3463 if (scrollParts[i] == mHScrollbarBox) {
3464 MOZ_ASSERT(!scrollDirection.isSome());
3465 scrollDirection.emplace(ScrollDirection::eHorizontal);
3466 appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
3469 // The display port doesn't necessarily include the scrollbars, so just
3470 // include all of the scrollbars if we are in a RCD-RSF. We only do
3471 // this for the root scrollframe of the root content document, which is
3472 // zoomable, and where the scrollbar sizes are bounded by the widget.
3473 const nsRect visible =
3474 mIsRoot && PresContext()->IsRootContentDocumentCrossProcess()
3475 ? scrollParts[i]->InkOverflowRectRelativeToParent()
3476 : aBuilder->GetVisibleRect();
3477 if (visible.IsEmpty()) {
3478 continue;
3480 const nsRect dirty =
3481 mIsRoot && PresContext()->IsRootContentDocumentCrossProcess()
3482 ? scrollParts[i]->InkOverflowRectRelativeToParent()
3483 : aBuilder->GetDirtyRect();
3485 // Always create layers for overlay scrollbars so that we don't create a
3486 // giant layer covering the whole scrollport if both scrollbars are visible.
3487 const bool isOverlayScrollbar =
3488 scrollDirection.isSome() && overlayScrollbars;
3489 const bool createLayer =
3490 aCreateLayer || isOverlayScrollbar ||
3491 StaticPrefs::layout_scrollbars_always_layerize_track();
3493 nsDisplayListCollection partList(aBuilder);
3495 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
3496 aBuilder, this, visible, dirty);
3498 nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter infoSetter(
3499 aBuilder, scrollTargetId, scrollDirection, createLayer);
3500 BuildDisplayListForChild(
3501 aBuilder, scrollParts[i], partList,
3502 nsIFrame::DisplayChildFlag::ForceStackingContext);
3505 // DisplayChildFlag::ForceStackingContext put everything into
3506 // partList.PositionedDescendants().
3507 if (partList.PositionedDescendants()->IsEmpty()) {
3508 continue;
3511 if (createLayer) {
3512 appendToTopFlags |= APPEND_OWN_LAYER;
3514 if (aPositioned) {
3515 appendToTopFlags |= APPEND_POSITIONED;
3518 if (isOverlayScrollbar || scrollParts[i] == mResizerBox) {
3519 if (isOverlayScrollbar && mIsRoot) {
3520 appendToTopFlags |= APPEND_TOP;
3521 } else {
3522 appendToTopFlags |= APPEND_OVERLAY;
3523 aBuilder->SetDisablePartialUpdates(true);
3528 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
3529 aBuilder, scrollParts[i], visible + GetOffsetTo(scrollParts[i]),
3530 dirty + GetOffsetTo(scrollParts[i]));
3531 if (scrollParts[i]->IsTransformed()) {
3532 nsPoint toOuterReferenceFrame;
3533 const nsIFrame* outerReferenceFrame = aBuilder->FindReferenceFrameFor(
3534 scrollParts[i]->GetParent(), &toOuterReferenceFrame);
3535 toOuterReferenceFrame += scrollParts[i]->GetPosition();
3537 buildingForChild.SetReferenceFrameAndCurrentOffset(
3538 outerReferenceFrame, toOuterReferenceFrame);
3540 nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter infoSetter(
3541 aBuilder, scrollTargetId, scrollDirection, createLayer);
3543 ::AppendToTop(aBuilder, aLists, partList.PositionedDescendants(),
3544 scrollParts[i], this, appendToTopFlags);
3549 nsRect nsHTMLScrollFrame::ExpandRectToNearlyVisible(const nsRect& aRect) const {
3550 // We don't want to expand a rect in a direction that we can't scroll, so we
3551 // check the scroll range.
3552 nsRect scrollRange = GetLayoutScrollRange();
3553 nsPoint scrollPos = GetScrollPosition();
3554 nsMargin expand(0, 0, 0, 0);
3556 nscoord vertShift =
3557 StaticPrefs::layout_framevisibility_numscrollportheights() * aRect.height;
3558 if (scrollRange.y < scrollPos.y) {
3559 expand.top = vertShift;
3561 if (scrollPos.y < scrollRange.YMost()) {
3562 expand.bottom = vertShift;
3565 nscoord horzShift =
3566 StaticPrefs::layout_framevisibility_numscrollportwidths() * aRect.width;
3567 if (scrollRange.x < scrollPos.x) {
3568 expand.left = horzShift;
3570 if (scrollPos.x < scrollRange.XMost()) {
3571 expand.right = horzShift;
3574 nsRect rect = aRect;
3575 rect.Inflate(expand);
3576 return rect;
3579 static bool ShouldBeClippedByFrame(nsIFrame* aClipFrame,
3580 nsIFrame* aClippedFrame) {
3581 return nsLayoutUtils::IsProperAncestorFrame(aClipFrame, aClippedFrame);
3584 static void ClipItemsExceptCaret(
3585 nsDisplayList* aList, nsDisplayListBuilder* aBuilder, nsIFrame* aClipFrame,
3586 const DisplayItemClipChain* aExtraClip,
3587 nsTHashMap<nsPtrHashKey<const DisplayItemClipChain>,
3588 const DisplayItemClipChain*>& aCache) {
3589 for (nsDisplayItem* i : *aList) {
3590 if (!ShouldBeClippedByFrame(aClipFrame, i->Frame())) {
3591 continue;
3594 const DisplayItemType type = i->GetType();
3595 if (type != DisplayItemType::TYPE_CARET &&
3596 type != DisplayItemType::TYPE_CONTAINER) {
3597 const DisplayItemClipChain* clip = i->GetClipChain();
3598 const DisplayItemClipChain* intersection = nullptr;
3599 if (aCache.Get(clip, &intersection)) {
3600 i->SetClipChain(intersection, true);
3601 } else {
3602 i->IntersectClip(aBuilder, aExtraClip, true);
3603 aCache.InsertOrUpdate(clip, i->GetClipChain());
3606 nsDisplayList* children = i->GetSameCoordinateSystemChildren();
3607 if (children) {
3608 ClipItemsExceptCaret(children, aBuilder, aClipFrame, aExtraClip, aCache);
3613 static void ClipListsExceptCaret(nsDisplayListCollection* aLists,
3614 nsDisplayListBuilder* aBuilder,
3615 nsIFrame* aClipFrame,
3616 const DisplayItemClipChain* aExtraClip) {
3617 nsTHashMap<nsPtrHashKey<const DisplayItemClipChain>,
3618 const DisplayItemClipChain*>
3619 cache;
3620 ClipItemsExceptCaret(aLists->BorderBackground(), aBuilder, aClipFrame,
3621 aExtraClip, cache);
3622 ClipItemsExceptCaret(aLists->BlockBorderBackgrounds(), aBuilder, aClipFrame,
3623 aExtraClip, cache);
3624 ClipItemsExceptCaret(aLists->Floats(), aBuilder, aClipFrame, aExtraClip,
3625 cache);
3626 ClipItemsExceptCaret(aLists->PositionedDescendants(), aBuilder, aClipFrame,
3627 aExtraClip, cache);
3628 ClipItemsExceptCaret(aLists->Outlines(), aBuilder, aClipFrame, aExtraClip,
3629 cache);
3630 ClipItemsExceptCaret(aLists->Content(), aBuilder, aClipFrame, aExtraClip,
3631 cache);
3634 // This is similar to a "save-restore" RAII class for
3635 // DisplayListBuilder::ContainsBlendMode(), with a slight enhancement.
3636 // If this class is put on the stack and then unwound, the DL builder's
3637 // ContainsBlendMode flag will be effectively the same as if this class wasn't
3638 // put on the stack. However, if the CaptureContainsBlendMode method is called,
3639 // there will be a difference - the blend mode in the descendant display lists
3640 // will be "captured" and extracted.
3641 // The main goal here is to allow conditionally capturing the flag that
3642 // indicates whether or not a blend mode was encountered in the descendant part
3643 // of the display list.
3644 class MOZ_RAII AutoContainsBlendModeCapturer {
3645 nsDisplayListBuilder& mBuilder;
3646 bool mSavedContainsBlendMode;
3648 public:
3649 explicit AutoContainsBlendModeCapturer(nsDisplayListBuilder& aBuilder)
3650 : mBuilder(aBuilder),
3651 mSavedContainsBlendMode(aBuilder.ContainsBlendMode()) {
3652 mBuilder.SetContainsBlendMode(false);
3655 bool CaptureContainsBlendMode() {
3656 // "Capture" the flag by extracting and clearing the ContainsBlendMode flag
3657 // on the builder.
3658 bool capturedBlendMode = mBuilder.ContainsBlendMode();
3659 mBuilder.SetContainsBlendMode(false);
3660 return capturedBlendMode;
3663 ~AutoContainsBlendModeCapturer() {
3664 // If CaptureContainsBlendMode() was called, the descendant blend mode was
3665 // "captured" and so uncapturedContainsBlendMode will be false. If
3666 // CaptureContainsBlendMode() wasn't called, then no capture occurred, and
3667 // uncapturedContainsBlendMode may be true if there was a descendant blend
3668 // mode. In that case, we set the flag on the DL builder so that we restore
3669 // state to what it would have been without this RAII class on the stack.
3670 bool uncapturedContainsBlendMode = mBuilder.ContainsBlendMode();
3671 mBuilder.SetContainsBlendMode(mSavedContainsBlendMode ||
3672 uncapturedContainsBlendMode);
3676 void nsHTMLScrollFrame::MaybeCreateTopLayerAndWrapRootItems(
3677 nsDisplayListBuilder* aBuilder, nsDisplayListCollection& aSet,
3678 bool aCreateAsyncZoom,
3679 AutoContainsBlendModeCapturer* aAsyncZoomBlendCapture,
3680 const nsRect& aAsyncZoomClipRect, nscoord* aRadii) {
3681 if (!mIsRoot) {
3682 return;
3685 // Create any required items for the 'top layer' and check if they'll be
3686 // opaque over the entire area of the viewport. If they are, then we can
3687 // skip building display items for the rest of the page.
3688 if (ViewportFrame* viewport = do_QueryFrame(GetParent())) {
3689 bool topLayerIsOpaque = false;
3690 if (nsDisplayWrapList* topLayerWrapList =
3691 viewport->BuildDisplayListForTopLayer(aBuilder,
3692 &topLayerIsOpaque)) {
3693 // If the top layer content is opaque, and we're the root content document
3694 // in the process, we can drop the display items behind it. We only
3695 // support doing this for the root content document in the process, since
3696 // the top layer content might have fixed position items that have a
3697 // scrolltarget referencing the APZ data for the document. APZ builds this
3698 // data implicitly for the root content document in the process, but
3699 // subdocuments etc need their display items to generate it, so we can't
3700 // cull those.
3701 if (topLayerIsOpaque && PresContext()->IsRootContentDocumentInProcess()) {
3702 aSet.DeleteAll(aBuilder);
3704 aSet.PositionedDescendants()->AppendToTop(topLayerWrapList);
3708 nsDisplayList rootResultList(aBuilder);
3710 bool serializedList = false;
3711 auto SerializeList = [&] {
3712 if (!serializedList) {
3713 serializedList = true;
3714 aSet.SerializeWithCorrectZOrder(&rootResultList, GetContent());
3718 if (nsIFrame* rootStyleFrame = GetFrameForStyle()) {
3719 bool usingBackdropFilter =
3720 rootStyleFrame->StyleEffects()->HasBackdropFilters() &&
3721 rootStyleFrame->IsVisibleForPainting();
3723 if (rootStyleFrame->StyleEffects()->HasFilters() &&
3724 !aBuilder->IsForGenerateGlyphMask()) {
3725 SerializeList();
3726 rootResultList.AppendNewToTop<nsDisplayFilters>(
3727 aBuilder, this, &rootResultList, rootStyleFrame, usingBackdropFilter);
3730 if (usingBackdropFilter) {
3731 SerializeList();
3732 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3733 nsRect backdropRect =
3734 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
3735 rootResultList.AppendNewToTop<nsDisplayBackdropFilters>(
3736 aBuilder, this, &rootResultList, backdropRect, rootStyleFrame);
3740 if (aCreateAsyncZoom) {
3741 MOZ_ASSERT(mIsRoot);
3743 // Wrap all our scrolled contents in an nsDisplayAsyncZoom. This will be
3744 // the layer that gets scaled for APZ zooming. It does not have the
3745 // scrolled ASR, but it does have the composition bounds clip applied to
3746 // it. The children have the layout viewport clip applied to them (above).
3747 // Effectively we are double clipping to the viewport, at potentially
3748 // different async scales.
3749 SerializeList();
3751 if (aAsyncZoomBlendCapture->CaptureContainsBlendMode()) {
3752 // The async zoom contents contain a mix-blend mode, so let's wrap all
3753 // those contents into a blend container, and then wrap the blend
3754 // container in the async zoom container. Otherwise the blend container
3755 // ends up outside the zoom container which results in blend failure for
3756 // WebRender.
3757 nsDisplayItem* blendContainer =
3758 nsDisplayBlendContainer::CreateForMixBlendMode(
3759 aBuilder, this, &rootResultList,
3760 aBuilder->CurrentActiveScrolledRoot());
3761 rootResultList.AppendToTop(blendContainer);
3763 // Blend containers can be created or omitted during partial updates
3764 // depending on the dirty rect. So we basically can't do partial updates
3765 // if there's a blend container involved. There is equivalent code to this
3766 // in the BuildDisplayListForStackingContext function as well, with a more
3767 // detailed comment explaining things better.
3768 if (aBuilder->IsRetainingDisplayList()) {
3769 if (aBuilder->IsPartialUpdate()) {
3770 aBuilder->SetPartialBuildFailed(true);
3771 } else {
3772 aBuilder->SetDisablePartialUpdates(true);
3777 mozilla::layers::FrameMetrics::ViewID viewID =
3778 nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent());
3780 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3781 clipState.ClipContentDescendants(aAsyncZoomClipRect, aRadii);
3783 rootResultList.AppendNewToTop<nsDisplayAsyncZoom>(
3784 aBuilder, this, &rootResultList, aBuilder->CurrentActiveScrolledRoot(),
3785 viewID);
3788 if (serializedList) {
3789 aSet.Content()->AppendToTop(&rootResultList);
3793 void nsHTMLScrollFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
3794 const nsDisplayListSet& aLists) {
3795 SetAndNullOnExit<const nsIFrame> tmpBuilder(
3796 mReferenceFrameDuringPainting, aBuilder->GetCurrentReferenceFrame());
3797 if (aBuilder->IsForFrameVisibility()) {
3798 NotifyApproximateFrameVisibilityUpdate(false);
3801 DisplayBorderBackgroundOutline(aBuilder, aLists);
3803 const bool isRootContent =
3804 mIsRoot && PresContext()->IsRootContentDocumentCrossProcess();
3806 nsRect effectiveScrollPort = mScrollPort;
3807 if (isRootContent && PresContext()->HasDynamicToolbar()) {
3808 // Expand the scroll port to the size including the area covered by dynamic
3809 // toolbar in the case where the dynamic toolbar is being used since
3810 // position:fixed elements attached to this root scroller might be taller
3811 // than its scroll port (e.g 100vh). Even if the dynamic toolbar covers the
3812 // taller area, it doesn't mean the area is clipped by the toolbar because
3813 // the dynamic toolbar is laid out outside of our topmost window and it
3814 // transitions without changing our topmost window size.
3815 effectiveScrollPort.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
3816 PresContext(), effectiveScrollPort.Size()));
3819 // It's safe to get this value before the DecideScrollableLayer call below
3820 // because that call cannot create a displayport for root scroll frames,
3821 // and hence it cannot create an ignore scroll frame.
3822 const bool ignoringThisScrollFrame = aBuilder->GetIgnoreScrollFrame() == this;
3824 // Overflow clipping can never clip frames outside our subtree, so there
3825 // is no need to worry about whether we are a moving frame that might clip
3826 // non-moving frames.
3827 // Not all our descendants will be clipped by overflow clipping, but all
3828 // the ones that aren't clipped will be out of flow frames that have already
3829 // had dirty rects saved for them by their parent frames calling
3830 // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
3831 // dirty rect here.
3832 nsRect visibleRect = aBuilder->GetVisibleRect();
3833 nsRect dirtyRect = aBuilder->GetDirtyRect();
3834 if (!ignoringThisScrollFrame) {
3835 visibleRect = visibleRect.Intersect(effectiveScrollPort);
3836 dirtyRect = dirtyRect.Intersect(effectiveScrollPort);
3839 bool dirtyRectHasBeenOverriden = false;
3840 Unused << DecideScrollableLayer(aBuilder, &visibleRect, &dirtyRect,
3841 /* aSetBase = */ !mIsRoot,
3842 &dirtyRectHasBeenOverriden);
3844 if (aBuilder->IsForFrameVisibility()) {
3845 // We expand the dirty rect to catch frames just outside of the scroll port.
3846 // We use the dirty rect instead of the whole scroll port to prevent
3847 // too much expansion in the presence of very large (bigger than the
3848 // viewport) scroll ports.
3849 dirtyRect = ExpandRectToNearlyVisible(dirtyRect);
3850 visibleRect = dirtyRect;
3853 // We put non-overlay scrollbars in their own layers when this is the root
3854 // scroll frame and we are a toplevel content document. In this situation,
3855 // the scrollbar(s) would normally be assigned their own layer anyway, since
3856 // they're not scrolled with the rest of the document. But when both
3857 // scrollbars are visible, the layer's visible rectangle would be the size
3858 // of the viewport, so most layer implementations would create a layer buffer
3859 // that's much larger than necessary. Creating independent layers for each
3860 // scrollbar works around the problem.
3861 const bool createLayersForScrollbars = isRootContent;
3863 nsDisplayListCollection set(aBuilder);
3865 if (ignoringThisScrollFrame) {
3866 // If we are a root scroll frame that has a display port we want to add
3867 // scrollbars, they will be children of the scrollable layer, but they get
3868 // adjusted by the APZC automatically.
3869 bool addScrollBars =
3870 mIsRoot && mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow();
3872 if (addScrollBars) {
3873 // Add classic scrollbars.
3874 AppendScrollPartsTo(aBuilder, set, createLayersForScrollbars, false);
3878 nsDisplayListBuilder::AutoBuildingDisplayList building(
3879 aBuilder, this, visibleRect, dirtyRect);
3881 // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
3882 // The scrolled frame shouldn't have its own background/border, so we
3883 // can just pass aLists directly.
3884 BuildDisplayListForChild(aBuilder, mScrolledFrame, set);
3887 MaybeCreateTopLayerAndWrapRootItems(aBuilder, set,
3888 /* aCreateAsyncZoom = */ false, nullptr,
3889 nsRect(), nullptr);
3891 if (addScrollBars) {
3892 // Add overlay scrollbars.
3893 AppendScrollPartsTo(aBuilder, set, createLayersForScrollbars, true);
3896 set.MoveTo(aLists);
3897 return;
3900 // Whether we might want to build a scrollable layer for this scroll frame
3901 // at some point in the future. This controls whether we add the information
3902 // to the layer tree (a scroll info layer if necessary, and add the right
3903 // area to the dispatch to content layer event regions) necessary to activate
3904 // a scroll frame so it creates a scrollable layer.
3905 const bool couldBuildLayer = [&] {
3906 if (!aBuilder->IsPaintingToWindow()) {
3907 return false;
3909 if (mWillBuildScrollableLayer) {
3910 return true;
3912 return StyleVisibility()->IsVisible() &&
3913 nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll();
3914 }();
3916 // Now display the scrollbars and scrollcorner. These parts are drawn
3917 // in the border-background layer, on top of our own background and
3918 // borders and underneath borders and backgrounds of later elements
3919 // in the tree.
3920 // Note that this does not apply for overlay scrollbars; those are drawn
3921 // in the positioned-elements layer on top of everything else by the call
3922 // to AppendScrollPartsTo(..., true) further down.
3923 AppendScrollPartsTo(aBuilder, aLists, createLayersForScrollbars, false);
3925 const nsStyleDisplay* disp = StyleDisplay();
3926 if (aBuilder->IsForPainting() &&
3927 disp->mWillChange.bits & StyleWillChangeBits::SCROLL) {
3928 aBuilder->AddToWillChangeBudget(this, GetVisualViewportSize());
3931 mScrollParentID = aBuilder->GetCurrentScrollParentId();
3933 Maybe<nsRect> contentBoxClip;
3934 Maybe<const DisplayItemClipChain*> extraContentBoxClipForNonCaretContent;
3935 if (MOZ_UNLIKELY(
3936 disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
3937 disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox)) {
3938 WritingMode wm = mScrolledFrame->GetWritingMode();
3939 bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
3940 : disp->mOverflowClipBoxInline) ==
3941 StyleOverflowClipBox::ContentBox;
3942 bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
3943 : disp->mOverflowClipBoxBlock) ==
3944 StyleOverflowClipBox::ContentBox;
3945 // We only clip if there is *scrollable* overflow, to avoid clipping
3946 // *ink* overflow unnecessarily.
3947 nsRect clipRect = effectiveScrollPort + aBuilder->ToReferenceFrame(this);
3948 nsMargin padding = GetUsedPadding();
3949 if (!cbH) {
3950 padding.left = padding.right = nscoord(0);
3952 if (!cbV) {
3953 padding.top = padding.bottom = nscoord(0);
3955 clipRect.Deflate(padding);
3957 nsRect so = mScrolledFrame->ScrollableOverflowRect();
3958 if ((cbH && (clipRect.width != so.width || so.x < 0)) ||
3959 (cbV && (clipRect.height != so.height || so.y < 0))) {
3960 // The non-inflated clip needs to be set on all non-caret items.
3961 // We prepare an extra DisplayItemClipChain here that will be intersected
3962 // with those items after they've been created.
3963 const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
3965 DisplayItemClip newClip;
3966 newClip.SetTo(clipRect);
3968 const DisplayItemClipChain* extraClip =
3969 aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr);
3971 extraContentBoxClipForNonCaretContent = Some(extraClip);
3973 nsIFrame* caretFrame = aBuilder->GetCaretFrame();
3974 // Avoid clipping it in a zero-height line box (heuristic only).
3975 if (caretFrame && caretFrame->GetRect().height != 0) {
3976 nsRect caretRect = aBuilder->GetCaretRect();
3977 // Allow the caret to stick out of the content box clip by half the
3978 // caret height on the top, and its full width on the right.
3979 nsRect inflatedClip = clipRect;
3980 inflatedClip.Inflate(
3981 nsMargin(caretRect.height / 2, caretRect.width, 0, 0));
3982 contentBoxClip = Some(inflatedClip);
3987 AutoContainsBlendModeCapturer blendCapture(*aBuilder);
3989 const bool willBuildAsyncZoomContainer =
3990 mWillBuildScrollableLayer && aBuilder->ShouldBuildAsyncZoomContainer() &&
3991 isRootContent;
3993 nsRect scrollPortClip =
3994 effectiveScrollPort + aBuilder->ToReferenceFrame(this);
3995 nsRect clipRect = scrollPortClip;
3996 // Our override of GetBorderRadii ensures we never have a radius at
3997 // the corners where we have a scrollbar.
3998 nscoord radii[8];
3999 const bool haveRadii = GetPaddingBoxBorderRadii(radii);
4000 if (mIsRoot) {
4001 clipRect.SizeTo(nsLayoutUtils::CalculateCompositionSizeForFrame(
4002 this, true /* aSubtractScrollbars */,
4003 nullptr /* aOverrideScrollPortSize */,
4004 // With the dynamic toolbar, this CalculateCompositionSizeForFrame call
4005 // basically expands the region being covered up by the dynamic toolbar,
4006 // but if the root scroll container is not scrollable, e.g. the root
4007 // element has `overflow: hidden` or `position: fixed`, the function
4008 // doesn't expand the region since expanding the region in such cases
4009 // will prevent the content from restoring zooming to 1.0 zoom level
4010 // such as bug 1652190. That said, this `clipRect` which will be used
4011 // for the async zoom container needs to be expanded because zoomed-in
4012 // contents can be scrollable __visually__ so that the region under the
4013 // dynamic toolbar area will be revealed.
4014 nsLayoutUtils::IncludeDynamicToolbar::Force));
4016 // The composition size is essentially in visual coordinates.
4017 // If we are hit-testing in layout coordinates, transform the clip rect
4018 // to layout coordinates to match.
4019 if (aBuilder->IsRelativeToLayoutViewport() && isRootContent) {
4020 clipRect = ViewportUtils::VisualToLayout(clipRect, PresShell());
4025 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
4027 // If we're building an async zoom container, clip the contents inside
4028 // to the layout viewport (scrollPortClip). The composition bounds clip
4029 // (clipRect) will be applied to the zoom container itself in
4030 // MaybeCreateTopLayerAndWrapRootItems.
4031 nsRect clipRectForContents =
4032 willBuildAsyncZoomContainer ? scrollPortClip : clipRect;
4033 if (mIsRoot) {
4034 clipState.ClipContentDescendants(clipRectForContents,
4035 haveRadii ? radii : nullptr);
4036 } else {
4037 clipState.ClipContainingBlockDescendants(clipRectForContents,
4038 haveRadii ? radii : nullptr);
4041 Maybe<DisplayListClipState::AutoSaveRestore> contentBoxClipState;
4042 if (contentBoxClip) {
4043 contentBoxClipState.emplace(aBuilder);
4044 if (mIsRoot) {
4045 contentBoxClipState->ClipContentDescendants(*contentBoxClip);
4046 } else {
4047 contentBoxClipState->ClipContainingBlockDescendants(*contentBoxClip);
4051 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
4052 aBuilder);
4054 if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
4055 // If this scroll frame has a first scrollable frame sequence number,
4056 // ensure that it matches the current paint sequence number. If it does
4057 // not, reset it so that we can expire the displayport. The stored
4058 // sequence number will not match that of the current paint if the dom
4059 // was mutated in some way that alters the order of scroll frames.
4060 if (IsFirstScrollableFrameSequenceNumber().isSome() &&
4061 *IsFirstScrollableFrameSequenceNumber() !=
4062 nsDisplayListBuilder::GetPaintSequenceNumber()) {
4063 SetIsFirstScrollableFrameSequenceNumber(Nothing());
4065 asrSetter.EnterScrollFrame(this);
4068 if (couldBuildLayer && mScrolledFrame->GetContent()) {
4069 asrSetter.SetCurrentScrollParentId(
4070 nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent()));
4073 if (mWillBuildScrollableLayer && aBuilder->BuildCompositorHitTestInfo()) {
4074 // Create a hit test info item for the scrolled content that's not
4075 // clipped to the displayport. This ensures that within the bounds
4076 // of the scroll frame, the scrolled content is always hit, even
4077 // if we are checkerboarding.
4078 CompositorHitTestInfo info =
4079 mScrolledFrame->GetCompositorHitTestInfo(aBuilder);
4081 if (info != CompositorHitTestInvisibleToHit) {
4082 auto* hitInfo =
4083 MakeDisplayItemWithIndex<nsDisplayCompositorHitTestInfo>(
4084 aBuilder, mScrolledFrame, 1);
4085 if (hitInfo) {
4086 aBuilder->SetCompositorHitTestInfo(info);
4087 set.BorderBackground()->AppendToTop(hitInfo);
4093 // Clip our contents to the unsnapped scrolled rect. This makes sure
4094 // that we don't have display items over the subpixel seam at the edge
4095 // of the scrolled area.
4096 DisplayListClipState::AutoSaveRestore scrolledRectClipState(aBuilder);
4097 nsRect scrolledRectClip =
4098 GetUnsnappedScrolledRectInternal(
4099 mScrolledFrame->ScrollableOverflowRect(), mScrollPort.Size()) +
4100 mScrolledFrame->GetPosition();
4101 bool clippedToDisplayPort = false;
4102 if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
4103 // Clip the contents to the display port.
4104 // The dirty rect already acts kind of like a clip, in that
4105 // FrameLayerBuilder intersects item bounds and opaque regions with
4106 // it, but it doesn't have the consistent snapping behavior of a
4107 // true clip.
4108 // For a case where this makes a difference, imagine the following
4109 // scenario: The display port has an edge that falls on a fractional
4110 // layer pixel, and there's an opaque display item that covers the
4111 // whole display port up until that fractional edge, and there is a
4112 // transparent display item that overlaps the edge. We want to prevent
4113 // this transparent item from enlarging the scrolled layer's visible
4114 // region beyond its opaque region. The dirty rect doesn't do that -
4115 // it gets rounded out, whereas a true clip gets rounded to nearest
4116 // pixels.
4117 // If there is no display port, we don't need this because the clip
4118 // from the scroll port is still applied.
4119 scrolledRectClip = scrolledRectClip.Intersect(visibleRect);
4120 clippedToDisplayPort = scrolledRectClip.IsEqualEdges(visibleRect);
4122 scrolledRectClipState.ClipContainingBlockDescendants(
4123 scrolledRectClip + aBuilder->ToReferenceFrame(this));
4124 if (clippedToDisplayPort) {
4125 // We have to do this after the ClipContainingBlockDescendants call
4126 // above, otherwise that call will clobber the flag set by this call
4127 // to SetClippedToDisplayPort.
4128 scrolledRectClipState.SetClippedToDisplayPort();
4131 nsRect visibleRectForChildren = visibleRect;
4132 nsRect dirtyRectForChildren = dirtyRect;
4134 // If we are entering the RCD-RSF, we are crossing the async zoom
4135 // container boundary, and need to convert from visual to layout
4136 // coordinates.
4137 if (willBuildAsyncZoomContainer && aBuilder->IsForEventDelivery()) {
4138 MOZ_ASSERT(ViewportUtils::IsZoomedContentRoot(mScrolledFrame));
4139 visibleRectForChildren =
4140 ViewportUtils::VisualToLayout(visibleRectForChildren, PresShell());
4141 dirtyRectForChildren =
4142 ViewportUtils::VisualToLayout(dirtyRectForChildren, PresShell());
4145 nsDisplayListBuilder::AutoBuildingDisplayList building(
4146 aBuilder, this, visibleRectForChildren, dirtyRectForChildren);
4148 BuildDisplayListForChild(aBuilder, mScrolledFrame, set);
4150 if (dirtyRectHasBeenOverriden &&
4151 StaticPrefs::layout_display_list_show_rebuild_area()) {
4152 nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
4153 aBuilder, this,
4154 dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
4155 NS_RGBA(0, 0, 255, 64), false);
4156 if (color) {
4157 color->SetOverrideZIndex(INT32_MAX);
4158 set.PositionedDescendants()->AppendToTop(color);
4163 if (extraContentBoxClipForNonCaretContent) {
4164 // The items were built while the inflated content box clip was in
4165 // effect, so that the caret wasn't clipped unnecessarily. We apply
4166 // the non-inflated clip to the non-caret items now, by intersecting
4167 // it with their existing clip.
4168 ClipListsExceptCaret(&set, aBuilder, mScrolledFrame,
4169 *extraContentBoxClipForNonCaretContent);
4172 if (aBuilder->IsPaintingToWindow()) {
4173 mIsParentToActiveScrollFrames =
4174 ShouldActivateAllScrollFrames()
4175 ? asrSetter.GetContainsNonMinimalDisplayPort()
4176 : asrSetter.ShouldForceLayerForScrollParent();
4179 if (asrSetter.ShouldForceLayerForScrollParent()) {
4180 // Note that forcing layerization of scroll parents follows the scroll
4181 // handoff chain which is subject to the out-of-flow-frames caveat noted
4182 // above (where the asrSetter variable is created).
4183 MOZ_ASSERT(couldBuildLayer && mScrolledFrame->GetContent() &&
4184 aBuilder->IsPaintingToWindow());
4185 if (!mWillBuildScrollableLayer) {
4186 // Set a displayport so next paint we don't have to force layerization
4187 // after the fact. It's ok to pass DoNotRepaint here, since we've
4188 // already painted the change and we're just optimizing it to be
4189 // detected earlier. We also won't confuse RetainedDisplayLists
4190 // with the silent change, since we explicitly request partial updates
4191 // to be disabled on the next paint.
4192 DisplayPortUtils::SetDisplayPortMargins(
4193 GetContent(), PresShell(), DisplayPortMargins::Empty(GetContent()),
4194 DisplayPortUtils::ClearMinimalDisplayPortProperty::Yes, 0,
4195 DisplayPortUtils::RepaintMode::DoNotRepaint);
4196 // Call DecideScrollableLayer to recompute mWillBuildScrollableLayer
4197 // and recompute the current animated geometry root if needed. It's
4198 // too late to change the dirty rect so pass a copy.
4199 nsRect copyOfDirtyRect = dirtyRect;
4200 nsRect copyOfVisibleRect = visibleRect;
4201 Unused << DecideScrollableLayer(aBuilder, &copyOfVisibleRect,
4202 &copyOfDirtyRect,
4203 /* aSetBase = */ false, nullptr);
4204 if (mWillBuildScrollableLayer) {
4205 #ifndef MOZ_WIDGET_ANDROID
4206 gfxCriticalNoteOnce << "inserted scroll frame";
4207 #endif
4208 asrSetter.InsertScrollFrame(this);
4209 aBuilder->SetDisablePartialUpdates(true);
4215 if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
4216 aBuilder->ForceLayerForScrollParent();
4219 MaybeCreateTopLayerAndWrapRootItems(
4220 aBuilder, set, willBuildAsyncZoomContainer, &blendCapture, clipRect,
4221 haveRadii ? radii : nullptr);
4223 // We want to call SetContainsNonMinimalDisplayPort if
4224 // mWillBuildScrollableLayer is true for any reason other than having a
4225 // minimal display port.
4226 if (aBuilder->IsPaintingToWindow()) {
4227 if (DisplayPortUtils::HasNonMinimalDisplayPort(GetContent()) ||
4228 mZoomableByAPZ || nsContentUtils::HasScrollgrab(GetContent())) {
4229 aBuilder->SetContainsNonMinimalDisplayPort();
4233 if (couldBuildLayer) {
4234 CompositorHitTestInfo info(CompositorHitTestFlags::eVisibleToHitTest,
4235 CompositorHitTestFlags::eInactiveScrollframe);
4236 // If the scroll frame has non-default overscroll-behavior, instruct
4237 // APZ to require a target confirmation before processing events that
4238 // hit this scroll frame (that is, to drop the events if a
4239 // confirmation does not arrive within the timeout period). Otherwise,
4240 // APZ's fallback behaviour of scrolling the enclosing scroll frame
4241 // would violate the specified overscroll-behavior.
4242 auto overscroll = GetOverscrollBehaviorInfo();
4243 if (overscroll.mBehaviorX != OverscrollBehavior::Auto ||
4244 overscroll.mBehaviorY != OverscrollBehavior::Auto) {
4245 info += CompositorHitTestFlags::eRequiresTargetConfirmation;
4248 nsRect area = effectiveScrollPort + aBuilder->ToReferenceFrame(this);
4250 // Make sure that APZ will dispatch events back to content so we can
4251 // create a displayport for this frame. We'll add the item later on.
4252 if (!mWillBuildScrollableLayer && aBuilder->BuildCompositorHitTestInfo()) {
4253 // Make sure the z-index of the inactive item is at least zero.
4254 // Otherwise, it will end up behind non-positioned items in the scrolled
4255 // content.
4256 int32_t zIndex = MaxZIndexInListOfItemsContainedInFrame(
4257 set.PositionedDescendants(), this)
4258 .valueOr(0);
4259 if (aBuilder->IsPartialUpdate()) {
4260 for (nsDisplayItem* item : mScrolledFrame->DisplayItems()) {
4261 if (item->GetType() ==
4262 DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
4263 auto* hitTestItem =
4264 static_cast<nsDisplayCompositorHitTestInfo*>(item);
4265 if (hitTestItem->GetHitTestInfo().Info().contains(
4266 CompositorHitTestFlags::eInactiveScrollframe)) {
4267 zIndex = std::max(zIndex, hitTestItem->ZIndex());
4268 item->SetCantBeReused();
4273 nsDisplayCompositorHitTestInfo* hitInfo =
4274 MakeDisplayItemWithIndex<nsDisplayCompositorHitTestInfo>(
4275 aBuilder, mScrolledFrame, 1, area, info);
4276 if (hitInfo) {
4277 AppendInternalItemToTop(set, hitInfo, Some(zIndex));
4278 aBuilder->SetCompositorHitTestInfo(info);
4282 if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
4283 aBuilder->AppendNewScrollInfoItemForHoisting(
4284 MakeDisplayItem<nsDisplayScrollInfoLayer>(aBuilder, mScrolledFrame,
4285 this, info, area));
4289 // Now display overlay scrollbars and the resizer, if we have one.
4290 AppendScrollPartsTo(aBuilder, set, createLayersForScrollbars, true);
4292 set.MoveTo(aLists);
4295 nsRect nsHTMLScrollFrame::RestrictToRootDisplayPort(
4296 const nsRect& aDisplayportBase) {
4297 // This function clips aDisplayportBase so that it is no larger than the
4298 // root frame's displayport (or the root composition bounds, if we can't
4299 // obtain the root frame's displayport). This is useful for ensuring that
4300 // the displayport of a tall scrollframe doesn't gobble up all the memory.
4302 nsPresContext* pc = PresContext();
4303 const nsPresContext* rootPresContext =
4304 pc->GetInProcessRootContentDocumentPresContext();
4305 if (!rootPresContext) {
4306 rootPresContext = pc->GetRootPresContext();
4308 if (!rootPresContext) {
4309 return aDisplayportBase;
4311 const mozilla::PresShell* const rootPresShell = rootPresContext->PresShell();
4312 nsIFrame* rootFrame = rootPresShell->GetRootScrollFrame();
4313 if (!rootFrame) {
4314 rootFrame = rootPresShell->GetRootFrame();
4316 if (!rootFrame) {
4317 return aDisplayportBase;
4320 // Make sure we aren't trying to restrict to our own displayport, which is a
4321 // circular dependency.
4322 MOZ_ASSERT(!mIsRoot || rootPresContext != pc);
4324 nsRect rootDisplayPort;
4325 bool hasDisplayPort =
4326 rootFrame->GetContent() && DisplayPortUtils::GetDisplayPort(
4327 rootFrame->GetContent(), &rootDisplayPort);
4328 if (hasDisplayPort) {
4329 // The display port of the root frame already factors in it's callback
4330 // transform, so subtract it out here, the GetCumulativeApzCallbackTransform
4331 // call below will add it back.
4332 MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
4333 ("RestrictToRootDisplayPort: Existing root displayport is %s\n",
4334 ToString(rootDisplayPort).c_str()));
4335 if (nsIContent* content = rootFrame->GetContent()) {
4336 if (void* property =
4337 content->GetProperty(nsGkAtoms::apzCallbackTransform)) {
4338 rootDisplayPort -=
4339 CSSPoint::ToAppUnits(*static_cast<CSSPoint*>(property));
4342 } else {
4343 // If we don't have a display port on the root frame let's fall back to
4344 // the root composition bounds instead.
4345 nsRect rootCompBounds =
4346 nsRect(nsPoint(0, 0),
4347 nsLayoutUtils::CalculateCompositionSizeForFrame(rootFrame));
4349 // If rootFrame is the RCD-RSF then
4350 // CalculateCompositionSizeForFrame did not take the document's
4351 // resolution into account, so we must.
4352 if (rootPresContext->IsRootContentDocumentCrossProcess() &&
4353 rootFrame == rootPresShell->GetRootScrollFrame()) {
4354 MOZ_LOG(
4355 sDisplayportLog, LogLevel::Verbose,
4356 ("RestrictToRootDisplayPort: Removing resolution %f from root "
4357 "composition bounds %s\n",
4358 rootPresShell->GetResolution(), ToString(rootCompBounds).c_str()));
4359 rootCompBounds =
4360 rootCompBounds.RemoveResolution(rootPresShell->GetResolution());
4363 rootDisplayPort = rootCompBounds;
4365 MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
4366 ("RestrictToRootDisplayPort: Intermediate root displayport %s\n",
4367 ToString(rootDisplayPort).c_str()));
4369 // We want to convert the root display port from the
4370 // coordinate space of |rootFrame| to the coordinate space of
4371 // |this|. We do that with the TransformRect call below.
4372 // However, since we care about the root display port
4373 // relative to what the user is actually seeing, we also need to
4374 // incorporate the APZ callback transforms into this. Most of the
4375 // time those transforms are negligible, but in some cases (e.g.
4376 // when a zoom is applied on an overflow:hidden document) it is
4377 // not (see bug 1280013).
4378 // XXX: Eventually we may want to create a modified version of
4379 // TransformRect that includes the APZ callback transforms
4380 // directly.
4381 nsLayoutUtils::TransformRect(rootFrame, this, rootDisplayPort);
4382 MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
4383 ("RestrictToRootDisplayPort: Transformed root displayport %s\n",
4384 ToString(rootDisplayPort).c_str()));
4385 rootDisplayPort += CSSPoint::ToAppUnits(
4386 nsLayoutUtils::GetCumulativeApzCallbackTransform(this));
4387 MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
4388 ("RestrictToRootDisplayPort: Final root displayport %s\n",
4389 ToString(rootDisplayPort).c_str()));
4391 // We want to limit aDisplayportBase to be no larger than
4392 // rootDisplayPort on either axis, but we don't want to just
4393 // blindly intersect the two, because rootDisplayPort might be
4394 // offset from where aDisplayportBase is (see bug 1327095 comment
4395 // 8). Instead, we translate rootDisplayPort so as to maximize the
4396 // overlap with aDisplayportBase, and *then* do the intersection.
4397 if (rootDisplayPort.x > aDisplayportBase.x &&
4398 rootDisplayPort.XMost() > aDisplayportBase.XMost()) {
4399 // rootDisplayPort is at a greater x-position for both left and
4400 // right, so translate it such that the XMost() values are the
4401 // same. This will line up the right edge of the two rects, and
4402 // might mean that rootDisplayPort.x is smaller than
4403 // aDisplayportBase.x. We can avoid that by taking the min of the
4404 // x delta and XMost() delta, but it doesn't really matter
4405 // because the intersection between the two rects below will end
4406 // up the same.
4407 rootDisplayPort.x -= (rootDisplayPort.XMost() - aDisplayportBase.XMost());
4408 } else if (rootDisplayPort.x < aDisplayportBase.x &&
4409 rootDisplayPort.XMost() < aDisplayportBase.XMost()) {
4410 // Analaogous code for when the rootDisplayPort is at a smaller
4411 // x-position.
4412 rootDisplayPort.x = aDisplayportBase.x;
4414 // Do the same for y-axis
4415 if (rootDisplayPort.y > aDisplayportBase.y &&
4416 rootDisplayPort.YMost() > aDisplayportBase.YMost()) {
4417 rootDisplayPort.y -= (rootDisplayPort.YMost() - aDisplayportBase.YMost());
4418 } else if (rootDisplayPort.y < aDisplayportBase.y &&
4419 rootDisplayPort.YMost() < aDisplayportBase.YMost()) {
4420 rootDisplayPort.y = aDisplayportBase.y;
4422 MOZ_LOG(
4423 sDisplayportLog, LogLevel::Verbose,
4424 ("RestrictToRootDisplayPort: Root displayport translated to %s to "
4425 "better enclose %s\n",
4426 ToString(rootDisplayPort).c_str(), ToString(aDisplayportBase).c_str()));
4428 // Now we can do the intersection
4429 return aDisplayportBase.Intersect(rootDisplayPort);
4432 /* static */ bool nsHTMLScrollFrame::ShouldActivateAllScrollFrames() {
4433 return (StaticPrefs::apz_wr_activate_all_scroll_frames() ||
4434 (StaticPrefs::apz_wr_activate_all_scroll_frames_when_fission() &&
4435 FissionAutostart()));
4438 bool nsHTMLScrollFrame::DecideScrollableLayer(
4439 nsDisplayListBuilder* aBuilder, nsRect* aVisibleRect, nsRect* aDirtyRect,
4440 bool aSetBase, bool* aDirtyRectHasBeenOverriden) {
4441 nsIContent* content = GetContent();
4442 // For hit testing purposes with fission we want to create a
4443 // minimal display port for every scroll frame that could be active. (We only
4444 // do this when aSetBase is true because we only want to do this the first
4445 // time this function is called for the same scroll frame.)
4446 if (ShouldActivateAllScrollFrames() &&
4447 !DisplayPortUtils::HasDisplayPort(content) &&
4448 nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll() &&
4449 aBuilder->IsPaintingToWindow() && aSetBase) {
4450 // SetDisplayPortMargins calls TriggerDisplayPortExpiration which starts a
4451 // display port expiry timer for display ports that do expire. However
4452 // minimal display ports do not expire, so the display port has to be
4453 // marked before the SetDisplayPortMargins call so the expiry timer
4454 // doesn't get started.
4455 content->SetProperty(nsGkAtoms::MinimalDisplayPort,
4456 reinterpret_cast<void*>(true));
4458 DisplayPortUtils::SetDisplayPortMargins(
4459 content, PresShell(), DisplayPortMargins::Empty(content),
4460 DisplayPortUtils::ClearMinimalDisplayPortProperty::No, 0,
4461 DisplayPortUtils::RepaintMode::DoNotRepaint);
4464 bool usingDisplayPort = DisplayPortUtils::HasDisplayPort(content);
4465 if (aBuilder->IsPaintingToWindow()) {
4466 if (aSetBase) {
4467 nsRect displayportBase = *aVisibleRect;
4468 nsPresContext* pc = PresContext();
4470 bool isContentRootDoc = pc->IsRootContentDocumentCrossProcess();
4471 bool isChromeRootDoc =
4472 !pc->Document()->IsContentDocument() && !pc->GetParentPresContext();
4474 if (mIsRoot && (isContentRootDoc || isChromeRootDoc)) {
4475 displayportBase =
4476 nsRect(nsPoint(0, 0),
4477 nsLayoutUtils::CalculateCompositionSizeForFrame(this));
4478 } else {
4479 // Make the displayport base equal to the visible rect restricted to
4480 // the scrollport and the root composition bounds, relative to the
4481 // scrollport.
4482 displayportBase = aVisibleRect->Intersect(mScrollPort);
4484 mozilla::layers::ScrollableLayerGuid::ViewID viewID =
4485 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
4486 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Verbose)) {
4487 nsLayoutUtils::FindIDFor(GetContent(), &viewID);
4488 MOZ_LOG(
4489 sDisplayportLog, LogLevel::Verbose,
4490 ("Scroll id %" PRIu64 " has visible rect %s, scroll port %s\n",
4491 viewID, ToString(*aVisibleRect).c_str(),
4492 ToString(mScrollPort).c_str()));
4495 // Only restrict to the root displayport bounds if necessary,
4496 // as the required coordinate transformation is expensive.
4497 // And don't call RestrictToRootDisplayPort if we would be trying to
4498 // restrict to our own display port, which doesn't make sense (ie if we
4499 // are a root scroll frame in a process root prescontext).
4500 if (usingDisplayPort && (!mIsRoot || pc->GetParentPresContext())) {
4501 displayportBase = RestrictToRootDisplayPort(displayportBase);
4502 MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
4503 ("Scroll id %" PRIu64 " has restricted base %s\n", viewID,
4504 ToString(displayportBase).c_str()));
4506 displayportBase -= mScrollPort.TopLeft();
4509 DisplayPortUtils::SetDisplayPortBase(GetContent(), displayportBase);
4512 // If we don't have aSetBase == true then should have already
4513 // been called with aSetBase == true which should have set a
4514 // displayport base.
4515 MOZ_ASSERT(content->GetProperty(nsGkAtoms::DisplayPortBase));
4516 nsRect displayPort;
4517 usingDisplayPort = DisplayPortUtils::GetDisplayPort(
4518 content, &displayPort,
4519 DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame));
4521 auto OverrideDirtyRect = [&](const nsRect& aRect) {
4522 *aDirtyRect = aRect;
4523 if (aDirtyRectHasBeenOverriden) {
4524 *aDirtyRectHasBeenOverriden = true;
4528 if (usingDisplayPort) {
4529 // Override the dirty rectangle if the displayport has been set.
4530 *aVisibleRect = displayPort;
4531 if (aBuilder->IsReusingStackingContextItems() ||
4532 !aBuilder->IsPartialUpdate() || aBuilder->InInvalidSubtree() ||
4533 IsFrameModified()) {
4534 OverrideDirtyRect(displayPort);
4535 } else if (HasOverrideDirtyRegion()) {
4536 nsRect* rect = GetProperty(
4537 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
4538 if (rect) {
4539 OverrideDirtyRect(*rect);
4542 } else if (mIsRoot) {
4543 // The displayPort getter takes care of adjusting for resolution. So if
4544 // we have resolution but no displayPort then we need to adjust for
4545 // resolution here.
4546 auto* presShell = PresShell();
4547 *aVisibleRect =
4548 aVisibleRect->RemoveResolution(presShell->GetResolution());
4549 *aDirtyRect = aDirtyRect->RemoveResolution(presShell->GetResolution());
4553 // Since making new layers is expensive, only create a scrollable layer
4554 // for some scroll frames.
4555 // When a displayport is being used, force building of a layer so that
4556 // the compositor can find the scrollable layer for async scrolling.
4557 // If the element is marked 'scrollgrab', also force building of a layer
4558 // so that APZ can implement scroll grabbing.
4559 mWillBuildScrollableLayer = usingDisplayPort ||
4560 nsContentUtils::HasScrollgrab(content) ||
4561 mZoomableByAPZ;
4562 return mWillBuildScrollableLayer;
4565 void nsHTMLScrollFrame::NotifyApzTransaction() {
4566 mAllowScrollOriginDowngrade = true;
4567 mApzScrollPos = GetScrollPosition();
4568 mApzAnimationRequested = IsLastScrollUpdateAnimating();
4569 mApzAnimationTriggeredByScriptRequested =
4570 IsLastScrollUpdateTriggeredByScriptAnimating();
4571 mScrollUpdates.Clear();
4572 if (mIsRoot) {
4573 PresShell()->SetResolutionUpdated(false);
4577 Maybe<ScrollMetadata> nsHTMLScrollFrame::ComputeScrollMetadata(
4578 WebRenderLayerManager* aLayerManager, const nsIFrame* aItemFrame,
4579 const nsPoint& aOffsetToReferenceFrame) const {
4580 if (!mWillBuildScrollableLayer) {
4581 return Nothing();
4584 bool isRootContent =
4585 mIsRoot && PresContext()->IsRootContentDocumentCrossProcess();
4587 MOZ_ASSERT(mScrolledFrame->GetContent());
4589 return Some(nsLayoutUtils::ComputeScrollMetadata(
4590 mScrolledFrame, this, GetContent(), aItemFrame, aOffsetToReferenceFrame,
4591 aLayerManager, mScrollParentID, mScrollPort.Size(), isRootContent));
4594 bool nsHTMLScrollFrame::IsRectNearlyVisible(const nsRect& aRect) const {
4595 // Use the right rect depending on if a display port is set.
4596 nsRect displayPort;
4597 bool usingDisplayport = DisplayPortUtils::GetDisplayPort(
4598 GetContent(), &displayPort,
4599 DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame));
4601 if (mIsRoot && !usingDisplayport &&
4602 PresContext()->IsRootContentDocumentInProcess() &&
4603 !PresContext()->IsRootContentDocumentCrossProcess()) {
4604 // In the case of the root scroller of OOP iframes, there are cases where
4605 // any display port value isn't set, e.g. the iframe element is out of view
4606 // in the parent document. In such cases we'd consider the iframe is not
4607 // visible.
4608 return false;
4611 return aRect.Intersects(
4612 ExpandRectToNearlyVisible(usingDisplayport ? displayPort : mScrollPort));
4615 OverscrollBehaviorInfo nsHTMLScrollFrame::GetOverscrollBehaviorInfo() const {
4616 nsIFrame* frame = GetFrameForStyle();
4617 if (!frame) {
4618 return {};
4621 auto& disp = *frame->StyleDisplay();
4622 return OverscrollBehaviorInfo::FromStyleConstants(disp.mOverscrollBehaviorX,
4623 disp.mOverscrollBehaviorY);
4626 ScrollStyles nsHTMLScrollFrame::GetScrollStyles() const {
4627 nsPresContext* presContext = PresContext();
4628 if (!presContext->IsDynamic() &&
4629 !(mIsRoot && presContext->HasPaginatedScrolling())) {
4630 return ScrollStyles(StyleOverflow::Hidden, StyleOverflow::Hidden);
4633 if (!mIsRoot) {
4634 return ScrollStyles(*StyleDisplay(),
4635 ScrollStyles::MapOverflowToValidScrollStyle);
4638 ScrollStyles result = presContext->GetViewportScrollStylesOverride();
4639 if (nsDocShell* ds = presContext->GetDocShell()) {
4640 switch (ds->ScrollbarPreference()) {
4641 case ScrollbarPreference::Auto:
4642 break;
4643 case ScrollbarPreference::Never:
4644 result.mHorizontal = result.mVertical = StyleOverflow::Hidden;
4645 break;
4648 return result;
4651 nsRect nsHTMLScrollFrame::GetLayoutScrollRange() const {
4652 return GetScrollRange(mScrollPort.width, mScrollPort.height);
4655 nsRect nsHTMLScrollFrame::GetScrollRange(nscoord aWidth,
4656 nscoord aHeight) const {
4657 nsRect range = GetScrolledRect();
4658 range.width = std::max(range.width - aWidth, 0);
4659 range.height = std::max(range.height - aHeight, 0);
4660 return range;
4663 nsRect nsHTMLScrollFrame::GetVisualScrollRange() const {
4664 nsSize visualViewportSize = GetVisualViewportSize();
4665 return GetScrollRange(visualViewportSize.width, visualViewportSize.height);
4668 nsSize nsHTMLScrollFrame::GetVisualViewportSize() const {
4669 auto* presShell = PresShell();
4670 if (mIsRoot && presShell->IsVisualViewportSizeSet()) {
4671 return presShell->GetVisualViewportSize();
4673 return mScrollPort.Size();
4676 nsPoint nsHTMLScrollFrame::GetVisualViewportOffset() const {
4677 if (mIsRoot) {
4678 auto* presShell = PresShell();
4679 if (auto pendingUpdate = presShell->GetPendingVisualScrollUpdate()) {
4680 // The pending visual scroll update on the PresShell contains a raw,
4681 // unclamped offset (basically, whatever was passed to ScrollToVisual()).
4682 // It will be clamped on the APZ side, but if we use it as the
4683 // main-thread's visual viewport offset we need to clamp it ourselves.
4684 // Use GetScrollRangeForUserInputEvents() to do the clamping because this
4685 // the scroll range that APZ will use.
4686 return GetScrollRangeForUserInputEvents().ClampPoint(
4687 pendingUpdate->mVisualScrollOffset);
4689 return presShell->GetVisualViewportOffset();
4691 return GetScrollPosition();
4694 bool nsHTMLScrollFrame::SetVisualViewportOffset(const nsPoint& aOffset,
4695 bool aRepaint) {
4696 MOZ_ASSERT(mIsRoot);
4697 AutoWeakFrame weakFrame(this);
4698 AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame,
4699 !aRepaint);
4701 bool retVal =
4702 PresShell()->SetVisualViewportOffset(aOffset, GetScrollPosition());
4703 if (!weakFrame.IsAlive()) {
4704 return false;
4706 return retVal;
4709 nsRect nsHTMLScrollFrame::GetVisualOptimalViewingRect() const {
4710 auto* presShell = PresShell();
4711 nsRect rect = mScrollPort;
4712 if (mIsRoot && presShell->IsVisualViewportSizeSet() &&
4713 presShell->IsVisualViewportOffsetSet()) {
4714 rect = nsRect(mScrollPort.TopLeft() - GetScrollPosition() +
4715 presShell->GetVisualViewportOffset(),
4716 presShell->GetVisualViewportSize());
4718 // NOTE: We intentionally resolve scroll-padding percentages against the
4719 // scrollport even when the visual viewport is set, see
4720 // https://github.com/w3c/csswg-drafts/issues/4393.
4721 rect.Deflate(GetScrollPadding());
4722 return rect;
4725 static void AdjustDestinationForWholeDelta(const nsIntPoint& aDelta,
4726 const nsRect& aScrollRange,
4727 nsPoint& aPoint) {
4728 if (aDelta.x < 0) {
4729 aPoint.x = aScrollRange.X();
4730 } else if (aDelta.x > 0) {
4731 aPoint.x = aScrollRange.XMost();
4733 if (aDelta.y < 0) {
4734 aPoint.y = aScrollRange.Y();
4735 } else if (aDelta.y > 0) {
4736 aPoint.y = aScrollRange.YMost();
4741 * Calculate lower/upper scrollBy range in given direction.
4742 * @param aDelta specifies scrollBy direction, if 0 then range will be 0 size
4743 * @param aPos desired destination in AppUnits
4744 * @param aNeg/PosTolerance defines relative range distance
4745 * below and above of aPos point
4746 * @param aMultiplier used for conversion of tolerance into appUnis
4748 static void CalcRangeForScrollBy(int32_t aDelta, nscoord aPos,
4749 float aNegTolerance, float aPosTolerance,
4750 nscoord aMultiplier, nscoord* aLower,
4751 nscoord* aUpper) {
4752 if (!aDelta) {
4753 *aLower = *aUpper = aPos;
4754 return;
4756 *aLower = aPos - NSToCoordRound(aMultiplier *
4757 (aDelta > 0 ? aNegTolerance : aPosTolerance));
4758 *aUpper = aPos + NSToCoordRound(aMultiplier *
4759 (aDelta > 0 ? aPosTolerance : aNegTolerance));
4762 void nsHTMLScrollFrame::ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit,
4763 ScrollMode aMode, nsIntPoint* aOverflow,
4764 ScrollOrigin aOrigin,
4765 nsIScrollableFrame::ScrollMomentum aMomentum,
4766 ScrollSnapFlags aSnapFlags) {
4767 // When a smooth scroll is being processed on a frame, mouse wheel and
4768 // trackpad momentum scroll event updates must notcancel the SMOOTH or
4769 // SMOOTH_MSD scroll animations, enabling Javascript that depends on them to
4770 // be responsive without forcing the user to wait for the fling animations to
4771 // completely stop.
4772 switch (aMomentum) {
4773 case nsIScrollableFrame::NOT_MOMENTUM:
4774 mIgnoreMomentumScroll = false;
4775 break;
4776 case nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT:
4777 if (mIgnoreMomentumScroll) {
4778 return;
4780 break;
4783 if (mAsyncSmoothMSDScroll != nullptr) {
4784 // When CSSOM-View scroll-behavior smooth scrolling is interrupted,
4785 // the scroll is not completed to avoid non-smooth snapping to the
4786 // prior smooth scroll's destination.
4787 mDestination = GetScrollPosition();
4790 nsSize deltaMultiplier;
4791 float negativeTolerance;
4792 float positiveTolerance;
4793 if (aOrigin == ScrollOrigin::NotSpecified) {
4794 aOrigin = ScrollOrigin::Other;
4796 bool isGenericOrigin = (aOrigin == ScrollOrigin::Other);
4798 bool askApzToDoTheScroll = false;
4799 if ((aSnapFlags == ScrollSnapFlags::Disabled || !NeedsScrollSnap()) &&
4800 gfxPlatform::UseDesktopZoomingScrollbars() &&
4801 nsLayoutUtils::AsyncPanZoomEnabled(this) &&
4802 !nsLayoutUtils::ShouldDisableApzForElement(GetContent()) &&
4803 (WantAsyncScroll() || mZoomableByAPZ) &&
4804 CanApzScrollInTheseDirections(DirectionsInDelta(aDelta))) {
4805 askApzToDoTheScroll = true;
4808 switch (aUnit) {
4809 case ScrollUnit::DEVICE_PIXELS: {
4810 nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
4811 deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
4812 if (isGenericOrigin) {
4813 aOrigin = ScrollOrigin::Pixels;
4815 negativeTolerance = positiveTolerance = 0.5f;
4816 break;
4818 case ScrollUnit::LINES: {
4819 deltaMultiplier = GetLineScrollAmount();
4820 if (isGenericOrigin) {
4821 aOrigin = ScrollOrigin::Lines;
4823 negativeTolerance = positiveTolerance = 0.1f;
4824 break;
4826 case ScrollUnit::PAGES: {
4827 deltaMultiplier = GetPageScrollAmount();
4828 if (isGenericOrigin) {
4829 aOrigin = ScrollOrigin::Pages;
4831 negativeTolerance = 0.05f;
4832 positiveTolerance = 0;
4833 break;
4835 case ScrollUnit::WHOLE: {
4836 if (askApzToDoTheScroll) {
4837 MOZ_ASSERT(aDelta.x >= -1 && aDelta.x <= 1 && aDelta.y >= -1 &&
4838 aDelta.y <= 1);
4839 deltaMultiplier = GetScrollRangeForUserInputEvents().Size();
4840 break;
4841 } else {
4842 nsPoint pos = GetScrollPosition();
4843 AdjustDestinationForWholeDelta(aDelta, GetLayoutScrollRange(), pos);
4844 ScrollToWithOrigin(
4845 pos, nullptr /* range */,
4846 ScrollOperationParams{aMode, ScrollOrigin::Other, aSnapFlags,
4847 ScrollTriggeredByScript::No});
4848 // 'this' might be destroyed here
4849 if (aOverflow) {
4850 *aOverflow = nsIntPoint(0, 0);
4852 return;
4855 default:
4856 NS_ERROR("Invalid scroll mode");
4857 return;
4860 if (askApzToDoTheScroll) {
4861 nsPoint delta(
4862 NSCoordSaturatingNonnegativeMultiply(aDelta.x, deltaMultiplier.width),
4863 NSCoordSaturatingNonnegativeMultiply(aDelta.y, deltaMultiplier.height));
4865 AppendScrollUpdate(
4866 ScrollPositionUpdate::NewPureRelativeScroll(aOrigin, aMode, delta));
4868 nsIContent* content = GetContent();
4869 if (!DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(content)) {
4870 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) {
4871 mozilla::layers::ScrollableLayerGuid::ViewID viewID =
4872 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
4873 nsLayoutUtils::FindIDFor(content, &viewID);
4874 MOZ_LOG(
4875 sDisplayportLog, LogLevel::Debug,
4876 ("ScrollBy setting displayport on scrollId=%" PRIu64 "\n", viewID));
4879 DisplayPortUtils::CalculateAndSetDisplayPortMargins(
4880 GetScrollTargetFrame(), DisplayPortUtils::RepaintMode::Repaint);
4881 nsIFrame* frame = do_QueryFrame(GetScrollTargetFrame());
4882 DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
4883 frame);
4886 SchedulePaint();
4887 return;
4890 nsPoint newPos(NSCoordSaturatingAdd(mDestination.x,
4891 NSCoordSaturatingNonnegativeMultiply(
4892 aDelta.x, deltaMultiplier.width)),
4893 NSCoordSaturatingAdd(mDestination.y,
4894 NSCoordSaturatingNonnegativeMultiply(
4895 aDelta.y, deltaMultiplier.height)));
4897 Maybe<SnapTarget> snapTarget;
4898 if (aSnapFlags != ScrollSnapFlags::Disabled) {
4899 if (NeedsScrollSnap()) {
4900 nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
4901 deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
4902 negativeTolerance = 0.1f;
4903 positiveTolerance = 0;
4904 ScrollUnit snapUnit = aUnit;
4905 if (aOrigin == ScrollOrigin::MouseWheel) {
4906 // When using a clicky scroll wheel, snap point selection works the same
4907 // as keyboard up/down/left/right navigation, but with varying amounts
4908 // of scroll delta.
4909 snapUnit = ScrollUnit::LINES;
4911 snapTarget = GetSnapPointForDestination(snapUnit, aSnapFlags,
4912 mDestination, newPos);
4913 if (snapTarget) {
4914 newPos = snapTarget->mPosition;
4919 // Calculate desired range values.
4920 nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY;
4921 CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance,
4922 deltaMultiplier.width, &rangeLowerX, &rangeUpperX);
4923 CalcRangeForScrollBy(aDelta.y, newPos.y, negativeTolerance, positiveTolerance,
4924 deltaMultiplier.height, &rangeLowerY, &rangeUpperY);
4925 nsRect range(rangeLowerX, rangeLowerY, rangeUpperX - rangeLowerX,
4926 rangeUpperY - rangeLowerY);
4927 AutoWeakFrame weakFrame(this);
4928 ScrollToWithOrigin(
4929 newPos, &range,
4930 snapTarget ? ScrollOperationParams{aMode, aOrigin,
4931 std::move(snapTarget->mTargetIds)}
4932 : ScrollOperationParams{aMode, aOrigin});
4933 if (!weakFrame.IsAlive()) {
4934 return;
4937 if (aOverflow) {
4938 nsPoint clampAmount = newPos - mDestination;
4939 float appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
4940 *aOverflow =
4941 nsIntPoint(NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel),
4942 NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel));
4945 if (aUnit == ScrollUnit::DEVICE_PIXELS &&
4946 !nsLayoutUtils::AsyncPanZoomEnabled(this)) {
4947 // When APZ is disabled, we must track the velocity
4948 // on the main thread; otherwise, the APZC will manage this.
4949 mVelocityQueue.Sample(GetScrollPosition());
4953 void nsHTMLScrollFrame::ScrollByCSSPixelsInternal(const CSSIntPoint& aDelta,
4954 ScrollMode aMode,
4955 ScrollSnapFlags aSnapFlags) {
4956 nsPoint current = GetScrollPosition();
4957 // `current` value above might be a value which was aligned to in
4958 // layer-pixels, so starting from such points will make the difference between
4959 // the given position in script (e.g. scrollTo) and the aligned position
4960 // larger, in the worst case the difference can be observed in CSS pixels.
4961 // To avoid it, we use the current position in CSS pixels as the start
4962 // position. Hopefully it exactly matches the position where it was given by
4963 // the previous scrolling operation, but there may be some edge cases where
4964 // the current position in CSS pixels differs from the given position, the
4965 // cases should be fixed in bug 1556685.
4966 CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels();
4967 nsPoint pt = CSSPoint::ToAppUnits(currentCSSPixels + aDelta);
4969 nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
4970 nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2 * halfPixel - 1,
4971 2 * halfPixel - 1);
4972 // XXX I don't think the following blocks are needed anymore, now that
4973 // ScrollToImpl simply tries to scroll an integer number of layer
4974 // pixels from the current position
4975 if (aDelta.x == 0.0f) {
4976 pt.x = current.x;
4977 range.x = pt.x;
4978 range.width = 0;
4980 if (aDelta.y == 0.0f) {
4981 pt.y = current.y;
4982 range.y = pt.y;
4983 range.height = 0;
4985 ScrollToWithOrigin(
4986 pt, &range,
4987 ScrollOperationParams{aMode, ScrollOrigin::Relative, aSnapFlags,
4988 ScrollTriggeredByScript::Yes});
4989 // 'this' might be destroyed here
4992 void nsHTMLScrollFrame::ScrollSnap(ScrollMode aMode) {
4993 float flingSensitivity =
4994 StaticPrefs::layout_css_scroll_snap_prediction_sensitivity();
4995 int maxVelocity =
4996 StaticPrefs::layout_css_scroll_snap_prediction_max_velocity();
4997 maxVelocity = nsPresContext::CSSPixelsToAppUnits(maxVelocity);
4998 int maxOffset = maxVelocity * flingSensitivity;
4999 nsPoint velocity = mVelocityQueue.GetVelocity();
5000 // Multiply each component individually to avoid integer multiply
5001 nsPoint predictedOffset =
5002 nsPoint(velocity.x * flingSensitivity, velocity.y * flingSensitivity);
5003 predictedOffset.Clamp(maxOffset);
5004 nsPoint pos = GetScrollPosition();
5005 nsPoint destinationPos = pos + predictedOffset;
5006 ScrollSnap(destinationPos, aMode);
5009 void nsHTMLScrollFrame::ScrollSnap(const nsPoint& aDestination,
5010 ScrollMode aMode) {
5011 nsRect scrollRange = GetLayoutScrollRange();
5012 nsPoint pos = GetScrollPosition();
5013 nsPoint snapDestination = scrollRange.ClampPoint(aDestination);
5014 ScrollSnapFlags snapFlags = ScrollSnapFlags::IntendedEndPosition;
5015 if (mVelocityQueue.GetVelocity() != nsPoint()) {
5016 snapFlags |= ScrollSnapFlags::IntendedDirection;
5019 // Bug 1776624 : Consider using mDestination as |aStartPos| argument for this
5020 // GetSnapPointForDestination call, this function call is the only one call
5021 // site using `GetScrollPosition()` as |aStartPos|.
5022 if (auto snapTarget = GetSnapPointForDestination(
5023 ScrollUnit::DEVICE_PIXELS, snapFlags, pos, snapDestination)) {
5024 snapDestination = snapTarget->mPosition;
5025 ScrollToWithOrigin(
5026 snapDestination, nullptr /* range */,
5027 ScrollOperationParams{aMode, ScrollOrigin::Other,
5028 std::move(snapTarget->mTargetIds)});
5032 nsSize nsHTMLScrollFrame::GetLineScrollAmount() const {
5033 RefPtr<nsFontMetrics> fm =
5034 nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
5035 NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
5036 int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
5037 nscoord minScrollAmountInAppUnits =
5038 std::max(1, StaticPrefs::mousewheel_min_line_scroll_amount()) *
5039 appUnitsPerDevPixel;
5040 nscoord horizontalAmount = fm ? fm->AveCharWidth() : 0;
5041 nscoord verticalAmount = fm ? fm->MaxHeight() : 0;
5042 return nsSize(std::max(horizontalAmount, minScrollAmountInAppUnits),
5043 std::max(verticalAmount, minScrollAmountInAppUnits));
5047 * Compute the scrollport size excluding any fixed-pos and sticky-pos (that are
5048 * stuck) headers and footers. A header or footer is an box that spans that
5049 * entire width of the viewport and touches the top (or bottom, respectively) of
5050 * the viewport. We also want to consider fixed/sticky elements that stack or
5051 * overlap to effectively create a larger header or footer. Headers and footers
5052 * that cover more than a third of the the viewport are ignored since they
5053 * probably aren't true headers and footers and we don't want to restrict
5054 * scrolling too much in such cases. This is a bit conservative --- some
5055 * pages use elements as headers or footers that don't span the entire width
5056 * of the viewport --- but it should be a good start.
5058 * If aViewportFrame is non-null then the scroll frame is the root scroll
5059 * frame and we should consider fixed-pos items.
5061 struct TopAndBottom {
5062 TopAndBottom(nscoord aTop, nscoord aBottom) : top(aTop), bottom(aBottom) {}
5064 nscoord top, bottom;
5066 struct TopComparator {
5067 bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
5068 return A.top == B.top;
5070 bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
5071 return A.top < B.top;
5074 struct ReverseBottomComparator {
5075 bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
5076 return A.bottom == B.bottom;
5078 bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
5079 return A.bottom > B.bottom;
5083 static void AddToListIfHeaderFooter(nsIFrame* aFrame,
5084 nsIFrame* aScrollPortFrame,
5085 const nsRect& aScrollPort,
5086 nsTArray<TopAndBottom>& aList) {
5087 nsRect r = aFrame->GetRectRelativeToSelf();
5088 r = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, r, aScrollPortFrame);
5089 r = r.Intersect(aScrollPort);
5090 if ((r.width >= aScrollPort.width / 2 ||
5091 r.width >= NSIntPixelsToAppUnits(800, AppUnitsPerCSSPixel())) &&
5092 r.height <= aScrollPort.height / 3) {
5093 aList.AppendElement(TopAndBottom(r.y, r.YMost()));
5097 static nsSize GetScrollPortSizeExcludingHeadersAndFooters(
5098 nsIFrame* aScrollFrame, nsIFrame* aViewportFrame,
5099 const nsRect& aScrollPort) {
5100 AutoTArray<TopAndBottom, 10> list;
5101 if (aViewportFrame) {
5102 for (nsIFrame* f : aViewportFrame->GetChildList(FrameChildListID::Fixed)) {
5103 AddToListIfHeaderFooter(f, aViewportFrame, aScrollPort, list);
5107 // Add sticky frames that are currently in "fixed" positions
5108 StickyScrollContainer* ssc =
5109 StickyScrollContainer::GetStickyScrollContainerForScrollFrame(
5110 aScrollFrame);
5111 if (ssc) {
5112 const nsTArray<nsIFrame*>& stickyFrames = ssc->GetFrames();
5113 for (nsIFrame* f : stickyFrames) {
5114 // If it's acting like fixed position.
5115 if (ssc->IsStuckInYDirection(f)) {
5116 AddToListIfHeaderFooter(f, aScrollFrame, aScrollPort, list);
5121 list.Sort(TopComparator());
5122 nscoord headerBottom = 0;
5123 for (uint32_t i = 0; i < list.Length(); ++i) {
5124 if (list[i].top <= headerBottom) {
5125 headerBottom = std::max(headerBottom, list[i].bottom);
5129 list.Sort(ReverseBottomComparator());
5130 nscoord footerTop = aScrollPort.height;
5131 for (uint32_t i = 0; i < list.Length(); ++i) {
5132 if (list[i].bottom >= footerTop) {
5133 footerTop = std::min(footerTop, list[i].top);
5137 headerBottom = std::min(aScrollPort.height / 3, headerBottom);
5138 footerTop = std::max(aScrollPort.height - aScrollPort.height / 3, footerTop);
5140 return nsSize(aScrollPort.width, footerTop - headerBottom);
5143 nsSize nsHTMLScrollFrame::GetPageScrollAmount() const {
5144 nsSize effectiveScrollPortSize;
5146 if (GetVisualViewportSize() != mScrollPort.Size()) {
5147 // We want to use the visual viewport size if one is set.
5148 // The headers/footers adjustment is too complicated to do if there is a
5149 // visual viewport that differs from the layout viewport, this is probably
5150 // okay.
5151 effectiveScrollPortSize = GetVisualViewportSize();
5152 } else {
5153 // Reduce effective scrollport height by the height of any
5154 // fixed-pos/sticky-pos headers or footers
5155 effectiveScrollPortSize = GetScrollPortSizeExcludingHeadersAndFooters(
5156 const_cast<nsHTMLScrollFrame*>(this),
5157 mIsRoot ? PresShell()->GetRootFrame() : nullptr, mScrollPort);
5160 nsSize lineScrollAmount = GetLineScrollAmount();
5162 // The page increment is the size of the page, minus the smaller of
5163 // 10% of the size or 2 lines.
5164 return nsSize(effectiveScrollPortSize.width -
5165 std::min(effectiveScrollPortSize.width / 10,
5166 2 * lineScrollAmount.width),
5167 effectiveScrollPortSize.height -
5168 std::min(effectiveScrollPortSize.height / 10,
5169 2 * lineScrollAmount.height));
5173 * this code is resposible for restoring the scroll position back to some
5174 * saved position. if the user has not moved the scroll position manually
5175 * we keep scrolling down until we get to our original position. keep in
5176 * mind that content could incrementally be coming in. we only want to stop
5177 * when we reach our new position.
5179 void nsHTMLScrollFrame::ScrollToRestoredPosition() {
5180 if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
5181 return;
5183 // make sure our scroll position did not change for where we last put
5184 // it. if it does then the user must have moved it, and we no longer
5185 // need to restore.
5187 // In the RTL case, we check whether the scroll position changed using the
5188 // logical scroll position, but we scroll to the physical scroll position in
5189 // all cases
5191 // The layout offset we want to restore is the same as the visual offset
5192 // (for now, may change in bug 1499210), but clamped to the layout scroll
5193 // range (which can be a subset of the visual scroll range).
5194 // Note that we can't do the clamping when initializing mRestorePos in
5195 // RestoreState(), since the scrollable rect (which the clamping depends
5196 // on) can change over the course of the restoration process.
5197 nsPoint layoutRestorePos = GetLayoutScrollRange().ClampPoint(mRestorePos);
5198 nsPoint visualRestorePos = GetVisualScrollRange().ClampPoint(mRestorePos);
5200 // Continue restoring until both the layout and visual scroll positions
5201 // reach the destination. (Note that the two can only be different for
5202 // the root content document's root scroll frame, and when zoomed in).
5203 // This is necessary to avoid situations where the two offsets get stuck
5204 // at different values and nothing reconciles them (see bug 1519621 comment
5205 // 8).
5206 nsPoint logicalLayoutScrollPos = GetLogicalScrollPosition();
5208 SCROLLRESTORE_LOG(
5209 "%p: ScrollToRestoredPosition (mRestorePos=%s, mLastPos=%s, "
5210 "layoutRestorePos=%s, visualRestorePos=%s, "
5211 "logicalLayoutScrollPos=%s, "
5212 "GetLogicalVisualViewportOffset()=%s)\n",
5213 this, ToString(mRestorePos).c_str(), ToString(mLastPos).c_str(),
5214 ToString(layoutRestorePos).c_str(), ToString(visualRestorePos).c_str(),
5215 ToString(logicalLayoutScrollPos).c_str(),
5216 ToString(GetLogicalVisualViewportOffset()).c_str());
5218 // if we didn't move, we still need to restore
5219 if (GetLogicalVisualViewportOffset() == mLastPos ||
5220 logicalLayoutScrollPos == mLastPos) {
5221 // if our desired position is different to the scroll position, scroll.
5222 // remember that we could be incrementally loading so we may enter
5223 // and scroll many times.
5224 if (mRestorePos != mLastPos /* GetLogicalVisualViewportOffset() */ ||
5225 layoutRestorePos != logicalLayoutScrollPos) {
5226 LoadingState state = GetPageLoadingState();
5227 if (state == LoadingState::Stopped && !IsSubtreeDirty()) {
5228 return;
5230 nsPoint visualScrollToPos = visualRestorePos;
5231 nsPoint layoutScrollToPos = layoutRestorePos;
5232 if (!IsPhysicalLTR()) {
5233 // convert from logical to physical scroll position
5234 visualScrollToPos.x -=
5235 (GetVisualViewportSize().width - mScrolledFrame->GetRect().width);
5236 layoutScrollToPos.x -=
5237 (GetVisualViewportSize().width - mScrolledFrame->GetRect().width);
5239 AutoWeakFrame weakFrame(this);
5240 // It's very important to pass ScrollOrigin::Restore here, so
5241 // ScrollToWithOrigin won't clear out mRestorePos.
5242 ScrollToWithOrigin(
5243 layoutScrollToPos, nullptr,
5244 ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Restore});
5245 if (!weakFrame.IsAlive()) {
5246 return;
5248 if (mIsRoot) {
5249 PresShell()->ScrollToVisual(visualScrollToPos, FrameMetrics::eRestore,
5250 ScrollMode::Instant);
5252 if (state == LoadingState::Loading || IsSubtreeDirty()) {
5253 // If we're trying to do a history scroll restore, then we want to
5254 // keep trying this until we succeed, because the page can be loading
5255 // incrementally. So re-get the scroll position for the next iteration,
5256 // it might not be exactly equal to mRestorePos due to rounding and
5257 // clamping.
5258 mLastPos = GetLogicalVisualViewportOffset();
5259 return;
5262 // If we get here, either we reached the desired position (mLastPos ==
5263 // mRestorePos) or we're not trying to do a history scroll restore, so
5264 // we can stop after the scroll attempt above.
5265 mRestorePos.y = -1;
5266 mLastPos.x = -1;
5267 mLastPos.y = -1;
5268 } else {
5269 // user moved the position, so we won't need to restore
5270 mLastPos.x = -1;
5271 mLastPos.y = -1;
5275 auto nsHTMLScrollFrame::GetPageLoadingState() -> LoadingState {
5276 bool loadCompleted = false, stopped = false;
5277 nsCOMPtr<nsIDocShell> ds = GetContent()->GetComposedDoc()->GetDocShell();
5278 if (ds) {
5279 nsCOMPtr<nsIContentViewer> cv;
5280 ds->GetContentViewer(getter_AddRefs(cv));
5281 if (cv) {
5282 loadCompleted = cv->GetLoadCompleted();
5283 stopped = cv->GetIsStopped();
5286 return loadCompleted
5287 ? (stopped ? LoadingState::Stopped : LoadingState::Loaded)
5288 : LoadingState::Loading;
5291 nsHTMLScrollFrame::OverflowState nsHTMLScrollFrame::GetOverflowState() const {
5292 nsSize scrollportSize = mScrollPort.Size();
5293 nsSize childSize = GetScrolledRect().Size();
5295 OverflowState result = OverflowState::None;
5297 if (childSize.height > scrollportSize.height) {
5298 result |= OverflowState::Vertical;
5301 if (childSize.width > scrollportSize.width) {
5302 result |= OverflowState::Horizontal;
5305 return result;
5308 nsresult nsHTMLScrollFrame::FireScrollPortEvent() {
5309 mAsyncScrollPortEvent.Forget();
5311 // TODO(emilio): why do we need the whole WillPaintObserver infrastructure and
5312 // can't use AddScriptRunner & co? I guess it made sense when we used
5313 // WillPaintObserver for scroll events too, or when this used to flush.
5315 // Should we remove this?
5317 OverflowState overflowState = GetOverflowState();
5319 bool newVerticalOverflow = !!(overflowState & OverflowState::Vertical);
5320 bool vertChanged = mVerticalOverflow != newVerticalOverflow;
5322 bool newHorizontalOverflow = !!(overflowState & OverflowState::Horizontal);
5323 bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
5325 if (!vertChanged && !horizChanged) {
5326 return NS_OK;
5329 // If both either overflowed or underflowed then we dispatch only one
5330 // DOM event.
5331 bool both = vertChanged && horizChanged &&
5332 newVerticalOverflow == newHorizontalOverflow;
5333 InternalScrollPortEvent::OrientType orient;
5334 if (both) {
5335 orient = InternalScrollPortEvent::eBoth;
5336 mHorizontalOverflow = newHorizontalOverflow;
5337 mVerticalOverflow = newVerticalOverflow;
5338 } else if (vertChanged) {
5339 orient = InternalScrollPortEvent::eVertical;
5340 mVerticalOverflow = newVerticalOverflow;
5341 if (horizChanged) {
5342 // We need to dispatch a separate horizontal DOM event. Do that the next
5343 // time around since dispatching the vertical DOM event might destroy
5344 // the frame.
5345 PostOverflowEvent();
5347 } else {
5348 orient = InternalScrollPortEvent::eHorizontal;
5349 mHorizontalOverflow = newHorizontalOverflow;
5352 InternalScrollPortEvent event(
5353 true,
5354 (orient == InternalScrollPortEvent::eHorizontal ? mHorizontalOverflow
5355 : mVerticalOverflow)
5356 ? eScrollPortOverflow
5357 : eScrollPortUnderflow,
5358 nullptr);
5359 event.mOrient = orient;
5361 RefPtr<nsIContent> content = GetContent();
5362 RefPtr<nsPresContext> presContext = PresContext();
5363 return EventDispatcher::Dispatch(content, presContext, &event);
5366 void nsHTMLScrollFrame::PostScrollEndEvent() {
5367 if (mScrollEndEvent) {
5368 return;
5371 // The ScrollEndEvent constructor registers itself with the refresh driver.
5372 mScrollEndEvent = new ScrollEndEvent(this);
5375 void nsHTMLScrollFrame::FireScrollEndEvent() {
5376 MOZ_ASSERT(GetContent());
5377 MOZ_ASSERT(mScrollEndEvent);
5379 RefPtr<nsPresContext> presContext = PresContext();
5380 mScrollEndEvent->Revoke();
5381 mScrollEndEvent = nullptr;
5383 nsEventStatus status = nsEventStatus_eIgnore;
5384 WidgetGUIEvent event(true, eScrollend, nullptr);
5385 event.mFlags.mBubbles = mIsRoot;
5386 event.mFlags.mCancelable = false;
5387 // If apz.scrollend-event.content.enabled is not set, the event should
5388 // only be dispatched to the browser chrome.
5389 event.mFlags.mOnlyChromeDispatch =
5390 !StaticPrefs::apz_scrollend_event_content_enabled();
5391 RefPtr<nsINode> target =
5392 mIsRoot ? static_cast<nsINode*>(presContext->Document()) : GetContent();
5393 EventDispatcher::Dispatch(target, presContext, &event, nullptr, &status);
5396 void nsHTMLScrollFrame::ReloadChildFrames() {
5397 mScrolledFrame = nullptr;
5398 mHScrollbarBox = nullptr;
5399 mVScrollbarBox = nullptr;
5400 mScrollCornerBox = nullptr;
5401 mResizerBox = nullptr;
5403 for (nsIFrame* frame : PrincipalChildList()) {
5404 nsIContent* content = frame->GetContent();
5405 if (content == GetContent()) {
5406 NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
5407 mScrolledFrame = frame;
5408 } else {
5409 nsAutoString value;
5410 if (content->IsElement()) {
5411 content->AsElement()->GetAttr(nsGkAtoms::orient, value);
5413 if (!value.IsEmpty()) {
5414 // probably a scrollbar then
5415 if (value.LowerCaseEqualsLiteral("horizontal")) {
5416 NS_ASSERTION(!mHScrollbarBox,
5417 "Found multiple horizontal scrollbars?");
5418 mHScrollbarBox = do_QueryFrame(frame);
5419 MOZ_ASSERT(mHScrollbarBox, "Not a scrollbar?");
5420 } else {
5421 NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
5422 mVScrollbarBox = do_QueryFrame(frame);
5423 MOZ_ASSERT(mVScrollbarBox, "Not a scrollbar?");
5425 } else if (content->IsXULElement(nsGkAtoms::resizer)) {
5426 NS_ASSERTION(!mResizerBox, "Found multiple resizers");
5427 mResizerBox = frame;
5428 } else if (content->IsXULElement(nsGkAtoms::scrollcorner)) {
5429 // probably a scrollcorner
5430 NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
5431 mScrollCornerBox = frame;
5437 already_AddRefed<Element> nsHTMLScrollFrame::MakeScrollbar(
5438 NodeInfo* aNodeInfo, bool aVertical, AnonymousContentKey& aKey) {
5439 MOZ_ASSERT(aNodeInfo);
5440 MOZ_ASSERT(
5441 aNodeInfo->Equals(nsGkAtoms::scrollbar, nullptr, kNameSpaceID_XUL));
5443 static constexpr nsLiteralString kOrientValues[2] = {
5444 u"horizontal"_ns,
5445 u"vertical"_ns,
5448 aKey = AnonymousContentKey::Type_Scrollbar;
5449 if (aVertical) {
5450 aKey |= AnonymousContentKey::Flag_Vertical;
5453 RefPtr<Element> e;
5454 NS_TrustedNewXULElement(getter_AddRefs(e), do_AddRef(aNodeInfo));
5456 #ifdef DEBUG
5457 // Scrollbars can get restyled by theme changes. Whether such a restyle
5458 // will actually reconstruct them correctly if it involves a frame
5459 // reconstruct... I don't know. :(
5460 e->SetProperty(nsGkAtoms::restylableAnonymousNode,
5461 reinterpret_cast<void*>(true));
5462 #endif // DEBUG
5464 e->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, kOrientValues[aVertical],
5465 false);
5467 if (mIsRoot) {
5468 e->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
5469 reinterpret_cast<void*>(true));
5470 e->SetAttr(kNameSpaceID_None, nsGkAtoms::root_, u"true"_ns, false);
5472 // Don't bother making style caching take [root="true"] styles into account.
5473 aKey = AnonymousContentKey::None;
5476 return e.forget();
5479 bool nsHTMLScrollFrame::IsForTextControlWithNoScrollbars() const {
5480 // FIXME(emilio): we should probably make the scroller inside <input> an
5481 // internal pseudo-element, and then this would be simpler.
5483 // Also, this could just use scrollbar-width these days.
5484 auto* content = GetContent();
5485 if (!content) {
5486 return false;
5488 auto* input = content->GetClosestNativeAnonymousSubtreeRootParentOrHost();
5489 return input && input->IsHTMLElement(nsGkAtoms::input);
5492 auto nsHTMLScrollFrame::GetCurrentAnonymousContent() const
5493 -> EnumSet<AnonymousContentType> {
5494 EnumSet<AnonymousContentType> result;
5495 if (mHScrollbarContent) {
5496 result += AnonymousContentType::HorizontalScrollbar;
5498 if (mVScrollbarContent) {
5499 result += AnonymousContentType::VerticalScrollbar;
5501 if (mResizerContent) {
5502 result += AnonymousContentType::Resizer;
5504 return result;
5507 auto nsHTMLScrollFrame::GetNeededAnonymousContent() const
5508 -> EnumSet<AnonymousContentType> {
5509 nsPresContext* pc = PresContext();
5511 // Don't create scrollbars if we're an SVG document being used as an image,
5512 // or if we're printing/print previewing.
5513 // (In the printing case, we allow scrollbars if this is the child of the
5514 // viewport & paginated scrolling is enabled, because then we must be the
5515 // scroll frame for the print preview window, & that does need scrollbars.)
5516 if (pc->Document()->IsBeingUsedAsImage() ||
5517 (!pc->IsDynamic() && !(mIsRoot && pc->HasPaginatedScrolling()))) {
5518 return {};
5521 if (IsForTextControlWithNoScrollbars()) {
5522 return {};
5525 EnumSet<AnonymousContentType> result;
5526 // If we're the scrollframe for the root, then we want to construct our
5527 // scrollbar frames no matter what. That way later dynamic changes to
5528 // propagated overflow styles will show or hide scrollbars on the viewport
5529 // without requiring frame reconstruction of the viewport (good!).
5531 // TODO(emilio): Figure out if we can remove this special-case now that we
5532 // have more targeted optimizations.
5533 if (mIsRoot) {
5534 result += AnonymousContentType::HorizontalScrollbar;
5535 result += AnonymousContentType::VerticalScrollbar;
5536 // If scrollbar-width is none, don't generate scrollbars.
5537 } else if (StyleUIReset()->ScrollbarWidth() != StyleScrollbarWidth::None) {
5538 ScrollStyles styles = GetScrollStyles();
5539 if (styles.mHorizontal != StyleOverflow::Hidden) {
5540 result += AnonymousContentType::HorizontalScrollbar;
5542 if (styles.mVertical != StyleOverflow::Hidden) {
5543 result += AnonymousContentType::VerticalScrollbar;
5546 // If we have scrollbar-gutter, construct the scrollbar frames to query its
5547 // size to reserve the gutter space at the inline start or end edges.
5548 if (StyleDisplay()->mScrollbarGutter & StyleScrollbarGutter::STABLE) {
5549 result += GetWritingMode().IsVertical()
5550 ? AnonymousContentType::HorizontalScrollbar
5551 : AnonymousContentType::VerticalScrollbar;
5555 // Check if the frame is resizable. Note:
5556 // "The effect of the resize property on generated content is undefined.
5557 // Implementations should not apply the resize property to generated
5558 // content." [1]
5559 // For info on what is generated content, see [2].
5560 // [1]: https://drafts.csswg.org/css-ui/#resize
5561 // [2]: https://www.w3.org/TR/CSS2/generate.html#content
5562 auto resizeStyle = StyleDisplay()->mResize;
5563 if (resizeStyle != StyleResize::None &&
5564 !HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) {
5565 result += AnonymousContentType::Resizer;
5568 return result;
5571 nsresult nsHTMLScrollFrame::CreateAnonymousContent(
5572 nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements) {
5573 typedef nsIAnonymousContentCreator::ContentInfo ContentInfo;
5575 nsPresContext* presContext = PresContext();
5576 nsNodeInfoManager* nodeInfoManager =
5577 presContext->Document()->NodeInfoManager();
5579 auto neededAnonContent = GetNeededAnonymousContent();
5580 if (neededAnonContent.isEmpty()) {
5581 return NS_OK;
5585 RefPtr<NodeInfo> nodeInfo = nodeInfoManager->GetNodeInfo(
5586 nsGkAtoms::scrollbar, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
5587 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
5589 if (neededAnonContent.contains(AnonymousContentType::HorizontalScrollbar)) {
5590 AnonymousContentKey key;
5591 mHScrollbarContent = MakeScrollbar(nodeInfo, /* aVertical */ false, key);
5592 aElements.AppendElement(ContentInfo(mHScrollbarContent, key));
5595 if (neededAnonContent.contains(AnonymousContentType::VerticalScrollbar)) {
5596 AnonymousContentKey key;
5597 mVScrollbarContent = MakeScrollbar(nodeInfo, /* aVertical */ true, key);
5598 aElements.AppendElement(ContentInfo(mVScrollbarContent, key));
5602 if (neededAnonContent.contains(AnonymousContentType::Resizer)) {
5603 MOZ_ASSERT(!mIsRoot, "Root scroll frame shouldn't be resizable");
5605 RefPtr<NodeInfo> nodeInfo;
5606 nodeInfo = nodeInfoManager->GetNodeInfo(
5607 nsGkAtoms::resizer, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
5608 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
5610 NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget());
5612 nsAutoString dir;
5613 switch (StyleDisplay()->mResize) {
5614 case StyleResize::Horizontal:
5615 if (IsScrollbarOnRight()) {
5616 dir.AssignLiteral("right");
5617 } else {
5618 dir.AssignLiteral("left");
5620 break;
5621 case StyleResize::Vertical:
5622 dir.AssignLiteral("bottom");
5623 if (!IsScrollbarOnRight()) {
5624 mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::flip, u""_ns,
5625 false);
5627 break;
5628 case StyleResize::Both:
5629 if (IsScrollbarOnRight()) {
5630 dir.AssignLiteral("bottomright");
5631 } else {
5632 dir.AssignLiteral("bottomleft");
5634 break;
5635 default:
5636 NS_WARNING("only resizable types should have resizers");
5638 mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false);
5639 aElements.AppendElement(mResizerContent);
5642 if (neededAnonContent.contains(AnonymousContentType::HorizontalScrollbar) &&
5643 neededAnonContent.contains(AnonymousContentType::VerticalScrollbar)) {
5644 AnonymousContentKey key = AnonymousContentKey::Type_ScrollCorner;
5646 RefPtr<NodeInfo> nodeInfo =
5647 nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nullptr,
5648 kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
5649 NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent),
5650 nodeInfo.forget());
5651 if (mIsRoot) {
5652 mScrollCornerContent->SetProperty(
5653 nsGkAtoms::docLevelNativeAnonymousContent,
5654 reinterpret_cast<void*>(true));
5655 mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_,
5656 u"true"_ns, false);
5658 // Don't bother making style caching take [root="true"] styles into
5659 // account.
5660 key = AnonymousContentKey::None;
5662 aElements.AppendElement(ContentInfo(mScrollCornerContent, key));
5665 // Don't cache styles if we are a child of a <select> element, since we have
5666 // some UA style sheet rules that depend on the <select>'s attributes.
5667 if (GetContent()->IsHTMLElement(nsGkAtoms::select)) {
5668 for (auto& info : aElements) {
5669 info.mKey = AnonymousContentKey::None;
5673 return NS_OK;
5676 void nsHTMLScrollFrame::AppendAnonymousContentTo(
5677 nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
5678 if (mHScrollbarContent) {
5679 aElements.AppendElement(mHScrollbarContent);
5682 if (mVScrollbarContent) {
5683 aElements.AppendElement(mVScrollbarContent);
5686 if (mScrollCornerContent) {
5687 aElements.AppendElement(mScrollCornerContent);
5690 if (mResizerContent) {
5691 aElements.AppendElement(mResizerContent);
5695 void nsHTMLScrollFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
5696 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
5697 if (aOldComputedStyle && !mIsRoot &&
5698 StyleDisplay()->mScrollSnapType !=
5699 aOldComputedStyle->StyleDisplay()->mScrollSnapType) {
5700 PostPendingResnap();
5704 void nsHTMLScrollFrame::RemoveObservers() {
5705 if (mAsyncScroll) {
5706 mAsyncScroll->RemoveObserver();
5707 mAsyncScroll = nullptr;
5709 if (mAsyncSmoothMSDScroll) {
5710 mAsyncSmoothMSDScroll->RemoveObserver();
5711 mAsyncSmoothMSDScroll = nullptr;
5716 * Called when we want to update the scrollbar position, either because
5717 * scrolling happened or the user moved the scrollbar position and we need to
5718 * undo that (e.g., when the user clicks to scroll and we're using smooth
5719 * scrolling, so we need to put the thumb back to its initial position for the
5720 * start of the smooth sequence).
5722 void nsHTMLScrollFrame::UpdateScrollbarPosition() {
5723 AutoWeakFrame weakFrame(this);
5724 mFrameIsUpdatingScrollbar = true;
5726 nsPoint pt = GetScrollPosition();
5727 nsRect scrollRange = GetVisualScrollRange();
5729 if (gfxPlatform::UseDesktopZoomingScrollbars()) {
5730 pt = GetVisualViewportOffset();
5731 scrollRange = GetScrollRangeForUserInputEvents();
5734 if (mVScrollbarBox) {
5735 SetCoordAttribute(mVScrollbarBox->GetContent()->AsElement(),
5736 nsGkAtoms::curpos, pt.y - scrollRange.y);
5737 if (!weakFrame.IsAlive()) {
5738 return;
5741 if (mHScrollbarBox) {
5742 SetCoordAttribute(mHScrollbarBox->GetContent()->AsElement(),
5743 nsGkAtoms::curpos, pt.x - scrollRange.x);
5744 if (!weakFrame.IsAlive()) {
5745 return;
5749 mFrameIsUpdatingScrollbar = false;
5752 void nsHTMLScrollFrame::CurPosAttributeChangedInternal(nsIContent* aContent,
5753 bool aDoScroll) {
5754 NS_ASSERTION(aContent, "aContent must not be null");
5755 NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
5756 (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
5757 "unexpected child");
5758 MOZ_ASSERT(aContent->IsElement());
5760 // Attribute changes on the scrollbars happen in one of three ways:
5761 // 1) The scrollbar changed the attribute in response to some user event
5762 // 2) We changed the attribute in response to a ScrollPositionDidChange
5763 // callback from the scrolling view
5764 // 3) We changed the attribute to adjust the scrollbars for the start
5765 // of a smooth scroll operation
5767 // In cases 2 and 3 we do not need to scroll because we're just
5768 // updating our scrollbar.
5769 if (mFrameIsUpdatingScrollbar) {
5770 return;
5773 nsRect scrollRange = GetVisualScrollRange();
5775 nsPoint current = GetScrollPosition() - scrollRange.TopLeft();
5777 if (gfxPlatform::UseDesktopZoomingScrollbars()) {
5778 scrollRange = GetScrollRangeForUserInputEvents();
5779 current = GetVisualViewportOffset() - scrollRange.TopLeft();
5782 nsPoint dest;
5783 nsRect allowedRange;
5784 dest.x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos, current.x,
5785 &allowedRange.x, &allowedRange.width);
5786 dest.y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos, current.y,
5787 &allowedRange.y, &allowedRange.height);
5788 current += scrollRange.TopLeft();
5789 dest += scrollRange.TopLeft();
5790 allowedRange += scrollRange.TopLeft();
5792 // Don't try to scroll if we're already at an acceptable place.
5793 // Don't call Contains here since Contains returns false when the point is
5794 // on the bottom or right edge of the rectangle.
5795 if (allowedRange.ClampPoint(current) == current) {
5796 return;
5799 if (mScrollbarActivity &&
5800 (mHasHorizontalScrollbar || mHasVerticalScrollbar)) {
5801 RefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
5802 scrollbarActivity->ActivityOccurred();
5805 const bool isSmooth = aContent->AsElement()->HasAttr(nsGkAtoms::smooth);
5806 if (isSmooth) {
5807 // Make sure an attribute-setting callback occurs even if the view
5808 // didn't actually move yet. We need to make sure other listeners
5809 // see that the scroll position is not (yet) what they thought it
5810 // was.
5811 AutoWeakFrame weakFrame(this);
5812 UpdateScrollbarPosition();
5813 if (!weakFrame.IsAlive()) {
5814 return;
5818 if (aDoScroll) {
5819 ScrollToWithOrigin(dest, &allowedRange,
5820 ScrollOperationParams{
5821 isSmooth ? ScrollMode::Smooth : ScrollMode::Instant,
5822 ScrollOrigin::Scrollbars});
5824 // 'this' might be destroyed here
5827 /* ============= Scroll events ========== */
5829 nsHTMLScrollFrame::ScrollEvent::ScrollEvent(nsHTMLScrollFrame* aHelper,
5830 bool aDelayed)
5831 : Runnable("nsHTMLScrollFrame::ScrollEvent"), mHelper(aHelper) {
5832 mHelper->PresContext()->RefreshDriver()->PostScrollEvent(this, aDelayed);
5835 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
5836 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
5837 nsHTMLScrollFrame::ScrollEvent::Run() {
5838 if (mHelper) {
5839 mHelper->FireScrollEvent();
5841 return NS_OK;
5844 nsHTMLScrollFrame::ScrollEndEvent::ScrollEndEvent(nsHTMLScrollFrame* aHelper)
5845 : Runnable("nsHTMLScrollFrame::ScrollEndEvent"), mHelper(aHelper) {
5846 mHelper->PresContext()->RefreshDriver()->PostScrollEvent(this);
5849 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
5850 nsHTMLScrollFrame::ScrollEndEvent::Run() {
5851 if (mHelper) {
5852 mHelper->FireScrollEndEvent();
5854 return NS_OK;
5857 void nsHTMLScrollFrame::FireScrollEvent() {
5858 RefPtr<nsIContent> content = GetContent();
5859 RefPtr<nsPresContext> presContext = PresContext();
5860 AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "FireScrollEvent", GRAPHICS,
5861 presContext->GetDocShell());
5863 MOZ_ASSERT(mScrollEvent);
5864 mScrollEvent->Revoke();
5865 mScrollEvent = nullptr;
5867 // If event handling is suppressed, keep posting the scroll event to the
5868 // refresh driver until it is unsuppressed. The event is marked as delayed so
5869 // that the refresh driver does not continue ticking.
5870 if (content->GetComposedDoc() &&
5871 content->GetComposedDoc()->EventHandlingSuppressed()) {
5872 content->GetComposedDoc()->SetHasDelayedRefreshEvent();
5873 PostScrollEvent(/* aDelayed = */ true);
5874 return;
5877 bool oldProcessing = mProcessingScrollEvent;
5878 AutoWeakFrame weakFrame(this);
5879 auto RestoreProcessingScrollEvent = mozilla::MakeScopeExit([&] {
5880 if (weakFrame.IsAlive()) { // Otherwise `this` will be dead too.
5881 mProcessingScrollEvent = oldProcessing;
5885 mProcessingScrollEvent = true;
5887 WidgetGUIEvent event(true, eScroll, nullptr);
5888 nsEventStatus status = nsEventStatus_eIgnore;
5889 // Fire viewport scroll events at the document (where they
5890 // will bubble to the window)
5891 mozilla::layers::ScrollLinkedEffectDetector detector(
5892 content->GetComposedDoc(),
5893 presContext->RefreshDriver()->MostRecentRefresh());
5894 if (mIsRoot) {
5895 if (RefPtr<Document> doc = content->GetUncomposedDoc()) {
5896 // TODO: Bug 1506441
5897 EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(doc)), presContext,
5898 &event, nullptr, &status);
5900 } else {
5901 // scroll events fired at elements don't bubble (although scroll events
5902 // fired at documents do, to the window)
5903 event.mFlags.mBubbles = false;
5904 EventDispatcher::Dispatch(content, presContext, &event, nullptr, &status);
5908 void nsHTMLScrollFrame::PostScrollEvent(bool aDelayed) {
5909 if (mScrollEvent) {
5910 return;
5913 // The ScrollEvent constructor registers itself with the refresh driver.
5914 mScrollEvent = new ScrollEvent(this, aDelayed);
5917 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
5918 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
5919 nsHTMLScrollFrame::AsyncScrollPortEvent::Run() {
5920 return mHelper ? mHelper->FireScrollPortEvent() : NS_OK;
5923 void nsHTMLScrollFrame::PostOverflowEvent() {
5924 if (mAsyncScrollPortEvent.IsPending()) {
5925 return;
5928 OverflowState overflowState = GetOverflowState();
5930 bool newVerticalOverflow = !!(overflowState & OverflowState::Vertical);
5931 bool vertChanged = mVerticalOverflow != newVerticalOverflow;
5933 bool newHorizontalOverflow = !!(overflowState & OverflowState::Horizontal);
5934 bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
5936 if (!vertChanged && !horizChanged) {
5937 return;
5940 nsRootPresContext* rpc = PresContext()->GetRootPresContext();
5941 if (!rpc) {
5942 return;
5945 mAsyncScrollPortEvent = new AsyncScrollPortEvent(this);
5946 rpc->AddWillPaintObserver(mAsyncScrollPortEvent.get());
5949 nsIFrame* nsHTMLScrollFrame::GetFrameForStyle() const {
5950 nsIFrame* styleFrame = nullptr;
5951 if (mIsRoot) {
5952 if (const Element* rootElement =
5953 PresContext()->Document()->GetRootElement()) {
5954 styleFrame = rootElement->GetPrimaryFrame();
5956 } else {
5957 styleFrame = const_cast<nsHTMLScrollFrame*>(this);
5960 return styleFrame;
5963 bool nsHTMLScrollFrame::NeedsScrollSnap() const {
5964 nsIFrame* scrollSnapFrame = GetFrameForStyle();
5965 if (!scrollSnapFrame) {
5966 return false;
5968 return scrollSnapFrame->StyleDisplay()->mScrollSnapType.strictness !=
5969 StyleScrollSnapStrictness::None;
5972 nsSize nsHTMLScrollFrame::GetSnapportSize() const {
5973 nsRect snapport = GetScrollPortRect();
5974 nsMargin scrollPadding = GetScrollPadding();
5975 snapport.Deflate(scrollPadding);
5976 return snapport.Size();
5979 bool nsHTMLScrollFrame::IsScrollbarOnRight() const {
5980 nsPresContext* presContext = PresContext();
5982 // The position of the scrollbar in top-level windows depends on the pref
5983 // layout.scrollbar.side. For non-top-level elements, it depends only on the
5984 // directionaliy of the element (equivalent to a value of "1" for the pref).
5985 if (!mIsRoot) {
5986 return IsPhysicalLTR();
5988 switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) {
5989 default:
5990 case 0: // UI directionality
5991 return presContext->GetCachedIntPref(kPresContext_BidiDirection) ==
5992 IBMBIDI_TEXTDIRECTION_LTR;
5993 case 1: // Document / content directionality
5994 return IsPhysicalLTR();
5995 case 2: // Always right
5996 return true;
5997 case 3: // Always left
5998 return false;
6002 bool nsHTMLScrollFrame::IsScrollingActive() const {
6003 const nsStyleDisplay* disp = StyleDisplay();
6004 if (disp->mWillChange.bits & StyleWillChangeBits::SCROLL) {
6005 return true;
6008 nsIContent* content = GetContent();
6009 return mHasBeenScrolledRecently || IsAlwaysActive() ||
6010 DisplayPortUtils::HasDisplayPort(content) ||
6011 nsContentUtils::HasScrollgrab(content);
6014 void nsHTMLScrollFrame::FinishReflowForScrollbar(Element* aElement,
6015 nscoord aMinXY, nscoord aMaxXY,
6016 nscoord aCurPosXY,
6017 nscoord aPageIncrement,
6018 nscoord aIncrement) {
6019 // Scrollbars assume zero is the minimum position, so translate for them.
6020 SetCoordAttribute(aElement, nsGkAtoms::curpos, aCurPosXY - aMinXY);
6021 SetScrollbarEnabled(aElement, aMaxXY - aMinXY);
6022 SetCoordAttribute(aElement, nsGkAtoms::maxpos, aMaxXY - aMinXY);
6023 SetCoordAttribute(aElement, nsGkAtoms::pageincrement, aPageIncrement);
6024 SetCoordAttribute(aElement, nsGkAtoms::increment, aIncrement);
6027 class MOZ_RAII AutoMinimumScaleSizeChangeDetector final {
6028 public:
6029 explicit AutoMinimumScaleSizeChangeDetector(
6030 nsHTMLScrollFrame* ansHTMLScrollFrame)
6031 : mHelper(ansHTMLScrollFrame) {
6032 MOZ_ASSERT(mHelper);
6033 MOZ_ASSERT(mHelper->mIsRoot);
6035 mPreviousMinimumScaleSize = ansHTMLScrollFrame->mMinimumScaleSize;
6036 mPreviousIsUsingMinimumScaleSize =
6037 ansHTMLScrollFrame->mIsUsingMinimumScaleSize;
6039 ~AutoMinimumScaleSizeChangeDetector() {
6040 if (mPreviousMinimumScaleSize != mHelper->mMinimumScaleSize ||
6041 mPreviousIsUsingMinimumScaleSize != mHelper->mIsUsingMinimumScaleSize) {
6042 mHelper->mMinimumScaleSizeChanged = true;
6046 private:
6047 nsHTMLScrollFrame* mHelper;
6049 nsSize mPreviousMinimumScaleSize;
6050 bool mPreviousIsUsingMinimumScaleSize;
6053 nsSize nsHTMLScrollFrame::TrueOuterSize(nsDisplayListBuilder* aBuilder) const {
6054 if (!PresShell()->UsesMobileViewportSizing()) {
6055 return GetSize();
6058 RefPtr<MobileViewportManager> manager =
6059 PresShell()->GetMobileViewportManager();
6060 MOZ_ASSERT(manager);
6062 LayoutDeviceIntSize displaySize = manager->DisplaySize();
6064 MOZ_ASSERT(aBuilder);
6065 // In case of WebRender, we expand the outer size to include the dynamic
6066 // toolbar area here.
6067 // In case of non WebRender, we expand the size dynamically in
6068 // MoveScrollbarForLayerMargin in AsyncCompositionManager.cpp.
6069 WebRenderLayerManager* layerManager = aBuilder->GetWidgetLayerManager();
6070 if (layerManager) {
6071 displaySize.height += ViewAs<LayoutDevicePixel>(
6072 PresContext()->GetDynamicToolbarMaxHeight(),
6073 PixelCastJustification::LayoutDeviceIsScreenForBounds);
6076 return LayoutDeviceSize::ToAppUnits(displaySize,
6077 PresContext()->AppUnitsPerDevPixel());
6080 void nsHTMLScrollFrame::UpdateMinimumScaleSize(
6081 const nsRect& aScrollableOverflow, const nsSize& aICBSize) {
6082 MOZ_ASSERT(mIsRoot);
6084 AutoMinimumScaleSizeChangeDetector minimumScaleSizeChangeDetector(this);
6086 mIsUsingMinimumScaleSize = false;
6088 if (!PresShell()->UsesMobileViewportSizing()) {
6089 return;
6092 nsPresContext* pc = PresContext();
6093 MOZ_ASSERT(pc->IsRootContentDocumentCrossProcess(),
6094 "The pres context should be for the root content document");
6096 RefPtr<MobileViewportManager> manager =
6097 PresShell()->GetMobileViewportManager();
6098 MOZ_ASSERT(manager);
6100 ScreenIntSize displaySize = ViewAs<ScreenPixel>(
6101 manager->DisplaySize(),
6102 PixelCastJustification::LayoutDeviceIsScreenForBounds);
6103 if (displaySize.width == 0 || displaySize.height == 0) {
6104 return;
6106 if (aScrollableOverflow.IsEmpty()) {
6107 // Bail if the scrollable overflow rect is empty, as we're going to be
6108 // dividing by it.
6109 return;
6112 Document* doc = pc->Document();
6113 MOZ_ASSERT(doc, "The document should be valid");
6114 if (doc->GetFullscreenElement()) {
6115 // Don't use the minimum scale size in the case of fullscreen state.
6116 // FIXME: 1508177: We will no longer need this.
6117 return;
6120 nsViewportInfo viewportInfo = doc->GetViewportInfo(displaySize);
6121 if (!viewportInfo.IsZoomAllowed()) {
6122 // Don't apply the minimum scale size if user-scalable=no is specified.
6123 return;
6126 // The intrinsic minimum scale is the scale that fits the entire content
6127 // width into the visual viewport.
6128 CSSToScreenScale intrinsicMinScale(
6129 displaySize.width / CSSRect::FromAppUnits(aScrollableOverflow).XMost());
6131 // The scale used to compute the minimum-scale size is the larger of the
6132 // intrinsic minimum and the min-scale from the meta viewport tag.
6133 CSSToScreenScale minScale =
6134 std::max(intrinsicMinScale, viewportInfo.GetMinZoom());
6136 // The minimum-scale size is the size of the visual viewport when zoomed
6137 // to be the minimum scale.
6138 mMinimumScaleSize = CSSSize::ToAppUnits(ScreenSize(displaySize) / minScale);
6140 // Ensure the minimum-scale size is never smaller than the ICB size.
6141 // That could happen if a page has a meta viewport tag with large explicitly
6142 // specified viewport dimensions (making the ICB large) and also a large
6143 // minimum scale (making the min-scale size small).
6144 mMinimumScaleSize = Max(aICBSize, mMinimumScaleSize);
6146 mIsUsingMinimumScaleSize = true;
6149 bool nsHTMLScrollFrame::ReflowFinished() {
6150 mPostedReflowCallback = false;
6152 TryScheduleScrollAnimations();
6154 if (mIsRoot) {
6155 if (mMinimumScaleSizeChanged && PresShell()->UsesMobileViewportSizing() &&
6156 !PresShell()->IsResolutionUpdatedByApz()) {
6157 RefPtr<MobileViewportManager> manager =
6158 PresShell()->GetMobileViewportManager();
6159 MOZ_ASSERT(manager);
6161 manager->ShrinkToDisplaySizeIfNeeded();
6162 mMinimumScaleSizeChanged = false;
6165 if (!UsesOverlayScrollbars()) {
6166 // Layout scrollbars may have added or removed during reflow, so let's
6167 // update the visual viewport accordingly. Note that this may be a no-op
6168 // because we might have recomputed the visual viewport size during the
6169 // reflow itself, just before laying out the fixed-pos items. But there
6170 // might be cases where that code doesn't run, so this is a sort of
6171 // backstop to ensure we do that recomputation.
6172 if (RefPtr<MobileViewportManager> manager =
6173 PresShell()->GetMobileViewportManager()) {
6174 manager->UpdateVisualViewportSizeForPotentialScrollbarChange();
6178 #if defined(MOZ_WIDGET_ANDROID)
6179 const bool hasVerticalOverflow =
6180 GetOverflowState() & OverflowState::Vertical &&
6181 GetScrollStyles().mVertical != StyleOverflow::Hidden;
6182 if (!mFirstReflow && mHasVerticalOverflowForDynamicToolbar &&
6183 !hasVerticalOverflow) {
6184 PresShell()->MaybeNotifyShowDynamicToolbar();
6186 mHasVerticalOverflowForDynamicToolbar = hasVerticalOverflow;
6187 #endif // defined(MOZ_WIDGET_ANDROID)
6190 bool doScroll = true;
6191 if (IsSubtreeDirty()) {
6192 // We will get another call after the next reflow and scrolling
6193 // later is less janky.
6194 doScroll = false;
6197 if (mFirstReflow) {
6198 nsPoint currentScrollPos = GetScrollPosition();
6199 if (!mScrollUpdates.IsEmpty() &&
6200 mScrollUpdates.LastElement().GetOrigin() == ScrollOrigin::None &&
6201 currentScrollPos != nsPoint()) {
6202 // With frame reconstructions, the reconstructed frame may have a nonzero
6203 // scroll position by the end of the reflow, but without going through
6204 // RestoreState. In particular this can happen with RTL XUL scrollframes,
6205 // see https://bugzilla.mozilla.org/show_bug.cgi?id=1664638#c14.
6206 // Upon construction, the nsHTMLScrollFrame constructor will have inserted
6207 // a ScrollPositionUpdate into mScrollUpdates with origin None and a zero
6208 // scroll position, but here we update that to hold the correct scroll
6209 // position. Otherwise APZ may end up resetting the scroll position to
6210 // zero incorrectly. If we ever hit this codepath, it must be on a reflow
6211 // immediately following the scrollframe construction, so there should be
6212 // exactly one ScrollPositionUpdate in mScrollUpdates.
6213 MOZ_ASSERT(mScrollUpdates.Length() == 1);
6214 MOZ_ASSERT(mScrollUpdates.LastElement().GetGeneration() ==
6215 mScrollGeneration);
6216 MOZ_ASSERT(mScrollUpdates.LastElement().GetDestination() == CSSPoint());
6217 SCROLLRESTORE_LOG("%p: updating initial SPU to pos %s\n", this,
6218 ToString(currentScrollPos).c_str());
6219 mScrollUpdates.Clear();
6220 AppendScrollUpdate(
6221 ScrollPositionUpdate::NewScrollframe(currentScrollPos));
6224 mFirstReflow = false;
6227 nsAutoScriptBlocker scriptBlocker;
6229 if (mReclampVVOffsetInReflowFinished) {
6230 MOZ_ASSERT(mIsRoot && PresShell()->IsVisualViewportOffsetSet());
6231 mReclampVVOffsetInReflowFinished = false;
6232 AutoWeakFrame weakFrame(this);
6233 PresShell()->SetVisualViewportOffset(PresShell()->GetVisualViewportOffset(),
6234 GetScrollPosition());
6235 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
6238 if (doScroll) {
6239 ScrollToRestoredPosition();
6241 // Clamp current scroll position to new bounds. Normally this won't
6242 // do anything.
6243 nsPoint currentScrollPos = GetScrollPosition();
6244 ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)),
6245 ScrollOrigin::Clamp);
6246 if (ScrollAnimationState().isEmpty()) {
6247 // We need to have mDestination track the current scroll position,
6248 // in case it falls outside the new reflow area. mDestination is used
6249 // by ScrollBy as its starting position.
6250 mDestination = GetScrollPosition();
6254 if (!mUpdateScrollbarAttributes) {
6255 return false;
6257 mUpdateScrollbarAttributes = false;
6259 // Update scrollbar attributes.
6260 if (mMayHaveDirtyFixedChildren) {
6261 mMayHaveDirtyFixedChildren = false;
6262 nsIFrame* parentFrame = GetParent();
6263 for (nsIFrame* fixedChild =
6264 parentFrame->GetChildList(FrameChildListID::Fixed).FirstChild();
6265 fixedChild; fixedChild = fixedChild->GetNextSibling()) {
6266 // force a reflow of the fixed child
6267 PresShell()->FrameNeedsReflow(fixedChild, IntrinsicDirty::None,
6268 NS_FRAME_HAS_DIRTY_CHILDREN);
6272 // Suppress handling of the curpos attribute changes we make here.
6273 NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here");
6274 mFrameIsUpdatingScrollbar = true;
6276 // FIXME(emilio): Why this instead of mHScrollbarContent / mVScrollbarContent?
6277 RefPtr<Element> vScroll =
6278 mVScrollbarBox ? mVScrollbarBox->GetContent()->AsElement() : nullptr;
6279 RefPtr<Element> hScroll =
6280 mHScrollbarBox ? mHScrollbarBox->GetContent()->AsElement() : nullptr;
6282 // Note, in some cases this may get deleted while finishing reflow
6283 // for scrollbars. XXXmats is this still true now that we have a script
6284 // blocker in this scope? (if not, remove the weak frame checks below).
6285 if (vScroll || hScroll) {
6286 nsSize visualViewportSize = GetVisualViewportSize();
6287 nsRect scrollRange = GetVisualScrollRange();
6288 nsPoint scrollPos = GetScrollPosition();
6289 nsSize lineScrollAmount = GetLineScrollAmount();
6291 if (gfxPlatform::UseDesktopZoomingScrollbars()) {
6292 scrollRange = GetScrollRangeForUserInputEvents();
6293 scrollPos = GetVisualViewportOffset();
6296 // If modifying the logic here, be sure to modify the corresponding
6297 // compositor-side calculation in ScrollThumbUtils::ApplyTransformForAxis().
6298 AutoWeakFrame weakFrame(this);
6299 if (vScroll) {
6300 const double kScrollMultiplier =
6301 StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
6302 nscoord increment = lineScrollAmount.height * kScrollMultiplier;
6303 // We normally use (visualViewportSize.height - increment) for height of
6304 // page scrolling. However, it is too small when increment is very large.
6305 // (If increment is larger than visualViewportSize.height, direction of
6306 // scrolling will be opposite). To avoid it, we use
6307 // (float(visualViewportSize.height) * 0.8) as lower bound value of height
6308 // of page scrolling. (bug 383267)
6309 // XXX shouldn't we use GetPageScrollAmount here?
6310 nscoord pageincrement = nscoord(visualViewportSize.height - increment);
6311 nscoord pageincrementMin =
6312 nscoord(float(visualViewportSize.height) * 0.8);
6313 FinishReflowForScrollbar(
6314 vScroll, scrollRange.y, scrollRange.YMost(), scrollPos.y,
6315 std::max(pageincrement, pageincrementMin), increment);
6317 if (hScroll) {
6318 const double kScrollMultiplier =
6319 StaticPrefs::toolkit_scrollbox_horizontalScrollDistance();
6320 nscoord increment = lineScrollAmount.width * kScrollMultiplier;
6321 FinishReflowForScrollbar(
6322 hScroll, scrollRange.x, scrollRange.XMost(), scrollPos.x,
6323 nscoord(float(visualViewportSize.width) * 0.8), increment);
6325 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
6328 mFrameIsUpdatingScrollbar = false;
6329 // We used to rely on the curpos attribute changes above to scroll the
6330 // view. However, for scrolling to the left of the viewport, we
6331 // rescale the curpos attribute, which means that operations like
6332 // resizing the window while it is scrolled all the way to the left
6333 // hold the curpos attribute constant at 0 while still requiring
6334 // scrolling. So we suppress the effect of the changes above with
6335 // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here.
6336 // (It actually even works some of the time without this, thanks to
6337 // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
6338 // we hide the scrollbar on a large size change, such as
6339 // maximization.)
6340 if (!mHScrollbarBox && !mVScrollbarBox) {
6341 return false;
6343 CurPosAttributeChangedInternal(
6344 mVScrollbarBox ? mVScrollbarBox->GetContent()->AsElement()
6345 : mHScrollbarBox->GetContent()->AsElement(),
6346 doScroll);
6347 return doScroll;
6350 void nsHTMLScrollFrame::ReflowCallbackCanceled() {
6351 mPostedReflowCallback = false;
6354 bool nsHTMLScrollFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
6355 ScrollStyles ss = GetScrollStyles();
6357 // Reflow when the change in overflow leads to one of our scrollbars
6358 // changing or might require repositioning the scrolled content due to
6359 // reduced extents.
6360 nsRect scrolledRect = GetScrolledRect();
6361 ScrollDirections overflowChange =
6362 GetOverflowChange(scrolledRect, mPrevScrolledRect);
6363 mPrevScrolledRect = scrolledRect;
6365 bool needReflow = false;
6366 nsPoint scrollPosition = GetScrollPosition();
6367 if (overflowChange.contains(ScrollDirection::eHorizontal)) {
6368 if (ss.mHorizontal != StyleOverflow::Hidden || scrollPosition.x) {
6369 needReflow = true;
6372 if (overflowChange.contains(ScrollDirection::eVertical)) {
6373 if (ss.mVertical != StyleOverflow::Hidden || scrollPosition.y) {
6374 needReflow = true;
6378 if (needReflow) {
6379 // If there are scrollbars, or we're not at the beginning of the pane,
6380 // the scroll position may change. In this case, mark the frame as
6381 // needing reflow. Don't use NS_FRAME_IS_DIRTY as dirty as that means
6382 // we have to reflow the frame and all its descendants, and we don't
6383 // have to do that here. Only this frame needs to be reflowed.
6384 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None,
6385 NS_FRAME_HAS_DIRTY_CHILDREN);
6386 // Ensure that next time nsHTMLScrollFrame::Reflow runs, we don't skip
6387 // updating the scrollbars. (Because the overflow area of the scrolled
6388 // frame has probably just been updated, Reflow won't see it change.)
6389 mSkippedScrollbarLayout = true;
6390 return false; // reflowing will update overflow
6392 PostOverflowEvent();
6393 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
6396 void nsHTMLScrollFrame::UpdateSticky() {
6397 StickyScrollContainer* ssc =
6398 StickyScrollContainer::GetStickyScrollContainerForScrollFrame(this);
6399 if (ssc) {
6400 ssc->UpdatePositions(GetScrollPosition(), this);
6404 void nsHTMLScrollFrame::UpdatePrevScrolledRect() {
6405 // The layout scroll range is determinated by the scrolled rect and the scroll
6406 // port, so if the scrolled rect is updated, we may have to schedule the
6407 // associated scroll-driven animations' restyles.
6408 nsRect currScrolledRect = GetScrolledRect();
6409 if (!currScrolledRect.IsEqualEdges(mPrevScrolledRect)) {
6410 mMayScheduleScrollAnimations = true;
6412 mPrevScrolledRect = currScrolledRect;
6415 void nsHTMLScrollFrame::AdjustScrollbarRectForResizer(
6416 nsIFrame* aFrame, nsPresContext* aPresContext, nsRect& aRect,
6417 bool aHasResizer, ScrollDirection aDirection) {
6418 if ((aDirection == ScrollDirection::eVertical ? aRect.width : aRect.height) ==
6419 0) {
6420 return;
6423 // if a content resizer is present, use its size. Otherwise, check if the
6424 // widget has a resizer.
6425 nsRect resizerRect;
6426 if (aHasResizer) {
6427 resizerRect = mResizerBox->GetRect();
6428 } else {
6429 nsPoint offset;
6430 nsIWidget* widget = aFrame->GetNearestWidget(offset);
6431 LayoutDeviceIntRect widgetRect;
6432 if (!widget || !widget->ShowsResizeIndicator(&widgetRect)) {
6433 return;
6436 resizerRect =
6437 nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
6438 aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
6439 aPresContext->DevPixelsToAppUnits(widgetRect.width),
6440 aPresContext->DevPixelsToAppUnits(widgetRect.height));
6443 if (resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1))) {
6444 switch (aDirection) {
6445 case ScrollDirection::eVertical:
6446 aRect.height = std::max(0, resizerRect.y - aRect.y);
6447 break;
6448 case ScrollDirection::eHorizontal:
6449 aRect.width = std::max(0, resizerRect.x - aRect.x);
6450 break;
6452 } else if (resizerRect.Contains(aRect.BottomLeft() + nsPoint(1, -1))) {
6453 switch (aDirection) {
6454 case ScrollDirection::eVertical:
6455 aRect.height = std::max(0, resizerRect.y - aRect.y);
6456 break;
6457 case ScrollDirection::eHorizontal: {
6458 nscoord xmost = aRect.XMost();
6459 aRect.x = std::max(aRect.x, resizerRect.XMost());
6460 aRect.width = xmost - aRect.x;
6461 break;
6467 static void AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect) {
6468 if (aVRect.IsEmpty() || aHRect.IsEmpty()) return;
6470 const nsRect oldVRect = aVRect;
6471 const nsRect oldHRect = aHRect;
6472 if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) {
6473 aHRect.width = std::max(0, oldVRect.x - oldHRect.x);
6474 } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) {
6475 nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x);
6476 aHRect.x += overlap;
6477 aHRect.width -= overlap;
6479 if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) {
6480 aVRect.height = std::max(0, oldHRect.y - oldVRect.y);
6484 void nsHTMLScrollFrame::LayoutScrollbarPartAtRect(
6485 const ScrollReflowInput& aState, ReflowInput& aKidReflowInput,
6486 const nsRect& aRect) {
6487 nsPresContext* pc = PresContext();
6488 nsIFrame* kid = aKidReflowInput.mFrame;
6489 const auto wm = kid->GetWritingMode();
6490 ReflowOutput desiredSize(wm);
6491 MOZ_ASSERT(!wm.IsVertical(),
6492 "Scrollbar parts should have writing-mode: initial");
6493 MOZ_ASSERT(!wm.IsInlineReversed(),
6494 "Scrollbar parts should have writing-mode: initial");
6495 // XXX Maybe get a meaningful container size or something. Shouldn't matter
6496 // given our asserts above.
6497 const nsSize containerSize;
6498 aKidReflowInput.SetComputedISize(aRect.Width());
6499 aKidReflowInput.SetComputedBSize(aRect.Height());
6501 const LogicalPoint pos(wm, aRect.TopLeft(), containerSize);
6502 const auto flags = ReflowChildFlags::Default;
6503 nsReflowStatus status;
6504 ReflowOutput kidDesiredSize(wm);
6505 ReflowChild(kid, pc, kidDesiredSize, aKidReflowInput, wm, pos, containerSize,
6506 flags, status);
6507 FinishReflowChild(kid, pc, kidDesiredSize, &aKidReflowInput, wm, pos,
6508 containerSize, flags);
6511 void nsHTMLScrollFrame::LayoutScrollbars(ScrollReflowInput& aState,
6512 const nsRect& aInsideBorderArea,
6513 const nsRect& aOldScrollPort) {
6514 NS_ASSERTION(!mSuppressScrollbarUpdate, "This should have been suppressed");
6516 const bool scrollbarOnLeft = !IsScrollbarOnRight();
6517 const bool overlayScrollbars = UsesOverlayScrollbars();
6518 const bool overlayScrollBarsOnRoot = overlayScrollbars && mIsRoot;
6519 const bool showVScrollbar = mVScrollbarBox && mHasVerticalScrollbar;
6520 const bool showHScrollbar = mHScrollbarBox && mHasHorizontalScrollbar;
6522 nsSize compositionSize = mScrollPort.Size();
6523 if (overlayScrollBarsOnRoot) {
6524 compositionSize = nsLayoutUtils::CalculateCompositionSizeForFrame(
6525 this, false, &compositionSize);
6528 nsPresContext* presContext = mScrolledFrame->PresContext();
6529 nsRect vRect;
6530 if (showVScrollbar) {
6531 vRect.height =
6532 overlayScrollBarsOnRoot ? compositionSize.height : mScrollPort.height;
6533 vRect.y = mScrollPort.y;
6534 if (scrollbarOnLeft) {
6535 vRect.width = mScrollPort.x - aInsideBorderArea.x;
6536 vRect.x = aInsideBorderArea.x;
6537 } else {
6538 vRect.width = aInsideBorderArea.XMost() - mScrollPort.XMost();
6539 vRect.x = mScrollPort.x + compositionSize.width;
6541 if (overlayScrollbars || mOnlyNeedVScrollbarToScrollVVInsideLV) {
6542 const nscoord width = aState.VScrollbarPrefWidth();
6543 // There is no space reserved for the layout scrollbar, it is currently
6544 // not visible because it is positioned just outside the scrollport. But
6545 // we know that it needs to be made visible so we shift it back in.
6546 vRect.width += width;
6547 if (!scrollbarOnLeft) {
6548 vRect.x -= width;
6553 nsRect hRect;
6554 if (showHScrollbar) {
6555 hRect.width =
6556 overlayScrollBarsOnRoot ? compositionSize.width : mScrollPort.width;
6557 hRect.x = mScrollPort.x;
6558 hRect.height = aInsideBorderArea.YMost() - mScrollPort.YMost();
6559 hRect.y = mScrollPort.y + compositionSize.height;
6561 if (overlayScrollbars || mOnlyNeedHScrollbarToScrollVVInsideLV) {
6562 const nscoord height = aState.HScrollbarPrefHeight();
6563 hRect.height += height;
6564 // There is no space reserved for the layout scrollbar, it is currently
6565 // not visible because it is positioned just outside the scrollport. But
6566 // we know that it needs to be made visible so we shift it back in.
6567 hRect.y -= height;
6571 const bool hasVisualOnlyScrollbarsOnBothDirections =
6572 !overlayScrollbars && showHScrollbar &&
6573 mOnlyNeedHScrollbarToScrollVVInsideLV && showVScrollbar &&
6574 mOnlyNeedVScrollbarToScrollVVInsideLV;
6575 nsPresContext* pc = PresContext();
6577 // place the scrollcorner
6578 if (mScrollCornerBox) {
6579 nsRect r(0, 0, 0, 0);
6580 if (scrollbarOnLeft) {
6581 // scrollbar (if any) on left
6582 r.width = showVScrollbar ? mScrollPort.x - aInsideBorderArea.x : 0;
6583 r.x = aInsideBorderArea.x;
6584 } else {
6585 // scrollbar (if any) on right
6586 r.width =
6587 showVScrollbar ? aInsideBorderArea.XMost() - mScrollPort.XMost() : 0;
6588 r.x = aInsideBorderArea.XMost() - r.width;
6590 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
6592 if (showHScrollbar) {
6593 // scrollbar (if any) on bottom
6594 // Note we don't support the horizontal scrollbar at the top side.
6595 r.height = aInsideBorderArea.YMost() - mScrollPort.YMost();
6596 NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
6598 r.y = aInsideBorderArea.YMost() - r.height;
6600 // If we have layout scrollbars and both scrollbars are present and both are
6601 // only needed to scroll the VV inside the LV then we need a scrollcorner
6602 // but the above calculation will result in an empty rect, so adjust it.
6603 if (r.IsEmpty() && hasVisualOnlyScrollbarsOnBothDirections) {
6604 r.width = vRect.width;
6605 r.height = hRect.height;
6606 r.x = scrollbarOnLeft ? mScrollPort.x : mScrollPort.XMost() - r.width;
6607 r.y = mScrollPort.YMost() - r.height;
6610 ReflowInput scrollCornerRI(
6611 pc, aState.mReflowInput, mScrollCornerBox,
6612 LogicalSize(mScrollCornerBox->GetWritingMode(), r.Size()));
6613 LayoutScrollbarPartAtRect(aState, scrollCornerRI, r);
6616 if (mResizerBox) {
6617 // If a resizer is present, get its size.
6619 // TODO(emilio): Should this really account for scrollbar-width?
6620 auto scrollbarWidth = nsLayoutUtils::StyleForScrollbar(this)
6621 ->StyleUIReset()
6622 ->ScrollbarWidth();
6623 auto scrollbarSize = pc->Theme()->GetScrollbarSize(pc, scrollbarWidth,
6624 nsITheme::Overlay::No);
6625 ReflowInput resizerRI(pc, aState.mReflowInput, mResizerBox,
6626 LogicalSize(mResizerBox->GetWritingMode()));
6627 nsSize resizerMinSize = {resizerRI.ComputedMinWidth(),
6628 resizerRI.ComputedMinHeight()};
6630 nsRect r;
6631 nscoord vScrollbarWidth = pc->DevPixelsToAppUnits(scrollbarSize);
6632 r.width =
6633 std::max(std::max(r.width, vScrollbarWidth), resizerMinSize.width);
6634 r.x = scrollbarOnLeft ? aInsideBorderArea.x
6635 : aInsideBorderArea.XMost() - r.width;
6637 nscoord hScrollbarHeight = pc->DevPixelsToAppUnits(scrollbarSize);
6638 r.height =
6639 std::max(std::max(r.height, hScrollbarHeight), resizerMinSize.height);
6640 r.y = aInsideBorderArea.YMost() - r.height;
6642 LayoutScrollbarPartAtRect(aState, resizerRI, r);
6645 // Note that AdjustScrollbarRectForResizer has to be called after the
6646 // resizer has been laid out immediately above this because it gets the rect
6647 // of the resizer frame.
6648 if (mVScrollbarBox) {
6649 AdjustScrollbarRectForResizer(this, presContext, vRect, mResizerBox,
6650 ScrollDirection::eVertical);
6652 if (mHScrollbarBox) {
6653 AdjustScrollbarRectForResizer(this, presContext, hRect, mResizerBox,
6654 ScrollDirection::eHorizontal);
6657 // Layout scrollbars can overlap at this point if they are both present and
6658 // both only needed to scroll the VV inside the LV.
6659 if (!LookAndFeel::GetInt(LookAndFeel::IntID::AllowOverlayScrollbarsOverlap) ||
6660 hasVisualOnlyScrollbarsOnBothDirections) {
6661 AdjustOverlappingScrollbars(vRect, hRect);
6663 if (mVScrollbarBox) {
6664 ReflowInput vScrollbarRI(
6665 pc, aState.mReflowInput, mVScrollbarBox,
6666 LogicalSize(mVScrollbarBox->GetWritingMode(), vRect.Size()));
6667 LayoutScrollbarPartAtRect(aState, vScrollbarRI, vRect);
6669 if (mHScrollbarBox) {
6670 ReflowInput hScrollbarRI(
6671 pc, aState.mReflowInput, mHScrollbarBox,
6672 LogicalSize(mHScrollbarBox->GetWritingMode(), hRect.Size()));
6673 LayoutScrollbarPartAtRect(aState, hScrollbarRI, hRect);
6676 // may need to update fixed position children of the viewport,
6677 // if the client area changed size because of an incremental
6678 // reflow of a descendant. (If the outer frame is dirty, the fixed
6679 // children will be re-laid out anyway)
6680 if (aOldScrollPort.Size() != mScrollPort.Size() &&
6681 !HasAnyStateBits(NS_FRAME_IS_DIRTY) && mIsRoot) {
6682 mMayHaveDirtyFixedChildren = true;
6685 // post reflow callback to modify scrollbar attributes
6686 mUpdateScrollbarAttributes = true;
6687 if (!mPostedReflowCallback) {
6688 PresShell()->PostReflowCallback(this);
6689 mPostedReflowCallback = true;
6693 #if DEBUG
6694 static bool ShellIsAlive(nsWeakPtr& aWeakPtr) {
6695 RefPtr<PresShell> presShell = do_QueryReferent(aWeakPtr);
6696 return !!presShell;
6698 #endif
6700 void nsHTMLScrollFrame::SetScrollbarEnabled(Element* aElement,
6701 nscoord aMaxPos) {
6702 DebugOnly<nsWeakPtr> weakShell(do_GetWeakReference(PresShell()));
6703 if (aMaxPos) {
6704 aElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
6705 } else {
6706 aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, u"true"_ns, true);
6708 MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
6711 void nsHTMLScrollFrame::SetCoordAttribute(Element* aElement, nsAtom* aAtom,
6712 nscoord aSize) {
6713 DebugOnly<nsWeakPtr> weakShell(do_GetWeakReference(PresShell()));
6714 // convert to pixels
6715 int32_t pixelSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
6717 // only set the attribute if it changed.
6719 nsAutoString newValue;
6720 newValue.AppendInt(pixelSize);
6722 if (aElement->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters)) {
6723 return;
6726 AutoWeakFrame weakFrame(this);
6727 RefPtr<Element> kungFuDeathGrip = aElement;
6728 aElement->SetAttr(kNameSpaceID_None, aAtom, newValue, true);
6729 MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
6730 if (!weakFrame.IsAlive()) {
6731 return;
6734 if (mScrollbarActivity &&
6735 (mHasHorizontalScrollbar || mHasVerticalScrollbar)) {
6736 RefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
6737 scrollbarActivity->ActivityOccurred();
6741 static void ReduceRadii(nscoord aXBorder, nscoord aYBorder, nscoord& aXRadius,
6742 nscoord& aYRadius) {
6743 // In order to ensure that the inside edge of the border has no
6744 // curvature, we need at least one of its radii to be zero.
6745 if (aXRadius <= aXBorder || aYRadius <= aYBorder) return;
6747 // For any corner where we reduce the radii, preserve the corner's shape.
6748 double ratio =
6749 std::max(double(aXBorder) / aXRadius, double(aYBorder) / aYRadius);
6750 aXRadius *= ratio;
6751 aYRadius *= ratio;
6755 * Implement an override for nsIFrame::GetBorderRadii to ensure that
6756 * the clipping region for the border radius does not clip the scrollbars.
6758 * In other words, we require that the border radius be reduced until the
6759 * inner border radius at the inner edge of the border is 0 wherever we
6760 * have scrollbars.
6762 bool nsHTMLScrollFrame::GetBorderRadii(const nsSize& aFrameSize,
6763 const nsSize& aBorderArea,
6764 Sides aSkipSides,
6765 nscoord aRadii[8]) const {
6766 if (!nsContainerFrame::GetBorderRadii(aFrameSize, aBorderArea, aSkipSides,
6767 aRadii)) {
6768 return false;
6771 // Since we can use GetActualScrollbarSizes (rather than
6772 // GetDesiredScrollbarSizes) since this doesn't affect reflow, we
6773 // probably should.
6774 nsMargin sb = GetActualScrollbarSizes();
6775 nsMargin border = GetUsedBorder();
6777 if (sb.left > 0 || sb.top > 0) {
6778 ReduceRadii(border.left, border.top, aRadii[eCornerTopLeftX],
6779 aRadii[eCornerTopLeftY]);
6782 if (sb.top > 0 || sb.right > 0) {
6783 ReduceRadii(border.right, border.top, aRadii[eCornerTopRightX],
6784 aRadii[eCornerTopRightY]);
6787 if (sb.right > 0 || sb.bottom > 0) {
6788 ReduceRadii(border.right, border.bottom, aRadii[eCornerBottomRightX],
6789 aRadii[eCornerBottomRightY]);
6792 if (sb.bottom > 0 || sb.left > 0) {
6793 ReduceRadii(border.left, border.bottom, aRadii[eCornerBottomLeftX],
6794 aRadii[eCornerBottomLeftY]);
6797 return true;
6800 static nscoord SnapCoord(nscoord aCoord, double aRes,
6801 nscoord aAppUnitsPerPixel) {
6802 double snappedToLayerPixels = NS_round((aRes * aCoord) / aAppUnitsPerPixel);
6803 return NSToCoordRoundWithClamp(snappedToLayerPixels * aAppUnitsPerPixel /
6804 aRes);
6807 nsRect nsHTMLScrollFrame::GetScrolledRect() const {
6808 nsRect result = GetUnsnappedScrolledRectInternal(
6809 mScrolledFrame->ScrollableOverflowRect(), mScrollPort.Size());
6811 #if 0
6812 // This happens often enough.
6813 if (result.width < mScrollPort.width || result.height < mScrollPort.height) {
6814 NS_WARNING("Scrolled rect smaller than scrollport?");
6816 #endif
6818 // Expand / contract the result by up to half a layer pixel so that scrolling
6819 // to the right / bottom edge does not change the layer pixel alignment of
6820 // the scrolled contents.
6822 if (result.x == 0 && result.y == 0 && result.width == mScrollPort.width &&
6823 result.height == mScrollPort.height) {
6824 // The edges that we would snap are already aligned with the scroll port,
6825 // so we can skip all the work below.
6826 return result;
6829 // For that, we first convert the scroll port and the scrolled rect to rects
6830 // relative to the reference frame, since that's the space where painting does
6831 // snapping.
6832 nsSize visualViewportSize = GetVisualViewportSize();
6833 const nsIFrame* referenceFrame =
6834 mReferenceFrameDuringPainting ? mReferenceFrameDuringPainting
6835 : nsLayoutUtils::GetReferenceFrame(
6836 const_cast<nsHTMLScrollFrame*>(this));
6837 nsPoint toReferenceFrame = GetOffsetToCrossDoc(referenceFrame);
6838 nsRect scrollPort(mScrollPort.TopLeft() + toReferenceFrame,
6839 visualViewportSize);
6840 nsRect scrolledRect = result + scrollPort.TopLeft();
6842 if (scrollPort.Overflows() || scrolledRect.Overflows()) {
6843 return result;
6846 // Now, snap the bottom right corner of both of these rects.
6847 // We snap to layer pixels, so we need to respect the layer's scale.
6848 nscoord appUnitsPerDevPixel =
6849 mScrolledFrame->PresContext()->AppUnitsPerDevPixel();
6850 MatrixScales scale = GetPaintedLayerScaleForFrame(mScrolledFrame);
6851 if (scale.xScale == 0 || scale.yScale == 0) {
6852 scale = MatrixScales();
6855 // Compute bounds for the scroll position, and computed the snapped scrolled
6856 // rect from the scroll position bounds.
6857 nscoord snappedScrolledAreaBottom =
6858 SnapCoord(scrolledRect.YMost(), scale.yScale, appUnitsPerDevPixel);
6859 nscoord snappedScrollPortBottom =
6860 SnapCoord(scrollPort.YMost(), scale.yScale, appUnitsPerDevPixel);
6861 nscoord maximumScrollOffsetY =
6862 snappedScrolledAreaBottom - snappedScrollPortBottom;
6863 result.SetBottomEdge(scrollPort.height + maximumScrollOffsetY);
6865 if (GetScrolledFrameDir() == StyleDirection::Ltr) {
6866 nscoord snappedScrolledAreaRight =
6867 SnapCoord(scrolledRect.XMost(), scale.xScale, appUnitsPerDevPixel);
6868 nscoord snappedScrollPortRight =
6869 SnapCoord(scrollPort.XMost(), scale.xScale, appUnitsPerDevPixel);
6870 nscoord maximumScrollOffsetX =
6871 snappedScrolledAreaRight - snappedScrollPortRight;
6872 result.SetRightEdge(scrollPort.width + maximumScrollOffsetX);
6873 } else {
6874 // In RTL, the scrolled area's right edge is at scrollPort.XMost(),
6875 // and the scrolled area's x position is zero or negative. We want
6876 // the right edge to stay flush with the scroll port, so we snap the
6877 // left edge.
6878 nscoord snappedScrolledAreaLeft =
6879 SnapCoord(scrolledRect.x, scale.xScale, appUnitsPerDevPixel);
6880 nscoord snappedScrollPortLeft =
6881 SnapCoord(scrollPort.x, scale.xScale, appUnitsPerDevPixel);
6882 nscoord minimumScrollOffsetX =
6883 snappedScrolledAreaLeft - snappedScrollPortLeft;
6884 result.SetLeftEdge(minimumScrollOffsetX);
6887 return result;
6890 StyleDirection nsHTMLScrollFrame::GetScrolledFrameDir() const {
6891 // If the scrolled frame has unicode-bidi: plaintext, the paragraph
6892 // direction set by the text content overrides the direction of the frame
6893 if (mScrolledFrame->StyleTextReset()->mUnicodeBidi ==
6894 StyleUnicodeBidi::Plaintext) {
6895 if (nsIFrame* child = mScrolledFrame->PrincipalChildList().FirstChild()) {
6896 return nsBidiPresUtils::ParagraphDirection(child) ==
6897 mozilla::intl::BidiDirection::LTR
6898 ? StyleDirection::Ltr
6899 : StyleDirection::Rtl;
6902 return IsBidiLTR() ? StyleDirection::Ltr : StyleDirection::Rtl;
6905 nsRect nsHTMLScrollFrame::GetUnsnappedScrolledRectInternal(
6906 const nsRect& aScrolledOverflowArea, const nsSize& aScrollPortSize) const {
6907 return nsLayoutUtils::GetScrolledRect(mScrolledFrame, aScrolledOverflowArea,
6908 aScrollPortSize, GetScrolledFrameDir());
6911 nsMargin nsHTMLScrollFrame::GetActualScrollbarSizes(
6912 nsIScrollableFrame::ScrollbarSizesOptions
6913 aOptions /* = nsIScrollableFrame::ScrollbarSizesOptions::NONE */)
6914 const {
6915 nsRect r = GetPaddingRectRelativeToSelf();
6917 nsMargin m(mScrollPort.y - r.y, r.XMost() - mScrollPort.XMost(),
6918 r.YMost() - mScrollPort.YMost(), mScrollPort.x - r.x);
6920 if (aOptions == nsIScrollableFrame::ScrollbarSizesOptions::
6921 INCLUDE_VISUAL_VIEWPORT_SCROLLBARS &&
6922 !UsesOverlayScrollbars()) {
6923 // If we are using layout scrollbars and they only exist to scroll the
6924 // visual viewport then they do not take up any layout space (so the
6925 // scrollport is the same as the padding rect) but they do cover everything
6926 // below them so some callers may want to include this special type of
6927 // scrollbars in the returned value.
6928 if (mHScrollbarBox && mHasHorizontalScrollbar &&
6929 mOnlyNeedHScrollbarToScrollVVInsideLV) {
6930 m.bottom += mHScrollbarBox->GetRect().height;
6932 if (mVScrollbarBox && mHasVerticalScrollbar &&
6933 mOnlyNeedVScrollbarToScrollVVInsideLV) {
6934 if (IsScrollbarOnRight()) {
6935 m.right += mVScrollbarBox->GetRect().width;
6936 } else {
6937 m.left += mVScrollbarBox->GetRect().width;
6942 return m;
6945 void nsHTMLScrollFrame::SetScrollbarVisibility(nsIFrame* aScrollbar,
6946 bool aVisible) {
6947 nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar);
6948 if (scrollbar) {
6949 // See if we have a mediator.
6950 nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
6951 if (mediator) {
6952 // Inform the mediator of the visibility change.
6953 mediator->VisibilityChanged(aVisible);
6958 nscoord nsHTMLScrollFrame::GetCoordAttribute(nsIFrame* aBox, nsAtom* aAtom,
6959 nscoord aDefaultValue,
6960 nscoord* aRangeStart,
6961 nscoord* aRangeLength) {
6962 if (aBox) {
6963 nsIContent* content = aBox->GetContent();
6965 nsAutoString value;
6966 if (content->IsElement()) {
6967 content->AsElement()->GetAttr(aAtom, value);
6969 if (!value.IsEmpty()) {
6970 nsresult error;
6971 // convert it to appunits
6972 nscoord result =
6973 nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
6974 nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
6975 // Any nscoord value that would round to the attribute value when
6976 // converted to CSS pixels is allowed.
6977 *aRangeStart = result - halfPixel;
6978 *aRangeLength = halfPixel * 2 - 1;
6979 return result;
6983 // Only this exact default value is allowed.
6984 *aRangeStart = aDefaultValue;
6985 *aRangeLength = 0;
6986 return aDefaultValue;
6989 bool nsHTMLScrollFrame::IsLastScrollUpdateAnimating() const {
6990 if (!mScrollUpdates.IsEmpty()) {
6991 switch (mScrollUpdates.LastElement().GetMode()) {
6992 case ScrollMode::Smooth:
6993 case ScrollMode::SmoothMsd:
6994 return true;
6995 case ScrollMode::Instant:
6996 case ScrollMode::Normal:
6997 break;
7000 return false;
7003 bool nsHTMLScrollFrame::IsLastScrollUpdateTriggeredByScriptAnimating() const {
7004 if (!mScrollUpdates.IsEmpty()) {
7005 const ScrollPositionUpdate& lastUpdate = mScrollUpdates.LastElement();
7006 if (lastUpdate.WasTriggeredByScript() &&
7007 (mScrollUpdates.LastElement().GetMode() == ScrollMode::Smooth ||
7008 mScrollUpdates.LastElement().GetMode() == ScrollMode::SmoothMsd)) {
7009 return true;
7012 return false;
7015 using AnimationState = nsIScrollableFrame::AnimationState;
7016 EnumSet<AnimationState> nsHTMLScrollFrame::ScrollAnimationState() const {
7017 EnumSet<AnimationState> retval;
7018 if (IsApzAnimationInProgress()) {
7019 retval += AnimationState::APZInProgress;
7020 if (mCurrentAPZScrollAnimationType ==
7021 APZScrollAnimationType::TriggeredByScript) {
7022 retval += AnimationState::TriggeredByScript;
7026 if (mApzAnimationRequested) {
7027 retval += AnimationState::APZRequested;
7028 if (mApzAnimationTriggeredByScriptRequested) {
7029 retval += AnimationState::TriggeredByScript;
7033 if (IsLastScrollUpdateAnimating()) {
7034 retval += AnimationState::APZPending;
7035 if (IsLastScrollUpdateTriggeredByScriptAnimating()) {
7036 retval += AnimationState::TriggeredByScript;
7039 if (mAsyncScroll) {
7040 retval += AnimationState::MainThread;
7041 if (mAsyncScroll->WasTriggeredByScript()) {
7042 retval += AnimationState::TriggeredByScript;
7046 if (mAsyncSmoothMSDScroll) {
7047 retval += AnimationState::MainThread;
7048 if (mAsyncSmoothMSDScroll->WasTriggeredByScript()) {
7049 retval += AnimationState::TriggeredByScript;
7052 return retval;
7055 void nsHTMLScrollFrame::ResetScrollInfoIfNeeded(
7056 const MainThreadScrollGeneration& aGeneration,
7057 const APZScrollGeneration& aGenerationOnApz,
7058 APZScrollAnimationType aAPZScrollAnimationType,
7059 InScrollingGesture aInScrollingGesture) {
7060 if (aGeneration == mScrollGeneration) {
7061 mLastScrollOrigin = ScrollOrigin::None;
7062 mApzAnimationRequested = false;
7063 mApzAnimationTriggeredByScriptRequested = false;
7066 mScrollGenerationOnApz = aGenerationOnApz;
7067 // We can reset this regardless of scroll generation, as this is only set
7068 // here, as a response to APZ requesting a repaint.
7069 mCurrentAPZScrollAnimationType = aAPZScrollAnimationType;
7071 mInScrollingGesture = aInScrollingGesture;
7074 UniquePtr<PresState> nsHTMLScrollFrame::SaveState() {
7075 nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
7076 if (mediator) {
7077 // child handles its own scroll state, so don't bother saving state here
7078 return nullptr;
7081 // Don't store a scroll state if we never have been scrolled or restored
7082 // a previous scroll state, and we're not in the middle of a smooth scroll.
7083 auto scrollAnimationState = ScrollAnimationState();
7084 bool isScrollAnimating =
7085 scrollAnimationState.contains(AnimationState::MainThread) ||
7086 scrollAnimationState.contains(AnimationState::APZPending) ||
7087 scrollAnimationState.contains(AnimationState::APZRequested);
7088 if (!mHasBeenScrolled && !mDidHistoryRestore && !isScrollAnimating) {
7089 return nullptr;
7092 UniquePtr<PresState> state = NewPresState();
7093 bool allowScrollOriginDowngrade =
7094 !nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) ||
7095 mAllowScrollOriginDowngrade;
7096 // Save mRestorePos instead of our actual current scroll position, if it's
7097 // valid and we haven't moved since the last update of mLastPos (same check
7098 // that ScrollToRestoredPosition uses). This ensures if a reframe occurs
7099 // while we're in the process of loading content to scroll to a restored
7100 // position, we'll keep trying after the reframe. Similarly, if we're in the
7101 // middle of a smooth scroll, store the destination so that when we restore
7102 // we'll jump straight to the end of the scroll animation, rather than
7103 // effectively dropping it. Note that the mRestorePos will override the
7104 // smooth scroll destination if both are present.
7105 nsPoint pt = GetLogicalVisualViewportOffset();
7106 if (isScrollAnimating) {
7107 pt = mDestination;
7108 allowScrollOriginDowngrade = false;
7110 SCROLLRESTORE_LOG("%p: SaveState, pt=%s, mLastPos=%s, mRestorePos=%s\n", this,
7111 ToString(pt).c_str(), ToString(mLastPos).c_str(),
7112 ToString(mRestorePos).c_str());
7113 if (mRestorePos.y != -1 && pt == mLastPos) {
7114 pt = mRestorePos;
7116 state->scrollState() = pt;
7117 state->allowScrollOriginDowngrade() = allowScrollOriginDowngrade;
7118 if (mIsRoot) {
7119 // Only save resolution properties for root scroll frames
7120 state->resolution() = PresShell()->GetResolution();
7122 return state;
7125 NS_IMETHODIMP nsHTMLScrollFrame::RestoreState(PresState* aState) {
7126 mRestorePos = aState->scrollState();
7127 MOZ_ASSERT(mLastScrollOrigin == ScrollOrigin::None);
7128 mAllowScrollOriginDowngrade = aState->allowScrollOriginDowngrade();
7129 // When restoring state, we promote mLastScrollOrigin to a stronger value
7130 // from the default of eNone, to restore the behaviour that existed when
7131 // the state was saved. If mLastScrollOrigin was a weaker value previously,
7132 // then mAllowScrollOriginDowngrade will be true, and so the combination of
7133 // mAllowScrollOriginDowngrade and the stronger mLastScrollOrigin will allow
7134 // the same types of scrolls as before. It might be possible to also just
7135 // save and restore the mAllowScrollOriginDowngrade and mLastScrollOrigin
7136 // values directly without this sort of fiddling. Something to try in the
7137 // future or if we tinker with this code more.
7138 mLastScrollOrigin = ScrollOrigin::Other;
7139 mDidHistoryRestore = true;
7140 mLastPos = mScrolledFrame ? GetLogicalVisualViewportOffset() : nsPoint(0, 0);
7141 SCROLLRESTORE_LOG("%p: RestoreState, set mRestorePos=%s mLastPos=%s\n", this,
7142 ToString(mRestorePos).c_str(), ToString(mLastPos).c_str());
7144 // Resolution properties should only exist on root scroll frames.
7145 MOZ_ASSERT(mIsRoot || aState->resolution() == 1.0);
7147 if (mIsRoot) {
7148 PresShell()->SetResolutionAndScaleTo(
7149 aState->resolution(), ResolutionChangeOrigin::MainThreadRestore);
7151 return NS_OK;
7154 void nsHTMLScrollFrame::PostScrolledAreaEvent() {
7155 if (mScrolledAreaEvent.IsPending()) {
7156 return;
7158 mScrolledAreaEvent = new ScrolledAreaEvent(this);
7159 nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get());
7162 ////////////////////////////////////////////////////////////////////////////////
7163 // ScrolledArea change event dispatch
7165 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
7166 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
7167 nsHTMLScrollFrame::ScrolledAreaEvent::Run() {
7168 if (mHelper) {
7169 mHelper->FireScrolledAreaEvent();
7171 return NS_OK;
7174 void nsHTMLScrollFrame::FireScrolledAreaEvent() {
7175 mScrolledAreaEvent.Forget();
7177 InternalScrollAreaEvent event(true, eScrolledAreaChanged, nullptr);
7178 RefPtr<nsPresContext> presContext = PresContext();
7179 nsIContent* content = GetContent();
7181 event.mArea = mScrolledFrame->ScrollableOverflowRectRelativeToParent();
7182 if (RefPtr<Document> doc = content->GetUncomposedDoc()) {
7183 // TODO: Bug 1506441
7184 EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(doc)), presContext,
7185 &event, nullptr);
7189 ScrollDirections nsIScrollableFrame::GetAvailableScrollingDirections() const {
7190 nscoord oneDevPixel =
7191 GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
7192 ScrollDirections directions;
7193 nsRect scrollRange = GetScrollRange();
7194 if (scrollRange.width >= oneDevPixel) {
7195 directions += ScrollDirection::eHorizontal;
7197 if (scrollRange.height >= oneDevPixel) {
7198 directions += ScrollDirection::eVertical;
7200 return directions;
7203 nsRect nsHTMLScrollFrame::GetScrollRangeForUserInputEvents() const {
7204 // This function computes a scroll range based on a scrolled rect and scroll
7205 // port defined as follows:
7206 // scrollable rect = overflow:hidden ? layout viewport : scrollable rect
7207 // scroll port = have visual viewport ? visual viewport : layout viewport
7208 // The results in the same notion of scroll range that APZ uses (the combined
7209 // effect of FrameMetrics::CalculateScrollRange() and
7210 // nsLayoutUtils::CalculateScrollableRectForFrame).
7212 ScrollStyles ss = GetScrollStyles();
7214 nsPoint scrollPos = GetScrollPosition();
7216 nsRect scrolledRect = GetScrolledRect();
7217 if (StyleOverflow::Hidden == ss.mHorizontal) {
7218 scrolledRect.width = mScrollPort.width;
7219 scrolledRect.x = scrollPos.x;
7221 if (StyleOverflow::Hidden == ss.mVertical) {
7222 scrolledRect.height = mScrollPort.height;
7223 scrolledRect.y = scrollPos.y;
7226 nsSize scrollPort = GetVisualViewportSize();
7228 nsRect scrollRange = scrolledRect;
7229 scrollRange.width = std::max(scrolledRect.width - scrollPort.width, 0);
7230 scrollRange.height = std::max(scrolledRect.height - scrollPort.height, 0);
7232 return scrollRange;
7235 ScrollDirections
7236 nsHTMLScrollFrame::GetAvailableScrollingDirectionsForUserInputEvents() const {
7237 nsRect scrollRange = GetScrollRangeForUserInputEvents();
7239 // We check if there is at least one half of a screen pixel of scroll range to
7240 // roughly match what apz does when it checks if the change in scroll position
7241 // in screen pixels round to zero or not.
7242 // (https://searchfox.org/mozilla-central/rev/2f09184ec781a2667feec87499d4b81b32b6c48e/gfx/layers/apz/src/AsyncPanZoomController.cpp#3210)
7243 // This isn't quite half a screen pixel, it doesn't take into account CSS
7244 // transforms, but should be good enough.
7245 float halfScreenPixel =
7246 GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel() /
7247 (PresShell()->GetCumulativeResolution() * 2.f);
7248 ScrollDirections directions;
7249 if (scrollRange.width >= halfScreenPixel) {
7250 directions += ScrollDirection::eHorizontal;
7252 if (scrollRange.height >= halfScreenPixel) {
7253 directions += ScrollDirection::eVertical;
7255 return directions;
7259 * Append scroll positions for valid snap positions into |aSnapInfo| if
7260 * applicable.
7262 static void AppendScrollPositionsForSnap(
7263 const nsIFrame* aFrame, const nsIFrame* aScrolledFrame,
7264 const nsRect& aScrolledRect, const nsMargin& aScrollPadding,
7265 const nsRect& aScrollRange, WritingMode aWritingModeOnScroller,
7266 ScrollSnapInfo& aSnapInfo, nsHTMLScrollFrame::SnapTargetSet* aSnapTargets) {
7267 ScrollSnapTargetId targetId = ScrollSnapUtils::GetTargetIdFor(aFrame);
7269 nsRect snapArea =
7270 ScrollSnapUtils::GetSnapAreaFor(aFrame, aScrolledFrame, aScrolledRect);
7271 // Use the writing-mode on the target element if the snap area is larger than
7272 // the snapport.
7273 // https://drafts.csswg.org/css-scroll-snap/#snap-scope
7274 WritingMode writingMode = ScrollSnapUtils::NeedsToRespectTargetWritingMode(
7275 snapArea.Size(), aSnapInfo.mSnapportSize)
7276 ? aFrame->GetWritingMode()
7277 : aWritingModeOnScroller;
7279 // These snap range shouldn't be involved with scroll-margin since we just
7280 // need the visible range of the target element.
7281 if (snapArea.width > aSnapInfo.mSnapportSize.width) {
7282 aSnapInfo.mXRangeWiderThanSnapport.AppendElement(
7283 ScrollSnapInfo::ScrollSnapRange(snapArea.X(), snapArea.XMost(),
7284 targetId));
7286 if (snapArea.height > aSnapInfo.mSnapportSize.height) {
7287 aSnapInfo.mYRangeWiderThanSnapport.AppendElement(
7288 ScrollSnapInfo::ScrollSnapRange(snapArea.Y(), snapArea.YMost(),
7289 targetId));
7292 // Shift target rect position by the scroll padding to get the padded
7293 // position thus we don't need to take account scroll-padding values in
7294 // ScrollSnapUtils::GetSnapPointForDestination() when it gets called from
7295 // the compositor thread.
7296 snapArea.y -= aScrollPadding.top;
7297 snapArea.x -= aScrollPadding.left;
7299 LogicalRect logicalTargetRect(writingMode, snapArea, aSnapInfo.mSnapportSize);
7300 LogicalSize logicalSnapportRect(writingMode, aSnapInfo.mSnapportSize);
7301 LogicalRect logicalScrollRange(aWritingModeOnScroller, aScrollRange,
7302 // The origin of this logical coordinate system
7303 // what we need here is (0, 0), so we use an
7304 // empty size.
7305 nsSize());
7307 Maybe<nscoord> blockDirectionPosition;
7308 const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
7309 nscoord containerBSize = logicalSnapportRect.BSize(writingMode);
7310 switch (styleDisplay->mScrollSnapAlign.block) {
7311 case StyleScrollSnapAlignKeyword::None:
7312 break;
7313 case StyleScrollSnapAlignKeyword::Start:
7314 blockDirectionPosition.emplace(
7315 writingMode.IsVerticalRL() ? -logicalTargetRect.BStart(writingMode)
7316 : logicalTargetRect.BStart(writingMode));
7317 break;
7318 case StyleScrollSnapAlignKeyword::End: {
7319 nscoord candidate = std::clamp(
7320 // What we need here is the scroll position instead of the snap
7321 // position itself, so we need, for example, the top edge of the
7322 // scroll port on horizontal-tb when the frame is positioned at
7323 // the bottom edge of the scroll port. For this reason we subtract
7324 // containerBSize from BEnd of the target and clamp it inside the
7325 // scrollable range.
7326 logicalTargetRect.BEnd(writingMode) - containerBSize,
7327 logicalScrollRange.BStart(writingMode),
7328 logicalScrollRange.BEnd(writingMode));
7329 blockDirectionPosition.emplace(writingMode.IsVerticalRL() ? -candidate
7330 : candidate);
7331 break;
7333 case StyleScrollSnapAlignKeyword::Center: {
7334 nscoord targetCenter = (logicalTargetRect.BStart(writingMode) +
7335 logicalTargetRect.BEnd(writingMode)) /
7337 nscoord halfSnapportSize = containerBSize / 2;
7338 // Get the center of the target to align with the center of the snapport
7339 // depending on direction.
7340 nscoord candidate = std::clamp(targetCenter - halfSnapportSize,
7341 logicalScrollRange.BStart(writingMode),
7342 logicalScrollRange.BEnd(writingMode));
7343 blockDirectionPosition.emplace(writingMode.IsVerticalRL() ? -candidate
7344 : candidate);
7345 break;
7349 Maybe<nscoord> inlineDirectionPosition;
7350 nscoord containerISize = logicalSnapportRect.ISize(writingMode);
7351 switch (styleDisplay->mScrollSnapAlign.inline_) {
7352 case StyleScrollSnapAlignKeyword::None:
7353 break;
7354 case StyleScrollSnapAlignKeyword::Start:
7355 inlineDirectionPosition.emplace(
7356 writingMode.IsInlineReversed()
7357 ? -logicalTargetRect.IStart(writingMode)
7358 : logicalTargetRect.IStart(writingMode));
7359 break;
7360 case StyleScrollSnapAlignKeyword::End: {
7361 nscoord candidate = std::clamp(
7362 // Same as above BEnd case, we subtract containerISize.
7364 // Note that the logical scroll range is mapped to [0, x] range even
7365 // if it's in RTL contents. So for example, if the physical range is
7366 // [-200, 0], it's mapped to [0, 200], i.e. IStart() is 0, IEnd() is
7367 // 200. So we can just use std::clamp with the same arguments in both
7368 // RTL/LTR cases.
7369 logicalTargetRect.IEnd(writingMode) - containerISize,
7370 logicalScrollRange.IStart(writingMode),
7371 logicalScrollRange.IEnd(writingMode));
7372 inlineDirectionPosition.emplace(
7373 writingMode.IsInlineReversed() ? -candidate : candidate);
7374 break;
7376 case StyleScrollSnapAlignKeyword::Center: {
7377 nscoord targetCenter = (logicalTargetRect.IStart(writingMode) +
7378 logicalTargetRect.IEnd(writingMode)) /
7380 nscoord halfSnapportSize = containerISize / 2;
7381 // Get the center of the target to align with the center of the snapport
7382 // depending on direction.
7383 nscoord candidate = std::clamp(targetCenter - halfSnapportSize,
7384 logicalScrollRange.IStart(writingMode),
7385 logicalScrollRange.IEnd(writingMode));
7386 inlineDirectionPosition.emplace(
7387 writingMode.IsInlineReversed() ? -candidate : candidate);
7388 break;
7392 if (blockDirectionPosition || inlineDirectionPosition) {
7393 aSnapInfo.mSnapTargets.AppendElement(
7394 writingMode.IsVertical()
7395 ? ScrollSnapInfo::SnapTarget(
7396 std::move(blockDirectionPosition),
7397 std::move(inlineDirectionPosition), std::move(snapArea),
7398 styleDisplay->mScrollSnapStop, targetId)
7399 : ScrollSnapInfo::SnapTarget(
7400 std::move(inlineDirectionPosition),
7401 std::move(blockDirectionPosition), std::move(snapArea),
7402 styleDisplay->mScrollSnapStop, targetId));
7403 if (aSnapTargets) {
7404 aSnapTargets->EnsureInserted(aFrame->GetContent());
7410 * Collect the scroll positions corresponding to snap positions of frames in the
7411 * subtree rooted at |aFrame|, relative to |aScrolledFrame|, into |aSnapInfo|.
7413 static void CollectScrollPositionsForSnap(
7414 nsIFrame* aFrame, nsIFrame* aScrolledFrame, const nsRect& aScrolledRect,
7415 const nsMargin& aScrollPadding, const nsRect& aScrollRange,
7416 WritingMode aWritingModeOnScroller, ScrollSnapInfo& aSnapInfo,
7417 nsHTMLScrollFrame::SnapTargetSet* aSnapTargets) {
7418 // Snap positions only affect the nearest ancestor scroll container on the
7419 // element's containing block chain.
7420 nsIScrollableFrame* sf = do_QueryFrame(aFrame);
7421 if (sf) {
7422 return;
7425 for (const auto& childList : aFrame->ChildLists()) {
7426 for (nsIFrame* f : childList.mList) {
7427 const nsStyleDisplay* styleDisplay = f->StyleDisplay();
7428 if (styleDisplay->mScrollSnapAlign.inline_ !=
7429 StyleScrollSnapAlignKeyword::None ||
7430 styleDisplay->mScrollSnapAlign.block !=
7431 StyleScrollSnapAlignKeyword::None) {
7432 AppendScrollPositionsForSnap(
7433 f, aScrolledFrame, aScrolledRect, aScrollPadding, aScrollRange,
7434 aWritingModeOnScroller, aSnapInfo, aSnapTargets);
7436 CollectScrollPositionsForSnap(
7437 f, aScrolledFrame, aScrolledRect, aScrollPadding, aScrollRange,
7438 aWritingModeOnScroller, aSnapInfo, aSnapTargets);
7443 static nscoord ResolveScrollPaddingStyleValue(
7444 const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto>&
7445 aScrollPaddingStyle,
7446 Side aSide, const nsSize& aScrollPortSize) {
7447 if (aScrollPaddingStyle.Get(aSide).IsAuto()) {
7448 // https://drafts.csswg.org/css-scroll-snap-1/#valdef-scroll-padding-auto
7449 return 0;
7452 nscoord percentageBasis;
7453 switch (aSide) {
7454 case eSideTop:
7455 case eSideBottom:
7456 percentageBasis = aScrollPortSize.height;
7457 break;
7458 case eSideLeft:
7459 case eSideRight:
7460 percentageBasis = aScrollPortSize.width;
7461 break;
7464 return aScrollPaddingStyle.Get(aSide).AsLengthPercentage().Resolve(
7465 percentageBasis);
7468 static nsMargin ResolveScrollPaddingStyle(
7469 const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto>&
7470 aScrollPaddingStyle,
7471 const nsSize& aScrollPortSize) {
7472 return nsMargin(ResolveScrollPaddingStyleValue(aScrollPaddingStyle, eSideTop,
7473 aScrollPortSize),
7474 ResolveScrollPaddingStyleValue(aScrollPaddingStyle,
7475 eSideRight, aScrollPortSize),
7476 ResolveScrollPaddingStyleValue(aScrollPaddingStyle,
7477 eSideBottom, aScrollPortSize),
7478 ResolveScrollPaddingStyleValue(aScrollPaddingStyle, eSideLeft,
7479 aScrollPortSize));
7482 nsMargin nsHTMLScrollFrame::GetScrollPadding() const {
7483 nsIFrame* styleFrame = GetFrameForStyle();
7484 if (!styleFrame) {
7485 return nsMargin();
7488 // The spec says percentage values are relative to the scroll port size.
7489 // https://drafts.csswg.org/css-scroll-snap-1/#scroll-padding
7490 return ResolveScrollPaddingStyle(styleFrame->StylePadding()->mScrollPadding,
7491 GetScrollPortRect().Size());
7494 layers::ScrollSnapInfo nsHTMLScrollFrame::ComputeScrollSnapInfo() {
7495 ScrollSnapInfo result;
7497 nsIFrame* scrollSnapFrame = GetFrameForStyle();
7498 if (!scrollSnapFrame) {
7499 return result;
7502 const nsStyleDisplay* disp = scrollSnapFrame->StyleDisplay();
7503 if (disp->mScrollSnapType.strictness == StyleScrollSnapStrictness::None) {
7504 // We won't be snapping, short-circuit the computation.
7505 return result;
7508 WritingMode writingMode = GetWritingMode();
7509 result.InitializeScrollSnapStrictness(writingMode, disp);
7511 result.mSnapportSize = GetSnapportSize();
7512 CollectScrollPositionsForSnap(
7513 mScrolledFrame, mScrolledFrame, GetScrolledRect(), GetScrollPadding(),
7514 GetLayoutScrollRange(), writingMode, result, &mSnapTargets);
7515 return result;
7518 layers::ScrollSnapInfo nsHTMLScrollFrame::GetScrollSnapInfo() {
7519 // TODO(botond): Should we cache it?
7520 return ComputeScrollSnapInfo();
7523 Maybe<SnapTarget> nsHTMLScrollFrame::GetSnapPointForDestination(
7524 ScrollUnit aUnit, ScrollSnapFlags aFlags, const nsPoint& aStartPos,
7525 const nsPoint& aDestination) {
7526 // We can release the strong references for the previous snap target
7527 // elements here since calling this ComputeScrollSnapInfo means we are going
7528 // to evaluate new snap points, thus there's no chance to generating
7529 // nsIContent instances in between this function call and the function call
7530 // for the (re-)evaluation.
7531 mSnapTargets.Clear();
7532 return ScrollSnapUtils::GetSnapPointForDestination(
7533 ComputeScrollSnapInfo(), aUnit, aFlags, GetLayoutScrollRange(), aStartPos,
7534 aDestination);
7537 Maybe<SnapTarget> nsHTMLScrollFrame::GetSnapPointForResnap() {
7538 // Same as in GetSnapPointForDestination, We can release the strong references
7539 // for the previous snap targets here.
7540 mSnapTargets.Clear();
7541 nsIContent* focusedContent =
7542 GetContent()->GetComposedDoc()->GetUnretargetedFocusedContent();
7543 return ScrollSnapUtils::GetSnapPointForResnap(
7544 ComputeScrollSnapInfo(), GetLayoutScrollRange(), GetScrollPosition(),
7545 mLastSnapTargetIds, focusedContent);
7548 bool nsHTMLScrollFrame::NeedsResnap() {
7549 nsIContent* focusedContent =
7550 GetContent()->GetComposedDoc()->GetUnretargetedFocusedContent();
7551 return ScrollSnapUtils::GetSnapPointForResnap(
7552 ComputeScrollSnapInfo(), GetLayoutScrollRange(),
7553 GetScrollPosition(), mLastSnapTargetIds, focusedContent)
7554 .isSome();
7557 void nsHTMLScrollFrame::SetLastSnapTargetIds(
7558 UniquePtr<ScrollSnapTargetIds> aIds) {
7559 if (!aIds) {
7560 mLastSnapTargetIds = nullptr;
7561 return;
7564 // This SetLastSnapTargetIds gets called asynchronously so that there's a race
7565 // condition something like;
7566 // 1) an async scroll operation triggered snapping to a point on an element
7567 // 2) during the async scroll operation, the element got removed from this
7568 // scroll container
7569 // 3) re-snapping triggered
7570 // 4) this SetLastSnapTargetIds got called
7571 // In such case |aIds| are stale, we shouldn't use it.
7572 for (const auto* idList : {&aIds->mIdsOnX, &aIds->mIdsOnY}) {
7573 for (const auto id : *idList) {
7574 if (!mSnapTargets.Contains(reinterpret_cast<nsIContent*>(id))) {
7575 mLastSnapTargetIds = nullptr;
7576 return;
7581 mLastSnapTargetIds = std::move(aIds);
7584 bool nsHTMLScrollFrame::IsLastSnappedTarget(const nsIFrame* aFrame) const {
7585 ScrollSnapTargetId id = ScrollSnapUtils::GetTargetIdFor(aFrame);
7586 MOZ_ASSERT(id != ScrollSnapTargetId::None,
7587 "This function is supposed to be called for contents");
7589 if (!mLastSnapTargetIds) {
7590 return false;
7593 return mLastSnapTargetIds->mIdsOnX.Contains(id) ||
7594 mLastSnapTargetIds->mIdsOnY.Contains(id);
7597 void nsHTMLScrollFrame::TryResnap() {
7598 // If there's any async scroll is running or we are processing pan/touch
7599 // gestures or scroll thumb dragging, don't clobber the scroll.
7600 if (!ScrollAnimationState().isEmpty() ||
7601 mInScrollingGesture == InScrollingGesture::Yes) {
7602 return;
7605 if (auto snapTarget = GetSnapPointForResnap()) {
7606 // We are going to re-snap so that we need to clobber scroll anchoring.
7607 mAnchor.UserScrolled();
7609 // Snap to the nearest snap position if exists.
7610 ScrollToWithOrigin(
7611 snapTarget->mPosition, nullptr /* range */,
7612 ScrollOperationParams{
7613 IsSmoothScroll(ScrollBehavior::Auto) ? ScrollMode::SmoothMsd
7614 : ScrollMode::Instant,
7615 ScrollOrigin::Other, std::move(snapTarget->mTargetIds)});
7619 void nsHTMLScrollFrame::PostPendingResnapIfNeeded(const nsIFrame* aFrame) {
7620 if (!IsLastSnappedTarget(aFrame)) {
7621 return;
7624 PostPendingResnap();
7627 void nsHTMLScrollFrame::PostPendingResnap() {
7628 PresShell()->PostPendingScrollResnap(this);
7631 nsIScrollableFrame::PhysicalScrollSnapAlign
7632 nsHTMLScrollFrame::GetScrollSnapAlignFor(const nsIFrame* aFrame) const {
7633 StyleScrollSnapAlignKeyword alignForY = StyleScrollSnapAlignKeyword::None;
7634 StyleScrollSnapAlignKeyword alignForX = StyleScrollSnapAlignKeyword::None;
7636 nsIFrame* styleFrame = GetFrameForStyle();
7637 if (!styleFrame) {
7638 return {alignForX, alignForY};
7641 if (styleFrame->StyleDisplay()->mScrollSnapType.strictness ==
7642 StyleScrollSnapStrictness::None) {
7643 return {alignForX, alignForY};
7646 const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
7647 if (styleDisplay->mScrollSnapAlign.inline_ ==
7648 StyleScrollSnapAlignKeyword::None &&
7649 styleDisplay->mScrollSnapAlign.block ==
7650 StyleScrollSnapAlignKeyword::None) {
7651 return {alignForX, alignForY};
7654 nsSize snapAreaSize =
7655 ScrollSnapUtils::GetSnapAreaFor(aFrame, mScrolledFrame, GetScrolledRect())
7656 .Size();
7657 const WritingMode writingMode =
7658 ScrollSnapUtils::NeedsToRespectTargetWritingMode(snapAreaSize,
7659 GetSnapportSize())
7660 ? aFrame->GetWritingMode()
7661 : styleFrame->GetWritingMode();
7663 switch (styleFrame->StyleDisplay()->mScrollSnapType.axis) {
7664 case StyleScrollSnapAxis::X:
7665 alignForX = writingMode.IsVertical()
7666 ? styleDisplay->mScrollSnapAlign.block
7667 : styleDisplay->mScrollSnapAlign.inline_;
7668 break;
7669 case StyleScrollSnapAxis::Y:
7670 alignForY = writingMode.IsVertical()
7671 ? styleDisplay->mScrollSnapAlign.inline_
7672 : styleDisplay->mScrollSnapAlign.block;
7673 break;
7674 case StyleScrollSnapAxis::Block:
7675 if (writingMode.IsVertical()) {
7676 alignForX = styleDisplay->mScrollSnapAlign.block;
7677 } else {
7678 alignForY = styleDisplay->mScrollSnapAlign.block;
7680 break;
7681 case StyleScrollSnapAxis::Inline:
7682 if (writingMode.IsVertical()) {
7683 alignForY = styleDisplay->mScrollSnapAlign.inline_;
7684 } else {
7685 alignForX = styleDisplay->mScrollSnapAlign.inline_;
7687 break;
7688 case StyleScrollSnapAxis::Both:
7689 if (writingMode.IsVertical()) {
7690 alignForX = styleDisplay->mScrollSnapAlign.block;
7691 alignForY = styleDisplay->mScrollSnapAlign.inline_;
7692 } else {
7693 alignForX = styleDisplay->mScrollSnapAlign.inline_;
7694 alignForY = styleDisplay->mScrollSnapAlign.block;
7696 break;
7699 return {alignForX, alignForY};
7702 bool nsHTMLScrollFrame::UsesOverlayScrollbars() const {
7703 return PresContext()->UseOverlayScrollbars();
7706 bool nsHTMLScrollFrame::DragScroll(WidgetEvent* aEvent) {
7707 // Dragging is allowed while within a 20 pixel border. Note that device pixels
7708 // are used so that the same margin is used even when zoomed in or out.
7709 nscoord margin = 20 * PresContext()->AppUnitsPerDevPixel();
7711 // Don't drag scroll for small scrollareas.
7712 if (mScrollPort.width < margin * 2 || mScrollPort.height < margin * 2) {
7713 return false;
7716 // If willScroll is computed as false, then the frame is already scrolled as
7717 // far as it can go in both directions. Return false so that an ancestor
7718 // scrollframe can scroll instead.
7719 bool willScroll = false;
7720 nsPoint pnt =
7721 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this});
7722 nsPoint scrollPoint = GetScrollPosition();
7723 nsRect rangeRect = GetLayoutScrollRange();
7725 // Only drag scroll when a scrollbar is present.
7726 nsPoint offset;
7727 if (mHasHorizontalScrollbar) {
7728 if (pnt.x >= mScrollPort.x && pnt.x <= mScrollPort.x + margin) {
7729 offset.x = -margin;
7730 if (scrollPoint.x > 0) {
7731 willScroll = true;
7733 } else if (pnt.x >= mScrollPort.XMost() - margin &&
7734 pnt.x <= mScrollPort.XMost()) {
7735 offset.x = margin;
7736 if (scrollPoint.x < rangeRect.width) {
7737 willScroll = true;
7742 if (mHasVerticalScrollbar) {
7743 if (pnt.y >= mScrollPort.y && pnt.y <= mScrollPort.y + margin) {
7744 offset.y = -margin;
7745 if (scrollPoint.y > 0) {
7746 willScroll = true;
7748 } else if (pnt.y >= mScrollPort.YMost() - margin &&
7749 pnt.y <= mScrollPort.YMost()) {
7750 offset.y = margin;
7751 if (scrollPoint.y < rangeRect.height) {
7752 willScroll = true;
7757 if (offset.x || offset.y) {
7758 ScrollToWithOrigin(
7759 GetScrollPosition() + offset, nullptr /* range */,
7760 ScrollOperationParams{ScrollMode::Normal, ScrollOrigin::Other});
7763 return willScroll;
7766 static nsSliderFrame* GetSliderFrame(nsIFrame* aScrollbarFrame) {
7767 if (!aScrollbarFrame) {
7768 return nullptr;
7771 for (const auto& childList : aScrollbarFrame->ChildLists()) {
7772 for (nsIFrame* frame : childList.mList) {
7773 if (nsSliderFrame* sliderFrame = do_QueryFrame(frame)) {
7774 return sliderFrame;
7778 return nullptr;
7781 static void AsyncScrollbarDragInitiated(uint64_t aDragBlockId,
7782 nsIFrame* aScrollbar) {
7783 if (nsSliderFrame* sliderFrame = GetSliderFrame(aScrollbar)) {
7784 sliderFrame->AsyncScrollbarDragInitiated(aDragBlockId);
7788 void nsHTMLScrollFrame::AsyncScrollbarDragInitiated(
7789 uint64_t aDragBlockId, ScrollDirection aDirection) {
7790 switch (aDirection) {
7791 case ScrollDirection::eVertical:
7792 ::AsyncScrollbarDragInitiated(aDragBlockId, mVScrollbarBox);
7793 break;
7794 case ScrollDirection::eHorizontal:
7795 ::AsyncScrollbarDragInitiated(aDragBlockId, mHScrollbarBox);
7796 break;
7800 static void AsyncScrollbarDragRejected(nsIFrame* aScrollbar) {
7801 if (nsSliderFrame* sliderFrame = GetSliderFrame(aScrollbar)) {
7802 sliderFrame->AsyncScrollbarDragRejected();
7806 void nsHTMLScrollFrame::AsyncScrollbarDragRejected() {
7807 // We don't get told which scrollbar requested the async drag,
7808 // so we notify both.
7809 ::AsyncScrollbarDragRejected(mHScrollbarBox);
7810 ::AsyncScrollbarDragRejected(mVScrollbarBox);
7813 void nsHTMLScrollFrame::ApzSmoothScrollTo(
7814 const nsPoint& aDestination, ScrollMode aMode, ScrollOrigin aOrigin,
7815 ScrollTriggeredByScript aTriggeredByScript,
7816 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds) {
7817 if (mApzSmoothScrollDestination == Some(aDestination)) {
7818 // If we already sent APZ a smooth-scroll request to this
7819 // destination (i.e. it was the last request
7820 // we sent), then don't send another one because it is redundant.
7821 // This is to avoid a scenario where pages do repeated scrollBy
7822 // calls, incrementing the generation counter, and blocking APZ from
7823 // syncing the scroll offset back to the main thread.
7824 // Note that if we get two smooth-scroll requests to the same
7825 // destination with some other scroll in between,
7826 // mApzSmoothScrollDestination will get reset to Nothing() and so
7827 // we shouldn't have the problem where this check discards a
7828 // legitimate smooth-scroll.
7829 return;
7832 // The animation will be handled in the compositor, pass the
7833 // information needed to start the animation and skip the main-thread
7834 // animation for this scroll.
7835 MOZ_ASSERT(aOrigin != ScrollOrigin::None);
7836 mApzSmoothScrollDestination = Some(aDestination);
7837 AppendScrollUpdate(ScrollPositionUpdate::NewSmoothScroll(
7838 aMode, aOrigin, aDestination, aTriggeredByScript,
7839 std::move(aSnapTargetIds)));
7841 nsIContent* content = GetContent();
7842 if (!DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(content)) {
7843 // If this frame doesn't have a displayport then there won't be an
7844 // APZC instance for it and so there won't be anything to process
7845 // this smooth scroll request. We should set a displayport on this
7846 // frame to force an APZC which can handle the request.
7847 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) {
7848 mozilla::layers::ScrollableLayerGuid::ViewID viewID =
7849 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
7850 nsLayoutUtils::FindIDFor(content, &viewID);
7851 MOZ_LOG(
7852 sDisplayportLog, LogLevel::Debug,
7853 ("ApzSmoothScrollTo setting displayport on scrollId=%" PRIu64 "\n",
7854 viewID));
7857 DisplayPortUtils::CalculateAndSetDisplayPortMargins(
7858 GetScrollTargetFrame(), DisplayPortUtils::RepaintMode::Repaint);
7859 nsIFrame* frame = do_QueryFrame(GetScrollTargetFrame());
7860 DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame);
7863 // Schedule a paint to ensure that the frame metrics get updated on
7864 // the compositor thread.
7865 SchedulePaint();
7868 bool nsHTMLScrollFrame::CanApzScrollInTheseDirections(
7869 ScrollDirections aDirections) {
7870 ScrollStyles styles = GetScrollStyles();
7871 if (aDirections.contains(ScrollDirection::eHorizontal) &&
7872 styles.mHorizontal == StyleOverflow::Hidden)
7873 return false;
7874 if (aDirections.contains(ScrollDirection::eVertical) &&
7875 styles.mVertical == StyleOverflow::Hidden)
7876 return false;
7877 return true;
7880 bool nsHTMLScrollFrame::SmoothScrollVisual(
7881 const nsPoint& aVisualViewportOffset,
7882 FrameMetrics::ScrollOffsetUpdateType aUpdateType) {
7883 bool canDoApzSmoothScroll =
7884 nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll();
7885 if (!canDoApzSmoothScroll) {
7886 return false;
7889 // Clamp the destination to the visual scroll range.
7890 // There is a potential issue here, where |mDestination| is usually
7891 // clamped to the layout scroll range, and so e.g. a subsequent
7892 // window.scrollBy() may have an undesired effect. However, as this function
7893 // is only called internally, this should not be a problem in practice.
7894 // If it turns out to be, the fix would be:
7895 // - add a new "destination" field that doesn't have to be clamped to
7896 // the layout scroll range
7897 // - clamp mDestination to the layout scroll range here
7898 // - make sure ComputeScrollMetadata() picks up the former as the
7899 // smooth scroll destination to send to APZ.
7900 mDestination = GetVisualScrollRange().ClampPoint(aVisualViewportOffset);
7902 UniquePtr<ScrollSnapTargetIds> snapTargetIds;
7903 // Perform the scroll.
7904 ApzSmoothScrollTo(mDestination, ScrollMode::SmoothMsd,
7905 aUpdateType == FrameMetrics::eRestore
7906 ? ScrollOrigin::Restore
7907 : ScrollOrigin::Other,
7908 ScrollTriggeredByScript::No, std::move(snapTargetIds));
7909 return true;
7912 bool nsHTMLScrollFrame::IsSmoothScroll(dom::ScrollBehavior aBehavior) const {
7913 if (aBehavior == dom::ScrollBehavior::Instant) {
7914 return false;
7917 // The user smooth scrolling preference should be honored for any requested
7918 // smooth scrolls. A requested smooth scroll when smooth scrolling is
7919 // disabled should be equivalent to an instant scroll. This is not enforced
7920 // for the <scrollbox> XUL element to allow for the browser chrome to
7921 // override this behavior when toolkit.scrollbox.smoothScroll is enabled.
7922 if (!GetContent()->IsXULElement(nsGkAtoms::scrollbox)) {
7923 if (!nsLayoutUtils::IsSmoothScrollingEnabled()) {
7924 return false;
7926 } else {
7927 if (!StaticPrefs::toolkit_scrollbox_smoothScroll()) {
7928 return false;
7932 if (aBehavior == dom::ScrollBehavior::Smooth) {
7933 return true;
7936 nsIFrame* styleFrame = GetFrameForStyle();
7937 if (!styleFrame) {
7938 return false;
7940 return (aBehavior == dom::ScrollBehavior::Auto &&
7941 styleFrame->StyleDisplay()->mScrollBehavior ==
7942 StyleScrollBehavior::Smooth);
7945 nsTArray<ScrollPositionUpdate> nsHTMLScrollFrame::GetScrollUpdates() const {
7946 return mScrollUpdates.Clone();
7949 void nsHTMLScrollFrame::AppendScrollUpdate(
7950 const ScrollPositionUpdate& aUpdate) {
7951 mScrollGeneration = aUpdate.GetGeneration();
7952 mScrollUpdates.AppendElement(aUpdate);
7955 void nsHTMLScrollFrame::ScheduleScrollAnimations() {
7956 nsIContent* content = GetContent();
7957 MOZ_ASSERT(content && content->IsElement(),
7958 "The nsIScrollableFrame should have the element.");
7960 const Element* elementOrPseudo = content->AsElement();
7961 PseudoStyleType pseudo = elementOrPseudo->GetPseudoElementType();
7962 if (pseudo != PseudoStyleType::NotPseudo &&
7963 !AnimationUtils::IsSupportedPseudoForAnimations(pseudo)) {
7964 // This is not an animatable pseudo element, and so we don't generate
7965 // scroll-timeline for it.
7966 return;
7969 const auto [element, type] =
7970 AnimationUtils::GetElementPseudoPair(elementOrPseudo);
7971 const auto* scheduler = ProgressTimelineScheduler::Get(element, type);
7972 if (!scheduler) {
7973 // We don't have scroll timelines associated with this frame.
7974 return;
7977 scheduler->ScheduleAnimations();