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/. */
6 
7 /* rendering object to wrap rendering objects that should be scrollable */
8 
9 #include "nsGfxScrollFrame.h"
10 
11 #include "ActiveLayerTracker.h"
12 #include "base/compiler_specific.h"
13 #include "DisplayItemClip.h"
14 #include "nsCOMPtr.h"
15 #include "nsIContentViewer.h"
16 #include "nsPresContext.h"
17 #include "nsView.h"
18 #include "nsViewportInfo.h"
19 #include "nsContainerFrame.h"
20 #include "nsGkAtoms.h"
21 #include "nsNameSpaceManager.h"
22 #include "mozilla/dom/DocumentInlines.h"
23 #include "mozilla/gfx/gfxVars.h"
24 #include "nsFontMetrics.h"
25 #include "nsBoxLayoutState.h"
26 #include "mozilla/dom/NodeInfo.h"
27 #include "nsScrollbarFrame.h"
28 #include "nsINode.h"
29 #include "nsIScrollbarMediator.h"
30 #include "nsITextControlFrame.h"
31 #include "nsILayoutHistoryState.h"
32 #include "nsNodeInfoManager.h"
33 #include "nsContentCreatorFunctions.h"
34 #include "nsStyleTransformMatrix.h"
35 #include "mozilla/PresState.h"
36 #include "nsContentUtils.h"
37 #include "nsHTMLDocument.h"
38 #include "nsLayoutUtils.h"
39 #include "nsBidiPresUtils.h"
40 #include "nsBidiUtils.h"
41 #include "nsDocShell.h"
42 #include "mozilla/ContentEvents.h"
43 #include "mozilla/EventDispatcher.h"
44 #include "mozilla/Preferences.h"
45 #include "mozilla/PresShell.h"
46 #include "mozilla/ScrollbarPreferences.h"
47 #include "mozilla/ViewportUtils.h"
48 #include "mozilla/LookAndFeel.h"
49 #include "mozilla/dom/Element.h"
50 #include "mozilla/dom/Event.h"
51 #include "mozilla/dom/HTMLTextAreaElement.h"
52 #include <stdint.h>
53 #include "mozilla/MathAlgorithms.h"
54 #include "mozilla/Telemetry.h"
55 #include "FrameLayerBuilder.h"
56 #include "nsSubDocumentFrame.h"
57 #include "nsSVGOuterSVGFrame.h"
58 #include "mozilla/Attributes.h"
59 #include "ScrollbarActivity.h"
60 #include "nsRefreshDriver.h"
61 #include "nsStyleConsts.h"
62 #include "nsSVGIntegrationUtils.h"
63 #include "nsIScrollPositionListener.h"
64 #include "StickyScrollContainer.h"
65 #include "nsIFrameInlines.h"
66 #include "gfxPlatform.h"
67 #include "mozilla/StaticPrefs_apz.h"
68 #include "mozilla/StaticPrefs_general.h"
69 #include "mozilla/StaticPrefs_layers.h"
70 #include "mozilla/StaticPrefs_layout.h"
71 #include "mozilla/StaticPrefs_mousewheel.h"
72 #include "ScrollAnimationPhysics.h"
73 #include "ScrollAnimationBezierPhysics.h"
74 #include "ScrollAnimationMSDPhysics.h"
75 #include "ScrollSnap.h"
76 #include "UnitTransforms.h"
77 #include "nsPluginFrame.h"
78 #include "nsSliderFrame.h"
79 #include "ViewportFrame.h"
80 #include "mozilla/gfx/gfxVars.h"
81 #include "mozilla/layers/APZCCallbackHelper.h"
82 #include "mozilla/layers/AxisPhysicsModel.h"
83 #include "mozilla/layers/AxisPhysicsMSDModel.h"
84 #include "mozilla/layers/LayerTransactionChild.h"
85 #include "mozilla/layers/ScrollLinkedEffectDetector.h"
86 #include "mozilla/Unused.h"
87 #include "MobileViewportManager.h"
88 #include "VisualViewport.h"
89 #include "LayersLogging.h"  // for Stringify
90 #include <algorithm>
91 #include <cstdlib>  // for std::abs(int/long)
92 #include <cmath>    // for std::abs(float/double)
93 
94 static mozilla::LazyLogModule sApzPaintSkipLog("apz.paintskip");
95 #define PAINT_SKIP_LOG(...) \
96   MOZ_LOG(sApzPaintSkipLog, LogLevel::Debug, (__VA_ARGS__))
97 
98 using namespace mozilla;
99 using namespace mozilla::dom;
100 using namespace mozilla::gfx;
101 using namespace mozilla::layers;
102 using namespace mozilla::layout;
103 using nsStyleTransformMatrix::TransformReferenceBox;
104 
GetOverflowChange(const nsRect & aCurScrolledRect,const nsRect & aPrevScrolledRect)105 static uint32_t GetOverflowChange(const nsRect& aCurScrolledRect,
106                                   const nsRect& aPrevScrolledRect) {
107   uint32_t result = 0;
108   if (aPrevScrolledRect.x != aCurScrolledRect.x ||
109       aPrevScrolledRect.width != aCurScrolledRect.width) {
110     result |= nsIScrollableFrame::HORIZONTAL;
111   }
112   if (aPrevScrolledRect.y != aCurScrolledRect.y ||
113       aPrevScrolledRect.height != aCurScrolledRect.height) {
114     result |= nsIScrollableFrame::VERTICAL;
115   }
116   return result;
117 }
118 
119 /**
120  * This class handles the dispatching of scroll events to content.
121  *
122  * Scroll events are posted to the refresh driver via
123  * nsRefreshDriver::PostScrollEvent(), and they are fired during a refresh
124  * driver tick, after running requestAnimationFrame callbacks but before
125  * the style flush. This allows rAF callbacks to perform scrolling and have
126  * that scrolling be reflected on the same refresh driver tick, while at
127  * the same time allowing scroll event listeners to make style changes and
128  * have those style changes be reflected on the same refresh driver tick.
129  *
130  * ScrollEvents cannot be refresh observers, because none of the existing
131  * categories of refresh observers (FlushType::Style, FlushType::Layout,
132  * and FlushType::Display) are run at the desired time in a refresh driver
133  * tick. They behave similarly to refresh observers in that their presence
134  * causes the refresh driver to tick.
135  *
136  * ScrollEvents are one-shot runnables; the refresh driver drops them after
137  * running them.
138  */
139 class ScrollFrameHelper::ScrollEvent : public Runnable {
140  public:
141   NS_DECL_NSIRUNNABLE
142   explicit ScrollEvent(ScrollFrameHelper* aHelper, bool aDelayed);
Revoke()143   void Revoke() { mHelper = nullptr; }
144 
145  private:
146   ScrollFrameHelper* mHelper;
147 };
148 
149 class ScrollFrameHelper::ScrollEndEvent : public Runnable {
150  public:
151   NS_DECL_NSIRUNNABLE
152   explicit ScrollEndEvent(ScrollFrameHelper* aHelper);
Revoke()153   void Revoke() { mHelper = nullptr; }
154 
155  private:
156   ScrollFrameHelper* mHelper;
157 };
158 
159 class ScrollFrameHelper::AsyncScrollPortEvent : public Runnable {
160  public:
161   NS_DECL_NSIRUNNABLE
AsyncScrollPortEvent(ScrollFrameHelper * helper)162   explicit AsyncScrollPortEvent(ScrollFrameHelper* helper)
163       : Runnable("ScrollFrameHelper::AsyncScrollPortEvent"), mHelper(helper) {}
Revoke()164   void Revoke() { mHelper = nullptr; }
165 
166  private:
167   ScrollFrameHelper* mHelper;
168 };
169 
170 class ScrollFrameHelper::ScrolledAreaEvent : public Runnable {
171  public:
172   NS_DECL_NSIRUNNABLE
ScrolledAreaEvent(ScrollFrameHelper * helper)173   explicit ScrolledAreaEvent(ScrollFrameHelper* helper)
174       : Runnable("ScrollFrameHelper::ScrolledAreaEvent"), mHelper(helper) {}
Revoke()175   void Revoke() { mHelper = nullptr; }
176 
177  private:
178   ScrollFrameHelper* mHelper;
179 };
180 
181 //----------------------------------------------------------------------
182 
183 //----------nsHTMLScrollFrame-------------------------------------------
184 
NS_NewHTMLScrollFrame(PresShell * aPresShell,ComputedStyle * aStyle,bool aIsRoot)185 nsHTMLScrollFrame* NS_NewHTMLScrollFrame(PresShell* aPresShell,
186                                          ComputedStyle* aStyle, bool aIsRoot) {
187   return new (aPresShell)
188       nsHTMLScrollFrame(aStyle, aPresShell->GetPresContext(), aIsRoot);
189 }
190 
NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)191 NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)
192 
193 nsHTMLScrollFrame::nsHTMLScrollFrame(ComputedStyle* aStyle,
194                                      nsPresContext* aPresContext,
195                                      nsIFrame::ClassID aID, bool aIsRoot)
196     : nsContainerFrame(aStyle, aPresContext, aID),
197       mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot) {}
198 
ScrollbarActivityStarted() const199 void nsHTMLScrollFrame::ScrollbarActivityStarted() const {
200   if (mHelper.mScrollbarActivity) {
201     mHelper.mScrollbarActivity->ActivityStarted();
202   }
203 }
204 
ScrollbarActivityStopped() const205 void nsHTMLScrollFrame::ScrollbarActivityStopped() const {
206   if (mHelper.mScrollbarActivity) {
207     mHelper.mScrollbarActivity->ActivityStopped();
208   }
209 }
210 
CreateAnonymousContent(nsTArray<ContentInfo> & aElements)211 nsresult nsHTMLScrollFrame::CreateAnonymousContent(
212     nsTArray<ContentInfo>& aElements) {
213   return mHelper.CreateAnonymousContent(aElements);
214 }
215 
AppendAnonymousContentTo(nsTArray<nsIContent * > & aElements,uint32_t aFilter)216 void nsHTMLScrollFrame::AppendAnonymousContentTo(
217     nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
218   mHelper.AppendAnonymousContentTo(aElements, aFilter);
219 }
220 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)221 void nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot,
222                                     PostDestroyData& aPostDestroyData) {
223   DestroyAbsoluteFrames(aDestructRoot, aPostDestroyData);
224   mHelper.Destroy(aPostDestroyData);
225   nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
226 }
227 
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)228 void nsHTMLScrollFrame::SetInitialChildList(ChildListID aListID,
229                                             nsFrameList& aChildList) {
230   nsContainerFrame::SetInitialChildList(aListID, aChildList);
231   mHelper.ReloadChildFrames();
232 }
233 
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)234 void nsHTMLScrollFrame::AppendFrames(ChildListID aListID,
235                                      nsFrameList& aFrameList) {
236   NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
237   mFrames.AppendFrames(nullptr, aFrameList);
238   mHelper.ReloadChildFrames();
239 }
240 
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & aFrameList)241 void nsHTMLScrollFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
242                                      const nsLineList::iterator* aPrevFrameLine,
243                                      nsFrameList& aFrameList) {
244   NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
245   NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
246                "inserting after sibling frame with different parent");
247   mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
248   mHelper.ReloadChildFrames();
249 }
250 
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)251 void nsHTMLScrollFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
252   NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
253   mFrames.DestroyFrame(aOldFrame);
254   mHelper.ReloadChildFrames();
255 }
256 
257 /**
258  HTML scrolling implementation
259 
260  All other things being equal, we prefer layouts with fewer scrollbars showing.
261 */
262 
263 namespace mozilla {
264 
265 enum class ShowScrollbar : uint8_t {
266   Auto,
267   Always,
268   Never,
269 };
270 
ShouldShowScrollbar(StyleOverflow aOverflow)271 static ShowScrollbar ShouldShowScrollbar(StyleOverflow aOverflow) {
272   switch (aOverflow) {
273     case StyleOverflow::Scroll:
274       return ShowScrollbar::Always;
275     case StyleOverflow::Hidden:
276       return ShowScrollbar::Never;
277     default:
278     case StyleOverflow::Auto:
279       return ShowScrollbar::Auto;
280   }
281 }
282 
283 struct MOZ_STACK_CLASS ScrollReflowInput {
284   const ReflowInput& mReflowInput;
285   nsBoxLayoutState mBoxState;
286   ShowScrollbar mHScrollbar;
287   ShowScrollbar mVScrollbar;
288   nsMargin mComputedBorder;
289 
290   // === Filled in by ReflowScrolledFrame ===
291   nsOverflowAreas mContentsOverflowAreas;
292   MOZ_INIT_OUTSIDE_CTOR
293   bool mReflowedContentsWithHScrollbar;
294   MOZ_INIT_OUTSIDE_CTOR
295   bool mReflowedContentsWithVScrollbar;
296 
297   // === Filled in when TryLayout succeeds ===
298   // The size of the inside-border area
299   nsSize mInsideBorderSize;
300   // Whether we decided to show the horizontal scrollbar
301   MOZ_INIT_OUTSIDE_CTOR
302   bool mShowHScrollbar;
303   // Whether we decided to show the vertical scrollbar
304   MOZ_INIT_OUTSIDE_CTOR
305   bool mShowVScrollbar;
306 
ScrollReflowInputmozilla::ScrollReflowInput307   ScrollReflowInput(nsIScrollableFrame* aFrame, const ReflowInput& aReflowInput)
308       : mReflowInput(aReflowInput),
309         // mBoxState is just used for scrollbars so we don't need to
310         // worry about the reflow depth here
311         mBoxState(aReflowInput.mFrame->PresContext(),
312                   aReflowInput.mRenderingContext) {
313     ScrollStyles styles = aFrame->GetScrollStyles();
314     mHScrollbar = ShouldShowScrollbar(styles.mHorizontal);
315     mVScrollbar = ShouldShowScrollbar(styles.mVertical);
316   }
317 };
318 
319 }  // namespace mozilla
320 
321 // XXXldb Can this go away?
ComputeInsideBorderSize(ScrollReflowInput * aState,const nsSize & aDesiredInsideBorderSize)322 static nsSize ComputeInsideBorderSize(ScrollReflowInput* aState,
323                                       const nsSize& aDesiredInsideBorderSize) {
324   // aDesiredInsideBorderSize is the frame size; i.e., it includes
325   // borders and padding (but the scrolled child doesn't have
326   // borders). The scrolled child has the same padding as us.
327   nscoord contentWidth = aState->mReflowInput.ComputedWidth();
328   if (contentWidth == NS_UNCONSTRAINEDSIZE) {
329     contentWidth = aDesiredInsideBorderSize.width -
330                    aState->mReflowInput.ComputedPhysicalPadding().LeftRight();
331   }
332   nscoord contentHeight = aState->mReflowInput.ComputedHeight();
333   if (contentHeight == NS_UNCONSTRAINEDSIZE) {
334     contentHeight = aDesiredInsideBorderSize.height -
335                     aState->mReflowInput.ComputedPhysicalPadding().TopBottom();
336   }
337 
338   contentWidth = aState->mReflowInput.ApplyMinMaxWidth(contentWidth);
339   contentHeight = aState->mReflowInput.ApplyMinMaxHeight(contentHeight);
340   return nsSize(
341       contentWidth + aState->mReflowInput.ComputedPhysicalPadding().LeftRight(),
342       contentHeight +
343           aState->mReflowInput.ComputedPhysicalPadding().TopBottom());
344 }
345 
GetScrollbarMetrics(nsBoxLayoutState & aState,nsIFrame * aBox,nsSize * aMin,nsSize * aPref)346 static void GetScrollbarMetrics(nsBoxLayoutState& aState, nsIFrame* aBox,
347                                 nsSize* aMin, nsSize* aPref) {
348   NS_ASSERTION(aState.GetRenderingContext(),
349                "Must have rendering context in layout state for size "
350                "computations");
351 
352   if (aMin) {
353     *aMin = aBox->GetXULMinSize(aState);
354     nsIFrame::AddXULMargin(aBox, *aMin);
355     if (aMin->width < 0) {
356       aMin->width = 0;
357     }
358     if (aMin->height < 0) {
359       aMin->height = 0;
360     }
361   }
362 
363   if (aPref) {
364     *aPref = aBox->GetXULPrefSize(aState);
365     nsIFrame::AddXULMargin(aBox, *aPref);
366     if (aPref->width < 0) {
367       aPref->width = 0;
368     }
369     if (aPref->height < 0) {
370       aPref->height = 0;
371     }
372   }
373 }
374 
375 /**
376  * Assuming that we know the metrics for our wrapped frame and
377  * whether the horizontal and/or vertical scrollbars are present,
378  * compute the resulting layout and return true if the layout is
379  * consistent. If the layout is consistent then we fill in the
380  * computed fields of the ScrollReflowInput.
381  *
382  * The layout is consistent when both scrollbars are showing if and only
383  * if they should be showing. A horizontal scrollbar should be showing if all
384  * following conditions are met:
385  * 1) the style is not HIDDEN
386  * 2) our inside-border height is at least the scrollbar height (i.e., the
387  * scrollbar fits vertically)
388  * 3) the style is SCROLL, or the kid's overflow-area XMost is
389  * greater than the scrollport width
390  *
391  * @param aForce if true, then we just assume the layout is consistent.
392  */
TryLayout(ScrollReflowInput * aState,ReflowOutput * aKidMetrics,bool aAssumeHScroll,bool aAssumeVScroll,bool aForce)393 bool nsHTMLScrollFrame::TryLayout(ScrollReflowInput* aState,
394                                   ReflowOutput* aKidMetrics,
395                                   bool aAssumeHScroll, bool aAssumeVScroll,
396                                   bool aForce) {
397   if ((aState->mVScrollbar == ShowScrollbar::Never && aAssumeVScroll) ||
398       (aState->mHScrollbar == ShowScrollbar::Never && aAssumeHScroll)) {
399     NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
400     return false;
401   }
402 
403   if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar ||
404       (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar &&
405        ScrolledContentDependsOnHeight(aState))) {
406     if (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar) {
407       nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(
408           mHelper.mScrolledFrame);
409     }
410     aKidMetrics->mOverflowAreas.Clear();
411     ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll, aKidMetrics);
412   }
413 
414   nsSize vScrollbarMinSize(0, 0);
415   nsSize vScrollbarPrefSize(0, 0);
416   if (mHelper.mVScrollbarBox) {
417     GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox,
418                         &vScrollbarMinSize,
419                         aAssumeVScroll ? &vScrollbarPrefSize : nullptr);
420     nsScrollbarFrame* scrollbar = do_QueryFrame(mHelper.mVScrollbarBox);
421     scrollbar->SetScrollbarMediatorContent(mContent);
422   }
423   nscoord vScrollbarDesiredWidth =
424       aAssumeVScroll ? vScrollbarPrefSize.width : 0;
425 
426   nsSize hScrollbarMinSize(0, 0);
427   nsSize hScrollbarPrefSize(0, 0);
428   if (mHelper.mHScrollbarBox) {
429     GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox,
430                         &hScrollbarMinSize,
431                         aAssumeHScroll ? &hScrollbarPrefSize : nullptr);
432     nsScrollbarFrame* scrollbar = do_QueryFrame(mHelper.mHScrollbarBox);
433     scrollbar->SetScrollbarMediatorContent(mContent);
434   }
435 
436   nscoord hScrollbarDesiredHeight =
437       aAssumeHScroll ? hScrollbarPrefSize.height : 0;
438 
439   // First, compute our inside-border size and scrollport size
440   // XXXldb Can we depend more on ComputeSize here?
441   nsSize kidSize = aState->mReflowInput.mStyleDisplay->IsContainSize()
442                        ? nsSize(0, 0)
443                        : aKidMetrics->PhysicalSize();
444   nsSize desiredInsideBorderSize;
445   desiredInsideBorderSize.width = vScrollbarDesiredWidth + kidSize.width;
446   desiredInsideBorderSize.height = hScrollbarDesiredHeight + kidSize.height;
447   aState->mInsideBorderSize =
448       ComputeInsideBorderSize(aState, desiredInsideBorderSize);
449 
450   nsSize layoutSize = mHelper.mIsUsingMinimumScaleSize
451                           ? mHelper.mMinimumScaleSize
452                           : aState->mInsideBorderSize;
453 
454   const nsSize scrollPortSize =
455       nsSize(std::max(0, layoutSize.width - vScrollbarDesiredWidth),
456              std::max(0, layoutSize.height - hScrollbarDesiredHeight));
457   if (mHelper.mIsUsingMinimumScaleSize) {
458     mHelper.mICBSize = nsSize(
459         std::max(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth),
460         std::max(0,
461                  aState->mInsideBorderSize.height - hScrollbarDesiredHeight));
462   }
463 
464   nsSize visualViewportSize = scrollPortSize;
465   mozilla::PresShell* presShell = PresShell();
466   if (mHelper.mIsRoot && presShell->IsVisualViewportSizeSet()) {
467     nsSize compositionSize =
468         nsLayoutUtils::CalculateCompositionSizeForFrame(this, false);
469     float resolution = presShell->GetResolution();
470     compositionSize.width /= resolution;
471     compositionSize.height /= resolution;
472     visualViewportSize =
473         nsSize(std::max(0, compositionSize.width - vScrollbarDesiredWidth),
474                std::max(0, compositionSize.height - hScrollbarDesiredHeight));
475   }
476 
477   nsRect overflowRect = aState->mContentsOverflowAreas.ScrollableOverflow();
478   // If the content height expanded by the minimum-scale will be taller than
479   // the scrollable overflow area, we need to expand the area here to tell
480   // properly whether we need to render the overlay vertical scrollbar.
481   // NOTE: This expanded size should NOT be used for non-overley scrollbars
482   // cases since putting the vertical non-overlay scrollbar will make the
483   // content width narrow a little bit, which in turn the minimum scale value
484   // becomes a bit bigger than before, then the vertical scrollbar is no longer
485   // needed, which means the content width becomes the original width, then the
486   // minimum-scale is changed to the original one, and so forth.
487   if (mHelper.UsesOverlayScrollbars() && mHelper.mIsUsingMinimumScaleSize &&
488       mHelper.mMinimumScaleSize.height > overflowRect.YMost()) {
489     MOZ_ASSERT(StaticPrefs::layout_viewport_contains_no_contents_area());
490     overflowRect.height +=
491         mHelper.mMinimumScaleSize.height - overflowRect.YMost();
492   }
493   nsRect scrolledRect =
494       mHelper.GetUnsnappedScrolledRectInternal(overflowRect, scrollPortSize);
495   nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1);
496 
497   if (!aForce) {
498     // If the style is HIDDEN then we already know that aAssumeHScroll is false
499     if (aState->mHScrollbar != ShowScrollbar::Never) {
500       bool wantHScrollbar =
501           aState->mHScrollbar == ShowScrollbar::Always ||
502           scrolledRect.XMost() >= visualViewportSize.width + oneDevPixel ||
503           scrolledRect.x <= -oneDevPixel;
504       // TODO(emilio): This should probably check this scrollbar's minimum size
505       // in both axes, for consistency?
506       if (aState->mHScrollbar == ShowScrollbar::Auto &&
507           scrollPortSize.width < hScrollbarMinSize.width) {
508         wantHScrollbar = false;
509       }
510       if (wantHScrollbar != aAssumeHScroll) {
511         return false;
512       }
513     }
514 
515     // If the style is HIDDEN then we already know that aAssumeVScroll is false
516     if (aState->mVScrollbar != ShowScrollbar::Never) {
517       bool wantVScrollbar =
518           aState->mVScrollbar == ShowScrollbar::Always ||
519           scrolledRect.YMost() >= visualViewportSize.height + oneDevPixel ||
520           scrolledRect.y <= -oneDevPixel;
521       // TODO(emilio): This should probably check this scrollbar's minimum size
522       // in both axes, for consistency?
523       if (aState->mVScrollbar == ShowScrollbar::Auto &&
524           scrollPortSize.height < vScrollbarMinSize.height) {
525         wantVScrollbar = false;
526       }
527       if (wantVScrollbar != aAssumeVScroll) {
528         return false;
529       }
530     }
531   }
532 
533   aState->mShowHScrollbar = aAssumeHScroll;
534   aState->mShowVScrollbar = aAssumeVScroll;
535   nsPoint scrollPortOrigin(aState->mComputedBorder.left,
536                            aState->mComputedBorder.top);
537   if (!IsScrollbarOnRight()) {
538     nscoord vScrollbarActualWidth = layoutSize.width - scrollPortSize.width;
539     scrollPortOrigin.x += vScrollbarActualWidth;
540   }
541   mHelper.mScrollPort = nsRect(scrollPortOrigin, scrollPortSize);
542   return true;
543 }
544 
545 // XXX Height/BSize mismatch needs to be addressed here; check the caller!
546 // Currently this will only behave as expected for horizontal writing modes.
547 // (See bug 1175509.)
ScrolledContentDependsOnHeight(ScrollReflowInput * aState)548 bool nsHTMLScrollFrame::ScrolledContentDependsOnHeight(
549     ScrollReflowInput* aState) {
550   // Return true if ReflowScrolledFrame is going to do something different
551   // based on the presence of a horizontal scrollbar.
552   return mHelper.mScrolledFrame->HasAnyStateBits(
553              NS_FRAME_CONTAINS_RELATIVE_BSIZE |
554              NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE) ||
555          aState->mReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
556          aState->mReflowInput.ComputedMinBSize() > 0 ||
557          aState->mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE;
558 }
559 
ReflowScrolledFrame(ScrollReflowInput * aState,bool aAssumeHScroll,bool aAssumeVScroll,ReflowOutput * aMetrics)560 void nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowInput* aState,
561                                             bool aAssumeHScroll,
562                                             bool aAssumeVScroll,
563                                             ReflowOutput* aMetrics) {
564   WritingMode wm = mHelper.mScrolledFrame->GetWritingMode();
565 
566   // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should
567   // be OK
568   LogicalMargin padding = aState->mReflowInput.ComputedLogicalPadding();
569   nscoord availISize =
570       aState->mReflowInput.ComputedISize() + padding.IStartEnd(wm);
571 
572   nscoord computedBSize = aState->mReflowInput.ComputedBSize();
573   nscoord computedMinBSize = aState->mReflowInput.ComputedMinBSize();
574   nscoord computedMaxBSize = aState->mReflowInput.ComputedMaxBSize();
575   if (!ShouldPropagateComputedBSizeToScrolledContent()) {
576     computedBSize = NS_UNCONSTRAINEDSIZE;
577     computedMinBSize = 0;
578     computedMaxBSize = NS_UNCONSTRAINEDSIZE;
579   }
580 
581   if (wm.IsVertical()) {
582     if (aAssumeVScroll) {
583       nsSize vScrollbarPrefSize;
584       GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox, nullptr,
585                           &vScrollbarPrefSize);
586       if (computedBSize != NS_UNCONSTRAINEDSIZE) {
587         computedBSize = std::max(0, computedBSize - vScrollbarPrefSize.width);
588       }
589       computedMinBSize =
590           std::max(0, computedMinBSize - vScrollbarPrefSize.width);
591       if (computedMaxBSize != NS_UNCONSTRAINEDSIZE) {
592         computedMaxBSize =
593             std::max(0, computedMaxBSize - vScrollbarPrefSize.width);
594       }
595     }
596 
597     if (aAssumeHScroll) {
598       nsSize hScrollbarPrefSize;
599       GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, nullptr,
600                           &hScrollbarPrefSize);
601       availISize = std::max(0, availISize - hScrollbarPrefSize.height);
602     }
603   } else {
604     if (aAssumeHScroll) {
605       nsSize hScrollbarPrefSize;
606       GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, nullptr,
607                           &hScrollbarPrefSize);
608       if (computedBSize != NS_UNCONSTRAINEDSIZE) {
609         computedBSize = std::max(0, computedBSize - hScrollbarPrefSize.height);
610       }
611       computedMinBSize =
612           std::max(0, computedMinBSize - hScrollbarPrefSize.height);
613       if (computedMaxBSize != NS_UNCONSTRAINEDSIZE) {
614         computedMaxBSize =
615             std::max(0, computedMaxBSize - hScrollbarPrefSize.height);
616       }
617     }
618 
619     if (aAssumeVScroll) {
620       nsSize vScrollbarPrefSize;
621       GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox, nullptr,
622                           &vScrollbarPrefSize);
623       availISize = std::max(0, availISize - vScrollbarPrefSize.width);
624     }
625   }
626 
627   nsPresContext* presContext = PresContext();
628 
629   // Pass false for aInit so we can pass in the correct padding.
630   ReflowInput kidReflowInput(presContext, aState->mReflowInput,
631                              mHelper.mScrolledFrame,
632                              LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE),
633                              Nothing(), ReflowInput::CALLER_WILL_INIT);
634   const nsMargin physicalPadding = padding.GetPhysicalMargin(wm);
635   kidReflowInput.Init(presContext, Nothing(), nullptr, &physicalPadding);
636   kidReflowInput.mFlags.mAssumingHScrollbar = aAssumeHScroll;
637   kidReflowInput.mFlags.mAssumingVScrollbar = aAssumeVScroll;
638   kidReflowInput.SetComputedBSize(computedBSize);
639   kidReflowInput.ComputedMinBSize() = computedMinBSize;
640   kidReflowInput.ComputedMaxBSize() = computedMaxBSize;
641   const WritingMode kidWM = kidReflowInput.GetWritingMode();
642   if (aState->mReflowInput.IsBResizeForWM(kidWM)) {
643     kidReflowInput.SetBResize(true);
644   }
645   if (aState->mReflowInput.IsBResizeForPercentagesForWM(kidWM)) {
646     kidReflowInput.mFlags.mIsBResizeForPercentages = true;
647   }
648 
649   // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to
650   // reflect our assumptions while we reflow the child.
651   bool didHaveHorizontalScrollbar = mHelper.mHasHorizontalScrollbar;
652   bool didHaveVerticalScrollbar = mHelper.mHasVerticalScrollbar;
653   mHelper.mHasHorizontalScrollbar = aAssumeHScroll;
654   mHelper.mHasVerticalScrollbar = aAssumeVScroll;
655 
656   nsReflowStatus status;
657   // No need to pass a true container-size to ReflowChild or
658   // FinishReflowChild, because it's only used there when positioning
659   // the frame (i.e. if ReflowChildFlags::NoMoveFrame isn't set)
660   const nsSize dummyContainerSize;
661   ReflowChild(mHelper.mScrolledFrame, presContext, *aMetrics, kidReflowInput,
662               wm, LogicalPoint(wm), dummyContainerSize,
663               ReflowChildFlags::NoMoveFrame, status);
664 
665   mHelper.mHasHorizontalScrollbar = didHaveHorizontalScrollbar;
666   mHelper.mHasVerticalScrollbar = didHaveVerticalScrollbar;
667 
668   // Don't resize or position the view (if any) because we're going to resize
669   // it to the correct size anyway in PlaceScrollArea. Allowing it to
670   // resize here would size it to the natural height of the frame,
671   // which will usually be different from the scrollport height;
672   // invalidating the difference will cause unnecessary repainting.
673   FinishReflowChild(
674       mHelper.mScrolledFrame, presContext, *aMetrics, &kidReflowInput, wm,
675       LogicalPoint(wm), dummyContainerSize,
676       ReflowChildFlags::NoMoveFrame | ReflowChildFlags::NoSizeView);
677 
678   // XXX Some frames (e.g., nsPluginFrame, nsFrameFrame, nsTextFrame) don't
679   // bother setting their mOverflowArea. This is wrong because every frame
680   // should always set mOverflowArea. In fact nsPluginFrame and nsFrameFrame
681   // don't support the 'outline' property because of this. Rather than fix the
682   // world right now, just fix up the overflow area if necessary. Note that we
683   // don't check HasOverflowRect() because it could be set even though the
684   // overflow area doesn't include the frame bounds.
685   aMetrics->UnionOverflowAreasWithDesiredBounds();
686 
687   auto* disp = StyleDisplay();
688   if (MOZ_UNLIKELY(
689           disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
690           disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox)) {
691     nsOverflowAreas childOverflow;
692     nsLayoutUtils::UnionChildOverflow(mHelper.mScrolledFrame, childOverflow);
693     nsRect childScrollableOverflow = childOverflow.ScrollableOverflow();
694     if (disp->mOverflowClipBoxBlock == StyleOverflowClipBox::PaddingBox) {
695       padding.BStart(wm) = nscoord(0);
696       padding.BEnd(wm) = nscoord(0);
697     }
698     if (disp->mOverflowClipBoxInline == StyleOverflowClipBox::PaddingBox) {
699       padding.IStart(wm) = nscoord(0);
700       padding.IEnd(wm) = nscoord(0);
701     }
702     childScrollableOverflow.Inflate(padding.GetPhysicalMargin(wm));
703     nsRect contentArea = wm.IsVertical()
704                              ? nsRect(0, 0, computedBSize, availISize)
705                              : nsRect(0, 0, availISize, computedBSize);
706     if (!contentArea.Contains(childScrollableOverflow)) {
707       aMetrics->mOverflowAreas.ScrollableOverflow() = childScrollableOverflow;
708     }
709   }
710 
711   aState->mContentsOverflowAreas = aMetrics->mOverflowAreas;
712   aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
713   aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
714 }
715 
GuessHScrollbarNeeded(const ScrollReflowInput & aState)716 bool nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowInput& aState) {
717   if (aState.mHScrollbar != ShowScrollbar::Auto) {
718     // no guessing required
719     return aState.mHScrollbar == ShowScrollbar::Always;
720   }
721   return mHelper.mHasHorizontalScrollbar;
722 }
723 
GuessVScrollbarNeeded(const ScrollReflowInput & aState)724 bool nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowInput& aState) {
725   if (aState.mVScrollbar != ShowScrollbar::Auto) {
726     // no guessing required
727     return aState.mVScrollbar == ShowScrollbar::Always;
728   }
729 
730   // If we've had at least one non-initial reflow, then just assume
731   // the state of the vertical scrollbar will be what we determined
732   // last time.
733   if (mHelper.mHadNonInitialReflow) {
734     return mHelper.mHasVerticalScrollbar;
735   }
736 
737   // If this is the initial reflow, guess false because usually
738   // we have very little content by then.
739   if (InInitialReflow()) return false;
740 
741   if (mHelper.mIsRoot) {
742     nsIFrame* f = mHelper.mScrolledFrame->PrincipalChildList().FirstChild();
743     if (f && f->IsSVGOuterSVGFrame() &&
744         static_cast<nsSVGOuterSVGFrame*>(f)->VerticalScrollbarNotNeeded()) {
745       // Common SVG case - avoid a bad guess.
746       return false;
747     }
748     // Assume that there will be a scrollbar; it seems to me
749     // that 'most pages' do have a scrollbar, and anyway, it's cheaper
750     // to do an extra reflow for the pages that *don't* need a
751     // scrollbar (because on average they will have less content).
752     return true;
753   }
754 
755   // For non-viewports, just guess that we don't need a scrollbar.
756   // XXX I wonder if statistically this is the right idea; I'm
757   // basically guessing that there are a lot of overflow:auto DIVs
758   // that get their intrinsic size and don't overflow
759   return false;
760 }
761 
InInitialReflow() const762 bool nsHTMLScrollFrame::InInitialReflow() const {
763   // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a
764   // root scrollframe.  In that case we want to skip this clause altogether.
765   // The guess here is that there are lots of overflow:auto divs out there that
766   // end up auto-sizing so they don't overflow, and that the root basically
767   // always needs a scrollbar if it did last time we loaded this page (good
768   // assumption, because our initial reflow is no longer synchronous).
769   return !mHelper.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW);
770 }
771 
ReflowContents(ScrollReflowInput * aState,const ReflowOutput & aDesiredSize)772 void nsHTMLScrollFrame::ReflowContents(ScrollReflowInput* aState,
773                                        const ReflowOutput& aDesiredSize) {
774   ReflowOutput kidDesiredSize(aDesiredSize.GetWritingMode());
775   ReflowScrolledFrame(aState, GuessHScrollbarNeeded(*aState),
776                       GuessVScrollbarNeeded(*aState), &kidDesiredSize);
777 
778   // There's an important special case ... if the child appears to fit
779   // in the inside-border rect (but overflows the scrollport), we
780   // should try laying it out without a vertical scrollbar. It will
781   // usually fit because making the available-width wider will not
782   // normally make the child taller. (The only situation I can think
783   // of is when you have a line containing %-width inline replaced
784   // elements whose percentages sum to more than 100%, so increasing
785   // the available width makes the line break where it was fitting
786   // before.) If we don't treat this case specially, then we will
787   // decide that showing scrollbars is OK because the content
788   // overflows when we're showing scrollbars and we won't try to
789   // remove the vertical scrollbar.
790 
791   // Detecting when we enter this special case is important for when
792   // people design layouts that exactly fit the container "most of the
793   // time".
794 
795   // XXX Is this check really sufficient to catch all the incremental cases
796   // where the ideal case doesn't have a scrollbar?
797   if ((aState->mReflowedContentsWithHScrollbar ||
798        aState->mReflowedContentsWithVScrollbar) &&
799       aState->mVScrollbar != ShowScrollbar::Always &&
800       aState->mHScrollbar != ShowScrollbar::Always) {
801     nsSize kidSize = aState->mReflowInput.mStyleDisplay->IsContainSize()
802                          ? nsSize(0, 0)
803                          : kidDesiredSize.PhysicalSize();
804     nsSize insideBorderSize = ComputeInsideBorderSize(aState, kidSize);
805     nsRect scrolledRect = mHelper.GetUnsnappedScrolledRectInternal(
806         kidDesiredSize.ScrollableOverflow(), insideBorderSize);
807     if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
808       // Let's pretend we had no scrollbars coming in here
809       kidDesiredSize.mOverflowAreas.Clear();
810       ReflowScrolledFrame(aState, false, false, &kidDesiredSize);
811     }
812   }
813 
814   if (IsRootScrollFrameOfDocument()) {
815     mHelper.UpdateMinimumScaleSize(
816         aState->mContentsOverflowAreas.ScrollableOverflow(),
817         kidDesiredSize.PhysicalSize());
818   }
819 
820   // Try vertical scrollbar settings that leave the vertical scrollbar
821   // unchanged. Do this first because changing the vertical scrollbar setting is
822   // expensive, forcing a reflow always.
823 
824   // Try leaving the horizontal scrollbar unchanged first. This will be more
825   // efficient.
826   if (TryLayout(aState, &kidDesiredSize,
827                 aState->mReflowedContentsWithHScrollbar,
828                 aState->mReflowedContentsWithVScrollbar, false))
829     return;
830   if (TryLayout(aState, &kidDesiredSize,
831                 !aState->mReflowedContentsWithHScrollbar,
832                 aState->mReflowedContentsWithVScrollbar, false))
833     return;
834 
835   // OK, now try toggling the vertical scrollbar. The performance advantage
836   // of trying the status-quo horizontal scrollbar state
837   // does not exist here (we'll have to reflow due to the vertical scrollbar
838   // change), so always try no horizontal scrollbar first.
839   bool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar;
840   if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false))
841     return;
842   if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false))
843     return;
844 
845   // OK, we're out of ideas. Try again enabling whatever scrollbars we can
846   // enable and force the layout to stick even if it's inconsistent.
847   // This just happens sometimes.
848   TryLayout(aState, &kidDesiredSize,
849             aState->mHScrollbar != ShowScrollbar::Never,
850             aState->mVScrollbar != ShowScrollbar::Never, true);
851 }
852 
PlaceScrollArea(ScrollReflowInput & aState,const nsPoint & aScrollPosition)853 void nsHTMLScrollFrame::PlaceScrollArea(ScrollReflowInput& aState,
854                                         const nsPoint& aScrollPosition) {
855   nsIFrame* scrolledFrame = mHelper.mScrolledFrame;
856   // Set the x,y of the scrolled frame to the correct value
857   scrolledFrame->SetPosition(mHelper.mScrollPort.TopLeft() - aScrollPosition);
858 
859   // Recompute our scrollable overflow, taking perspective children into
860   // account. Note that this only recomputes the overflow areas stored on the
861   // helper (which are used to compute scrollable length and scrollbar thumb
862   // sizes) but not the overflow areas stored on the frame. This seems to work
863   // for now, but it's possible that we may need to update both in the future.
864   AdjustForPerspective(aState.mContentsOverflowAreas.ScrollableOverflow());
865 
866   nsRect scrolledArea;
867   // Preserve the width or height of empty rects
868   nsSize portSize = mHelper.mScrollPort.Size();
869   nsRect scrolledRect = mHelper.GetUnsnappedScrolledRectInternal(
870       aState.mContentsOverflowAreas.ScrollableOverflow(), portSize);
871   scrolledArea.UnionRectEdges(scrolledRect, nsRect(nsPoint(0, 0), portSize));
872 
873   // Store the new overflow area. Note that this changes where an outline
874   // of the scrolled frame would be painted, but scrolled frames can't have
875   // outlines (the outline would go on this scrollframe instead).
876   // Using FinishAndStoreOverflow is needed so the overflow rect
877   // gets set correctly.  It also messes with the overflow rect in the
878   // -moz-hidden-unscrollable case, but scrolled frames can't have
879   // 'overflow' either.
880   // This needs to happen before SyncFrameViewAfterReflow so
881   // HasOverflowRect() will return the correct value.
882   nsOverflowAreas overflow(scrolledArea, scrolledArea);
883   scrolledFrame->FinishAndStoreOverflow(overflow, scrolledFrame->GetSize());
884 
885   // Note that making the view *exactly* the size of the scrolled area
886   // is critical, since the view scrolling code uses the size of the
887   // scrolled view to clamp scroll requests.
888   // Normally the scrolledFrame won't have a view but in some cases it
889   // might create its own.
890   nsContainerFrame::SyncFrameViewAfterReflow(
891       scrolledFrame->PresContext(), scrolledFrame, scrolledFrame->GetView(),
892       scrolledArea, ReflowChildFlags::Default);
893 }
894 
GetIntrinsicVScrollbarWidth(gfxContext * aRenderingContext)895 nscoord nsHTMLScrollFrame::GetIntrinsicVScrollbarWidth(
896     gfxContext* aRenderingContext) {
897   ScrollStyles ss = GetScrollStyles();
898   if (ss.mVertical != StyleOverflow::Scroll || !mHelper.mVScrollbarBox)
899     return 0;
900 
901   // Don't need to worry about reflow depth here since it's
902   // just for scrollbars
903   nsBoxLayoutState bls(PresContext(), aRenderingContext, 0);
904   nsSize vScrollbarPrefSize(0, 0);
905   GetScrollbarMetrics(bls, mHelper.mVScrollbarBox, nullptr,
906                       &vScrollbarPrefSize);
907   return vScrollbarPrefSize.width;
908 }
909 
910 /* virtual */
GetMinISize(gfxContext * aRenderingContext)911 nscoord nsHTMLScrollFrame::GetMinISize(gfxContext* aRenderingContext) {
912   nscoord result = StyleDisplay()->IsContainSize()
913                        ? 0
914                        : mHelper.mScrolledFrame->GetMinISize(aRenderingContext);
915   DISPLAY_MIN_INLINE_SIZE(this, result);
916   return result + GetIntrinsicVScrollbarWidth(aRenderingContext);
917 }
918 
919 /* virtual */
GetPrefISize(gfxContext * aRenderingContext)920 nscoord nsHTMLScrollFrame::GetPrefISize(gfxContext* aRenderingContext) {
921   nscoord result =
922       StyleDisplay()->IsContainSize()
923           ? 0
924           : mHelper.mScrolledFrame->GetPrefISize(aRenderingContext);
925   DISPLAY_PREF_INLINE_SIZE(this, result);
926   return NSCoordSaturatingAdd(result,
927                               GetIntrinsicVScrollbarWidth(aRenderingContext));
928 }
929 
GetXULPadding(nsMargin & aMargin)930 nsresult nsHTMLScrollFrame::GetXULPadding(nsMargin& aMargin) {
931   // Our padding hangs out on the inside of the scrollframe, but XUL doesn't
932   // reaize that.  If we're stuck inside a XUL box, we need to claim no
933   // padding.
934   // @see also nsXULScrollFrame::GetXULPadding.
935   aMargin.SizeTo(0, 0, 0, 0);
936   return NS_OK;
937 }
938 
IsXULCollapsed()939 bool nsHTMLScrollFrame::IsXULCollapsed() {
940   // We're never collapsed in the box sense.
941   return false;
942 }
943 
944 // When we have perspective set on the outer scroll frame, and transformed
945 // children (possibly with preserve-3d) then the effective transform on the
946 // child depends on the offset to the scroll frame, which changes as we scroll.
947 // This perspective transform can cause the element to move relative to the
948 // scrolled inner frame, which would cause the scrollable length changes during
949 // scrolling if we didn't account for it. Since we don't want scrollHeight/Width
950 // and the size of scrollbar thumbs to change during scrolling, we compute the
951 // scrollable overflow by determining the scroll position at which the child
952 // becomes completely visible within the scrollport rather than using the union
953 // of the overflow areas at their current position.
GetScrollableOverflowForPerspective(nsIFrame * aScrolledFrame,nsIFrame * aCurrentFrame,const nsRect aScrollPort,nsPoint aOffset,nsRect & aScrolledFrameOverflowArea)954 static void GetScrollableOverflowForPerspective(
955     nsIFrame* aScrolledFrame, nsIFrame* aCurrentFrame, const nsRect aScrollPort,
956     nsPoint aOffset, nsRect& aScrolledFrameOverflowArea) {
957   // Iterate over all children except pop-ups.
958   FrameChildListIDs skip = {nsIFrame::kSelectPopupList, nsIFrame::kPopupList};
959   for (const auto& [list, listID] : aCurrentFrame->ChildLists()) {
960     if (skip.contains(listID)) {
961       continue;
962     }
963 
964     for (nsIFrame* child : list) {
965       nsPoint offset = aOffset;
966 
967       // When we reach a direct child of the scroll, then we record the offset
968       // to convert from that frame's coordinate into the scroll frame's
969       // coordinates. Preserve-3d descendant frames use the same offset as their
970       // ancestors, since TransformRect already converts us into the coordinate
971       // space of the preserve-3d root.
972       if (aScrolledFrame == aCurrentFrame) {
973         offset = child->GetPosition();
974       }
975 
976       if (child->Extend3DContext()) {
977         // If we're a preserve-3d frame, then recurse and include our
978         // descendants since overflow of preserve-3d frames is only included
979         // in the post-transform overflow area of the preserve-3d root frame.
980         GetScrollableOverflowForPerspective(aScrolledFrame, child, aScrollPort,
981                                             offset, aScrolledFrameOverflowArea);
982       }
983 
984       // If we're transformed, then we want to consider the possibility that
985       // this frame might move relative to the scrolled frame when scrolling.
986       // For preserve-3d, leaf frames have correct overflow rects relative to
987       // themselves. preserve-3d 'nodes' (intermediate frames and the root) have
988       // only their untransformed children included in their overflow relative
989       // to self, which is what we want to include here.
990       if (child->IsTransformed()) {
991         // Compute the overflow rect for this leaf transform frame in the
992         // coordinate space of the scrolled frame.
993         nsPoint scrollPos = aScrolledFrame->GetPosition();
994         nsRect preScroll, postScroll;
995         {
996           // TODO: Can we reuse the reference box?
997           TransformReferenceBox refBox(child);
998           preScroll = nsDisplayTransform::TransformRect(
999               child->GetScrollableOverflowRectRelativeToSelf(), child, refBox);
1000         }
1001 
1002         // Temporarily override the scroll position of the scrolled frame by
1003         // 10 CSS pixels, and then recompute what the overflow rect would be.
1004         // This scroll position may not be valid, but that shouldn't matter
1005         // for our calculations.
1006         {
1007           aScrolledFrame->SetPosition(scrollPos + nsPoint(600, 600));
1008           TransformReferenceBox refBox(child);
1009           postScroll = nsDisplayTransform::TransformRect(
1010               child->GetScrollableOverflowRectRelativeToSelf(), child, refBox);
1011           aScrolledFrame->SetPosition(scrollPos);
1012         }
1013 
1014         // Compute how many app units the overflow rects moves by when we adjust
1015         // the scroll position by 1 app unit.
1016         double rightDelta =
1017             (postScroll.XMost() - preScroll.XMost() + 600.0) / 600.0;
1018         double bottomDelta =
1019             (postScroll.YMost() - preScroll.YMost() + 600.0) / 600.0;
1020 
1021         // We can't ever have negative scrolling.
1022         NS_ASSERTION(rightDelta > 0.0f && bottomDelta > 0.0f,
1023                      "Scrolling can't be reversed!");
1024 
1025         // Move preScroll into the coordinate space of the scrollport.
1026         preScroll += offset + scrollPos;
1027 
1028         // For each of the four edges of preScroll, figure out how far they
1029         // extend beyond the scrollport. Ignore negative values since that means
1030         // that side is already scrolled in to view and we don't need to add
1031         // overflow to account for it.
1032         nsMargin overhang(std::max(0, aScrollPort.Y() - preScroll.Y()),
1033                           std::max(0, preScroll.XMost() - aScrollPort.XMost()),
1034                           std::max(0, preScroll.YMost() - aScrollPort.YMost()),
1035                           std::max(0, aScrollPort.X() - preScroll.X()));
1036 
1037         // Scale according to rightDelta/bottomDelta to adjust for the different
1038         // scroll rates.
1039         overhang.top /= bottomDelta;
1040         overhang.right /= rightDelta;
1041         overhang.bottom /= bottomDelta;
1042         overhang.left /= rightDelta;
1043 
1044         // Take the minimum overflow rect that would allow the current scroll
1045         // position, using the size of the scroll port and offset by the
1046         // inverse of the scroll position.
1047         nsRect overflow = aScrollPort - scrollPos;
1048 
1049         // Expand it by our margins to get an overflow rect that would allow all
1050         // edges of our transformed content to be scrolled into view.
1051         overflow.Inflate(overhang);
1052 
1053         // Merge it with the combined overflow
1054         aScrolledFrameOverflowArea.UnionRect(aScrolledFrameOverflowArea,
1055                                              overflow);
1056       } else if (aCurrentFrame == aScrolledFrame) {
1057         aScrolledFrameOverflowArea.UnionRect(
1058             aScrolledFrameOverflowArea,
1059             child->GetScrollableOverflowRectRelativeToParent());
1060       }
1061     }
1062   }
1063 }
1064 
GetLogicalBaseline(WritingMode aWritingMode) const1065 nscoord nsHTMLScrollFrame::GetLogicalBaseline(WritingMode aWritingMode) const {
1066   // This function implements some of the spec text here:
1067   //  https://drafts.csswg.org/css-align/#baseline-export
1068   //
1069   // Specifically: if our scrolled frame is a block, we just use the inherited
1070   // GetLogicalBaseline() impl, which synthesizes a baseline from the
1071   // margin-box. Otherwise, we defer to our scrolled frame, considering it
1072   // to be scrolled to its initial scroll position.
1073   if (mHelper.mScrolledFrame->IsBlockFrameOrSubclass() ||
1074       StyleDisplay()->IsContainLayout()) {
1075     return nsContainerFrame::GetLogicalBaseline(aWritingMode);
1076   }
1077 
1078   // OK, here's where we defer to our scrolled frame. We have to add our
1079   // border BStart thickness to whatever it returns, to produce an offset in
1080   // our frame-rect's coordinate system. (We don't have to add padding,
1081   // because the scrolled frame handles our padding.)
1082   LogicalMargin border = GetLogicalUsedBorder(aWritingMode);
1083 
1084   return border.BStart(aWritingMode) +
1085          mHelper.mScrolledFrame->GetLogicalBaseline(aWritingMode);
1086 }
1087 
AdjustForPerspective(nsRect & aScrollableOverflow)1088 void nsHTMLScrollFrame::AdjustForPerspective(nsRect& aScrollableOverflow) {
1089   // If we have perspective that is being applied to our children, then
1090   // the effective transform on the child depends on the relative position
1091   // of the child to us and changes during scrolling.
1092   if (!ChildrenHavePerspective()) {
1093     return;
1094   }
1095   aScrollableOverflow.SetEmpty();
1096   GetScrollableOverflowForPerspective(
1097       mHelper.mScrolledFrame, mHelper.mScrolledFrame, mHelper.mScrollPort,
1098       nsPoint(), aScrollableOverflow);
1099 }
1100 
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)1101 void nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext,
1102                                ReflowOutput& aDesiredSize,
1103                                const ReflowInput& aReflowInput,
1104                                nsReflowStatus& aStatus) {
1105   MarkInReflow();
1106   DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
1107   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
1108   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1109 
1110   mHelper.HandleScrollbarStyleSwitching();
1111 
1112   ScrollReflowInput state(this, aReflowInput);
1113   // sanity check: ensure that if we have no scrollbar, we treat it
1114   // as hidden.
1115   if (!mHelper.mVScrollbarBox || mHelper.mNeverHasVerticalScrollbar) {
1116     state.mVScrollbar = ShowScrollbar::Never;
1117   }
1118   if (!mHelper.mHScrollbarBox || mHelper.mNeverHasHorizontalScrollbar) {
1119     state.mHScrollbar = ShowScrollbar::Never;
1120   }
1121 
1122   //------------ Handle Incremental Reflow -----------------
1123   bool reflowHScrollbar = true;
1124   bool reflowVScrollbar = true;
1125   bool reflowScrollCorner = true;
1126   if (!aReflowInput.ShouldReflowAllKids()) {
1127 #define NEEDS_REFLOW(frame_) ((frame_) && NS_SUBTREE_DIRTY(frame_))
1128 
1129     reflowHScrollbar = NEEDS_REFLOW(mHelper.mHScrollbarBox);
1130     reflowVScrollbar = NEEDS_REFLOW(mHelper.mVScrollbarBox);
1131     reflowScrollCorner = NEEDS_REFLOW(mHelper.mScrollCornerBox) ||
1132                          NEEDS_REFLOW(mHelper.mResizerBox);
1133 
1134 #undef NEEDS_REFLOW
1135   }
1136 
1137   if (mHelper.mIsRoot) {
1138     mHelper.mCollapsedResizer = true;
1139     reflowScrollCorner = false;
1140 
1141     // Hide the scrollbar when the scrollbar-width is set to none.
1142     // This is only needed for root element because scrollbars of non-
1143     // root elements with "scrollbar-width: none" is already suppressed
1144     // in ScrollFrameHelper::CreateAnonymousContent.
1145     ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(this);
1146     auto scrollbarWidth = scrollbarStyle->StyleUIReset()->mScrollbarWidth;
1147     if (scrollbarWidth == StyleScrollbarWidth::None) {
1148       state.mVScrollbar = ShowScrollbar::Never;
1149       state.mHScrollbar = ShowScrollbar::Never;
1150     }
1151   }
1152 
1153   nsRect oldScrollAreaBounds = mHelper.mScrollPort;
1154   nsRect oldScrolledAreaBounds =
1155       mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
1156   nsPoint oldScrollPosition = mHelper.GetScrollPosition();
1157 
1158   state.mComputedBorder = aReflowInput.ComputedPhysicalBorderPadding() -
1159                           aReflowInput.ComputedPhysicalPadding();
1160 
1161   ReflowContents(&state, aDesiredSize);
1162 
1163   nsSize layoutSize = mHelper.mIsUsingMinimumScaleSize
1164                           ? mHelper.mMinimumScaleSize
1165                           : state.mInsideBorderSize;
1166   aDesiredSize.Width() = layoutSize.width + state.mComputedBorder.LeftRight();
1167   aDesiredSize.Height() = layoutSize.height + state.mComputedBorder.TopBottom();
1168 
1169   // Set the size of the frame now since computing the perspective-correct
1170   // overflow (within PlaceScrollArea) can rely on it.
1171   SetSize(aDesiredSize.GetWritingMode(),
1172           aDesiredSize.Size(aDesiredSize.GetWritingMode()));
1173 
1174   // Restore the old scroll position, for now, even if that's not valid anymore
1175   // because we changed size. We'll fix it up in a post-reflow callback, because
1176   // our current size may only be temporary (e.g. we're compute XUL desired
1177   // sizes).
1178   PlaceScrollArea(state, oldScrollPosition);
1179   if (!mHelper.mPostedReflowCallback) {
1180     // Make sure we'll try scrolling to restored position
1181     PresShell()->PostReflowCallback(&mHelper);
1182     mHelper.mPostedReflowCallback = true;
1183   }
1184 
1185   bool didHaveHScrollbar = mHelper.mHasHorizontalScrollbar;
1186   bool didHaveVScrollbar = mHelper.mHasVerticalScrollbar;
1187   mHelper.mHasHorizontalScrollbar = state.mShowHScrollbar;
1188   mHelper.mHasVerticalScrollbar = state.mShowVScrollbar;
1189   nsRect newScrollAreaBounds = mHelper.mScrollPort;
1190   nsRect newScrolledAreaBounds =
1191       mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
1192   if (mHelper.mSkippedScrollbarLayout || reflowHScrollbar || reflowVScrollbar ||
1193       reflowScrollCorner || (GetStateBits() & NS_FRAME_IS_DIRTY) ||
1194       didHaveHScrollbar != state.mShowHScrollbar ||
1195       didHaveVScrollbar != state.mShowVScrollbar ||
1196       !oldScrollAreaBounds.IsEqualEdges(newScrollAreaBounds) ||
1197       !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
1198     if (!mHelper.mSuppressScrollbarUpdate) {
1199       mHelper.mSkippedScrollbarLayout = false;
1200       ScrollFrameHelper::SetScrollbarVisibility(mHelper.mHScrollbarBox,
1201                                                 state.mShowHScrollbar);
1202       ScrollFrameHelper::SetScrollbarVisibility(mHelper.mVScrollbarBox,
1203                                                 state.mShowVScrollbar);
1204       // place and reflow scrollbars
1205       nsRect insideBorderArea =
1206           nsRect(nsPoint(state.mComputedBorder.left, state.mComputedBorder.top),
1207                  layoutSize);
1208       mHelper.LayoutScrollbars(state.mBoxState, insideBorderArea,
1209                                oldScrollAreaBounds);
1210     } else {
1211       mHelper.mSkippedScrollbarLayout = true;
1212     }
1213   }
1214 
1215   aDesiredSize.SetOverflowAreasToDesiredBounds();
1216 
1217   mHelper.UpdateSticky();
1218   FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput,
1219                                  aStatus);
1220 
1221   if (!InInitialReflow() && !mHelper.mHadNonInitialReflow) {
1222     mHelper.mHadNonInitialReflow = true;
1223   }
1224 
1225   if (mHelper.mIsRoot &&
1226       !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
1227     mHelper.PostScrolledAreaEvent();
1228   }
1229 
1230   mHelper.UpdatePrevScrolledRect();
1231 
1232   aStatus.Reset();  // This type of frame can't be split.
1233   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
1234   mHelper.PostOverflowEvent();
1235 }
1236 
DidReflow(nsPresContext * aPresContext,const ReflowInput * aReflowInput)1237 void nsHTMLScrollFrame::DidReflow(nsPresContext* aPresContext,
1238                                   const ReflowInput* aReflowInput) {
1239   nsContainerFrame::DidReflow(aPresContext, aReflowInput);
1240   PresShell()->PostPendingScrollAnchorAdjustment(Anchor());
1241 }
1242 
1243 ////////////////////////////////////////////////////////////////////////////////
1244 
1245 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const1246 nsresult nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const {
1247   return MakeFrameName(NS_LITERAL_STRING("HTMLScroll"), aResult);
1248 }
1249 #endif
1250 
1251 #ifdef ACCESSIBILITY
AccessibleType()1252 a11y::AccType nsHTMLScrollFrame::AccessibleType() {
1253   if (IsTableCaption()) {
1254     return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
1255   }
1256 
1257   // Create an accessible regardless of focusable state because the state can be
1258   // changed during frame life cycle without any notifications to accessibility.
1259   if (mContent->IsRootOfNativeAnonymousSubtree() ||
1260       GetScrollStyles().IsHiddenInBothDirections()) {
1261     return a11y::eNoType;
1262   }
1263 
1264   return a11y::eHyperTextType;
1265 }
1266 #endif
1267 
1268 NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)1269   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
1270   NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
1271   NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
1272   NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
1273   NS_QUERYFRAME_ENTRY(nsHTMLScrollFrame)
1274 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
1275 
1276 //----------nsXULScrollFrame-------------------------------------------
1277 
1278 nsXULScrollFrame* NS_NewXULScrollFrame(PresShell* aPresShell,
1279                                        ComputedStyle* aStyle, bool aIsRoot,
1280                                        bool aClipAllDescendants) {
1281   return new (aPresShell) nsXULScrollFrame(aStyle, aPresShell->GetPresContext(),
1282                                            aIsRoot, aClipAllDescendants);
1283 }
1284 
NS_IMPL_FRAMEARENA_HELPERS(nsXULScrollFrame)1285 NS_IMPL_FRAMEARENA_HELPERS(nsXULScrollFrame)
1286 
1287 nsXULScrollFrame::nsXULScrollFrame(ComputedStyle* aStyle,
1288                                    nsPresContext* aPresContext, bool aIsRoot,
1289                                    bool aClipAllDescendants)
1290     : nsBoxFrame(aStyle, aPresContext, kClassID, aIsRoot),
1291       mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot) {
1292   SetXULLayoutManager(nullptr);
1293   mHelper.mClipAllDescendants = aClipAllDescendants;
1294 }
1295 
ScrollbarActivityStarted() const1296 void nsXULScrollFrame::ScrollbarActivityStarted() const {
1297   if (mHelper.mScrollbarActivity) {
1298     mHelper.mScrollbarActivity->ActivityStarted();
1299   }
1300 }
1301 
ScrollbarActivityStopped() const1302 void nsXULScrollFrame::ScrollbarActivityStopped() const {
1303   if (mHelper.mScrollbarActivity) {
1304     mHelper.mScrollbarActivity->ActivityStopped();
1305   }
1306 }
1307 
GetDesiredScrollbarSizes(nsBoxLayoutState * aState)1308 nsMargin ScrollFrameHelper::GetDesiredScrollbarSizes(nsBoxLayoutState* aState) {
1309   NS_ASSERTION(aState && aState->GetRenderingContext(),
1310                "Must have rendering context in layout state for size "
1311                "computations");
1312 
1313   nsMargin result(0, 0, 0, 0);
1314 
1315   if (mVScrollbarBox) {
1316     nsSize size = mVScrollbarBox->GetXULPrefSize(*aState);
1317     nsIFrame::AddXULMargin(mVScrollbarBox, size);
1318     if (IsScrollbarOnRight())
1319       result.left = size.width;
1320     else
1321       result.right = size.width;
1322   }
1323 
1324   if (mHScrollbarBox) {
1325     nsSize size = mHScrollbarBox->GetXULPrefSize(*aState);
1326     nsIFrame::AddXULMargin(mHScrollbarBox, size);
1327     // We don't currently support any scripts that would require a scrollbar
1328     // at the top. (Are there any?)
1329     result.bottom = size.height;
1330   }
1331 
1332   return result;
1333 }
1334 
GetNondisappearingScrollbarWidth(nsBoxLayoutState * aState,WritingMode aWM)1335 nscoord ScrollFrameHelper::GetNondisappearingScrollbarWidth(
1336     nsBoxLayoutState* aState, WritingMode aWM) {
1337   NS_ASSERTION(aState && aState->GetRenderingContext(),
1338                "Must have rendering context in layout state for size "
1339                "computations");
1340 
1341   bool verticalWM = aWM.IsVertical();
1342   // We need to have the proper un-themed scrollbar size, regardless of whether
1343   // we're using e.g. scrollbar-width: thin, or overlay scrollbars. That's why
1344   // we use ScrollbarNonDisappearing.
1345   nsIFrame* box = verticalWM ? mHScrollbarBox : mVScrollbarBox;
1346   nsITheme* theme = aState->PresContext()->Theme();
1347   if (box &&
1348       theme->ThemeSupportsWidget(aState->PresContext(), box,
1349                                  StyleAppearance::ScrollbarNonDisappearing)) {
1350     LayoutDeviceIntSize size;
1351     bool canOverride = true;
1352     theme->GetMinimumWidgetSize(aState->PresContext(), box,
1353                                 StyleAppearance::ScrollbarNonDisappearing,
1354                                 &size, &canOverride);
1355     return aState->PresContext()->DevPixelsToAppUnits(verticalWM ? size.height
1356                                                                  : size.width);
1357   }
1358 
1359   nsMargin sizes(GetDesiredScrollbarSizes(aState));
1360   return verticalWM ? sizes.TopBottom() : sizes.LeftRight();
1361 }
1362 
HandleScrollbarStyleSwitching()1363 void ScrollFrameHelper::HandleScrollbarStyleSwitching() {
1364   // Check if we switched between scrollbar styles.
1365   if (mScrollbarActivity &&
1366       LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) == 0) {
1367     mScrollbarActivity->Destroy();
1368     mScrollbarActivity = nullptr;
1369   } else if (!mScrollbarActivity &&
1370              LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) !=
1371                  0) {
1372     mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(mOuter));
1373   }
1374 }
1375 
1376 #if defined(MOZ_WIDGET_ANDROID)
IsFocused(nsIContent * aContent)1377 static bool IsFocused(nsIContent* aContent) {
1378   // Some content elements, like the GetContent() of a scroll frame
1379   // for a text input field, are inside anonymous subtrees, but the focus
1380   // manager always reports a non-anonymous element as the focused one, so
1381   // walk up the tree until we reach a non-anonymous element.
1382   while (aContent && aContent->IsInNativeAnonymousSubtree()) {
1383     aContent = aContent->GetParent();
1384   }
1385 
1386   return aContent ? nsContentUtils::IsFocusedContent(aContent) : false;
1387 }
1388 #endif
1389 
SetScrollableByAPZ(bool aScrollable)1390 void ScrollFrameHelper::SetScrollableByAPZ(bool aScrollable) {
1391   mScrollableByAPZ = aScrollable;
1392 }
1393 
SetZoomableByAPZ(bool aZoomable)1394 void ScrollFrameHelper::SetZoomableByAPZ(bool aZoomable) {
1395   if (mZoomableByAPZ != aZoomable) {
1396     // We might be changing the result of WantAsyncScroll() so schedule a
1397     // paint to make sure we pick up the result of that change.
1398     mZoomableByAPZ = aZoomable;
1399     mOuter->SchedulePaint();
1400   }
1401 }
1402 
SetHasOutOfFlowContentInsideFilter()1403 void ScrollFrameHelper::SetHasOutOfFlowContentInsideFilter() {
1404   mHasOutOfFlowContentInsideFilter = true;
1405 }
1406 
WantAsyncScroll() const1407 bool ScrollFrameHelper::WantAsyncScroll() const {
1408   // If zooming is allowed, and this is a frame that's allowed to zoom, then
1409   // we want it to be async-scrollable or zooming will not be permitted.
1410   if (mZoomableByAPZ) {
1411     return true;
1412   }
1413 
1414   ScrollStyles styles = GetScrollStylesFromFrame();
1415   nscoord oneDevPixel =
1416       GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
1417   nsRect scrollRange = GetLayoutScrollRange();
1418 
1419   // If the page has a visual viewport size that's different from
1420   // the layout viewport size at the current zoom level, we need to be
1421   // able to scroll the visual viewport inside the layout viewport
1422   // even if the page is not zoomable.
1423   if (!GetVisualScrollRange().IsEqualInterior(scrollRange)) {
1424     return true;
1425   }
1426 
1427   bool isVScrollable = (scrollRange.height >= oneDevPixel) &&
1428                        (styles.mVertical != StyleOverflow::Hidden);
1429   bool isHScrollable = (scrollRange.width >= oneDevPixel) &&
1430                        (styles.mHorizontal != StyleOverflow::Hidden);
1431 
1432 #if defined(MOZ_WIDGET_ANDROID)
1433   // Mobile platforms need focus to scroll text inputs.
1434   bool canScrollWithoutScrollbars =
1435       !IsForTextControlWithNoScrollbars() || IsFocused(mOuter->GetContent());
1436 #else
1437   bool canScrollWithoutScrollbars = true;
1438 #endif
1439 
1440   // The check for scroll bars was added in bug 825692 to prevent layerization
1441   // of text inputs for performance reasons.
1442   bool isVAsyncScrollable =
1443       isVScrollable && (mVScrollbarBox || canScrollWithoutScrollbars);
1444   bool isHAsyncScrollable =
1445       isHScrollable && (mHScrollbarBox || canScrollWithoutScrollbars);
1446   return isVAsyncScrollable || isHAsyncScrollable;
1447 }
1448 
GetOnePixelRangeAroundPoint(const nsPoint & aPoint,bool aIsHorizontal)1449 static nsRect GetOnePixelRangeAroundPoint(const nsPoint& aPoint,
1450                                           bool aIsHorizontal) {
1451   nsRect allowedRange(aPoint, nsSize());
1452   nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
1453   if (aIsHorizontal) {
1454     allowedRange.x = aPoint.x - halfPixel;
1455     allowedRange.width = halfPixel * 2 - 1;
1456   } else {
1457     allowedRange.y = aPoint.y - halfPixel;
1458     allowedRange.height = halfPixel * 2 - 1;
1459   }
1460   return allowedRange;
1461 }
1462 
ScrollByPage(nsScrollbarFrame * aScrollbar,int32_t aDirection,nsIScrollbarMediator::ScrollSnapMode aSnap)1463 void ScrollFrameHelper::ScrollByPage(
1464     nsScrollbarFrame* aScrollbar, int32_t aDirection,
1465     nsIScrollbarMediator::ScrollSnapMode aSnap) {
1466   ScrollByUnit(aScrollbar, ScrollMode::Smooth, aDirection, ScrollUnit::PAGES,
1467                aSnap);
1468 }
1469 
ScrollByWhole(nsScrollbarFrame * aScrollbar,int32_t aDirection,nsIScrollbarMediator::ScrollSnapMode aSnap)1470 void ScrollFrameHelper::ScrollByWhole(
1471     nsScrollbarFrame* aScrollbar, int32_t aDirection,
1472     nsIScrollbarMediator::ScrollSnapMode aSnap) {
1473   ScrollByUnit(aScrollbar, ScrollMode::Instant, aDirection, ScrollUnit::WHOLE,
1474                aSnap);
1475 }
1476 
ScrollByLine(nsScrollbarFrame * aScrollbar,int32_t aDirection,nsIScrollbarMediator::ScrollSnapMode aSnap)1477 void ScrollFrameHelper::ScrollByLine(
1478     nsScrollbarFrame* aScrollbar, int32_t aDirection,
1479     nsIScrollbarMediator::ScrollSnapMode aSnap) {
1480   bool isHorizontal = aScrollbar->IsXULHorizontal();
1481   nsIntPoint delta;
1482   if (isHorizontal) {
1483     const double kScrollMultiplier =
1484         Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
1485                             NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
1486     delta.x = aDirection * kScrollMultiplier;
1487     if (GetLineScrollAmount().width * delta.x > GetPageScrollAmount().width) {
1488       // The scroll frame is so small that the delta would be more
1489       // than an entire page.  Scroll by one page instead to maintain
1490       // context.
1491       ScrollByPage(aScrollbar, aDirection);
1492       return;
1493     }
1494   } else {
1495     const double kScrollMultiplier =
1496         Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
1497                             NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
1498     delta.y = aDirection * kScrollMultiplier;
1499     if (GetLineScrollAmount().height * delta.y > GetPageScrollAmount().height) {
1500       // The scroll frame is so small that the delta would be more
1501       // than an entire page.  Scroll by one page instead to maintain
1502       // context.
1503       ScrollByPage(aScrollbar, aDirection);
1504       return;
1505     }
1506   }
1507 
1508   nsIntPoint overflow;
1509   ScrollBy(delta, ScrollUnit::LINES, ScrollMode::Smooth, &overflow,
1510            nsGkAtoms::other, nsIScrollableFrame::NOT_MOMENTUM, aSnap);
1511 }
1512 
RepeatButtonScroll(nsScrollbarFrame * aScrollbar)1513 void ScrollFrameHelper::RepeatButtonScroll(nsScrollbarFrame* aScrollbar) {
1514   aScrollbar->MoveToNewPosition();
1515 }
1516 
ThumbMoved(nsScrollbarFrame * aScrollbar,nscoord aOldPos,nscoord aNewPos)1517 void ScrollFrameHelper::ThumbMoved(nsScrollbarFrame* aScrollbar,
1518                                    nscoord aOldPos, nscoord aNewPos) {
1519   MOZ_ASSERT(aScrollbar != nullptr);
1520   bool isHorizontal = aScrollbar->IsXULHorizontal();
1521   nsPoint current = GetScrollPosition();
1522   nsPoint dest = current;
1523   if (isHorizontal) {
1524     dest.x = IsPhysicalLTR() ? aNewPos : aNewPos - GetLayoutScrollRange().width;
1525   } else {
1526     dest.y = aNewPos;
1527   }
1528   nsRect allowedRange = GetOnePixelRangeAroundPoint(dest, isHorizontal);
1529 
1530   // Don't try to scroll if we're already at an acceptable place.
1531   // Don't call Contains here since Contains returns false when the point is
1532   // on the bottom or right edge of the rectangle.
1533   if (allowedRange.ClampPoint(current) == current) {
1534     return;
1535   }
1536 
1537   ScrollTo(dest, ScrollMode::Instant, nsGkAtoms::other, &allowedRange);
1538 }
1539 
ScrollbarReleased(nsScrollbarFrame * aScrollbar)1540 void ScrollFrameHelper::ScrollbarReleased(nsScrollbarFrame* aScrollbar) {
1541   // Scrollbar scrolling does not result in fling gestures, clear any
1542   // accumulated velocity
1543   mVelocityQueue.Reset();
1544 
1545   // Perform scroll snapping, if needed.  Scrollbar movement uses the same
1546   // smooth scrolling animation as keyboard scrolling.
1547   ScrollSnap(mDestination, ScrollMode::Smooth);
1548 }
1549 
ScrollByUnit(nsScrollbarFrame * aScrollbar,ScrollMode aMode,int32_t aDirection,ScrollUnit aUnit,nsIScrollbarMediator::ScrollSnapMode aSnap)1550 void ScrollFrameHelper::ScrollByUnit(
1551     nsScrollbarFrame* aScrollbar, ScrollMode aMode, int32_t aDirection,
1552     ScrollUnit aUnit, nsIScrollbarMediator::ScrollSnapMode aSnap) {
1553   MOZ_ASSERT(aScrollbar != nullptr);
1554   bool isHorizontal = aScrollbar->IsXULHorizontal();
1555   nsIntPoint delta;
1556   if (isHorizontal) {
1557     delta.x = aDirection;
1558   } else {
1559     delta.y = aDirection;
1560   }
1561   nsIntPoint overflow;
1562   ScrollBy(delta, aUnit, aMode, &overflow, nsGkAtoms::other,
1563            nsIScrollableFrame::NOT_MOMENTUM, aSnap);
1564 }
1565 
CreateAnonymousContent(nsTArray<ContentInfo> & aElements)1566 nsresult nsXULScrollFrame::CreateAnonymousContent(
1567     nsTArray<ContentInfo>& aElements) {
1568   return mHelper.CreateAnonymousContent(aElements);
1569 }
1570 
AppendAnonymousContentTo(nsTArray<nsIContent * > & aElements,uint32_t aFilter)1571 void nsXULScrollFrame::AppendAnonymousContentTo(
1572     nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
1573   mHelper.AppendAnonymousContentTo(aElements, aFilter);
1574 }
1575 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)1576 void nsXULScrollFrame::DestroyFrom(nsIFrame* aDestructRoot,
1577                                    PostDestroyData& aPostDestroyData) {
1578   mHelper.Destroy(aPostDestroyData);
1579   nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
1580 }
1581 
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)1582 void nsXULScrollFrame::SetInitialChildList(ChildListID aListID,
1583                                            nsFrameList& aChildList) {
1584   nsBoxFrame::SetInitialChildList(aListID, aChildList);
1585   if (aListID == kPrincipalList) {
1586     mHelper.ReloadChildFrames();
1587   }
1588 }
1589 
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)1590 void nsXULScrollFrame::AppendFrames(ChildListID aListID,
1591                                     nsFrameList& aFrameList) {
1592   nsBoxFrame::AppendFrames(aListID, aFrameList);
1593   mHelper.ReloadChildFrames();
1594 }
1595 
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & aFrameList)1596 void nsXULScrollFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
1597                                     const nsLineList::iterator* aPrevFrameLine,
1598                                     nsFrameList& aFrameList) {
1599   nsBoxFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, aFrameList);
1600   mHelper.ReloadChildFrames();
1601 }
1602 
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)1603 void nsXULScrollFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
1604   nsBoxFrame::RemoveFrame(aListID, aOldFrame);
1605   mHelper.ReloadChildFrames();
1606 }
1607 
GetXULPadding(nsMargin & aMargin)1608 nsresult nsXULScrollFrame::GetXULPadding(nsMargin& aMargin) {
1609   aMargin.SizeTo(0, 0, 0, 0);
1610   return NS_OK;
1611 }
1612 
GetXULBoxAscent(nsBoxLayoutState & aState)1613 nscoord nsXULScrollFrame::GetXULBoxAscent(nsBoxLayoutState& aState) {
1614   if (!mHelper.mScrolledFrame) return 0;
1615 
1616   nscoord ascent = mHelper.mScrolledFrame->GetXULBoxAscent(aState);
1617   nsMargin m(0, 0, 0, 0);
1618   GetXULBorderAndPadding(m);
1619   ascent += m.top;
1620   GetXULMargin(m);
1621   ascent += m.top;
1622 
1623   return ascent;
1624 }
1625 
GetXULPrefSize(nsBoxLayoutState & aState)1626 nsSize nsXULScrollFrame::GetXULPrefSize(nsBoxLayoutState& aState) {
1627   nsSize pref = mHelper.mScrolledFrame->GetXULPrefSize(aState);
1628 
1629   ScrollStyles styles = GetScrollStyles();
1630 
1631   // scrolled frames don't have their own margins
1632 
1633   if (mHelper.mVScrollbarBox && styles.mVertical == StyleOverflow::Scroll) {
1634     nsSize vSize = mHelper.mVScrollbarBox->GetXULPrefSize(aState);
1635     nsIFrame::AddXULMargin(mHelper.mVScrollbarBox, vSize);
1636     pref.width += vSize.width;
1637   }
1638 
1639   if (mHelper.mHScrollbarBox && styles.mHorizontal == StyleOverflow::Scroll) {
1640     nsSize hSize = mHelper.mHScrollbarBox->GetXULPrefSize(aState);
1641     nsIFrame::AddXULMargin(mHelper.mHScrollbarBox, hSize);
1642     pref.height += hSize.height;
1643   }
1644 
1645   AddXULBorderAndPadding(pref);
1646   bool widthSet, heightSet;
1647   nsIFrame::AddXULPrefSize(this, pref, widthSet, heightSet);
1648   return pref;
1649 }
1650 
GetXULMinSize(nsBoxLayoutState & aState)1651 nsSize nsXULScrollFrame::GetXULMinSize(nsBoxLayoutState& aState) {
1652   nsSize min = mHelper.mScrolledFrame->GetXULMinSizeForScrollArea(aState);
1653 
1654   ScrollStyles styles = GetScrollStyles();
1655 
1656   if (mHelper.mVScrollbarBox && styles.mVertical == StyleOverflow::Scroll) {
1657     nsSize vSize = mHelper.mVScrollbarBox->GetXULMinSize(aState);
1658     AddXULMargin(mHelper.mVScrollbarBox, vSize);
1659     min.width += vSize.width;
1660     if (min.height < vSize.height) min.height = vSize.height;
1661   }
1662 
1663   if (mHelper.mHScrollbarBox && styles.mHorizontal == StyleOverflow::Scroll) {
1664     nsSize hSize = mHelper.mHScrollbarBox->GetXULMinSize(aState);
1665     AddXULMargin(mHelper.mHScrollbarBox, hSize);
1666     min.height += hSize.height;
1667     if (min.width < hSize.width) min.width = hSize.width;
1668   }
1669 
1670   AddXULBorderAndPadding(min);
1671   bool widthSet, heightSet;
1672   nsIFrame::AddXULMinSize(this, min, widthSet, heightSet);
1673   return min;
1674 }
1675 
GetXULMaxSize(nsBoxLayoutState & aState)1676 nsSize nsXULScrollFrame::GetXULMaxSize(nsBoxLayoutState& aState) {
1677   nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1678 
1679   AddXULBorderAndPadding(maxSize);
1680   bool widthSet, heightSet;
1681   nsIFrame::AddXULMaxSize(this, maxSize, widthSet, heightSet);
1682   return maxSize;
1683 }
1684 
1685 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const1686 nsresult nsXULScrollFrame::GetFrameName(nsAString& aResult) const {
1687   return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult);
1688 }
1689 #endif
1690 
1691 NS_IMETHODIMP
DoXULLayout(nsBoxLayoutState & aState)1692 nsXULScrollFrame::DoXULLayout(nsBoxLayoutState& aState) {
1693   ReflowChildFlags flags = aState.LayoutFlags();
1694   nsresult rv = XULLayout(aState);
1695   aState.SetLayoutFlags(flags);
1696   return rv;
1697 }
1698 
1699 NS_QUERYFRAME_HEAD(nsXULScrollFrame)
1700   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
1701   NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
1702   NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
1703   NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
1704 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
1705 
1706 //-------------------- Helper ----------------------
1707 
1708 // AsyncSmoothMSDScroll has ref counting.
1709 class ScrollFrameHelper::AsyncSmoothMSDScroll final
1710     : public nsARefreshObserver {
1711  public:
AsyncSmoothMSDScroll(const nsPoint & aInitialPosition,const nsPoint & aInitialDestination,const nsSize & aInitialVelocity,const nsRect & aRange,const mozilla::TimeStamp & aStartTime,nsPresContext * aPresContext)1712   AsyncSmoothMSDScroll(const nsPoint& aInitialPosition,
1713                        const nsPoint& aInitialDestination,
1714                        const nsSize& aInitialVelocity, const nsRect& aRange,
1715                        const mozilla::TimeStamp& aStartTime,
1716                        nsPresContext* aPresContext)
1717       : mXAxisModel(aInitialPosition.x, aInitialDestination.x,
1718                     aInitialVelocity.width,
1719                     StaticPrefs::layout_css_scroll_behavior_spring_constant(),
1720                     StaticPrefs::layout_css_scroll_behavior_damping_ratio()),
1721         mYAxisModel(aInitialPosition.y, aInitialDestination.y,
1722                     aInitialVelocity.height,
1723                     StaticPrefs::layout_css_scroll_behavior_spring_constant(),
1724                     StaticPrefs::layout_css_scroll_behavior_damping_ratio()),
1725         mRange(aRange),
1726         mLastRefreshTime(aStartTime),
1727         mCallee(nullptr),
1728         mOneDevicePixelInAppUnits(aPresContext->DevPixelsToAppUnits(1)) {
1729     Telemetry::SetHistogramRecordingEnabled(
1730         Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, true);
1731   }
1732 
NS_INLINE_DECL_REFCOUNTING(AsyncSmoothMSDScroll,override)1733   NS_INLINE_DECL_REFCOUNTING(AsyncSmoothMSDScroll, override)
1734 
1735   nsSize GetVelocity() {
1736     // In nscoords per second
1737     return nsSize(mXAxisModel.GetVelocity(), mYAxisModel.GetVelocity());
1738   }
1739 
GetPosition()1740   nsPoint GetPosition() {
1741     // In nscoords
1742     return nsPoint(NSToCoordRound(mXAxisModel.GetPosition()),
1743                    NSToCoordRound(mYAxisModel.GetPosition()));
1744   }
1745 
SetDestination(const nsPoint & aDestination)1746   void SetDestination(const nsPoint& aDestination) {
1747     mXAxisModel.SetDestination(static_cast<int32_t>(aDestination.x));
1748     mYAxisModel.SetDestination(static_cast<int32_t>(aDestination.y));
1749   }
1750 
SetRange(const nsRect & aRange)1751   void SetRange(const nsRect& aRange) { mRange = aRange; }
1752 
GetRange()1753   nsRect GetRange() { return mRange; }
1754 
Simulate(const TimeDuration & aDeltaTime)1755   void Simulate(const TimeDuration& aDeltaTime) {
1756     mXAxisModel.Simulate(aDeltaTime);
1757     mYAxisModel.Simulate(aDeltaTime);
1758 
1759     nsPoint desired = GetPosition();
1760     nsPoint clamped = mRange.ClampPoint(desired);
1761     if (desired.x != clamped.x) {
1762       // The scroll has hit the "wall" at the left or right edge of the allowed
1763       // scroll range.
1764       // Absorb the impact to avoid bounceback effect.
1765       mXAxisModel.SetVelocity(0.0);
1766       mXAxisModel.SetPosition(clamped.x);
1767     }
1768 
1769     if (desired.y != clamped.y) {
1770       // The scroll has hit the "wall" at the left or right edge of the allowed
1771       // scroll range.
1772       // Absorb the impact to avoid bounceback effect.
1773       mYAxisModel.SetVelocity(0.0);
1774       mYAxisModel.SetPosition(clamped.y);
1775     }
1776   }
1777 
IsFinished()1778   bool IsFinished() {
1779     return mXAxisModel.IsFinished(mOneDevicePixelInAppUnits) &&
1780            mYAxisModel.IsFinished(mOneDevicePixelInAppUnits);
1781   }
1782 
WillRefresh(mozilla::TimeStamp aTime)1783   virtual void WillRefresh(mozilla::TimeStamp aTime) override {
1784     mozilla::TimeDuration deltaTime = aTime - mLastRefreshTime;
1785     mLastRefreshTime = aTime;
1786 
1787     // The callback may release "this".
1788     // We don't access members after returning, so no need for KungFuDeathGrip.
1789     ScrollFrameHelper::AsyncSmoothMSDScrollCallback(mCallee, deltaTime);
1790   }
1791 
1792   /*
1793    * Set a refresh observer for smooth scroll iterations (and start observing).
1794    * Should be used at most once during the lifetime of this object.
1795    */
SetRefreshObserver(ScrollFrameHelper * aCallee)1796   void SetRefreshObserver(ScrollFrameHelper* aCallee) {
1797     NS_ASSERTION(aCallee && !mCallee,
1798                  "AsyncSmoothMSDScroll::SetRefreshObserver - Invalid usage.");
1799 
1800     RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style);
1801     mCallee = aCallee;
1802   }
1803 
1804   /**
1805    * The mCallee holds a strong ref to us since the refresh driver doesn't.
1806    * Our dtor and mCallee's Destroy() method both call RemoveObserver() -
1807    * whichever comes first removes us from the refresh driver.
1808    */
RemoveObserver()1809   void RemoveObserver() {
1810     if (mCallee) {
1811       RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style);
1812       mCallee = nullptr;
1813     }
1814   }
1815 
1816  private:
1817   // Private destructor, to discourage deletion outside of Release():
~AsyncSmoothMSDScroll()1818   ~AsyncSmoothMSDScroll() {
1819     RemoveObserver();
1820     Telemetry::SetHistogramRecordingEnabled(
1821         Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, false);
1822   }
1823 
RefreshDriver(ScrollFrameHelper * aCallee)1824   nsRefreshDriver* RefreshDriver(ScrollFrameHelper* aCallee) {
1825     return aCallee->mOuter->PresContext()->RefreshDriver();
1826   }
1827 
1828   mozilla::layers::AxisPhysicsMSDModel mXAxisModel, mYAxisModel;
1829   nsRect mRange;
1830   mozilla::TimeStamp mLastRefreshTime;
1831   ScrollFrameHelper* mCallee;
1832   nscoord mOneDevicePixelInAppUnits;
1833 };
1834 
1835 // AsyncScroll has ref counting.
1836 class ScrollFrameHelper::AsyncScroll final : public nsARefreshObserver {
1837  public:
1838   typedef mozilla::TimeStamp TimeStamp;
1839   typedef mozilla::TimeDuration TimeDuration;
1840 
AsyncScroll()1841   explicit AsyncScroll() : mCallee(nullptr) {
1842     Telemetry::SetHistogramRecordingEnabled(
1843         Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, true);
1844   }
1845 
1846  private:
1847   // Private destructor, to discourage deletion outside of Release():
~AsyncScroll()1848   ~AsyncScroll() {
1849     RemoveObserver();
1850     Telemetry::SetHistogramRecordingEnabled(
1851         Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, false);
1852   }
1853 
1854  public:
1855   void InitSmoothScroll(TimeStamp aTime, nsPoint aInitialPosition,
1856                         nsPoint aDestination, nsAtom* aOrigin,
1857                         const nsRect& aRange, const nsSize& aCurrentVelocity);
Init(const nsRect & aRange)1858   void Init(const nsRect& aRange) {
1859     mAnimationPhysics = nullptr;
1860     mRange = aRange;
1861   }
1862 
IsSmoothScroll()1863   bool IsSmoothScroll() { return mAnimationPhysics != nullptr; }
1864 
IsFinished(const TimeStamp & aTime) const1865   bool IsFinished(const TimeStamp& aTime) const {
1866     MOZ_RELEASE_ASSERT(mAnimationPhysics);
1867     return mAnimationPhysics->IsFinished(aTime);
1868   }
1869 
PositionAt(const TimeStamp & aTime) const1870   nsPoint PositionAt(const TimeStamp& aTime) const {
1871     MOZ_RELEASE_ASSERT(mAnimationPhysics);
1872     return mAnimationPhysics->PositionAt(aTime);
1873   }
1874 
VelocityAt(const TimeStamp & aTime) const1875   nsSize VelocityAt(const TimeStamp& aTime) const {
1876     MOZ_RELEASE_ASSERT(mAnimationPhysics);
1877     return mAnimationPhysics->VelocityAt(aTime);
1878   }
1879 
1880   // Most recent scroll origin.
1881   RefPtr<nsAtom> mOrigin;
1882 
1883   // Allowed destination positions around mDestination
1884   nsRect mRange;
1885 
1886  private:
1887   void InitPreferences(TimeStamp aTime, nsAtom* aOrigin);
1888 
1889   UniquePtr<ScrollAnimationPhysics> mAnimationPhysics;
1890 
1891   // The next section is observer/callback management
1892   // Bodies of WillRefresh and RefreshDriver contain ScrollFrameHelper specific
1893   // code.
1894  public:
NS_INLINE_DECL_REFCOUNTING(AsyncScroll,override)1895   NS_INLINE_DECL_REFCOUNTING(AsyncScroll, override)
1896 
1897   /*
1898    * Set a refresh observer for smooth scroll iterations (and start observing).
1899    * Should be used at most once during the lifetime of this object.
1900    */
1901   void SetRefreshObserver(ScrollFrameHelper* aCallee) {
1902     NS_ASSERTION(aCallee && !mCallee,
1903                  "AsyncScroll::SetRefreshObserver - Invalid usage.");
1904 
1905     RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style);
1906     mCallee = aCallee;
1907     PresShell* presShell = mCallee->mOuter->PresShell();
1908     MOZ_ASSERT(presShell);
1909     presShell->SuppressDisplayport(true);
1910   }
1911 
WillRefresh(mozilla::TimeStamp aTime)1912   virtual void WillRefresh(mozilla::TimeStamp aTime) override {
1913     // The callback may release "this".
1914     // We don't access members after returning, so no need for KungFuDeathGrip.
1915     ScrollFrameHelper::AsyncScrollCallback(mCallee, aTime);
1916   }
1917 
1918   /**
1919    * The mCallee holds a strong ref to us since the refresh driver doesn't.
1920    * Our dtor and mCallee's Destroy() method both call RemoveObserver() -
1921    * whichever comes first removes us from the refresh driver.
1922    */
RemoveObserver()1923   void RemoveObserver() {
1924     if (mCallee) {
1925       RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style);
1926       PresShell* presShell = mCallee->mOuter->PresShell();
1927       MOZ_ASSERT(presShell);
1928       presShell->SuppressDisplayport(false);
1929       mCallee = nullptr;
1930     }
1931   }
1932 
1933  private:
1934   ScrollFrameHelper* mCallee;
1935 
RefreshDriver(ScrollFrameHelper * aCallee)1936   nsRefreshDriver* RefreshDriver(ScrollFrameHelper* aCallee) {
1937     return aCallee->mOuter->PresContext()->RefreshDriver();
1938   }
1939 };
1940 
1941 /*
1942  * Calculate duration, possibly dynamically according to events rate and event
1943  * origin. (also maintain previous timestamps - which are only used here).
1944  */
1945 static ScrollAnimationBezierPhysicsSettings
ComputeBezierAnimationSettingsForOrigin(nsAtom * aOrigin)1946 ComputeBezierAnimationSettingsForOrigin(nsAtom* aOrigin) {
1947   int32_t minMS = 0;
1948   int32_t maxMS = 0;
1949   bool isOriginSmoothnessEnabled = false;
1950   double intervalRatio = 1;
1951 
1952   // Default values for all preferences are defined in all.js
1953   static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150;
1954   static const bool kDefaultIsSmoothEnabled = true;
1955 
1956   nsAutoCString originName;
1957   aOrigin->ToUTF8String(originName);
1958   nsAutoCString prefBase =
1959       NS_LITERAL_CSTRING("general.smoothScroll.") + originName;
1960 
1961   isOriginSmoothnessEnabled =
1962       Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled);
1963   if (isOriginSmoothnessEnabled) {
1964     nsAutoCString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS");
1965     nsAutoCString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS");
1966     minMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS);
1967     maxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS);
1968 
1969     static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000;
1970     maxMS = clamped(maxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS);
1971     minMS = clamped(minMS, 0, maxMS);
1972   }
1973 
1974   // Keep the animation duration longer than the average event intervals
1975   // (to "connect" consecutive scroll animations before the scroll comes to a
1976   // stop).
1977   //
1978   // Default value is duplicated in all.js.
1979   static const double kDefaultDurationToIntervalRatio = 2;
1980   intervalRatio =
1981       Preferences::GetInt("general.smoothScroll.durationToIntervalRatio",
1982                           kDefaultDurationToIntervalRatio * 100) /
1983       100.0;
1984 
1985   // Duration should be at least as long as the intervals -> ratio is at least 1
1986   intervalRatio = std::max(1.0, intervalRatio);
1987 
1988   return ScrollAnimationBezierPhysicsSettings{minMS, maxMS, intervalRatio};
1989 }
1990 
InitSmoothScroll(TimeStamp aTime,nsPoint aInitialPosition,nsPoint aDestination,nsAtom * aOrigin,const nsRect & aRange,const nsSize & aCurrentVelocity)1991 void ScrollFrameHelper::AsyncScroll::InitSmoothScroll(
1992     TimeStamp aTime, nsPoint aInitialPosition, nsPoint aDestination,
1993     nsAtom* aOrigin, const nsRect& aRange, const nsSize& aCurrentVelocity) {
1994   if (!aOrigin || aOrigin == nsGkAtoms::restore ||
1995       aOrigin == nsGkAtoms::relative) {
1996     // We don't have special prefs for "restore", just treat it as "other".
1997     // "restore" scrolls are (for now) always instant anyway so unless something
1998     // changes we should never have aOrigin == nsGkAtoms::restore here.
1999     aOrigin = nsGkAtoms::other;
2000   }
2001   // Likewise we should never get APZ-triggered scrolls here, and if that
2002   // changes something is likely broken somewhere.
2003   MOZ_ASSERT(aOrigin != nsGkAtoms::apz);
2004 
2005   // Read preferences only on first iteration or for a different event origin.
2006   if (!mAnimationPhysics || aOrigin != mOrigin) {
2007     mOrigin = aOrigin;
2008     if (StaticPrefs::general_smoothScroll_msdPhysics_enabled()) {
2009       mAnimationPhysics =
2010           MakeUnique<ScrollAnimationMSDPhysics>(aInitialPosition);
2011     } else {
2012       ScrollAnimationBezierPhysicsSettings settings =
2013           ComputeBezierAnimationSettingsForOrigin(mOrigin);
2014       mAnimationPhysics =
2015           MakeUnique<ScrollAnimationBezierPhysics>(aInitialPosition, settings);
2016     }
2017   }
2018 
2019   mRange = aRange;
2020 
2021   mAnimationPhysics->Update(aTime, aDestination, aCurrentVelocity);
2022 }
2023 
IsSmoothScrollingEnabled()2024 bool ScrollFrameHelper::IsSmoothScrollingEnabled() {
2025   return StaticPrefs::general_smoothScroll();
2026 }
2027 
2028 class ScrollFrameActivityTracker final
2029     : public nsExpirationTracker<ScrollFrameHelper, 4> {
2030  public:
2031   // Wait for 3-4s between scrolls before we remove our layers.
2032   // That's 4 generations of 1s each.
2033   enum { TIMEOUT_MS = 1000 };
ScrollFrameActivityTracker(nsIEventTarget * aEventTarget)2034   explicit ScrollFrameActivityTracker(nsIEventTarget* aEventTarget)
2035       : nsExpirationTracker<ScrollFrameHelper, 4>(
2036             TIMEOUT_MS, "ScrollFrameActivityTracker", aEventTarget) {}
~ScrollFrameActivityTracker()2037   ~ScrollFrameActivityTracker() { AgeAllGenerations(); }
2038 
NotifyExpired(ScrollFrameHelper * aObject)2039   virtual void NotifyExpired(ScrollFrameHelper* aObject) override {
2040     RemoveObject(aObject);
2041     aObject->MarkNotRecentlyScrolled();
2042   }
2043 };
2044 
2045 static ScrollFrameActivityTracker* gScrollFrameActivityTracker = nullptr;
2046 
2047 // There are situations when a scroll frame is destroyed and then re-created
2048 // for the same content element. In this case we want to increment the scroll
2049 // generation between the old and new scrollframes. If the new one knew about
2050 // the old one then it could steal the old generation counter and increment it
2051 // but it doesn't have that reference so instead we use a static global to
2052 // ensure the new one gets a fresh value.
2053 static uint32_t sScrollGenerationCounter = 0;
2054 
ScrollFrameHelper(nsContainerFrame * aOuter,bool aIsRoot)2055 ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter, bool aIsRoot)
2056     : mHScrollbarBox(nullptr),
2057       mVScrollbarBox(nullptr),
2058       mScrolledFrame(nullptr),
2059       mScrollCornerBox(nullptr),
2060       mResizerBox(nullptr),
2061       mOuter(aOuter),
2062       mReferenceFrameDuringPainting(nullptr),
2063       mAsyncScroll(nullptr),
2064       mAsyncSmoothMSDScroll(nullptr),
2065       mLastScrollOrigin(nsGkAtoms::other),
2066       mLastSmoothScrollOrigin(nullptr),
2067       mScrollGeneration(++sScrollGenerationCounter),
2068       mDestination(0, 0),
2069       mRestorePos(-1, -1),
2070       mLastPos(-1, -1),
2071       mApzScrollPos(0, 0),
2072       mScrollPosForLayerPixelAlignment(-1, -1),
2073       mLastUpdateFramesPos(-1, -1),
2074       mDisplayPortAtLastFrameUpdate(),
2075       mScrollParentID(mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID),
2076       mAnchor(this),
2077       mAllowScrollOriginDowngrade(false),
2078       mHadDisplayPortAtLastFrameUpdate(false),
2079       mNeverHasVerticalScrollbar(false),
2080       mNeverHasHorizontalScrollbar(false),
2081       mHasVerticalScrollbar(false),
2082       mHasHorizontalScrollbar(false),
2083       mFrameIsUpdatingScrollbar(false),
2084       mDidHistoryRestore(false),
2085       mIsRoot(aIsRoot),
2086       mClipAllDescendants(aIsRoot),
2087       mSuppressScrollbarUpdate(false),
2088       mSkippedScrollbarLayout(false),
2089       mHadNonInitialReflow(false),
2090       mHorizontalOverflow(false),
2091       mVerticalOverflow(false),
2092       mPostedReflowCallback(false),
2093       mMayHaveDirtyFixedChildren(false),
2094       mUpdateScrollbarAttributes(false),
2095       mHasBeenScrolledRecently(false),
2096       mCollapsedResizer(false),
2097       mWillBuildScrollableLayer(false),
2098       mIsScrollParent(false),
2099       mAddClipRectToLayer(false),
2100       mHasBeenScrolled(false),
2101       mIgnoreMomentumScroll(false),
2102       mTransformingByAPZ(false),
2103       mScrollableByAPZ(false),
2104       mZoomableByAPZ(false),
2105       mHasOutOfFlowContentInsideFilter(false),
2106       mSuppressScrollbarRepaints(false),
2107       mIsUsingMinimumScaleSize(false),
2108       mMinimumScaleSizeChanged(false),
2109       mProcessingScrollEvent(false),
2110       mVelocityQueue(aOuter->PresContext()) {
2111   if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
2112     mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
2113   }
2114 
2115   if (IsAlwaysActive() && StaticPrefs::layers_enable_tiles_AtStartup() &&
2116       !nsLayoutUtils::UsesAsyncScrolling(mOuter) && mOuter->GetContent()) {
2117     // If we have tiling but no APZ, then set a 0-margin display port on
2118     // active scroll containers so that we paint by whole tile increments
2119     // when scrolling.
2120     nsLayoutUtils::SetDisplayPortMargins(
2121         mOuter->GetContent(), mOuter->PresShell(), ScreenMargin(), 0);
2122     nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(mOuter);
2123   }
2124 }
2125 
~ScrollFrameHelper()2126 ScrollFrameHelper::~ScrollFrameHelper() {
2127   if (mScrollEvent) {
2128     mScrollEvent->Revoke();
2129   }
2130   if (mScrollEndEvent) {
2131     mScrollEndEvent->Revoke();
2132   }
2133 }
2134 
2135 /*
2136  * Callback function from AsyncSmoothMSDScroll, used in
2137  * ScrollFrameHelper::ScrollTo
2138  */
AsyncSmoothMSDScrollCallback(ScrollFrameHelper * aInstance,mozilla::TimeDuration aDeltaTime)2139 void ScrollFrameHelper::AsyncSmoothMSDScrollCallback(
2140     ScrollFrameHelper* aInstance, mozilla::TimeDuration aDeltaTime) {
2141   NS_ASSERTION(aInstance != nullptr, "aInstance must not be null");
2142   NS_ASSERTION(aInstance->mAsyncSmoothMSDScroll,
2143                "Did not expect AsyncSmoothMSDScrollCallback without an active "
2144                "MSD scroll.");
2145 
2146   nsRect range = aInstance->mAsyncSmoothMSDScroll->GetRange();
2147   aInstance->mAsyncSmoothMSDScroll->Simulate(aDeltaTime);
2148 
2149   if (!aInstance->mAsyncSmoothMSDScroll->IsFinished()) {
2150     nsPoint destination = aInstance->mAsyncSmoothMSDScroll->GetPosition();
2151     // Allow this scroll operation to land on any pixel boundary within the
2152     // allowed scroll range for this frame.
2153     // If the MSD is under-dampened or the destination is changed rapidly,
2154     // it is expected (and desired) that the scrolling may overshoot.
2155     nsRect intermediateRange = nsRect(destination, nsSize()).UnionEdges(range);
2156     aInstance->ScrollToImpl(destination, intermediateRange);
2157     // 'aInstance' might be destroyed here
2158     return;
2159   }
2160 
2161   aInstance->CompleteAsyncScroll(range);
2162 }
2163 
2164 /*
2165  * Callback function from AsyncScroll, used in ScrollFrameHelper::ScrollTo
2166  */
AsyncScrollCallback(ScrollFrameHelper * aInstance,mozilla::TimeStamp aTime)2167 void ScrollFrameHelper::AsyncScrollCallback(ScrollFrameHelper* aInstance,
2168                                             mozilla::TimeStamp aTime) {
2169   MOZ_ASSERT(aInstance != nullptr, "aInstance must not be null");
2170   MOZ_ASSERT(
2171       aInstance->mAsyncScroll,
2172       "Did not expect AsyncScrollCallback without an active async scroll.");
2173 
2174   if (!aInstance || !aInstance->mAsyncScroll) {
2175     return;  // XXX wallpaper bug 1107353 for now.
2176   }
2177 
2178   nsRect range = aInstance->mAsyncScroll->mRange;
2179   if (aInstance->mAsyncScroll->IsSmoothScroll()) {
2180     if (!aInstance->mAsyncScroll->IsFinished(aTime)) {
2181       nsPoint destination = aInstance->mAsyncScroll->PositionAt(aTime);
2182       // Allow this scroll operation to land on any pixel boundary between the
2183       // current position and the final allowed range.  (We don't want
2184       // intermediate steps to be more constrained than the final step!)
2185       nsRect intermediateRange =
2186           nsRect(aInstance->GetScrollPosition(), nsSize()).UnionEdges(range);
2187       aInstance->ScrollToImpl(destination, intermediateRange);
2188       // 'aInstance' might be destroyed here
2189       return;
2190     }
2191   }
2192 
2193   aInstance->CompleteAsyncScroll(range);
2194 }
2195 
CompleteAsyncScroll(const nsRect & aRange,nsAtom * aOrigin)2196 void ScrollFrameHelper::CompleteAsyncScroll(const nsRect& aRange,
2197                                             nsAtom* aOrigin) {
2198   // Apply desired destination range since this is the last step of scrolling.
2199   RemoveObservers();
2200   AutoWeakFrame weakFrame(mOuter);
2201   ScrollToImpl(mDestination, aRange, aOrigin);
2202   if (!weakFrame.IsAlive()) {
2203     return;
2204   }
2205   // We are done scrolling, set our destination to wherever we actually ended
2206   // up scrolling to.
2207   mDestination = GetScrollPosition();
2208   PostScrollEndEvent();
2209 }
2210 
HasPluginFrames()2211 bool ScrollFrameHelper::HasPluginFrames() {
2212 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
2213   if (XRE_IsContentProcess()) {
2214     nsPresContext* presContext = mOuter->PresContext();
2215     nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
2216     if (!rootPresContext ||
2217         rootPresContext->NeedToComputePluginGeometryUpdates()) {
2218       return true;
2219     }
2220   }
2221 #endif
2222   return false;
2223 }
2224 
HasBgAttachmentLocal() const2225 bool ScrollFrameHelper::HasBgAttachmentLocal() const {
2226   const nsStyleBackground* bg = mOuter->StyleBackground();
2227   return bg->HasLocalBackground();
2228 }
2229 
ScrollTo(nsPoint aScrollPosition,ScrollMode aMode,nsAtom * aOrigin,const nsRect * aRange,nsIScrollbarMediator::ScrollSnapMode aSnap)2230 void ScrollFrameHelper::ScrollTo(nsPoint aScrollPosition, ScrollMode aMode,
2231                                  nsAtom* aOrigin, const nsRect* aRange,
2232                                  nsIScrollbarMediator::ScrollSnapMode aSnap) {
2233   if (aOrigin == nullptr) {
2234     aOrigin = nsGkAtoms::other;
2235   }
2236   ScrollToWithOrigin(aScrollPosition, aMode, aOrigin, aRange, aSnap);
2237 }
2238 
ScrollToCSSPixels(const CSSIntPoint & aScrollPosition,ScrollMode aMode,nsIScrollbarMediator::ScrollSnapMode aSnap,nsAtom * aOrigin)2239 void ScrollFrameHelper::ScrollToCSSPixels(
2240     const CSSIntPoint& aScrollPosition, ScrollMode aMode,
2241     nsIScrollbarMediator::ScrollSnapMode aSnap, nsAtom* aOrigin) {
2242   nsPoint current = GetScrollPosition();
2243   CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels();
2244   nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
2245   if (aSnap == nsIScrollableFrame::DEFAULT) {
2246     aSnap = nsIScrollableFrame::ENABLE_SNAP;
2247   }
2248 
2249   if (aOrigin == nullptr) {
2250     aOrigin = nsGkAtoms::other;
2251   }
2252 
2253   nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
2254   nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2 * halfPixel - 1,
2255                2 * halfPixel - 1);
2256   // XXX I don't think the following blocks are needed anymore, now that
2257   // ScrollToImpl simply tries to scroll an integer number of layer
2258   // pixels from the current position
2259   if (currentCSSPixels.x == aScrollPosition.x) {
2260     pt.x = current.x;
2261     range.x = pt.x;
2262     range.width = 0;
2263   }
2264   if (currentCSSPixels.y == aScrollPosition.y) {
2265     pt.y = current.y;
2266     range.y = pt.y;
2267     range.height = 0;
2268   }
2269   ScrollTo(pt, aMode, aOrigin, &range, aSnap);
2270   // 'this' might be destroyed here
2271 }
2272 
ScrollToCSSPixelsApproximate(const CSSPoint & aScrollPosition,nsAtom * aOrigin)2273 void ScrollFrameHelper::ScrollToCSSPixelsApproximate(
2274     const CSSPoint& aScrollPosition, nsAtom* aOrigin) {
2275   nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
2276   nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000);
2277   nsRect range(pt.x - halfRange, pt.y - halfRange, 2 * halfRange - 1,
2278                2 * halfRange - 1);
2279   ScrollToWithOrigin(pt, ScrollMode::Instant, aOrigin, &range);
2280   // 'this' might be destroyed here
2281 }
2282 
GetScrollPositionCSSPixels()2283 CSSIntPoint ScrollFrameHelper::GetScrollPositionCSSPixels() {
2284   return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition());
2285 }
2286 
2287 /*
2288  * this method wraps calls to ScrollToImpl(), either in one shot or
2289  * incrementally, based on the setting of the smoothness scroll pref
2290  */
ScrollToWithOrigin(nsPoint aScrollPosition,ScrollMode aMode,nsAtom * aOrigin,const nsRect * aRange,nsIScrollbarMediator::ScrollSnapMode aSnap)2291 void ScrollFrameHelper::ScrollToWithOrigin(
2292     nsPoint aScrollPosition, ScrollMode aMode, nsAtom* aOrigin,
2293     const nsRect* aRange, nsIScrollbarMediator::ScrollSnapMode aSnap) {
2294   if (aOrigin != nsGkAtoms::restore) {
2295     // If we're doing a non-restore scroll, we don't want to later
2296     // override it by restoring our saved scroll position.
2297     mRestorePos.x = mRestorePos.y = -1;
2298   }
2299 
2300   bool willSnap = false;
2301   if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
2302     willSnap = GetSnapPointForDestination(ScrollUnit::DEVICE_PIXELS,
2303                                           mDestination, aScrollPosition);
2304   }
2305 
2306   nsRect scrollRange = GetLayoutScrollRange();
2307   mDestination = scrollRange.ClampPoint(aScrollPosition);
2308   if (mDestination != aScrollPosition && aOrigin == nsGkAtoms::restore &&
2309       GetPageLoadingState() != LoadingState::Loading) {
2310     // If we're doing a restore but the scroll position is clamped, promote
2311     // the origin from one that APZ can clobber to one that it can't clobber.
2312     aOrigin = nsGkAtoms::other;
2313   }
2314 
2315   nsRect range =
2316       aRange && !willSnap ? *aRange : nsRect(aScrollPosition, nsSize(0, 0));
2317 
2318   if (aMode != ScrollMode::SmoothMsd) {
2319     // If we get a non-smooth-scroll, reset the cached APZ scroll destination,
2320     // so that we know to process the next smooth-scroll destined for APZ.
2321     mApzSmoothScrollDestination = Nothing();
2322   }
2323 
2324   if (aMode == ScrollMode::Instant) {
2325     // Asynchronous scrolling is not allowed, so we'll kill any existing
2326     // async-scrolling process and do an instant scroll.
2327     CompleteAsyncScroll(range, aOrigin);
2328     return;
2329   }
2330 
2331   nsPresContext* presContext = mOuter->PresContext();
2332   TimeStamp now =
2333       presContext->RefreshDriver()->IsTestControllingRefreshesEnabled()
2334           ? presContext->RefreshDriver()->MostRecentRefresh()
2335           : TimeStamp::Now();
2336   bool isSmoothScroll =
2337       aMode == ScrollMode::Smooth && IsSmoothScrollingEnabled();
2338 
2339   nsSize currentVelocity(0, 0);
2340 
2341   if (StaticPrefs::layout_css_scroll_behavior_enabled()) {
2342     if (aMode == ScrollMode::SmoothMsd) {
2343       mIgnoreMomentumScroll = true;
2344       if (!mAsyncSmoothMSDScroll) {
2345         nsPoint sv = mVelocityQueue.GetVelocity();
2346         currentVelocity.width = sv.x;
2347         currentVelocity.height = sv.y;
2348         if (mAsyncScroll) {
2349           if (mAsyncScroll->IsSmoothScroll()) {
2350             currentVelocity = mAsyncScroll->VelocityAt(now);
2351           }
2352           mAsyncScroll = nullptr;
2353         }
2354 
2355         if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && WantAsyncScroll()) {
2356           ApzSmoothScrollTo(mDestination, aOrigin);
2357           return;
2358         }
2359 
2360         mAsyncSmoothMSDScroll = new AsyncSmoothMSDScroll(
2361             GetScrollPosition(), mDestination, currentVelocity,
2362             GetLayoutScrollRange(), now, presContext);
2363 
2364         mAsyncSmoothMSDScroll->SetRefreshObserver(this);
2365       } else {
2366         // A previous smooth MSD scroll is still in progress, so we just need to
2367         // update its range and destination.
2368         mAsyncSmoothMSDScroll->SetRange(GetLayoutScrollRange());
2369         mAsyncSmoothMSDScroll->SetDestination(mDestination);
2370       }
2371 
2372       return;
2373     } else {
2374       if (mAsyncSmoothMSDScroll) {
2375         currentVelocity = mAsyncSmoothMSDScroll->GetVelocity();
2376         mAsyncSmoothMSDScroll = nullptr;
2377       }
2378     }
2379   }
2380 
2381   if (!mAsyncScroll) {
2382     mAsyncScroll = new AsyncScroll();
2383     mAsyncScroll->SetRefreshObserver(this);
2384   }
2385 
2386   if (isSmoothScroll) {
2387     mAsyncScroll->InitSmoothScroll(now, GetScrollPosition(), mDestination,
2388                                    aOrigin, range, currentVelocity);
2389   } else {
2390     mAsyncScroll->Init(range);
2391   }
2392 }
2393 
2394 // We can't use nsContainerFrame::PositionChildViews here because
2395 // we don't want to invalidate views that have moved.
AdjustViews(nsIFrame * aFrame)2396 static void AdjustViews(nsIFrame* aFrame) {
2397   nsView* view = aFrame->GetView();
2398   if (view) {
2399     nsPoint pt;
2400     aFrame->GetParent()->GetClosestView(&pt);
2401     pt += aFrame->GetPosition();
2402     view->SetPosition(pt.x, pt.y);
2403 
2404     return;
2405   }
2406 
2407   if (!(aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
2408     return;
2409   }
2410 
2411   // Call AdjustViews recursively for all child frames except the popup list as
2412   // the views for popups are not scrolled.
2413   for (const auto& [list, listID] : aFrame->ChildLists()) {
2414     if (listID == nsIFrame::kPopupList) {
2415       continue;
2416     }
2417     for (nsIFrame* child : list) {
2418       AdjustViews(child);
2419     }
2420   }
2421 }
2422 
MarkScrollbarsDirtyForReflow() const2423 void ScrollFrameHelper::MarkScrollbarsDirtyForReflow() const {
2424   PresShell* presShell = mOuter->PresShell();
2425   if (mVScrollbarBox) {
2426     presShell->FrameNeedsReflow(mVScrollbarBox, IntrinsicDirty::Resize,
2427                                 NS_FRAME_IS_DIRTY);
2428   }
2429   if (mHScrollbarBox) {
2430     presShell->FrameNeedsReflow(mHScrollbarBox, IntrinsicDirty::Resize,
2431                                 NS_FRAME_IS_DIRTY);
2432   }
2433 }
2434 
IsAlwaysActive() const2435 bool ScrollFrameHelper::IsAlwaysActive() const {
2436   if (nsDisplayItem::ForceActiveLayers()) {
2437     return true;
2438   }
2439 
2440   // Unless this is the root scrollframe for a non-chrome document
2441   // which is the direct child of a chrome document, we default to not
2442   // being "active".
2443   if (!(mIsRoot && mOuter->PresContext()->IsRootContentDocument())) {
2444     return false;
2445   }
2446 
2447   // If we have scrolled before, then we should stay active.
2448   if (mHasBeenScrolled) {
2449     return true;
2450   }
2451 
2452   // If we're overflow:hidden, then start as inactive until
2453   // we get scrolled manually.
2454   ScrollStyles styles = GetScrollStylesFromFrame();
2455   return (styles.mHorizontal != StyleOverflow::Hidden &&
2456           styles.mVertical != StyleOverflow::Hidden);
2457 }
2458 
RemoveDisplayPortCallback(nsITimer * aTimer,void * aClosure)2459 static void RemoveDisplayPortCallback(nsITimer* aTimer, void* aClosure) {
2460   ScrollFrameHelper* helper = static_cast<ScrollFrameHelper*>(aClosure);
2461 
2462   // This function only ever gets called from the expiry timer, so it must
2463   // be non-null here. Set it to null here so that we don't keep resetting
2464   // it unnecessarily in MarkRecentlyScrolled().
2465   MOZ_ASSERT(helper->mDisplayPortExpiryTimer);
2466   helper->mDisplayPortExpiryTimer = nullptr;
2467 
2468   if (!helper->AllowDisplayPortExpiration() || helper->mIsScrollParent) {
2469     // If this is a scroll parent for some other scrollable frame, don't
2470     // expire the displayport because it would break scroll handoff. Once the
2471     // descendant scrollframes have their displayports expired, they will
2472     // trigger the displayport expiration on this scrollframe as well, and
2473     // mIsScrollParent will presumably be false when that kicks in.
2474     return;
2475   }
2476 
2477   // Remove the displayport from this scrollframe if it's been a while
2478   // since it's scrolled, except if it needs to be always active. Note that
2479   // there is one scrollframe that doesn't fall under this general rule, and
2480   // that is the one that nsLayoutUtils::MaybeCreateDisplayPort decides to put
2481   // a displayport on (i.e. the first scrollframe that WantAsyncScroll()s).
2482   // If that scrollframe is this one, we remove the displayport anyway, and
2483   // as part of the next paint MaybeCreateDisplayPort will put another
2484   // displayport back on it. Although the displayport will "flicker" off and
2485   // back on, the layer itself should never disappear, because this all
2486   // happens between actual painting. If the displayport is reset to a
2487   // different position that's ok; this scrollframe hasn't been scrolled
2488   // recently and so the reset should be correct.
2489   nsLayoutUtils::RemoveDisplayPort(helper->mOuter->GetContent());
2490   nsLayoutUtils::ExpireDisplayPortOnAsyncScrollableAncestor(helper->mOuter);
2491   helper->mOuter->SchedulePaint();
2492   // Be conservative and unflag this this scrollframe as being scrollable by
2493   // APZ. If it is still scrollable this will get flipped back soon enough.
2494   helper->mScrollableByAPZ = false;
2495 }
2496 
MarkEverScrolled()2497 void ScrollFrameHelper::MarkEverScrolled() {
2498   // Mark this frame as having been scrolled. If this is the root
2499   // scroll frame of a content document, then IsAlwaysActive()
2500   // will return true from now on and MarkNotRecentlyScrolled() won't
2501   // have any effect.
2502   mHasBeenScrolled = true;
2503 }
2504 
MarkNotRecentlyScrolled()2505 void ScrollFrameHelper::MarkNotRecentlyScrolled() {
2506   if (!mHasBeenScrolledRecently) return;
2507 
2508   mHasBeenScrolledRecently = false;
2509   mOuter->SchedulePaint();
2510 }
2511 
MarkRecentlyScrolled()2512 void ScrollFrameHelper::MarkRecentlyScrolled() {
2513   mHasBeenScrolledRecently = true;
2514   if (IsAlwaysActive()) {
2515     return;
2516   }
2517 
2518   if (mActivityExpirationState.IsTracked()) {
2519     gScrollFrameActivityTracker->MarkUsed(this);
2520   } else {
2521     if (!gScrollFrameActivityTracker) {
2522       gScrollFrameActivityTracker =
2523           new ScrollFrameActivityTracker(GetMainThreadSerialEventTarget());
2524     }
2525     gScrollFrameActivityTracker->AddObject(this);
2526   }
2527 
2528   // If we just scrolled and there's a displayport expiry timer in place,
2529   // reset the timer.
2530   ResetDisplayPortExpiryTimer();
2531 }
2532 
ResetDisplayPortExpiryTimer()2533 void ScrollFrameHelper::ResetDisplayPortExpiryTimer() {
2534   if (mDisplayPortExpiryTimer) {
2535     mDisplayPortExpiryTimer->InitWithNamedFuncCallback(
2536         RemoveDisplayPortCallback, this,
2537         StaticPrefs::apz_displayport_expiry_ms(), nsITimer::TYPE_ONE_SHOT,
2538         "ScrollFrameHelper::ResetDisplayPortExpiryTimer");
2539   }
2540 }
2541 
AllowDisplayPortExpiration()2542 bool ScrollFrameHelper::AllowDisplayPortExpiration() {
2543   if (IsAlwaysActive()) {
2544     return false;
2545   }
2546   if (mIsRoot && mOuter->PresContext()->IsRoot()) {
2547     return false;
2548   }
2549   return true;
2550 }
2551 
TriggerDisplayPortExpiration()2552 void ScrollFrameHelper::TriggerDisplayPortExpiration() {
2553   if (!AllowDisplayPortExpiration()) {
2554     return;
2555   }
2556 
2557   if (!StaticPrefs::apz_displayport_expiry_ms()) {
2558     // a zero time disables the expiry
2559     return;
2560   }
2561 
2562   if (!mDisplayPortExpiryTimer) {
2563     mDisplayPortExpiryTimer = NS_NewTimer();
2564   }
2565   ResetDisplayPortExpiryTimer();
2566 }
2567 
ScrollVisual()2568 void ScrollFrameHelper::ScrollVisual() {
2569   MarkEverScrolled();
2570 
2571   AdjustViews(mScrolledFrame);
2572   // We need to call this after fixing up the view positions
2573   // to be consistent with the frame hierarchy.
2574   MarkRecentlyScrolled();
2575 }
2576 
2577 /**
2578  * Clamp desired scroll position aDesired and range [aDestLower, aDestUpper]
2579  * to [aBoundLower, aBoundUpper] and then select the appunit value from among
2580  * aBoundLower, aBoundUpper and those such that (aDesired - aCurrent) *
2581  * aRes/aAppUnitsPerPixel is an integer (or as close as we can get
2582  * modulo rounding to appunits) that is in [aDestLower, aDestUpper] and
2583  * closest to aDesired.  If no such value exists, return the nearest in
2584  * [aDestLower, aDestUpper].
2585  */
ClampAndAlignWithPixels(nscoord aDesired,nscoord aBoundLower,nscoord aBoundUpper,nscoord aDestLower,nscoord aDestUpper,nscoord aAppUnitsPerPixel,double aRes,nscoord aCurrent)2586 static nscoord ClampAndAlignWithPixels(nscoord aDesired, nscoord aBoundLower,
2587                                        nscoord aBoundUpper, nscoord aDestLower,
2588                                        nscoord aDestUpper,
2589                                        nscoord aAppUnitsPerPixel, double aRes,
2590                                        nscoord aCurrent) {
2591   // Intersect scroll range with allowed range, by clamping the ends
2592   // of aRange to be within bounds
2593   nscoord destLower = clamped(aDestLower, aBoundLower, aBoundUpper);
2594   nscoord destUpper = clamped(aDestUpper, aBoundLower, aBoundUpper);
2595 
2596   nscoord desired = clamped(aDesired, destLower, destUpper);
2597 
2598   double currentLayerVal = (aRes * aCurrent) / aAppUnitsPerPixel;
2599   double desiredLayerVal = (aRes * desired) / aAppUnitsPerPixel;
2600   double delta = desiredLayerVal - currentLayerVal;
2601   double nearestLayerVal = NS_round(delta) + currentLayerVal;
2602 
2603   // Convert back from PaintedLayer space to appunits relative to the top-left
2604   // of the scrolled frame.
2605   nscoord aligned =
2606       aRes == 0.0
2607           ? 0.0
2608           : NSToCoordRoundWithClamp(nearestLayerVal * aAppUnitsPerPixel / aRes);
2609 
2610   // Use a bound if it is within the allowed range and closer to desired than
2611   // the nearest pixel-aligned value.
2612   if (aBoundUpper == destUpper &&
2613       static_cast<decltype(Abs(desired))>(aBoundUpper - desired) <
2614           Abs(desired - aligned)) {
2615     return aBoundUpper;
2616   }
2617 
2618   if (aBoundLower == destLower &&
2619       static_cast<decltype(Abs(desired))>(desired - aBoundLower) <
2620           Abs(aligned - desired)) {
2621     return aBoundLower;
2622   }
2623 
2624   // Accept the nearest pixel-aligned value if it is within the allowed range.
2625   if (aligned >= destLower && aligned <= destUpper) {
2626     return aligned;
2627   }
2628 
2629   // Check if opposite pixel boundary fits into allowed range.
2630   double oppositeLayerVal =
2631       nearestLayerVal + ((nearestLayerVal < desiredLayerVal) ? 1.0 : -1.0);
2632   nscoord opposite = aRes == 0.0
2633                          ? 0.0
2634                          : NSToCoordRoundWithClamp(oppositeLayerVal *
2635                                                    aAppUnitsPerPixel / aRes);
2636   if (opposite >= destLower && opposite <= destUpper) {
2637     return opposite;
2638   }
2639 
2640   // No alignment available.
2641   return desired;
2642 }
2643 
2644 /**
2645  * Clamp desired scroll position aPt to aBounds and then snap
2646  * it to the same layer pixel edges as aCurrent, keeping it within aRange
2647  * during snapping. aCurrent is the current scroll position.
2648  */
ClampAndAlignWithLayerPixels(const nsPoint & aPt,const nsRect & aBounds,const nsRect & aRange,const nsPoint & aCurrent,nscoord aAppUnitsPerPixel,const gfxSize & aScale)2649 static nsPoint ClampAndAlignWithLayerPixels(
2650     const nsPoint& aPt, const nsRect& aBounds, const nsRect& aRange,
2651     const nsPoint& aCurrent, nscoord aAppUnitsPerPixel, const gfxSize& aScale) {
2652   return nsPoint(
2653       ClampAndAlignWithPixels(aPt.x, aBounds.x, aBounds.XMost(), aRange.x,
2654                               aRange.XMost(), aAppUnitsPerPixel, aScale.width,
2655                               aCurrent.x),
2656       ClampAndAlignWithPixels(aPt.y, aBounds.y, aBounds.YMost(), aRange.y,
2657                               aRange.YMost(), aAppUnitsPerPixel, aScale.height,
2658                               aCurrent.y));
2659 }
2660 
2661 /* static */
ScrollActivityCallback(nsITimer * aTimer,void * anInstance)2662 void ScrollFrameHelper::ScrollActivityCallback(nsITimer* aTimer,
2663                                                void* anInstance) {
2664   ScrollFrameHelper* self = static_cast<ScrollFrameHelper*>(anInstance);
2665 
2666   // Fire the synth mouse move.
2667   self->mScrollActivityTimer->Cancel();
2668   self->mScrollActivityTimer = nullptr;
2669   self->mOuter->PresShell()->SynthesizeMouseMove(true);
2670 }
2671 
ScheduleSyntheticMouseMove()2672 void ScrollFrameHelper::ScheduleSyntheticMouseMove() {
2673   if (!mScrollActivityTimer) {
2674     mScrollActivityTimer = NS_NewTimer(
2675         mOuter->PresContext()->Document()->EventTargetFor(TaskCategory::Other));
2676     if (!mScrollActivityTimer) {
2677       return;
2678     }
2679   }
2680 
2681   mScrollActivityTimer->InitWithNamedFuncCallback(
2682       ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT,
2683       "ScrollFrameHelper::ScheduleSyntheticMouseMove");
2684 }
2685 
NotifyApproximateFrameVisibilityUpdate(bool aIgnoreDisplayPort)2686 void ScrollFrameHelper::NotifyApproximateFrameVisibilityUpdate(
2687     bool aIgnoreDisplayPort) {
2688   mLastUpdateFramesPos = GetScrollPosition();
2689   if (aIgnoreDisplayPort) {
2690     mHadDisplayPortAtLastFrameUpdate = false;
2691     mDisplayPortAtLastFrameUpdate = nsRect();
2692   } else {
2693     mHadDisplayPortAtLastFrameUpdate = nsLayoutUtils::GetDisplayPort(
2694         mOuter->GetContent(), &mDisplayPortAtLastFrameUpdate);
2695   }
2696 }
2697 
GetDisplayPortAtLastApproximateFrameVisibilityUpdate(nsRect * aDisplayPort)2698 bool ScrollFrameHelper::GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
2699     nsRect* aDisplayPort) {
2700   if (mHadDisplayPortAtLastFrameUpdate) {
2701     *aDisplayPort = mDisplayPortAtLastFrameUpdate;
2702   }
2703   return mHadDisplayPortAtLastFrameUpdate;
2704 }
2705 
ScrollToImpl(nsPoint aPt,const nsRect & aRange,nsAtom * aOrigin)2706 void ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange,
2707                                      nsAtom* aOrigin) {
2708   // Figure out the effective origin for this scroll request.
2709   if (aOrigin == nullptr) {
2710     // If no origin was specified, we still want to set it to something that's
2711     // non-null, so that we can use nullness to distinguish if the frame was
2712     // scrolled at all. Default it to some generic placeholder.
2713     aOrigin = nsGkAtoms::other;
2714   }
2715 
2716   // If this scroll is |relative|, but we've already had a user scroll that
2717   // was not relative, promote this origin to |other|. This ensures that we
2718   // may only transmit a relative update to APZ if all scrolls since the last
2719   // transaction or repaint request have been relative.
2720   if (aOrigin == nsGkAtoms::relative &&
2721       (mLastScrollOrigin && mLastScrollOrigin != nsGkAtoms::relative &&
2722        mLastScrollOrigin != nsGkAtoms::apz)) {
2723     aOrigin = nsGkAtoms::other;
2724   }
2725 
2726   // If the origin is a downgrade, and downgrades are allowed, process the
2727   // downgrade even if we're going to early-exit because we're already at
2728   // the correct scroll position. This ensures that if there wasn't a main-
2729   // thread scroll update pending before a frame reconstruction (as indicated
2730   // by mAllowScrollOriginDowngrade=true), then after the frame reconstruction
2731   // the origin is downgraded to "restore" even if the layout scroll offset to
2732   // be restored is (0,0) (which will take the early-exit below). This is
2733   // important so that restoration of a *visual* scroll offset (which might be
2734   // to something other than (0,0)) isn't clobbered.
2735   bool isScrollOriginDowngrade =
2736       nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) &&
2737       !nsLayoutUtils::CanScrollOriginClobberApz(aOrigin);
2738   bool allowScrollOriginChange =
2739       mAllowScrollOriginDowngrade && isScrollOriginDowngrade;
2740 
2741   if (allowScrollOriginChange) {
2742     mLastScrollOrigin = aOrigin;
2743     mAllowScrollOriginDowngrade = false;
2744   }
2745 
2746   nsPresContext* presContext = mOuter->PresContext();
2747   nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
2748   // 'scale' is our estimate of the scale factor that will be applied
2749   // when rendering the scrolled content to its own PaintedLayer.
2750   gfxSize scale =
2751       FrameLayerBuilder::GetPaintedLayerScaleForFrame(mScrolledFrame);
2752   nsPoint curPos = GetScrollPosition();
2753 
2754   // Below, we clamp |aPt| to the layout scroll range, compare the clamped
2755   // value with the current scroll position, and early exit if they are the
2756   // same. The early exit bypasses the update of |mLastScrollOrigin|, which
2757   // is important to avoid sending a main-thread layout scroll update to APZ,
2758   // which can clobber the visual scroll offset as well.
2759   // If the layout viewport has shrunk in size since the last scroll, the
2760   // clamped target position can be different from the current position, so
2761   // we don't take the early exit, but conceptually we still want to avoid
2762   // updating |mLastScrollOrigin| and clobbering the visual scroll offset.
2763   // We only do this if the visual and layout scroll offsets can diverge
2764   // in the first place, because it causes various regressions (bug 1543485
2765   // tracks a proper fix).
2766   bool suppressScrollOriginChange = false;
2767   if (presContext->PresShell()->GetIsViewportOverridden() && aPt == curPos) {
2768     suppressScrollOriginChange = true;
2769   }
2770 
2771   nsPoint alignWithPos = mScrollPosForLayerPixelAlignment == nsPoint(-1, -1)
2772                              ? curPos
2773                              : mScrollPosForLayerPixelAlignment;
2774   // Try to align aPt with curPos so they have an integer number of layer
2775   // pixels between them. This gives us the best chance of scrolling without
2776   // having to invalidate due to changes in subpixel rendering.
2777   // Note that when we actually draw into a PaintedLayer, the coordinates
2778   // that get mapped onto the layer buffer pixels are from the display list,
2779   // which are relative to the display root frame's top-left increasing down,
2780   // whereas here our coordinates are scroll positions which increase upward
2781   // and are relative to the scrollport top-left. This difference doesn't
2782   // actually matter since all we are about is that there be an integer number
2783   // of layer pixels between pt and curPos.
2784   nsPoint pt =
2785       ClampAndAlignWithLayerPixels(aPt, GetLayoutScrollRange(), aRange,
2786                                    alignWithPos, appUnitsPerDevPixel, scale);
2787   if (pt == curPos) {
2788     return;
2789   }
2790 
2791   // If we are scrolling the RCD-RSF, and a visual scroll update is pending,
2792   // cancel it; otherwise, it will clobber this scroll.
2793   if (IsRootScrollFrameOfDocument() && presContext->IsRootContentDocument()) {
2794     PresShell* ps = presContext->GetPresShell();
2795     if (const auto& visualScrollUpdate = ps->GetPendingVisualScrollUpdate()) {
2796       if (visualScrollUpdate->mVisualScrollOffset != aPt) {
2797         // Only clobber if the scroll was originated by the main thread.
2798         // Respect the priority of origins (an "eRestore" layout scroll should
2799         // not clobber an "eMainThread" visual scroll.)
2800         bool shouldClobber =
2801             aOrigin == nsGkAtoms::other ||
2802             (aOrigin == nsGkAtoms::restore &&
2803              visualScrollUpdate->mUpdateType == FrameMetrics::eRestore);
2804         if (shouldClobber) {
2805           ps->AcknowledgePendingVisualScrollUpdate();
2806           ps->ClearPendingVisualScrollUpdate();
2807         }
2808       }
2809     }
2810   }
2811 
2812   bool needFrameVisibilityUpdate = mLastUpdateFramesPos == nsPoint(-1, -1);
2813 
2814   nsPoint dist(std::abs(pt.x - mLastUpdateFramesPos.x),
2815                std::abs(pt.y - mLastUpdateFramesPos.y));
2816   nsSize visualViewportSize = GetVisualViewportSize();
2817   nscoord horzAllowance = std::max(
2818       visualViewportSize.width /
2819           std::max(
2820               StaticPrefs::
2821                   layout_framevisibility_amountscrollbeforeupdatehorizontal(),
2822               1),
2823       AppUnitsPerCSSPixel());
2824   nscoord vertAllowance = std::max(
2825       visualViewportSize.height /
2826           std::max(
2827               StaticPrefs::
2828                   layout_framevisibility_amountscrollbeforeupdatevertical(),
2829               1),
2830       AppUnitsPerCSSPixel());
2831   if (dist.x >= horzAllowance || dist.y >= vertAllowance) {
2832     needFrameVisibilityUpdate = true;
2833   }
2834 
2835   // notify the listeners.
2836   for (uint32_t i = 0; i < mListeners.Length(); i++) {
2837     mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
2838   }
2839 
2840   nsRect oldDisplayPort;
2841   nsIContent* content = mOuter->GetContent();
2842   nsLayoutUtils::GetHighResolutionDisplayPort(content, &oldDisplayPort);
2843   oldDisplayPort.MoveBy(-mScrolledFrame->GetPosition());
2844 
2845   // Update frame position for scrolling
2846   mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
2847 
2848   // If |mLastScrollOrigin| is already set to something that can clobber APZ's
2849   // scroll offset, then we don't want to change it to something that can't.
2850   // If we allowed this, then we could end up in a state where APZ ignores
2851   // legitimate scroll offset updates because the origin has been masked by
2852   // a later change within the same refresh driver tick.
2853   allowScrollOriginChange =
2854       (mAllowScrollOriginDowngrade || !isScrollOriginDowngrade) &&
2855       !suppressScrollOriginChange;
2856 
2857   if (allowScrollOriginChange) {
2858     mLastScrollOrigin = aOrigin;
2859     mAllowScrollOriginDowngrade = false;
2860   }
2861   mLastSmoothScrollOrigin = nullptr;
2862   mScrollGeneration = ++sScrollGenerationCounter;
2863   if (mLastScrollOrigin == nsGkAtoms::apz) {
2864     mApzScrollPos = GetScrollPosition();
2865   }
2866 
2867   // If the new scroll offset is going to clobber APZ's scroll offset, for
2868   // the RCD-RSF this will have the effect of resetting the visual viewport
2869   // offset to be the same as the new scroll (= layout viewport) offset.
2870   // The APZ callback transform, which reflects the difference between these
2871   // offsets, will subsequently be cleared. However, if we wait for APZ to
2872   // clear it, the main thread could end up using the old value and get
2873   // incorrect results, so just clear it now.
2874   if (mIsRoot && nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin)) {
2875     content->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(),
2876                          nsINode::DeleteProperty<CSSPoint>);
2877 
2878     // Similarly, update the main thread's view of the visual viewport
2879     // offset. Otherwise, if we perform calculations that depend on this
2880     // offset (e.g. by using nsIDOMWindowUtils.getVisualViewportOffset()
2881     // in chrome JS code) before it's updated by the next APZ repaint,
2882     // we could get incorrect results.
2883     presContext->PresShell()->SetVisualViewportOffset(pt, curPos);
2884   }
2885 
2886   ScrollVisual();
2887   mAnchor.UserScrolled();
2888 
2889   bool schedulePaint = true;
2890   if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) &&
2891       !nsLayoutUtils::ShouldDisableApzForElement(content) &&
2892       StaticPrefs::apz_paint_skipping_enabled()) {
2893     // If APZ is enabled with paint-skipping, there are certain conditions in
2894     // which we can skip paints:
2895     // 1) If APZ triggered this scroll, and the tile-aligned displayport is
2896     //    unchanged.
2897     // 2) If non-APZ triggered this scroll, but we can handle it by just asking
2898     //    APZ to update the scroll position. Again we make this conditional on
2899     //    the tile-aligned displayport being unchanged.
2900     // We do the displayport check first since it's common to all scenarios,
2901     // and then if the displayport is unchanged, we check if APZ triggered,
2902     // or can handle, this scroll. If so, we set schedulePaint to false and
2903     // skip the paint.
2904     // Because of bug 1264297, we also don't do paint-skipping for elements with
2905     // perspective, because the displayport may not have captured everything
2906     // that needs to be painted. So even if the final tile-aligned displayport
2907     // is the same, we force a repaint for these elements. Bug 1254260 tracks
2908     // fixing this properly.
2909     nsRect displayPort;
2910     bool usingDisplayPort =
2911         nsLayoutUtils::GetHighResolutionDisplayPort(content, &displayPort);
2912     displayPort.MoveBy(-mScrolledFrame->GetPosition());
2913 
2914     PAINT_SKIP_LOG(
2915         "New scrollpos %s usingDP %d dpEqual %d scrollableByApz %d plugins"
2916         "%d perspective %d bglocal %d filter %d\n",
2917         Stringify(CSSPoint::FromAppUnits(GetScrollPosition())).c_str(),
2918         usingDisplayPort, displayPort.IsEqualEdges(oldDisplayPort),
2919         mScrollableByAPZ, HasPluginFrames(), HasPerspective(),
2920         HasBgAttachmentLocal(), mHasOutOfFlowContentInsideFilter);
2921     if (usingDisplayPort && displayPort.IsEqualEdges(oldDisplayPort) &&
2922         !HasPerspective() && !HasBgAttachmentLocal() &&
2923         !mHasOutOfFlowContentInsideFilter) {
2924       bool haveScrollLinkedEffects =
2925           content->GetComposedDoc()->HasScrollLinkedEffect();
2926       bool apzDisabled = haveScrollLinkedEffects &&
2927                          StaticPrefs::apz_disable_for_scroll_linked_effects();
2928       if (!apzDisabled && !HasPluginFrames()) {
2929         if (LastScrollOrigin() == nsGkAtoms::apz) {
2930           schedulePaint = false;
2931           PAINT_SKIP_LOG("Skipping due to APZ scroll\n");
2932         } else if (mScrollableByAPZ) {
2933           nsIWidget* widget = presContext->GetNearestWidget();
2934           LayerManager* manager = widget ? widget->GetLayerManager() : nullptr;
2935           if (manager) {
2936             mozilla::layers::ScrollableLayerGuid::ViewID id;
2937             bool success = nsLayoutUtils::FindIDFor(content, &id);
2938             MOZ_ASSERT(success);  // we have a displayport, we better have an ID
2939 
2940             // Schedule an empty transaction to carry over the scroll offset
2941             // update, instead of a full transaction. This empty transaction
2942             // might still get squashed into a full transaction if something
2943             // happens to trigger one.
2944             success = manager->SetPendingScrollUpdateForNextTransaction(
2945                 id,
2946                 {mScrollGeneration, CSSPoint::FromAppUnits(GetScrollPosition()),
2947                  CSSPoint::FromAppUnits(GetApzScrollPosition()),
2948                  mLastScrollOrigin == nsGkAtoms::relative});
2949             if (success) {
2950               schedulePaint = false;
2951               mOuter->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY);
2952               PAINT_SKIP_LOG(
2953                   "Skipping due to APZ-forwarded main-thread scroll\n");
2954             } else {
2955               PAINT_SKIP_LOG(
2956                   "Failed to set pending scroll update on layer manager\n");
2957             }
2958           }
2959         }
2960       }
2961     }
2962   }
2963 
2964   if (schedulePaint) {
2965     mOuter->SchedulePaint();
2966 
2967     if (needFrameVisibilityUpdate) {
2968       presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
2969     }
2970   }
2971 
2972   if (mOuter->ChildrenHavePerspective()) {
2973     // The overflow areas of descendants may depend on the scroll position,
2974     // so ensure they get updated.
2975 
2976     // First we recompute the overflow areas of the transformed children
2977     // that use the perspective. FinishAndStoreOverflow only calls this
2978     // if the size changes, so we need to do it manually.
2979     mOuter->RecomputePerspectiveChildrenOverflow(mOuter);
2980 
2981     // Update the overflow for the scrolled frame to take any changes from the
2982     // children into account.
2983     mScrolledFrame->UpdateOverflow();
2984 
2985     // Update the overflow for the outer so that we recompute scrollbars.
2986     mOuter->UpdateOverflow();
2987   }
2988 
2989   ScheduleSyntheticMouseMove();
2990 
2991   PresShell::AutoAssertNoFlush noFlush(*mOuter->PresShell());
2992 
2993   {  // scope the AutoScrollbarRepaintSuppression
2994     AutoWeakFrame weakFrame(mOuter);
2995     AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame,
2996                                                        !schedulePaint);
2997     UpdateScrollbarPosition();
2998     if (!weakFrame.IsAlive()) {
2999       return;
3000     }
3001   }
3002 
3003   presContext->RecordInteractionTime(
3004       nsPresContext::InteractionType::ScrollInteraction, TimeStamp::Now());
3005 
3006   PostScrollEvent();
3007   // If this is a viewport scroll, this could affect the relative offset
3008   // between layout and visual viewport, so we might have to fire a visual
3009   // viewport scroll event as well.
3010   if (mIsRoot) {
3011     if (auto* window = nsGlobalWindowInner::Cast(
3012             mOuter->PresContext()->Document()->GetInnerWindow())) {
3013       window->VisualViewport()->PostScrollEvent(
3014           presContext->PresShell()->GetVisualViewportOffset(), curPos);
3015     }
3016   }
3017 
3018   // notify the listeners.
3019   for (uint32_t i = 0; i < mListeners.Length(); i++) {
3020     mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
3021   }
3022 
3023   if (nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell()) {
3024     docShell->NotifyScrollObservers();
3025   }
3026 }
3027 
MaxZIndexInList(nsDisplayList * aList,nsDisplayListBuilder * aBuilder)3028 static Maybe<int32_t> MaxZIndexInList(nsDisplayList* aList,
3029                                       nsDisplayListBuilder* aBuilder) {
3030   Maybe<int32_t> maxZIndex = Nothing();
3031   for (nsDisplayItem* item = aList->GetBottom(); item;
3032        item = item->GetAbove()) {
3033     int32_t zIndex = item->ZIndex();
3034     if (zIndex < 0) {
3035       continue;
3036     }
3037     if (!maxZIndex) {
3038       maxZIndex = Some(zIndex);
3039     } else {
3040       maxZIndex = Some(std::max(maxZIndex.value(), zIndex));
3041     }
3042   }
3043   return maxZIndex;
3044 }
3045 
3046 template <class T>
AppendInternalItemToTop(const nsDisplayListSet & aLists,T * aItem,const Maybe<int32_t> & aZIndex)3047 static void AppendInternalItemToTop(const nsDisplayListSet& aLists, T* aItem,
3048                                     const Maybe<int32_t>& aZIndex) {
3049   if (aZIndex) {
3050     aItem->SetOverrideZIndex(aZIndex.value());
3051     aLists.PositionedDescendants()->AppendToTop(aItem);
3052   } else {
3053     aLists.Content()->AppendToTop(aItem);
3054   }
3055 }
3056 
3057 static const uint32_t APPEND_OWN_LAYER = 0x1;
3058 static const uint32_t APPEND_POSITIONED = 0x2;
3059 static const uint32_t APPEND_SCROLLBAR_CONTAINER = 0x4;
3060 static const uint32_t APPEND_OVERLAY = 0x8;
3061 static const uint32_t APPEND_TOP = 0x10;
3062 
AppendToTop(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists,nsDisplayList * aSource,nsIFrame * aSourceFrame,uint32_t aFlags)3063 static void AppendToTop(nsDisplayListBuilder* aBuilder,
3064                         const nsDisplayListSet& aLists, nsDisplayList* aSource,
3065                         nsIFrame* aSourceFrame, uint32_t aFlags) {
3066   if (aSource->IsEmpty()) {
3067     return;
3068   }
3069 
3070   nsDisplayWrapList* newItem;
3071   const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
3072   if (aFlags & APPEND_OWN_LAYER) {
3073     ScrollbarData scrollbarData;
3074     if (aFlags & APPEND_SCROLLBAR_CONTAINER) {
3075       scrollbarData = ScrollbarData::CreateForScrollbarContainer(
3076           aBuilder->GetCurrentScrollbarDirection(),
3077           aBuilder->GetCurrentScrollbarTarget());
3078       // Direction should be set
3079       MOZ_ASSERT(scrollbarData.mDirection.isSome());
3080     }
3081 
3082     newItem = MakeDisplayItemWithIndex<nsDisplayOwnLayer>(
3083         aBuilder, aSourceFrame,
3084         /* aIndex = */ nsDisplayOwnLayer::OwnLayerForScrollbar, aSource, asr,
3085         nsDisplayOwnLayerFlags::None, scrollbarData, true, false);
3086   } else {
3087     // Build the wrap list with an index of 1, since the scrollbar frame itself
3088     // might have already built an nsDisplayWrapList.
3089     newItem = MakeDisplayItemWithIndex<nsDisplayWrapList>(
3090         aBuilder, aSourceFrame, 1, aSource, asr, false);
3091   }
3092   if (!newItem) {
3093     return;
3094   }
3095 
3096   if (aFlags & APPEND_POSITIONED) {
3097     // We want overlay scrollbars to always be on top of the scrolled content,
3098     // but we don't want them to unnecessarily cover overlapping elements from
3099     // outside our scroll frame.
3100     Maybe<int32_t> zIndex = Nothing();
3101     if (aFlags & APPEND_TOP) {
3102       zIndex = Some(INT32_MAX);
3103     } else if (aFlags & APPEND_OVERLAY) {
3104       zIndex = MaxZIndexInList(aLists.PositionedDescendants(), aBuilder);
3105     } else if (aSourceFrame->StylePosition()->mZIndex.IsInteger()) {
3106       zIndex = Some(aSourceFrame->StylePosition()->mZIndex.integer._0);
3107     }
3108     AppendInternalItemToTop(aLists, newItem, zIndex);
3109   } else {
3110     aLists.BorderBackground()->AppendToTop(newItem);
3111   }
3112 }
3113 
3114 struct HoveredStateComparator {
HoveredHoveredStateComparator3115   static bool Hovered(const nsIFrame* aFrame) {
3116     return aFrame->GetContent()->IsElement() &&
3117            aFrame->GetContent()->AsElement()->HasAttr(kNameSpaceID_None,
3118                                                       nsGkAtoms::hover);
3119   }
3120 
EqualsHoveredStateComparator3121   bool Equals(nsIFrame* A, nsIFrame* B) const {
3122     return Hovered(A) == Hovered(B);
3123   }
3124 
LessThanHoveredStateComparator3125   bool LessThan(nsIFrame* A, nsIFrame* B) const {
3126     return !Hovered(A) && Hovered(B);
3127   }
3128 };
3129 
AppendScrollPartsTo(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists,bool aCreateLayer,bool aPositioned)3130 void ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder,
3131                                             const nsDisplayListSet& aLists,
3132                                             bool aCreateLayer,
3133                                             bool aPositioned) {
3134   const bool overlayScrollbars =
3135       LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
3136 
3137   AutoTArray<nsIFrame*, 3> scrollParts;
3138   for (nsIFrame* kid : mOuter->PrincipalChildList()) {
3139     if (kid == mScrolledFrame ||
3140         (kid->IsAbsPosContainingBlock() || overlayScrollbars) != aPositioned) {
3141       continue;
3142     }
3143 
3144     scrollParts.AppendElement(kid);
3145   }
3146   if (scrollParts.IsEmpty()) {
3147     return;
3148   }
3149 
3150   // We can't check will-change budget during display list building phase.
3151   // This means that we will build scroll bar layers for out of budget
3152   // will-change: scroll position.
3153   const mozilla::layers::ScrollableLayerGuid::ViewID scrollTargetId =
3154       IsMaybeScrollingActive()
3155           ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
3156           : mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
3157 
3158   scrollParts.Sort(HoveredStateComparator());
3159 
3160   DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3161   // Don't let scrollparts extent outside our frame's border-box, if these are
3162   // viewport scrollbars. They would create layerization problems. This wouldn't
3163   // normally be an issue but themes can add overflow areas to scrollbar parts.
3164   if (mIsRoot) {
3165     nsRect scrollPartsClip(aBuilder->ToReferenceFrame(mOuter),
3166                            TrueOuterSize(aBuilder));
3167     clipState.ClipContentDescendants(scrollPartsClip);
3168   }
3169 
3170   for (uint32_t i = 0; i < scrollParts.Length(); ++i) {
3171     Maybe<ScrollDirection> scrollDirection;
3172     uint32_t appendToTopFlags = 0;
3173     if (scrollParts[i] == mVScrollbarBox) {
3174       scrollDirection.emplace(ScrollDirection::eVertical);
3175       appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
3176     }
3177     if (scrollParts[i] == mHScrollbarBox) {
3178       MOZ_ASSERT(!scrollDirection.isSome());
3179       scrollDirection.emplace(ScrollDirection::eHorizontal);
3180       appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
3181     }
3182     if (scrollParts[i] == mResizerBox && !HasResizer()) {
3183       continue;
3184     }
3185 
3186     // The display port doesn't necessarily include the scrollbars, so just
3187     // include all of the scrollbars if we are in a RCD-RSF. We only do
3188     // this for the root scrollframe of the root content document, which is
3189     // zoomable, and where the scrollbar sizes are bounded by the widget.
3190     const nsRect visible =
3191         mIsRoot && mOuter->PresContext()->IsRootContentDocument()
3192             ? scrollParts[i]->GetVisualOverflowRectRelativeToParent()
3193             : aBuilder->GetVisibleRect();
3194     if (visible.IsEmpty()) {
3195       continue;
3196     }
3197     const nsRect dirty =
3198         mIsRoot && mOuter->PresContext()->IsRootContentDocument()
3199             ? scrollParts[i]->GetVisualOverflowRectRelativeToParent()
3200             : aBuilder->GetDirtyRect();
3201 
3202     // Always create layers for overlay scrollbars so that we don't create a
3203     // giant layer covering the whole scrollport if both scrollbars are visible.
3204     const bool isOverlayScrollbar =
3205         scrollDirection.isSome() && overlayScrollbars;
3206     const bool createLayer =
3207         aCreateLayer || isOverlayScrollbar ||
3208         StaticPrefs::layout_scrollbars_always_layerize_track();
3209 
3210     nsDisplayListCollection partList(aBuilder);
3211     {
3212       nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
3213           aBuilder, mOuter, visible, dirty);
3214 
3215       nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter infoSetter(
3216           aBuilder, scrollTargetId, scrollDirection, createLayer);
3217       mOuter->BuildDisplayListForChild(
3218           aBuilder, scrollParts[i], partList,
3219           nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
3220     }
3221 
3222     // DISPLAY_CHILD_FORCE_STACKING_CONTEXT put everything into
3223     // partList.PositionedDescendants().
3224     if (partList.PositionedDescendants()->IsEmpty()) {
3225       continue;
3226     }
3227 
3228     if (createLayer) {
3229       appendToTopFlags |= APPEND_OWN_LAYER;
3230     }
3231     if (aPositioned) {
3232       appendToTopFlags |= APPEND_POSITIONED;
3233     }
3234 
3235     if (isOverlayScrollbar || scrollParts[i] == mResizerBox) {
3236       if (isOverlayScrollbar && mIsRoot) {
3237         appendToTopFlags |= APPEND_TOP;
3238       } else {
3239         appendToTopFlags |= APPEND_OVERLAY;
3240         aBuilder->SetDisablePartialUpdates(true);
3241       }
3242     }
3243 
3244     {
3245       nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
3246           aBuilder, scrollParts[i],
3247           visible + mOuter->GetOffsetTo(scrollParts[i]),
3248           dirty + mOuter->GetOffsetTo(scrollParts[i]));
3249       nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter infoSetter(
3250           aBuilder, scrollTargetId, scrollDirection, createLayer);
3251 
3252       ::AppendToTop(aBuilder, aLists, partList.PositionedDescendants(),
3253                     scrollParts[i], appendToTopFlags);
3254     }
3255   }
3256 }
3257 
ExpandRectToNearlyVisible(const nsRect & aRect) const3258 nsRect ScrollFrameHelper::ExpandRectToNearlyVisible(const nsRect& aRect) const {
3259   // We don't want to expand a rect in a direction that we can't scroll, so we
3260   // check the scroll range.
3261   nsRect scrollRange = GetLayoutScrollRange();
3262   nsPoint scrollPos = GetScrollPosition();
3263   nsMargin expand(0, 0, 0, 0);
3264 
3265   nscoord vertShift =
3266       StaticPrefs::layout_framevisibility_numscrollportheights() * aRect.height;
3267   if (scrollRange.y < scrollPos.y) {
3268     expand.top = vertShift;
3269   }
3270   if (scrollPos.y < scrollRange.YMost()) {
3271     expand.bottom = vertShift;
3272   }
3273 
3274   nscoord horzShift =
3275       StaticPrefs::layout_framevisibility_numscrollportwidths() * aRect.width;
3276   if (scrollRange.x < scrollPos.x) {
3277     expand.left = horzShift;
3278   }
3279   if (scrollPos.x < scrollRange.XMost()) {
3280     expand.right = horzShift;
3281   }
3282 
3283   nsRect rect = aRect;
3284   rect.Inflate(expand);
3285   return rect;
3286 }
3287 
ShouldBeClippedByFrame(nsIFrame * aClipFrame,nsIFrame * aClippedFrame)3288 static bool ShouldBeClippedByFrame(nsIFrame* aClipFrame,
3289                                    nsIFrame* aClippedFrame) {
3290   return nsLayoutUtils::IsProperAncestorFrame(aClipFrame, aClippedFrame);
3291 }
3292 
ClipItemsExceptCaret(nsDisplayList * aList,nsDisplayListBuilder * aBuilder,nsIFrame * aClipFrame,const DisplayItemClipChain * aExtraClip,nsDataHashtable<nsPtrHashKey<const DisplayItemClipChain>,const DisplayItemClipChain * > & aCache)3293 static void ClipItemsExceptCaret(
3294     nsDisplayList* aList, nsDisplayListBuilder* aBuilder, nsIFrame* aClipFrame,
3295     const DisplayItemClipChain* aExtraClip,
3296     nsDataHashtable<nsPtrHashKey<const DisplayItemClipChain>,
3297                     const DisplayItemClipChain*>& aCache) {
3298   for (nsDisplayItem* i = aList->GetBottom(); i; i = i->GetAbove()) {
3299     if (!ShouldBeClippedByFrame(aClipFrame, i->Frame())) {
3300       continue;
3301     }
3302 
3303     const DisplayItemType type = i->GetType();
3304     if (type != DisplayItemType::TYPE_CARET &&
3305         type != DisplayItemType::TYPE_CONTAINER) {
3306       const DisplayItemClipChain* clip = i->GetClipChain();
3307       const DisplayItemClipChain* intersection = nullptr;
3308       if (aCache.Get(clip, &intersection)) {
3309         i->SetClipChain(intersection, true);
3310       } else {
3311         i->IntersectClip(aBuilder, aExtraClip, true);
3312         aCache.Put(clip, i->GetClipChain());
3313       }
3314     }
3315     nsDisplayList* children = i->GetSameCoordinateSystemChildren();
3316     if (children) {
3317       ClipItemsExceptCaret(children, aBuilder, aClipFrame, aExtraClip, aCache);
3318     }
3319   }
3320 }
3321 
ClipListsExceptCaret(nsDisplayListCollection * aLists,nsDisplayListBuilder * aBuilder,nsIFrame * aClipFrame,const DisplayItemClipChain * aExtraClip)3322 static void ClipListsExceptCaret(nsDisplayListCollection* aLists,
3323                                  nsDisplayListBuilder* aBuilder,
3324                                  nsIFrame* aClipFrame,
3325                                  const DisplayItemClipChain* aExtraClip) {
3326   nsDataHashtable<nsPtrHashKey<const DisplayItemClipChain>,
3327                   const DisplayItemClipChain*>
3328       cache;
3329   ClipItemsExceptCaret(aLists->BorderBackground(), aBuilder, aClipFrame,
3330                        aExtraClip, cache);
3331   ClipItemsExceptCaret(aLists->BlockBorderBackgrounds(), aBuilder, aClipFrame,
3332                        aExtraClip, cache);
3333   ClipItemsExceptCaret(aLists->Floats(), aBuilder, aClipFrame, aExtraClip,
3334                        cache);
3335   ClipItemsExceptCaret(aLists->PositionedDescendants(), aBuilder, aClipFrame,
3336                        aExtraClip, cache);
3337   ClipItemsExceptCaret(aLists->Outlines(), aBuilder, aClipFrame, aExtraClip,
3338                        cache);
3339   ClipItemsExceptCaret(aLists->Content(), aBuilder, aClipFrame, aExtraClip,
3340                        cache);
3341 }
3342 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)3343 void ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
3344                                          const nsDisplayListSet& aLists) {
3345   SetAndNullOnExit<const nsIFrame> tmpBuilder(
3346       mReferenceFrameDuringPainting, aBuilder->GetCurrentReferenceFrame());
3347   if (aBuilder->IsForFrameVisibility()) {
3348     NotifyApproximateFrameVisibilityUpdate(false);
3349   }
3350 
3351   mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
3352 
3353   if (aBuilder->IsPaintingToWindow()) {
3354     if (IsMaybeScrollingActive() && !gfxVars::UseWebRender()) {
3355       if (mScrollPosForLayerPixelAlignment == nsPoint(-1, -1)) {
3356         mScrollPosForLayerPixelAlignment = GetScrollPosition();
3357       }
3358     } else {
3359       mScrollPosForLayerPixelAlignment = nsPoint(-1, -1);
3360     }
3361   }
3362 
3363   bool isRootContent =
3364       mIsRoot && mOuter->PresContext()->IsRootContentDocumentCrossProcess();
3365 
3366   nsRect effectiveScrollPort = mScrollPort;
3367   if (isRootContent && mOuter->PresContext()->HasDynamicToolbar()) {
3368     // Expand the scroll port to the size including the area covered by dynamic
3369     // toolbar in the case where the dynamic toolbar is being used since
3370     // position:fixed elements attached to this root scroller might be taller
3371     // than its scroll port (e.g 100vh). Even if the dynamic toolbar covers the
3372     // taller area, it doesn't mean the area is clipped by the toolbar because
3373     // the dynamic toolbar is laid out outside of our topmost window and it
3374     // transitions without changing our topmost window size.
3375     effectiveScrollPort.SizeTo(nsLayoutUtils::ExpandHeightForViewportUnits(
3376         mOuter->PresContext(), effectiveScrollPort.Size()));
3377   }
3378 
3379   // It's safe to get this value before the DecideScrollableLayer call below
3380   // because that call cannot create a displayport for root scroll frames,
3381   // and hence it cannot create an ignore scroll frame.
3382   bool ignoringThisScrollFrame = aBuilder->GetIgnoreScrollFrame() == mOuter;
3383 
3384   // Overflow clipping can never clip frames outside our subtree, so there
3385   // is no need to worry about whether we are a moving frame that might clip
3386   // non-moving frames.
3387   // Not all our descendants will be clipped by overflow clipping, but all
3388   // the ones that aren't clipped will be out of flow frames that have already
3389   // had dirty rects saved for them by their parent frames calling
3390   // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
3391   // dirty rect here.
3392   nsRect visibleRect = aBuilder->GetVisibleRect();
3393   nsRect dirtyRect = aBuilder->GetDirtyRect();
3394   if (!ignoringThisScrollFrame) {
3395     visibleRect = visibleRect.Intersect(effectiveScrollPort);
3396     dirtyRect = dirtyRect.Intersect(effectiveScrollPort);
3397   }
3398 
3399   bool dirtyRectHasBeenOverriden = false;
3400   Unused << DecideScrollableLayer(aBuilder, &visibleRect, &dirtyRect,
3401                                   /* aSetBase = */ !mIsRoot,
3402                                   &dirtyRectHasBeenOverriden);
3403 
3404   if (aBuilder->IsForFrameVisibility()) {
3405     // We expand the dirty rect to catch frames just outside of the scroll port.
3406     // We use the dirty rect instead of the whole scroll port to prevent
3407     // too much expansion in the presence of very large (bigger than the
3408     // viewport) scroll ports.
3409     dirtyRect = ExpandRectToNearlyVisible(dirtyRect);
3410     visibleRect = dirtyRect;
3411   }
3412 
3413   // We put non-overlay scrollbars in their own layers when this is the root
3414   // scroll frame and we are a toplevel content document. In this situation,
3415   // the scrollbar(s) would normally be assigned their own layer anyway, since
3416   // they're not scrolled with the rest of the document. But when both
3417   // scrollbars are visible, the layer's visible rectangle would be the size
3418   // of the viewport, so most layer implementations would create a layer buffer
3419   // that's much larger than necessary. Creating independent layers for each
3420   // scrollbar works around the problem.
3421   bool createLayersForScrollbars =
3422       mIsRoot && mOuter->PresContext()->IsRootContentDocument();
3423 
3424   nsIScrollableFrame* sf = do_QueryFrame(mOuter);
3425   MOZ_ASSERT(sf);
3426 
3427   if (ignoringThisScrollFrame) {
3428     // Root scrollframes have FrameMetrics and clipping on their container
3429     // layers, so don't apply clipping again.
3430     mAddClipRectToLayer = false;
3431 
3432     // If we are a root scroll frame that has a display port we want to add
3433     // scrollbars, they will be children of the scrollable layer, but they get
3434     // adjusted by the APZC automatically.
3435     bool addScrollBars =
3436         mIsRoot && mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow();
3437 
3438     if (addScrollBars) {
3439       // Add classic scrollbars.
3440       AppendScrollPartsTo(aBuilder, aLists, createLayersForScrollbars, false);
3441     }
3442 
3443     {
3444       nsDisplayListBuilder::AutoBuildingDisplayList building(
3445           aBuilder, mOuter, visibleRect, dirtyRect);
3446 
3447       // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
3448       // The scrolled frame shouldn't have its own background/border, so we
3449       // can just pass aLists directly.
3450       mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, aLists);
3451     }
3452 
3453     MaybeAddTopLayerItems(aBuilder, aLists);
3454 
3455     if (addScrollBars) {
3456       // Add overlay scrollbars.
3457       AppendScrollPartsTo(aBuilder, aLists, createLayersForScrollbars, true);
3458     }
3459 
3460     return;
3461   }
3462 
3463   // Root scrollframes have FrameMetrics and clipping on their container
3464   // layers, so don't apply clipping again.
3465   mAddClipRectToLayer =
3466       !(mIsRoot && mOuter->PresShell()->GetIsViewportOverridden());
3467 
3468   // Whether we might want to build a scrollable layer for this scroll frame
3469   // at some point in the future. This controls whether we add the information
3470   // to the layer tree (a scroll info layer if necessary, and add the right
3471   // area to the dispatch to content layer event regions) necessary to activate
3472   // a scroll frame so it creates a scrollable layer.
3473   bool couldBuildLayer = false;
3474   if (aBuilder->IsPaintingToWindow()) {
3475     if (mWillBuildScrollableLayer) {
3476       couldBuildLayer = true;
3477     } else {
3478       couldBuildLayer =
3479           nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && WantAsyncScroll();
3480     }
3481   }
3482 
3483   // Now display the scrollbars and scrollcorner. These parts are drawn
3484   // in the border-background layer, on top of our own background and
3485   // borders and underneath borders and backgrounds of later elements
3486   // in the tree.
3487   // Note that this does not apply for overlay scrollbars; those are drawn
3488   // in the positioned-elements layer on top of everything else by the call
3489   // to AppendScrollPartsTo(..., true) further down.
3490   AppendScrollPartsTo(aBuilder, aLists, createLayersForScrollbars, false);
3491 
3492   const nsStyleDisplay* disp = mOuter->StyleDisplay();
3493   if (aBuilder->IsForPainting() &&
3494       disp->mWillChange.bits & StyleWillChangeBits::SCROLL) {
3495     aBuilder->AddToWillChangeBudget(mOuter, GetVisualViewportSize());
3496   }
3497 
3498   mScrollParentID = aBuilder->GetCurrentScrollParentId();
3499 
3500   Maybe<nsRect> contentBoxClip;
3501   Maybe<const DisplayItemClipChain*> extraContentBoxClipForNonCaretContent;
3502   if (MOZ_UNLIKELY(
3503           disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
3504           disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox)) {
3505     WritingMode wm = mScrolledFrame->GetWritingMode();
3506     bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
3507                                 : disp->mOverflowClipBoxInline) ==
3508                StyleOverflowClipBox::ContentBox;
3509     bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
3510                                 : disp->mOverflowClipBoxBlock) ==
3511                StyleOverflowClipBox::ContentBox;
3512     // We only clip if there is *scrollable* overflow, to avoid clipping
3513     // *visual* overflow unnecessarily.
3514     nsRect clipRect = effectiveScrollPort + aBuilder->ToReferenceFrame(mOuter);
3515     nsRect so = mScrolledFrame->GetScrollableOverflowRect();
3516     if ((cbH && (clipRect.width != so.width || so.x < 0)) ||
3517         (cbV && (clipRect.height != so.height || so.y < 0))) {
3518       nsMargin padding = mOuter->GetUsedPadding();
3519       if (!cbH) {
3520         padding.left = padding.right = nscoord(0);
3521       }
3522       if (!cbV) {
3523         padding.top = padding.bottom = nscoord(0);
3524       }
3525       clipRect.Deflate(padding);
3526 
3527       // The non-inflated clip needs to be set on all non-caret items.
3528       // We prepare an extra DisplayItemClipChain here that will be intersected
3529       // with those items after they've been created.
3530       const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
3531 
3532       DisplayItemClip newClip;
3533       newClip.SetTo(clipRect);
3534 
3535       const DisplayItemClipChain* extraClip =
3536           aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr);
3537 
3538       extraContentBoxClipForNonCaretContent = Some(extraClip);
3539 
3540       nsIFrame* caretFrame = aBuilder->GetCaretFrame();
3541       // Avoid clipping it in a zero-height line box (heuristic only).
3542       if (caretFrame && caretFrame->GetRect().height != 0) {
3543         nsRect caretRect = aBuilder->GetCaretRect();
3544         // Allow the caret to stick out of the content box clip by half the
3545         // caret height on the top, and its full width on the right.
3546         nsRect inflatedClip = clipRect;
3547         inflatedClip.Inflate(
3548             nsMargin(caretRect.height / 2, caretRect.width, 0, 0));
3549         contentBoxClip = Some(inflatedClip);
3550       }
3551     }
3552   }
3553 
3554   nsDisplayListCollection set(aBuilder);
3555 
3556   bool willBuildAsyncZoomContainer =
3557       aBuilder->ShouldBuildAsyncZoomContainer() && isRootContent;
3558 
3559   nsRect scrollPortClip =
3560       effectiveScrollPort + aBuilder->ToReferenceFrame(mOuter);
3561   nsRect clipRect = scrollPortClip;
3562   // Our override of GetBorderRadii ensures we never have a radius at
3563   // the corners where we have a scrollbar.
3564   nscoord radii[8];
3565   bool haveRadii = mOuter->GetPaddingBoxBorderRadii(radii);
3566   if (mIsRoot) {
3567     clipRect.SizeTo(nsLayoutUtils::CalculateCompositionSizeForFrame(mOuter));
3568     // The composition size is essentially in visual coordinates.
3569     // If we are hit-testing in layout coordinates, transform the clip rect
3570     // to layout coordinates to match.
3571     if (aBuilder->IsRelativeToLayoutViewport() &&
3572         mOuter->PresContext()->IsRootContentDocument()) {
3573       clipRect = ViewportUtils::VisualToLayout(clipRect, mOuter->PresShell());
3574     }
3575   }
3576 
3577   {
3578     // Note that setting the current scroll parent id here means that positioned
3579     // children of this scroll info layer will pick up the scroll info layer as
3580     // their scroll handoff parent. This is intentional because that is what
3581     // happens for positioned children of scroll layers, and we want to maintain
3582     // consistent behaviour between scroll layers and scroll info layers.
3583     nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(
3584         aBuilder,
3585         couldBuildLayer && mScrolledFrame->GetContent()
3586             ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
3587             : aBuilder->GetCurrentScrollParentId());
3588 
3589     DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3590     // If we're building an async zoom container, clip the contents inside
3591     // to the layout viewport (scrollPortClip). The composition bounds clip
3592     // (clipRect) will be applied to the zoom container itself below.
3593     nsRect clipRectForContents =
3594         willBuildAsyncZoomContainer ? scrollPortClip : clipRect;
3595     if (mClipAllDescendants) {
3596       clipState.ClipContentDescendants(clipRectForContents,
3597                                        haveRadii ? radii : nullptr);
3598     } else {
3599       clipState.ClipContainingBlockDescendants(clipRectForContents,
3600                                                haveRadii ? radii : nullptr);
3601     }
3602 
3603     Maybe<DisplayListClipState::AutoSaveRestore> contentBoxClipState;
3604     ;
3605     if (contentBoxClip) {
3606       contentBoxClipState.emplace(aBuilder);
3607       if (mClipAllDescendants) {
3608         contentBoxClipState->ClipContentDescendants(*contentBoxClip);
3609       } else {
3610         contentBoxClipState->ClipContainingBlockDescendants(*contentBoxClip);
3611       }
3612     }
3613 
3614     nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
3615         aBuilder);
3616     if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
3617       asrSetter.EnterScrollFrame(sf);
3618     }
3619 
3620     if (mWillBuildScrollableLayer && aBuilder->BuildCompositorHitTestInfo()) {
3621       // Create a hit test info item for the scrolled content that's not
3622       // clipped to the displayport. This ensures that within the bounds
3623       // of the scroll frame, the scrolled content is always hit, even
3624       // if we are checkerboarding.
3625       CompositorHitTestInfo info =
3626           mScrolledFrame->GetCompositorHitTestInfo(aBuilder);
3627 
3628       if (info != CompositorHitTestInvisibleToHit) {
3629         auto* hitInfo =
3630             MakeDisplayItemWithIndex<nsDisplayCompositorHitTestInfo>(
3631                 aBuilder, mScrolledFrame, 1, info);
3632         if (hitInfo) {
3633           aBuilder->SetCompositorHitTestInfo(hitInfo->HitTestArea(),
3634                                              hitInfo->HitTestFlags());
3635           set.BorderBackground()->AppendToTop(hitInfo);
3636         }
3637       }
3638     }
3639 
3640     {
3641       // Clip our contents to the unsnapped scrolled rect. This makes sure
3642       // that we don't have display items over the subpixel seam at the edge
3643       // of the scrolled area.
3644       DisplayListClipState::AutoSaveRestore scrolledRectClipState(aBuilder);
3645       nsRect scrolledRectClip =
3646           GetUnsnappedScrolledRectInternal(
3647               mScrolledFrame->GetScrollableOverflowRect(), mScrollPort.Size()) +
3648           mScrolledFrame->GetPosition();
3649       bool clippedToDisplayPort = false;
3650       if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
3651         // Clip the contents to the display port.
3652         // The dirty rect already acts kind of like a clip, in that
3653         // FrameLayerBuilder intersects item bounds and opaque regions with
3654         // it, but it doesn't have the consistent snapping behavior of a
3655         // true clip.
3656         // For a case where this makes a difference, imagine the following
3657         // scenario: The display port has an edge that falls on a fractional
3658         // layer pixel, and there's an opaque display item that covers the
3659         // whole display port up until that fractional edge, and there is a
3660         // transparent display item that overlaps the edge. We want to prevent
3661         // this transparent item from enlarging the scrolled layer's visible
3662         // region beyond its opaque region. The dirty rect doesn't do that -
3663         // it gets rounded out, whereas a true clip gets rounded to nearest
3664         // pixels.
3665         // If there is no display port, we don't need this because the clip
3666         // from the scroll port is still applied.
3667         scrolledRectClip = scrolledRectClip.Intersect(visibleRect);
3668         clippedToDisplayPort = scrolledRectClip.IsEqualEdges(visibleRect);
3669       }
3670       scrolledRectClipState.ClipContainingBlockDescendants(
3671           scrolledRectClip + aBuilder->ToReferenceFrame(mOuter));
3672       if (clippedToDisplayPort) {
3673         // We have to do this after the ClipContainingBlockDescendants call
3674         // above, otherwise that call will clobber the flag set by this call
3675         // to SetClippedToDisplayPort.
3676         scrolledRectClipState.SetClippedToDisplayPort();
3677       }
3678 
3679       nsRect visibleRectForChildren = visibleRect;
3680       nsRect dirtyRectForChildren = dirtyRect;
3681 
3682       // If we are entering the RCD-RSF, we are crossing the async zoom
3683       // container boundary, and need to convert from visual to layout
3684       // coordinates.
3685       if (willBuildAsyncZoomContainer && aBuilder->IsForEventDelivery()) {
3686         MOZ_ASSERT(ViewportUtils::IsZoomedContentRoot(mScrolledFrame));
3687         visibleRectForChildren = ViewportUtils::VisualToLayout(
3688             visibleRectForChildren, mOuter->PresShell());
3689         dirtyRectForChildren = ViewportUtils::VisualToLayout(
3690             dirtyRectForChildren, mOuter->PresShell());
3691       }
3692 
3693       nsDisplayListBuilder::AutoBuildingDisplayList building(
3694           aBuilder, mOuter, visibleRectForChildren, dirtyRectForChildren);
3695 
3696       mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, set);
3697 
3698       if (dirtyRectHasBeenOverriden &&
3699           StaticPrefs::layout_display_list_show_rebuild_area()) {
3700         nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
3701             aBuilder, mOuter,
3702             dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
3703             NS_RGBA(0, 0, 255, 64), false);
3704         if (color) {
3705           color->SetOverrideZIndex(INT32_MAX);
3706           set.PositionedDescendants()->AppendToTop(color);
3707         }
3708       }
3709     }
3710 
3711     if (extraContentBoxClipForNonCaretContent) {
3712       // The items were built while the inflated content box clip was in
3713       // effect, so that the caret wasn't clipped unnecessarily. We apply
3714       // the non-inflated clip to the non-caret items now, by intersecting
3715       // it with their existing clip.
3716       ClipListsExceptCaret(&set, aBuilder, mScrolledFrame,
3717                            *extraContentBoxClipForNonCaretContent);
3718     }
3719 
3720     if (aBuilder->IsPaintingToWindow()) {
3721       mIsScrollParent = idSetter.ShouldForceLayerForScrollParent();
3722     }
3723     if (idSetter.ShouldForceLayerForScrollParent()) {
3724       // Note that forcing layerization of scroll parents follows the scroll
3725       // handoff chain which is subject to the out-of-flow-frames caveat noted
3726       // above (where the idSetter variable is created).
3727       MOZ_ASSERT(couldBuildLayer && mScrolledFrame->GetContent() &&
3728                  aBuilder->IsPaintingToWindow());
3729       if (!mWillBuildScrollableLayer) {
3730         // Set a displayport so next paint we don't have to force layerization
3731         // after the fact. It's ok to pass DoNotRepaint here, since we've
3732         // already painted the change and we're just optimizing it to be
3733         // detected earlier. We also won't confuse RetainedDisplayLists
3734         // with the silent change, since we explicitly request partial updates
3735         // to be disabled on the next paint.
3736         nsLayoutUtils::SetDisplayPortMargins(
3737             mOuter->GetContent(), mOuter->PresShell(), ScreenMargin(), 0,
3738             nsLayoutUtils::RepaintMode::DoNotRepaint);
3739         // Call DecideScrollableLayer to recompute mWillBuildScrollableLayer
3740         // and recompute the current animated geometry root if needed. It's
3741         // too late to change the dirty rect so pass a copy.
3742         nsRect copyOfDirtyRect = dirtyRect;
3743         nsRect copyOfVisibleRect = visibleRect;
3744         Unused << DecideScrollableLayer(aBuilder, &copyOfVisibleRect,
3745                                         &copyOfDirtyRect,
3746                                         /* aSetBase = */ false, nullptr);
3747         if (mWillBuildScrollableLayer) {
3748           asrSetter.InsertScrollFrame(sf);
3749           aBuilder->SetDisablePartialUpdates(true);
3750         }
3751       }
3752     }
3753   }
3754 
3755   MaybeAddTopLayerItems(aBuilder, set);
3756 
3757   if (willBuildAsyncZoomContainer) {
3758     MOZ_ASSERT(mClipAllDescendants);
3759 
3760     // Wrap all our scrolled contents in an nsDisplayAsyncZoom. This will be
3761     // the layer that gets scaled for APZ zooming. It does not have the
3762     // scrolled ASR, but it does have the composition bounds clip applied to
3763     // it. The children have the layout viewport clip applied to them (above).
3764     // Effectively we are double clipping to the viewport, at potentially
3765     // different async scales.
3766 
3767     nsDisplayList resultList;
3768     set.SerializeWithCorrectZOrder(&resultList, mOuter->GetContent());
3769 
3770     mozilla::layers::FrameMetrics::ViewID viewID =
3771         nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent());
3772 
3773     DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3774     clipState.ClipContentDescendants(clipRect, haveRadii ? radii : nullptr);
3775 
3776     set.Content()->AppendNewToTop<nsDisplayAsyncZoom>(
3777         aBuilder, mOuter, &resultList, aBuilder->CurrentActiveScrolledRoot(),
3778         viewID);
3779   }
3780 
3781   nsDisplayListCollection scrolledContent(aBuilder);
3782   set.MoveTo(scrolledContent);
3783 
3784   if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
3785     aBuilder->ForceLayerForScrollParent();
3786   }
3787 
3788   if (couldBuildLayer) {
3789     CompositorHitTestInfo info(CompositorHitTestFlags::eVisibleToHitTest,
3790                                CompositorHitTestFlags::eInactiveScrollframe);
3791     // If the scroll frame has non-default overscroll-behavior, instruct
3792     // APZ to require a target confirmation before processing events that
3793     // hit this scroll frame (that is, to drop the events if a
3794     // confirmation does not arrive within the timeout period). Otherwise,
3795     // APZ's fallback behaviour of scrolling the enclosing scroll frame
3796     // would violate the specified overscroll-behavior.
3797     auto overscroll = GetOverscrollBehaviorInfo();
3798     if (overscroll.mBehaviorX != OverscrollBehavior::Auto ||
3799         overscroll.mBehaviorY != OverscrollBehavior::Auto) {
3800       info += CompositorHitTestFlags::eRequiresTargetConfirmation;
3801     }
3802 
3803     nsRect area = effectiveScrollPort + aBuilder->ToReferenceFrame(mOuter);
3804 
3805     // Make sure that APZ will dispatch events back to content so we can
3806     // create a displayport for this frame. We'll add the item later on.
3807     if (!mWillBuildScrollableLayer) {
3808       if (aBuilder->BuildCompositorHitTestInfo()) {
3809         nsDisplayCompositorHitTestInfo* hitInfo =
3810             MakeDisplayItemWithIndex<nsDisplayCompositorHitTestInfo>(
3811                 aBuilder, mScrolledFrame, 1, info, Some(area));
3812         if (hitInfo) {
3813           AppendInternalItemToTop(scrolledContent, hitInfo, Some(INT32_MAX));
3814         }
3815       }
3816     }
3817 
3818     if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
3819       aBuilder->AppendNewScrollInfoItemForHoisting(
3820           MakeDisplayItem<nsDisplayScrollInfoLayer>(aBuilder, mScrolledFrame,
3821                                                     mOuter, info, area));
3822     }
3823   }
3824 
3825   // Now display overlay scrollbars and the resizer, if we have one.
3826   AppendScrollPartsTo(aBuilder, scrolledContent, createLayersForScrollbars,
3827                       true);
3828 
3829   scrolledContent.MoveTo(aLists);
3830 }
3831 
MaybeAddTopLayerItems(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)3832 void ScrollFrameHelper::MaybeAddTopLayerItems(nsDisplayListBuilder* aBuilder,
3833                                               const nsDisplayListSet& aLists) {
3834   if (mIsRoot) {
3835     if (ViewportFrame* viewportFrame = do_QueryFrame(mOuter->GetParent())) {
3836       nsDisplayList topLayerList;
3837       viewportFrame->BuildDisplayListForTopLayer(aBuilder, &topLayerList);
3838       if (!topLayerList.IsEmpty()) {
3839         nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
3840             aBuilder, viewportFrame);
3841 
3842         // Wrap the whole top layer in a single item with maximum z-index,
3843         // and append it at the very end, so that it stays at the topmost.
3844         nsDisplayWrapList* wrapList =
3845             MakeDisplayItemWithIndex<nsDisplayWrapList>(
3846                 aBuilder, viewportFrame, 2, &topLayerList,
3847                 aBuilder->CurrentActiveScrolledRoot(), false);
3848         if (wrapList) {
3849           wrapList->SetOverrideZIndex(
3850               std::numeric_limits<decltype(wrapList->ZIndex())>::max());
3851           aLists.PositionedDescendants()->AppendToTop(wrapList);
3852         }
3853       }
3854     }
3855   }
3856 }
3857 
RestrictToRootDisplayPort(const nsRect & aDisplayportBase)3858 nsRect ScrollFrameHelper::RestrictToRootDisplayPort(
3859     const nsRect& aDisplayportBase) {
3860   // This function clips aDisplayportBase so that it is no larger than the
3861   // root frame's displayport (or the root composition bounds, if we can't
3862   // obtain the root frame's displayport). This is useful for ensuring that
3863   // the displayport of a tall scrollframe doesn't gobble up all the memory.
3864 
3865   nsPresContext* pc = mOuter->PresContext();
3866   const nsPresContext* rootPresContext =
3867       pc->GetToplevelContentDocumentPresContext();
3868   if (!rootPresContext) {
3869     rootPresContext = pc->GetRootPresContext();
3870   }
3871   if (!rootPresContext) {
3872     return aDisplayportBase;
3873   }
3874   const mozilla::PresShell* const rootPresShell = rootPresContext->PresShell();
3875   nsIFrame* rootFrame = rootPresShell->GetRootScrollFrame();
3876   if (!rootFrame) {
3877     rootFrame = rootPresShell->GetRootFrame();
3878   }
3879   if (!rootFrame) {
3880     return aDisplayportBase;
3881   }
3882 
3883   nsRect rootDisplayPort;
3884   bool hasDisplayPort =
3885       rootFrame->GetContent() &&
3886       nsLayoutUtils::GetDisplayPort(rootFrame->GetContent(), &rootDisplayPort);
3887   if (hasDisplayPort) {
3888     // The display port of the root frame already factors in it's callback
3889     // transform, so subtract it out here, the GetCumulativeApzCallbackTransform
3890     // call below will add it back.
3891     if (nsIContent* content = rootFrame->GetContent()) {
3892       if (void* property =
3893               content->GetProperty(nsGkAtoms::apzCallbackTransform)) {
3894         rootDisplayPort -=
3895             CSSPoint::ToAppUnits(*static_cast<CSSPoint*>(property));
3896       }
3897     }
3898   } else {
3899     // If we don't have a display port on the root frame let's fall back to
3900     // the root composition bounds instead.
3901     nsRect rootCompBounds =
3902         nsRect(nsPoint(0, 0),
3903                nsLayoutUtils::CalculateCompositionSizeForFrame(rootFrame));
3904 
3905     // If rootFrame is the RCD-RSF then
3906     // CalculateCompositionSizeForFrame did not take the document's
3907     // resolution into account, so we must.
3908     if (rootPresContext->IsRootContentDocument() &&
3909         rootFrame == rootPresShell->GetRootScrollFrame()) {
3910       rootCompBounds =
3911           rootCompBounds.RemoveResolution(rootPresShell->GetResolution());
3912     }
3913 
3914     rootDisplayPort = rootCompBounds;
3915   }
3916 
3917   // We want to convert the root display port from the
3918   // coordinate space of |rootFrame| to the coordinate space of
3919   // |mOuter|. We do that with the TransformRect call below.
3920   // However, since we care about the root display port
3921   // relative to what the user is actually seeing, we also need to
3922   // incorporate the APZ callback transforms into this. Most of the
3923   // time those transforms are negligible, but in some cases (e.g.
3924   // when a zoom is applied on an overflow:hidden document) it is
3925   // not (see bug 1280013).
3926   // XXX: Eventually we may want to create a modified version of
3927   // TransformRect that includes the APZ callback transforms
3928   // directly.
3929   nsLayoutUtils::TransformRect(rootFrame, mOuter, rootDisplayPort);
3930   rootDisplayPort += CSSPoint::ToAppUnits(
3931       nsLayoutUtils::GetCumulativeApzCallbackTransform(mOuter));
3932 
3933   // We want to limit aDisplayportBase to be no larger than
3934   // rootDisplayPort on either axis, but we don't want to just
3935   // blindly intersect the two, because rootDisplayPort might be
3936   // offset from where aDisplayportBase is (see bug 1327095 comment
3937   // 8). Instead, we translate rootDisplayPort so as to maximize the
3938   // overlap with aDisplayportBase, and *then* do the intersection.
3939   if (rootDisplayPort.x > aDisplayportBase.x &&
3940       rootDisplayPort.XMost() > aDisplayportBase.XMost()) {
3941     // rootDisplayPort is at a greater x-position for both left and
3942     // right, so translate it such that the XMost() values are the
3943     // same. This will line up the right edge of the two rects, and
3944     // might mean that rootDisplayPort.x is smaller than
3945     // aDisplayportBase.x. We can avoid that by taking the min of the
3946     // x delta and XMost() delta, but it doesn't really matter
3947     // because the intersection between the two rects below will end
3948     // up the same.
3949     rootDisplayPort.x -= (rootDisplayPort.XMost() - aDisplayportBase.XMost());
3950   } else if (rootDisplayPort.x < aDisplayportBase.x &&
3951              rootDisplayPort.XMost() < aDisplayportBase.XMost()) {
3952     // Analaogous code for when the rootDisplayPort is at a smaller
3953     // x-position.
3954     rootDisplayPort.x = aDisplayportBase.x;
3955   }
3956   // Do the same for y-axis
3957   if (rootDisplayPort.y > aDisplayportBase.y &&
3958       rootDisplayPort.YMost() > aDisplayportBase.YMost()) {
3959     rootDisplayPort.y -= (rootDisplayPort.YMost() - aDisplayportBase.YMost());
3960   } else if (rootDisplayPort.y < aDisplayportBase.y &&
3961              rootDisplayPort.YMost() < aDisplayportBase.YMost()) {
3962     rootDisplayPort.y = aDisplayportBase.y;
3963   }
3964 
3965   // Now we can do the intersection
3966   return aDisplayportBase.Intersect(rootDisplayPort);
3967 }
3968 
DecideScrollableLayer(nsDisplayListBuilder * aBuilder,nsRect * aVisibleRect,nsRect * aDirtyRect,bool aSetBase,bool * aDirtyRectHasBeenOverriden)3969 bool ScrollFrameHelper::DecideScrollableLayer(
3970     nsDisplayListBuilder* aBuilder, nsRect* aVisibleRect, nsRect* aDirtyRect,
3971     bool aSetBase, bool* aDirtyRectHasBeenOverriden) {
3972   // Save and check if this changes so we can recompute the current agr.
3973   bool oldWillBuildScrollableLayer = mWillBuildScrollableLayer;
3974 
3975   nsIContent* content = mOuter->GetContent();
3976   bool usingDisplayPort = nsLayoutUtils::HasDisplayPort(content);
3977   if (aBuilder->IsPaintingToWindow()) {
3978     if (aSetBase) {
3979       nsRect displayportBase = *aVisibleRect;
3980       nsPresContext* pc = mOuter->PresContext();
3981 
3982       bool isContentRootDoc = pc->IsRootContentDocumentCrossProcess();
3983       bool isChromeRootDoc =
3984           !pc->Document()->IsContentDocument() && !pc->GetParentPresContext();
3985 
3986       if (mIsRoot && (isContentRootDoc || isChromeRootDoc)) {
3987         displayportBase =
3988             nsRect(nsPoint(0, 0),
3989                    nsLayoutUtils::CalculateCompositionSizeForFrame(mOuter));
3990       } else {
3991         // Make the displayport base equal to the visible rect restricted to
3992         // the scrollport and the root composition bounds, relative to the
3993         // scrollport.
3994         displayportBase = aVisibleRect->Intersect(mScrollPort);
3995 
3996         // Only restrict to the root displayport bounds if necessary,
3997         // as the required coordinate transformation is expensive.
3998         if (usingDisplayPort) {
3999           displayportBase = RestrictToRootDisplayPort(displayportBase);
4000         }
4001         displayportBase -= mScrollPort.TopLeft();
4002       }
4003 
4004       nsLayoutUtils::SetDisplayPortBase(mOuter->GetContent(), displayportBase);
4005     }
4006 
4007     // If we don't have aSetBase == true then should have already
4008     // been called with aSetBase == true which should have set a
4009     // displayport base.
4010     MOZ_ASSERT(content->GetProperty(nsGkAtoms::DisplayPortBase));
4011     nsRect displayPort;
4012     usingDisplayPort = nsLayoutUtils::GetDisplayPort(
4013         content, &displayPort, DisplayportRelativeTo::ScrollFrame);
4014 
4015     if (usingDisplayPort) {
4016       // Override the dirty rectangle if the displayport has been set.
4017       *aVisibleRect = displayPort;
4018       if (!aBuilder->IsPartialUpdate() || aBuilder->InInvalidSubtree() ||
4019           mOuter->IsFrameModified()) {
4020         *aDirtyRect = displayPort;
4021         if (aDirtyRectHasBeenOverriden) {
4022           *aDirtyRectHasBeenOverriden = true;
4023         }
4024       } else if (mOuter->HasOverrideDirtyRegion()) {
4025         nsRect* rect = mOuter->GetProperty(
4026             nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
4027         if (rect) {
4028           *aDirtyRect = *rect;
4029           if (aDirtyRectHasBeenOverriden) {
4030             *aDirtyRectHasBeenOverriden = true;
4031           }
4032         }
4033       }
4034     } else if (mIsRoot) {
4035       // The displayPort getter takes care of adjusting for resolution. So if
4036       // we have resolution but no displayPort then we need to adjust for
4037       // resolution here.
4038       PresShell* presShell = mOuter->PresShell();
4039       *aVisibleRect =
4040           aVisibleRect->RemoveResolution(presShell->GetResolution());
4041       *aDirtyRect = aDirtyRect->RemoveResolution(presShell->GetResolution());
4042     }
4043   }
4044 
4045   // Since making new layers is expensive, only create a scrollable layer
4046   // for some scroll frames.
4047   // When a displayport is being used, force building of a layer so that
4048   // the compositor can find the scrollable layer for async scrolling.
4049   // If the element is marked 'scrollgrab', also force building of a layer
4050   // so that APZ can implement scroll grabbing.
4051   mWillBuildScrollableLayer =
4052       usingDisplayPort || nsContentUtils::HasScrollgrab(content);
4053 
4054   // The cached animated geometry root for the display builder is out of
4055   // date if we just introduced a new animated geometry root.
4056   if (oldWillBuildScrollableLayer != mWillBuildScrollableLayer) {
4057     aBuilder->RecomputeCurrentAnimatedGeometryRoot();
4058   }
4059 
4060   return mWillBuildScrollableLayer;
4061 }
4062 
ComputeScrollMetadata(LayerManager * aLayerManager,const nsIFrame * aContainerReferenceFrame,const Maybe<ContainerLayerParameters> & aParameters,const DisplayItemClip * aClip) const4063 Maybe<ScrollMetadata> ScrollFrameHelper::ComputeScrollMetadata(
4064     LayerManager* aLayerManager, const nsIFrame* aContainerReferenceFrame,
4065     const Maybe<ContainerLayerParameters>& aParameters,
4066     const DisplayItemClip* aClip) const {
4067   if (!mWillBuildScrollableLayer) {
4068     return Nothing();
4069   }
4070 
4071   if (!nsLayoutUtils::UsesAsyncScrolling(mOuter)) {
4072     // Return early, since if we don't use APZ we don't need FrameMetrics.
4073     return Nothing();
4074   }
4075 
4076   nsPoint toReferenceFrame =
4077       mOuter->GetOffsetToCrossDoc(aContainerReferenceFrame);
4078 
4079   Maybe<nsRect> parentLayerClip;
4080   if (aClip && mAddClipRectToLayer) {
4081     parentLayerClip = Some(aClip->GetClipRect());
4082   }
4083 
4084   bool isRootContent =
4085       mIsRoot && mOuter->PresContext()->IsRootContentDocumentCrossProcess();
4086 
4087   MOZ_ASSERT(mScrolledFrame->GetContent());
4088 
4089   nsRect scrollport = mScrollPort + toReferenceFrame;
4090 
4091   return Some(nsLayoutUtils::ComputeScrollMetadata(
4092       mScrolledFrame, mOuter, mOuter->GetContent(), aContainerReferenceFrame,
4093       aLayerManager, mScrollParentID, scrollport, parentLayerClip,
4094       isRootContent, aParameters));
4095 }
4096 
ClipLayerToDisplayPort(Layer * aLayer,const DisplayItemClip * aClip,const ContainerLayerParameters & aParameters) const4097 void ScrollFrameHelper::ClipLayerToDisplayPort(
4098     Layer* aLayer, const DisplayItemClip* aClip,
4099     const ContainerLayerParameters& aParameters) const {
4100   // If APZ is not enabled, we still need the displayport to be clipped
4101   // in the compositor.
4102   if (!nsLayoutUtils::UsesAsyncScrolling(mOuter)) {
4103     Maybe<nsRect> parentLayerClip;
4104     if (aClip) {
4105       parentLayerClip = Some(aClip->GetClipRect());
4106     }
4107 
4108     if (parentLayerClip) {
4109       ParentLayerIntRect displayportClip =
4110           ViewAs<ParentLayerPixel>(parentLayerClip->ScaleToNearestPixels(
4111               aParameters.mXScale, aParameters.mYScale,
4112               mScrolledFrame->PresContext()->AppUnitsPerDevPixel()));
4113 
4114       ParentLayerIntRect layerClip;
4115       if (const ParentLayerIntRect* origClip =
4116               aLayer->GetClipRect().ptrOr(nullptr)) {
4117         layerClip = displayportClip.Intersect(*origClip);
4118       } else {
4119         layerClip = displayportClip;
4120       }
4121       aLayer->SetClipRect(Some(layerClip));
4122     }
4123   }
4124 }
4125 
IsRectNearlyVisible(const nsRect & aRect) const4126 bool ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const {
4127   // Use the right rect depending on if a display port is set.
4128   nsRect displayPort;
4129   bool usingDisplayport = nsLayoutUtils::GetDisplayPort(
4130       mOuter->GetContent(), &displayPort, DisplayportRelativeTo::ScrollFrame);
4131   return aRect.Intersects(
4132       ExpandRectToNearlyVisible(usingDisplayport ? displayPort : mScrollPort));
4133 }
4134 
GetOverscrollBehaviorInfo() const4135 OverscrollBehaviorInfo ScrollFrameHelper::GetOverscrollBehaviorInfo() const {
4136   nsIFrame* frame = GetFrameForStyle();
4137   if (!frame) {
4138     return {};
4139   }
4140 
4141   auto& disp = *frame->StyleDisplay();
4142   return OverscrollBehaviorInfo::FromStyleConstants(disp.mOverscrollBehaviorX,
4143                                                     disp.mOverscrollBehaviorY);
4144 }
4145 
GetScrollStylesFromFrame() const4146 ScrollStyles ScrollFrameHelper::GetScrollStylesFromFrame() const {
4147   nsPresContext* presContext = mOuter->PresContext();
4148   if (!presContext->IsDynamic() &&
4149       !(mIsRoot && presContext->HasPaginatedScrolling())) {
4150     return ScrollStyles(StyleOverflow::Hidden, StyleOverflow::Hidden);
4151   }
4152 
4153   if (!mIsRoot) {
4154     return ScrollStyles(*mOuter->StyleDisplay());
4155   }
4156 
4157   ScrollStyles result = presContext->GetViewportScrollStylesOverride();
4158   if (nsDocShell* ds = presContext->GetDocShell()) {
4159     switch (ds->ScrollbarPreference()) {
4160       case ScrollbarPreference::Auto:
4161         break;
4162       case ScrollbarPreference::Never:
4163         result.mHorizontal = result.mVertical = StyleOverflow::Hidden;
4164         break;
4165     }
4166   }
4167   return result;
4168 }
4169 
GetLayoutScrollRange() const4170 nsRect ScrollFrameHelper::GetLayoutScrollRange() const {
4171   return GetScrollRange(mScrollPort.width, mScrollPort.height);
4172 }
4173 
GetScrollRange(nscoord aWidth,nscoord aHeight) const4174 nsRect ScrollFrameHelper::GetScrollRange(nscoord aWidth,
4175                                          nscoord aHeight) const {
4176   nsRect range = GetScrolledRect();
4177   range.width = std::max(range.width - aWidth, 0);
4178   range.height = std::max(range.height - aHeight, 0);
4179   return range;
4180 }
4181 
GetVisualScrollRange() const4182 nsRect ScrollFrameHelper::GetVisualScrollRange() const {
4183   nsSize visualViewportSize = GetVisualViewportSize();
4184   return GetScrollRange(visualViewportSize.width, visualViewportSize.height);
4185 }
4186 
GetVisualViewportSize() const4187 nsSize ScrollFrameHelper::GetVisualViewportSize() const {
4188   PresShell* presShell = mOuter->PresShell();
4189   if (mIsRoot && presShell->IsVisualViewportSizeSet()) {
4190     return presShell->GetVisualViewportSize();
4191   }
4192   return mScrollPort.Size();
4193 }
4194 
GetVisualViewportOffset() const4195 nsPoint ScrollFrameHelper::GetVisualViewportOffset() const {
4196   PresShell* presShell = mOuter->PresShell();
4197   if (mIsRoot && presShell->IsVisualViewportSizeSet()) {
4198     return presShell->GetVisualViewportOffset();
4199   }
4200   return GetScrollPosition();
4201 }
4202 
GetVisualOptimalViewingRect() const4203 nsRect ScrollFrameHelper::GetVisualOptimalViewingRect() const {
4204   PresShell* presShell = mOuter->PresShell();
4205   nsRect rect = mScrollPort;
4206   if (mIsRoot && presShell->IsVisualViewportSizeSet() &&
4207       presShell->IsVisualViewportOffsetSet()) {
4208     rect = nsRect(mScrollPort.TopLeft() - GetScrollPosition() +
4209                       presShell->GetVisualViewportOffset(),
4210                   presShell->GetVisualViewportSize());
4211   }
4212   // NOTE: We intentionally resolve scroll-padding percentages against the
4213   // scrollport even when the visual viewport is set, see
4214   // https://github.com/w3c/csswg-drafts/issues/4393.
4215   rect.Deflate(GetScrollPadding());
4216   return rect;
4217 }
4218 
AdjustForWholeDelta(int32_t aDelta,nscoord * aCoord)4219 static void AdjustForWholeDelta(int32_t aDelta, nscoord* aCoord) {
4220   if (aDelta < 0) {
4221     *aCoord = nscoord_MIN;
4222   } else if (aDelta > 0) {
4223     *aCoord = nscoord_MAX;
4224   }
4225 }
4226 
4227 /**
4228  * Calculate lower/upper scrollBy range in given direction.
4229  * @param aDelta specifies scrollBy direction, if 0 then range will be 0 size
4230  * @param aPos desired destination in AppUnits
4231  * @param aNeg/PosTolerance defines relative range distance
4232  *   below and above of aPos point
4233  * @param aMultiplier used for conversion of tolerance into appUnis
4234  */
CalcRangeForScrollBy(int32_t aDelta,nscoord aPos,float aNegTolerance,float aPosTolerance,nscoord aMultiplier,nscoord * aLower,nscoord * aUpper)4235 static void CalcRangeForScrollBy(int32_t aDelta, nscoord aPos,
4236                                  float aNegTolerance, float aPosTolerance,
4237                                  nscoord aMultiplier, nscoord* aLower,
4238                                  nscoord* aUpper) {
4239   if (!aDelta) {
4240     *aLower = *aUpper = aPos;
4241     return;
4242   }
4243   *aLower = aPos - NSToCoordRound(aMultiplier *
4244                                   (aDelta > 0 ? aNegTolerance : aPosTolerance));
4245   *aUpper = aPos + NSToCoordRound(aMultiplier *
4246                                   (aDelta > 0 ? aPosTolerance : aNegTolerance));
4247 }
4248 
ScrollBy(nsIntPoint aDelta,ScrollUnit aUnit,ScrollMode aMode,nsIntPoint * aOverflow,nsAtom * aOrigin,nsIScrollableFrame::ScrollMomentum aMomentum,nsIScrollbarMediator::ScrollSnapMode aSnap)4249 void ScrollFrameHelper::ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit,
4250                                  ScrollMode aMode, nsIntPoint* aOverflow,
4251                                  nsAtom* aOrigin,
4252                                  nsIScrollableFrame::ScrollMomentum aMomentum,
4253                                  nsIScrollbarMediator::ScrollSnapMode aSnap) {
4254   // When a smooth scroll is being processed on a frame, mouse wheel and
4255   // trackpad momentum scroll event updates must notcancel the SMOOTH or
4256   // SMOOTH_MSD scroll animations, enabling Javascript that depends on them to
4257   // be responsive without forcing the user to wait for the fling animations to
4258   // completely stop.
4259   switch (aMomentum) {
4260     case nsIScrollableFrame::NOT_MOMENTUM:
4261       mIgnoreMomentumScroll = false;
4262       break;
4263     case nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT:
4264       if (mIgnoreMomentumScroll) {
4265         return;
4266       }
4267       break;
4268   }
4269 
4270   if (mAsyncSmoothMSDScroll != nullptr) {
4271     // When CSSOM-View scroll-behavior smooth scrolling is interrupted,
4272     // the scroll is not completed to avoid non-smooth snapping to the
4273     // prior smooth scroll's destination.
4274     mDestination = GetScrollPosition();
4275   }
4276 
4277   nsSize deltaMultiplier;
4278   float negativeTolerance;
4279   float positiveTolerance;
4280   if (!aOrigin) {
4281     aOrigin = nsGkAtoms::other;
4282   }
4283   bool isGenericOrigin = (aOrigin == nsGkAtoms::other);
4284   switch (aUnit) {
4285     case ScrollUnit::DEVICE_PIXELS: {
4286       nscoord appUnitsPerDevPixel =
4287           mOuter->PresContext()->AppUnitsPerDevPixel();
4288       deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
4289       if (isGenericOrigin) {
4290         aOrigin = nsGkAtoms::pixels;
4291       }
4292       negativeTolerance = positiveTolerance = 0.5f;
4293       break;
4294     }
4295     case ScrollUnit::LINES: {
4296       deltaMultiplier = GetLineScrollAmount();
4297       if (isGenericOrigin) {
4298         aOrigin = nsGkAtoms::lines;
4299       }
4300       negativeTolerance = positiveTolerance = 0.1f;
4301       break;
4302     }
4303     case ScrollUnit::PAGES: {
4304       deltaMultiplier = GetPageScrollAmount();
4305       if (isGenericOrigin) {
4306         aOrigin = nsGkAtoms::pages;
4307       }
4308       negativeTolerance = 0.05f;
4309       positiveTolerance = 0;
4310       break;
4311     }
4312     case ScrollUnit::WHOLE: {
4313       nsPoint pos = GetScrollPosition();
4314       AdjustForWholeDelta(aDelta.x, &pos.x);
4315       AdjustForWholeDelta(aDelta.y, &pos.y);
4316       if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
4317         GetSnapPointForDestination(aUnit, mDestination, pos);
4318       }
4319       ScrollTo(pos, aMode, nsGkAtoms::other);
4320       // 'this' might be destroyed here
4321       if (aOverflow) {
4322         *aOverflow = nsIntPoint(0, 0);
4323       }
4324       return;
4325     }
4326     default:
4327       NS_ERROR("Invalid scroll mode");
4328       return;
4329   }
4330 
4331   nsPoint newPos(NSCoordSaturatingAdd(mDestination.x,
4332                                       NSCoordSaturatingNonnegativeMultiply(
4333                                           aDelta.x, deltaMultiplier.width)),
4334                  NSCoordSaturatingAdd(mDestination.y,
4335                                       NSCoordSaturatingNonnegativeMultiply(
4336                                           aDelta.y, deltaMultiplier.height)));
4337 
4338   if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
4339     if (NeedsScrollSnap()) {
4340       nscoord appUnitsPerDevPixel =
4341           mOuter->PresContext()->AppUnitsPerDevPixel();
4342       deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
4343       negativeTolerance = 0.1f;
4344       positiveTolerance = 0;
4345       ScrollUnit snapUnit = aUnit;
4346       if (aOrigin == nsGkAtoms::mouseWheel) {
4347         // When using a clicky scroll wheel, snap point selection works the same
4348         // as keyboard up/down/left/right navigation, but with varying amounts
4349         // of scroll delta.
4350         snapUnit = ScrollUnit::LINES;
4351       }
4352       GetSnapPointForDestination(snapUnit, mDestination, newPos);
4353     }
4354   }
4355 
4356   // Calculate desired range values.
4357   nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY;
4358   CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance,
4359                        deltaMultiplier.width, &rangeLowerX, &rangeUpperX);
4360   CalcRangeForScrollBy(aDelta.y, newPos.y, negativeTolerance, positiveTolerance,
4361                        deltaMultiplier.height, &rangeLowerY, &rangeUpperY);
4362   nsRect range(rangeLowerX, rangeLowerY, rangeUpperX - rangeLowerX,
4363                rangeUpperY - rangeLowerY);
4364   AutoWeakFrame weakFrame(mOuter);
4365   ScrollToWithOrigin(newPos, aMode, aOrigin, &range);
4366   if (!weakFrame.IsAlive()) {
4367     return;
4368   }
4369 
4370   if (aOverflow) {
4371     nsPoint clampAmount = newPos - mDestination;
4372     float appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
4373     *aOverflow =
4374         nsIntPoint(NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel),
4375                    NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel));
4376   }
4377 
4378   if (aUnit == ScrollUnit::DEVICE_PIXELS &&
4379       !nsLayoutUtils::AsyncPanZoomEnabled(mOuter)) {
4380     // When APZ is disabled, we must track the velocity
4381     // on the main thread; otherwise, the APZC will manage this.
4382     mVelocityQueue.Sample(GetScrollPosition());
4383   }
4384 }
4385 
ScrollByCSSPixels(const CSSIntPoint & aDelta,ScrollMode aMode,nsAtom * aOrigin,nsIScrollbarMediator::ScrollSnapMode aSnap)4386 void ScrollFrameHelper::ScrollByCSSPixels(
4387     const CSSIntPoint& aDelta, ScrollMode aMode, nsAtom* aOrigin,
4388     nsIScrollbarMediator::ScrollSnapMode aSnap) {
4389   nsPoint current = GetScrollPosition();
4390   // `current` value above might be a value which was aligned to in
4391   // layer-pixels, so starting from such points will make the difference between
4392   // the given position in script (e.g. scrollTo) and the aligned position
4393   // larger, in the worst case the difference can be observed in CSS pixels.
4394   // To avoid it, we use the current position in CSS pixels as the start
4395   // position.  Hopefully it exactly matches the position where it was given by
4396   // the previous scrolling operation, but there may be some edge cases where
4397   // the current position in CSS pixels differs from the given position, the
4398   // cases should be fixed in bug 1556685.
4399   CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels();
4400   nsPoint pt = CSSPoint::ToAppUnits(currentCSSPixels + aDelta);
4401 
4402   if (aSnap == nsIScrollableFrame::DEFAULT) {
4403     aSnap = nsIScrollableFrame::ENABLE_SNAP;
4404   }
4405 
4406   if (aOrigin == nullptr) {
4407     aOrigin = nsGkAtoms::other;
4408   }
4409 
4410   nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
4411   nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2 * halfPixel - 1,
4412                2 * halfPixel - 1);
4413   // XXX I don't think the following blocks are needed anymore, now that
4414   // ScrollToImpl simply tries to scroll an integer number of layer
4415   // pixels from the current position
4416   if (aDelta.x == 0.0f) {
4417     pt.x = current.x;
4418     range.x = pt.x;
4419     range.width = 0;
4420   }
4421   if (aDelta.y == 0.0f) {
4422     pt.y = current.y;
4423     range.y = pt.y;
4424     range.height = 0;
4425   }
4426   ScrollToWithOrigin(pt, aMode, aOrigin, &range, aSnap);
4427   // 'this' might be destroyed here
4428 }
4429 
ScrollSnap(ScrollMode aMode)4430 void ScrollFrameHelper::ScrollSnap(ScrollMode aMode) {
4431   float flingSensitivity =
4432       StaticPrefs::layout_css_scroll_snap_prediction_sensitivity();
4433   int maxVelocity =
4434       StaticPrefs::layout_css_scroll_snap_prediction_max_velocity();
4435   maxVelocity = nsPresContext::CSSPixelsToAppUnits(maxVelocity);
4436   int maxOffset = maxVelocity * flingSensitivity;
4437   nsPoint velocity = mVelocityQueue.GetVelocity();
4438   // Multiply each component individually to avoid integer multiply
4439   nsPoint predictedOffset =
4440       nsPoint(velocity.x * flingSensitivity, velocity.y * flingSensitivity);
4441   predictedOffset.Clamp(maxOffset);
4442   nsPoint pos = GetScrollPosition();
4443   nsPoint destinationPos = pos + predictedOffset;
4444   ScrollSnap(destinationPos, aMode);
4445 }
4446 
ScrollSnap(const nsPoint & aDestination,ScrollMode aMode)4447 void ScrollFrameHelper::ScrollSnap(const nsPoint& aDestination,
4448                                    ScrollMode aMode) {
4449   nsRect scrollRange = GetLayoutScrollRange();
4450   nsPoint pos = GetScrollPosition();
4451   nsPoint snapDestination = scrollRange.ClampPoint(aDestination);
4452   if (GetSnapPointForDestination(ScrollUnit::DEVICE_PIXELS, pos,
4453                                  snapDestination)) {
4454     ScrollTo(snapDestination, aMode, nsGkAtoms::other);
4455   }
4456 }
4457 
GetLineScrollAmount() const4458 nsSize ScrollFrameHelper::GetLineScrollAmount() const {
4459   RefPtr<nsFontMetrics> fm =
4460       nsLayoutUtils::GetInflatedFontMetricsForFrame(mOuter);
4461   NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
4462   int32_t appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
4463   nscoord minScrollAmountInAppUnits =
4464       std::max(1, StaticPrefs::mousewheel_min_line_scroll_amount()) *
4465       appUnitsPerDevPixel;
4466   nscoord horizontalAmount = fm ? fm->AveCharWidth() : 0;
4467   nscoord verticalAmount = fm ? fm->MaxHeight() : 0;
4468   return nsSize(std::max(horizontalAmount, minScrollAmountInAppUnits),
4469                 std::max(verticalAmount, minScrollAmountInAppUnits));
4470 }
4471 
4472 /**
4473  * Compute the scrollport size excluding any fixed-pos and sticky-pos (that are
4474  * stuck) headers and footers. A header or footer is an box that spans that
4475  * entire width of the viewport and touches the top (or bottom, respectively) of
4476  * the viewport. We also want to consider fixed/sticky elements that stack or
4477  * overlap to effectively create a larger header or footer. Headers and footers
4478  * that cover more than a third of the the viewport are ignored since they
4479  * probably aren't true headers and footers and we don't want to restrict
4480  * scrolling too much in such cases. This is a bit conservative --- some
4481  * pages use elements as headers or footers that don't span the entire width
4482  * of the viewport --- but it should be a good start.
4483  *
4484  * If aViewportFrame is non-null then the scroll frame is the root scroll
4485  * frame and we should consider fixed-pos items.
4486  */
4487 struct TopAndBottom {
TopAndBottomTopAndBottom4488   TopAndBottom(nscoord aTop, nscoord aBottom) : top(aTop), bottom(aBottom) {}
4489 
4490   nscoord top, bottom;
4491 };
4492 struct TopComparator {
EqualsTopComparator4493   bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
4494     return A.top == B.top;
4495   }
LessThanTopComparator4496   bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
4497     return A.top < B.top;
4498   }
4499 };
4500 struct ReverseBottomComparator {
EqualsReverseBottomComparator4501   bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
4502     return A.bottom == B.bottom;
4503   }
LessThanReverseBottomComparator4504   bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
4505     return A.bottom > B.bottom;
4506   }
4507 };
4508 
AddToListIfHeaderFooter(nsIFrame * aFrame,nsIFrame * aScrollPortFrame,const nsRect & aScrollPort,nsTArray<TopAndBottom> & aList)4509 static void AddToListIfHeaderFooter(nsIFrame* aFrame,
4510                                     nsIFrame* aScrollPortFrame,
4511                                     const nsRect& aScrollPort,
4512                                     nsTArray<TopAndBottom>& aList) {
4513   nsRect r = aFrame->GetRectRelativeToSelf();
4514   r = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, r, aScrollPortFrame);
4515   r = r.Intersect(aScrollPort);
4516   if ((r.width >= aScrollPort.width / 2 ||
4517        r.width >= NSIntPixelsToAppUnits(800, AppUnitsPerCSSPixel())) &&
4518       r.height <= aScrollPort.height / 3) {
4519     aList.AppendElement(TopAndBottom(r.y, r.YMost()));
4520   }
4521 }
4522 
GetScrollPortSizeExcludingHeadersAndFooters(nsIFrame * aScrollFrame,nsIFrame * aViewportFrame,const nsRect & aScrollPort)4523 static nsSize GetScrollPortSizeExcludingHeadersAndFooters(
4524     nsIFrame* aScrollFrame, nsIFrame* aViewportFrame,
4525     const nsRect& aScrollPort) {
4526   AutoTArray<TopAndBottom, 10> list;
4527   if (aViewportFrame) {
4528     nsFrameList fixedFrames =
4529         aViewportFrame->GetChildList(nsIFrame::kFixedList);
4530     for (nsFrameList::Enumerator iterator(fixedFrames); !iterator.AtEnd();
4531          iterator.Next()) {
4532       AddToListIfHeaderFooter(iterator.get(), aViewportFrame, aScrollPort,
4533                               list);
4534     }
4535   }
4536 
4537   // Add sticky frames that are currently in "fixed" positions
4538   StickyScrollContainer* ssc =
4539       StickyScrollContainer::GetStickyScrollContainerForScrollFrame(
4540           aScrollFrame);
4541   if (ssc) {
4542     const nsTArray<nsIFrame*>& stickyFrames = ssc->GetFrames();
4543     for (nsIFrame* f : stickyFrames) {
4544       // If it's acting like fixed position.
4545       if (ssc->IsStuckInYDirection(f)) {
4546         AddToListIfHeaderFooter(f, aScrollFrame, aScrollPort, list);
4547       }
4548     }
4549   }
4550 
4551   list.Sort(TopComparator());
4552   nscoord headerBottom = 0;
4553   for (uint32_t i = 0; i < list.Length(); ++i) {
4554     if (list[i].top <= headerBottom) {
4555       headerBottom = std::max(headerBottom, list[i].bottom);
4556     }
4557   }
4558 
4559   list.Sort(ReverseBottomComparator());
4560   nscoord footerTop = aScrollPort.height;
4561   for (uint32_t i = 0; i < list.Length(); ++i) {
4562     if (list[i].bottom >= footerTop) {
4563       footerTop = std::min(footerTop, list[i].top);
4564     }
4565   }
4566 
4567   headerBottom = std::min(aScrollPort.height / 3, headerBottom);
4568   footerTop = std::max(aScrollPort.height - aScrollPort.height / 3, footerTop);
4569 
4570   return nsSize(aScrollPort.width, footerTop - headerBottom);
4571 }
4572 
GetPageScrollAmount() const4573 nsSize ScrollFrameHelper::GetPageScrollAmount() const {
4574   nsSize effectiveScrollPortSize;
4575 
4576   PresShell* presShell = mOuter->PresShell();
4577   if (mIsRoot && presShell->IsVisualViewportSizeSet()) {
4578     // We want to use the visual viewport size if one is set.
4579     // The headers/footers adjustment is too complicated to do if there is a
4580     // visual viewport that differs from the layout viewport, this is probably
4581     // okay.
4582     effectiveScrollPortSize = presShell->GetVisualViewportSize();
4583   } else {
4584     // Reduce effective scrollport height by the height of any
4585     // fixed-pos/sticky-pos headers or footers
4586     effectiveScrollPortSize = GetScrollPortSizeExcludingHeadersAndFooters(
4587         mOuter, mIsRoot ? mOuter->PresShell()->GetRootFrame() : nullptr,
4588         mScrollPort);
4589   }
4590 
4591   nsSize lineScrollAmount = GetLineScrollAmount();
4592 
4593   // The page increment is the size of the page, minus the smaller of
4594   // 10% of the size or 2 lines.
4595   return nsSize(effectiveScrollPortSize.width -
4596                     std::min(effectiveScrollPortSize.width / 10,
4597                              2 * lineScrollAmount.width),
4598                 effectiveScrollPortSize.height -
4599                     std::min(effectiveScrollPortSize.height / 10,
4600                              2 * lineScrollAmount.height));
4601 }
4602 
4603 /**
4604  * this code is resposible for restoring the scroll position back to some
4605  * saved position. if the user has not moved the scroll position manually
4606  * we keep scrolling down until we get to our original position. keep in
4607  * mind that content could incrementally be coming in. we only want to stop
4608  * when we reach our new position.
4609  */
ScrollToRestoredPosition()4610 void ScrollFrameHelper::ScrollToRestoredPosition() {
4611   if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
4612     return;
4613   }
4614   // make sure our scroll position did not change for where we last put
4615   // it. if it does then the user must have moved it, and we no longer
4616   // need to restore.
4617   //
4618   // In the RTL case, we check whether the scroll position changed using the
4619   // logical scroll position, but we scroll to the physical scroll position in
4620   // all cases
4621 
4622   // The layout offset we want to restore is the same as the visual offset
4623   // (for now, may change in bug 1499210), but clamped to the layout scroll
4624   // range (which can be a subset of the visual scroll range).
4625   // Note that we can't do the clamping when initializing mRestorePos in
4626   // RestoreState(), since the scrollable rect (which the clamping depends
4627   // on) can change over the course of the restoration process.
4628   nsPoint layoutRestorePos = GetLayoutScrollRange().ClampPoint(mRestorePos);
4629 
4630   // Continue restoring until both the layout and visual scroll positions
4631   // reach the destination. (Note that the two can only be different for
4632   // the root content document's root scroll frame, and when zoomed in).
4633   // This is necessary to avoid situations where the two offsets get stuck
4634   // at different values and nothing reconciles them (see bug 1519621 comment
4635   // 8).
4636   nsPoint logicalLayoutScrollPos = GetLogicalScrollPosition();
4637 
4638   // if we didn't move, we still need to restore
4639   if (GetLogicalVisualViewportOffset() == mLastPos ||
4640       logicalLayoutScrollPos == mLastPos) {
4641     // if our desired position is different to the scroll position, scroll.
4642     // remember that we could be incrementally loading so we may enter
4643     // and scroll many times.
4644     if (mRestorePos != mLastPos /* GetLogicalVisualViewportOffset() */ ||
4645         layoutRestorePos != logicalLayoutScrollPos) {
4646       LoadingState state = GetPageLoadingState();
4647       if (state == LoadingState::Stopped && !NS_SUBTREE_DIRTY(mOuter)) {
4648         return;
4649       }
4650       nsPoint visualScrollToPos = mRestorePos;
4651       nsPoint layoutScrollToPos = layoutRestorePos;
4652       if (!IsPhysicalLTR()) {
4653         // convert from logical to physical scroll position
4654         visualScrollToPos.x -=
4655             (GetVisualViewportSize().width - mScrolledFrame->GetRect().width);
4656         layoutScrollToPos.x -=
4657             (GetVisualViewportSize().width - mScrolledFrame->GetRect().width);
4658       }
4659       AutoWeakFrame weakFrame(mOuter);
4660       // It's very important to pass nsGkAtoms::restore here, so
4661       // ScrollToWithOrigin won't clear out mRestorePos.
4662       ScrollToWithOrigin(layoutScrollToPos, ScrollMode::Instant,
4663                          nsGkAtoms::restore, nullptr);
4664       if (!weakFrame.IsAlive()) {
4665         return;
4666       }
4667       if (mIsRoot && mOuter->PresContext()->IsRootContentDocument()) {
4668         mOuter->PresShell()->ScrollToVisual(
4669             visualScrollToPos, FrameMetrics::eRestore, ScrollMode::Instant);
4670       }
4671       if (state == LoadingState::Loading || NS_SUBTREE_DIRTY(mOuter)) {
4672         // If we're trying to do a history scroll restore, then we want to
4673         // keep trying this until we succeed, because the page can be loading
4674         // incrementally. So re-get the scroll position for the next iteration,
4675         // it might not be exactly equal to mRestorePos due to rounding and
4676         // clamping.
4677         mLastPos = GetLogicalVisualViewportOffset();
4678         return;
4679       }
4680     }
4681     // If we get here, either we reached the desired position (mLastPos ==
4682     // mRestorePos) or we're not trying to do a history scroll restore, so
4683     // we can stop after the scroll attempt above.
4684     mRestorePos.y = -1;
4685     mLastPos.x = -1;
4686     mLastPos.y = -1;
4687   } else {
4688     // user moved the position, so we won't need to restore
4689     mLastPos.x = -1;
4690     mLastPos.y = -1;
4691   }
4692 }
4693 
GetPageLoadingState()4694 auto ScrollFrameHelper::GetPageLoadingState() -> LoadingState {
4695   bool loadCompleted = false, stopped = false;
4696   nsCOMPtr<nsIDocShell> ds =
4697       mOuter->GetContent()->GetComposedDoc()->GetDocShell();
4698   if (ds) {
4699     nsCOMPtr<nsIContentViewer> cv;
4700     ds->GetContentViewer(getter_AddRefs(cv));
4701     loadCompleted = cv->GetLoadCompleted();
4702     stopped = cv->GetIsStopped();
4703   }
4704   return loadCompleted
4705              ? (stopped ? LoadingState::Stopped : LoadingState::Loaded)
4706              : LoadingState::Loading;
4707 }
4708 
FireScrollPortEvent()4709 nsresult ScrollFrameHelper::FireScrollPortEvent() {
4710   mAsyncScrollPortEvent.Forget();
4711 
4712   // Keep this in sync with PostOverflowEvent().
4713   nsSize scrollportSize = mScrollPort.Size();
4714   nsSize childSize = GetScrolledRect().Size();
4715 
4716   // TODO(emilio): why do we need the whole WillPaintObserver infrastructure and
4717   // can't use AddScriptRunner & co? I guess it made sense when we used
4718   // WillPaintObserver for scroll events too, or when this used to flush.
4719   //
4720   // Should we remove this?
4721 
4722   bool newVerticalOverflow = childSize.height > scrollportSize.height;
4723   bool vertChanged = mVerticalOverflow != newVerticalOverflow;
4724 
4725   bool newHorizontalOverflow = childSize.width > scrollportSize.width;
4726   bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
4727 
4728   if (!vertChanged && !horizChanged) {
4729     return NS_OK;
4730   }
4731 
4732   // If both either overflowed or underflowed then we dispatch only one
4733   // DOM event.
4734   bool both = vertChanged && horizChanged &&
4735               newVerticalOverflow == newHorizontalOverflow;
4736   InternalScrollPortEvent::OrientType orient;
4737   if (both) {
4738     orient = InternalScrollPortEvent::eBoth;
4739     mHorizontalOverflow = newHorizontalOverflow;
4740     mVerticalOverflow = newVerticalOverflow;
4741   } else if (vertChanged) {
4742     orient = InternalScrollPortEvent::eVertical;
4743     mVerticalOverflow = newVerticalOverflow;
4744     if (horizChanged) {
4745       // We need to dispatch a separate horizontal DOM event. Do that the next
4746       // time around since dispatching the vertical DOM event might destroy
4747       // the frame.
4748       PostOverflowEvent();
4749     }
4750   } else {
4751     orient = InternalScrollPortEvent::eHorizontal;
4752     mHorizontalOverflow = newHorizontalOverflow;
4753   }
4754 
4755   InternalScrollPortEvent event(
4756       true,
4757       (orient == InternalScrollPortEvent::eHorizontal ? mHorizontalOverflow
4758                                                       : mVerticalOverflow)
4759           ? eScrollPortOverflow
4760           : eScrollPortUnderflow,
4761       nullptr);
4762   event.mOrient = orient;
4763   return EventDispatcher::Dispatch(mOuter->GetContent(), mOuter->PresContext(),
4764                                    &event);
4765 }
4766 
PostScrollEndEvent()4767 void ScrollFrameHelper::PostScrollEndEvent() {
4768   if (mScrollEndEvent) {
4769     return;
4770   }
4771 
4772   // The ScrollEndEvent constructor registers itself with the refresh driver.
4773   mScrollEndEvent = new ScrollEndEvent(this);
4774 }
4775 
FireScrollEndEvent()4776 void ScrollFrameHelper::FireScrollEndEvent() {
4777   MOZ_ASSERT(mOuter->GetContent());
4778   MOZ_ASSERT(mScrollEndEvent);
4779   mScrollEndEvent->Revoke();
4780   mScrollEndEvent = nullptr;
4781 
4782   nsContentUtils::DispatchEventOnlyToChrome(
4783       mOuter->GetContent()->OwnerDoc(), mOuter->GetContent(),
4784       NS_LITERAL_STRING("scrollend"), CanBubble::eYes, Cancelable::eNo);
4785 }
4786 
ReloadChildFrames()4787 void ScrollFrameHelper::ReloadChildFrames() {
4788   mScrolledFrame = nullptr;
4789   mHScrollbarBox = nullptr;
4790   mVScrollbarBox = nullptr;
4791   mScrollCornerBox = nullptr;
4792   mResizerBox = nullptr;
4793 
4794   for (nsIFrame* frame : mOuter->PrincipalChildList()) {
4795     nsIContent* content = frame->GetContent();
4796     if (content == mOuter->GetContent()) {
4797       NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
4798       mScrolledFrame = frame;
4799     } else {
4800       nsAutoString value;
4801       if (content->IsElement()) {
4802         content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::orient,
4803                                       value);
4804       }
4805       if (!value.IsEmpty()) {
4806         // probably a scrollbar then
4807         if (value.LowerCaseEqualsLiteral("horizontal")) {
4808           NS_ASSERTION(!mHScrollbarBox,
4809                        "Found multiple horizontal scrollbars?");
4810           mHScrollbarBox = frame;
4811         } else {
4812           NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
4813           mVScrollbarBox = frame;
4814         }
4815       } else if (content->IsXULElement(nsGkAtoms::resizer)) {
4816         NS_ASSERTION(!mResizerBox, "Found multiple resizers");
4817         mResizerBox = frame;
4818       } else if (content->IsXULElement(nsGkAtoms::scrollcorner)) {
4819         // probably a scrollcorner
4820         NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
4821         mScrollCornerBox = frame;
4822       }
4823     }
4824   }
4825 }
4826 
MakeScrollbar(NodeInfo * aNodeInfo,bool aVertical,AnonymousContentKey & aKey)4827 already_AddRefed<Element> ScrollFrameHelper::MakeScrollbar(
4828     NodeInfo* aNodeInfo, bool aVertical, AnonymousContentKey& aKey) {
4829   MOZ_ASSERT(aNodeInfo);
4830   MOZ_ASSERT(
4831       aNodeInfo->Equals(nsGkAtoms::scrollbar, nullptr, kNameSpaceID_XUL));
4832 
4833   static constexpr nsLiteralString kOrientValues[2] = {
4834       NS_LITERAL_STRING("horizontal"),
4835       NS_LITERAL_STRING("vertical"),
4836   };
4837 
4838   aKey = AnonymousContentKey::Type_Scrollbar;
4839   if (aVertical) {
4840     aKey |= AnonymousContentKey::Flag_Vertical;
4841   }
4842 
4843   RefPtr<Element> e;
4844   NS_TrustedNewXULElement(getter_AddRefs(e), do_AddRef(aNodeInfo));
4845 
4846 #ifdef DEBUG
4847   // Scrollbars can get restyled by theme changes.  Whether such a restyle
4848   // will actually reconstruct them correctly if it involves a frame
4849   // reconstruct... I don't know.  :(
4850   e->SetProperty(nsGkAtoms::restylableAnonymousNode,
4851                  reinterpret_cast<void*>(true));
4852 #endif  // DEBUG
4853 
4854   e->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, kOrientValues[aVertical],
4855              false);
4856   e->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
4857              NS_LITERAL_STRING("always"), false);
4858 
4859   if (mIsRoot) {
4860     e->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
4861                    reinterpret_cast<void*>(true));
4862     e->SetAttr(kNameSpaceID_None, nsGkAtoms::root_, NS_LITERAL_STRING("true"),
4863                false);
4864 
4865     // Don't bother making style caching take [root="true"] styles into account.
4866     aKey = AnonymousContentKey::None;
4867   }
4868 
4869   return e.forget();
4870 }
4871 
IsForTextControlWithNoScrollbars() const4872 bool ScrollFrameHelper::IsForTextControlWithNoScrollbars() const {
4873   // FIXME(emilio): we should probably make the scroller inside <input> an
4874   // internal pseudo-element, and then this would be simpler.
4875   //
4876   // Also, this could just use scrollbar-width these days.
4877   auto* content = mOuter->GetContent();
4878   if (!content) {
4879     return false;
4880   }
4881   auto* input = content->GetClosestNativeAnonymousSubtreeRootParent();
4882   return input && input->IsHTMLElement(nsGkAtoms::input);
4883 }
4884 
CreateAnonymousContent(nsTArray<nsIAnonymousContentCreator::ContentInfo> & aElements)4885 nsresult ScrollFrameHelper::CreateAnonymousContent(
4886     nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements) {
4887   typedef nsIAnonymousContentCreator::ContentInfo ContentInfo;
4888 
4889   nsPresContext* presContext = mOuter->PresContext();
4890 
4891   // Don't create scrollbars if we're an SVG document being used as an image,
4892   // or if we're printing/print previewing.
4893   // (In the printing case, we allow scrollbars if this is the child of the
4894   // viewport & paginated scrolling is enabled, because then we must be the
4895   // scroll frame for the print preview window, & that does need scrollbars.)
4896   if (presContext->Document()->IsBeingUsedAsImage() ||
4897       (!presContext->IsDynamic() &&
4898        !(mIsRoot && presContext->HasPaginatedScrolling()))) {
4899     mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
4900     return NS_OK;
4901   }
4902 
4903   // Check if the frame is resizable. Note:
4904   // "The effect of the resize property on generated content is undefined.
4905   //  Implementations should not apply the resize property to generated
4906   //  content." [1]
4907   // For info on what is generated content, see [2].
4908   // [1]: https://drafts.csswg.org/css-ui/#resize
4909   // [2]: https://www.w3.org/TR/CSS2/generate.html#content
4910   auto resizeStyle = mOuter->StyleDisplay()->mResize;
4911   bool isResizable = resizeStyle != StyleResize::None &&
4912                      !mOuter->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT);
4913 
4914   nsIScrollableFrame* scrollable = do_QueryFrame(mOuter);
4915 
4916   // If we're the scrollframe for the root, then we want to construct
4917   // our scrollbar frames no matter what.  That way later dynamic
4918   // changes to propagated overflow styles will show or hide
4919   // scrollbars on the viewport without requiring frame reconstruction
4920   // of the viewport (good!).
4921   bool canHaveHorizontal;
4922   bool canHaveVertical;
4923   if (!mIsRoot) {
4924     if (mOuter->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::None) {
4925       // If scrollbar-width is none, don't generate scrollbars.
4926       canHaveHorizontal = false;
4927       canHaveVertical = false;
4928     } else {
4929       ScrollStyles styles = scrollable->GetScrollStyles();
4930       canHaveHorizontal = styles.mHorizontal != StyleOverflow::Hidden;
4931       canHaveVertical = styles.mVertical != StyleOverflow::Hidden;
4932     }
4933     if (!canHaveHorizontal && !canHaveVertical && !isResizable) {
4934       // Nothing to do.
4935       return NS_OK;
4936     }
4937   } else {
4938     canHaveHorizontal = true;
4939     canHaveVertical = true;
4940   }
4941 
4942   if (IsForTextControlWithNoScrollbars()) {
4943     mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
4944     return NS_OK;
4945   }
4946 
4947   nsNodeInfoManager* nodeInfoManager =
4948       presContext->Document()->NodeInfoManager();
4949 
4950   {
4951     RefPtr<NodeInfo> nodeInfo = nodeInfoManager->GetNodeInfo(
4952         nsGkAtoms::scrollbar, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
4953     NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
4954 
4955     if (canHaveHorizontal) {
4956       AnonymousContentKey key;
4957       mHScrollbarContent = MakeScrollbar(nodeInfo, /* aVertical */ false, key);
4958       aElements.AppendElement(ContentInfo(mHScrollbarContent, key));
4959     }
4960 
4961     if (canHaveVertical) {
4962       AnonymousContentKey key;
4963       mVScrollbarContent = MakeScrollbar(nodeInfo, /* aVertical */ true, key);
4964       aElements.AppendElement(ContentInfo(mVScrollbarContent, key));
4965     }
4966   }
4967 
4968   if (isResizable) {
4969     AnonymousContentKey key = AnonymousContentKey::Type_Resizer;
4970 
4971     RefPtr<NodeInfo> nodeInfo;
4972     nodeInfo = nodeInfoManager->GetNodeInfo(
4973         nsGkAtoms::resizer, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
4974     NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
4975 
4976     NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget());
4977 
4978     nsAutoString dir;
4979     switch (resizeStyle) {
4980       case StyleResize::Horizontal:
4981         if (IsScrollbarOnRight()) {
4982           dir.AssignLiteral("right");
4983           key |= AnonymousContentKey::Flag_Resizer_Right;
4984         } else {
4985           dir.AssignLiteral("left");
4986         }
4987         break;
4988       case StyleResize::Vertical:
4989         dir.AssignLiteral("bottom");
4990         if (!IsScrollbarOnRight()) {
4991           mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::flip,
4992                                    EmptyString(), false);
4993           key |= AnonymousContentKey::Flag_Resizer_Bottom_Flip;
4994         } else {
4995           key |= AnonymousContentKey::Flag_Resizer_Bottom;
4996         }
4997         break;
4998       case StyleResize::Both:
4999         if (IsScrollbarOnRight()) {
5000           dir.AssignLiteral("bottomright");
5001           key |= AnonymousContentKey::Flag_Resizer_BottomRight;
5002         } else {
5003           dir.AssignLiteral("bottomleft");
5004           key |= AnonymousContentKey::Flag_Resizer_BottomLeft;
5005         }
5006         break;
5007       default:
5008         NS_WARNING("only resizable types should have resizers");
5009     }
5010     mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false);
5011 
5012     if (mIsRoot) {
5013       mResizerContent->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
5014                                    reinterpret_cast<void*>(true));
5015 
5016       mCollapsedResizer = true;
5017     } else {
5018       mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element,
5019                                NS_LITERAL_STRING("_parent"), false);
5020     }
5021 
5022     mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
5023                              NS_LITERAL_STRING("always"), false);
5024 
5025     aElements.AppendElement(ContentInfo(mResizerContent, key));
5026   }
5027 
5028   if (canHaveHorizontal && canHaveVertical) {
5029     AnonymousContentKey key = AnonymousContentKey::Type_ScrollCorner;
5030 
5031     RefPtr<NodeInfo> nodeInfo =
5032         nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nullptr,
5033                                      kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
5034     NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent),
5035                             nodeInfo.forget());
5036     if (mIsRoot) {
5037       mScrollCornerContent->SetProperty(
5038           nsGkAtoms::docLevelNativeAnonymousContent,
5039           reinterpret_cast<void*>(true));
5040     }
5041     aElements.AppendElement(ContentInfo(mScrollCornerContent, key));
5042   }
5043 
5044   // Don't cache styles if we are a child of a <select> element, since we have
5045   // some UA style sheet rules that depend on the <select>'s attributes.
5046   if (mOuter->GetContent()->IsHTMLElement(nsGkAtoms::select)) {
5047     for (auto& info : aElements) {
5048       info.mKey = AnonymousContentKey::None;
5049     }
5050   }
5051 
5052   return NS_OK;
5053 }
5054 
AppendAnonymousContentTo(nsTArray<nsIContent * > & aElements,uint32_t aFilter)5055 void ScrollFrameHelper::AppendAnonymousContentTo(
5056     nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
5057   if (mHScrollbarContent) {
5058     aElements.AppendElement(mHScrollbarContent);
5059   }
5060 
5061   if (mVScrollbarContent) {
5062     aElements.AppendElement(mVScrollbarContent);
5063   }
5064 
5065   if (mScrollCornerContent) {
5066     aElements.AppendElement(mScrollCornerContent);
5067   }
5068 
5069   if (mResizerContent) {
5070     aElements.AppendElement(mResizerContent);
5071   }
5072 }
5073 
Destroy(PostDestroyData & aPostDestroyData)5074 void ScrollFrameHelper::Destroy(PostDestroyData& aPostDestroyData) {
5075   mAnchor.Destroy();
5076 
5077   if (mScrollbarActivity) {
5078     mScrollbarActivity->Destroy();
5079     mScrollbarActivity = nullptr;
5080   }
5081 
5082   // Unbind the content created in CreateAnonymousContent later...
5083   aPostDestroyData.AddAnonymousContent(mHScrollbarContent.forget());
5084   aPostDestroyData.AddAnonymousContent(mVScrollbarContent.forget());
5085   aPostDestroyData.AddAnonymousContent(mScrollCornerContent.forget());
5086   aPostDestroyData.AddAnonymousContent(mResizerContent.forget());
5087 
5088   if (mPostedReflowCallback) {
5089     mOuter->PresShell()->CancelReflowCallback(this);
5090     mPostedReflowCallback = false;
5091   }
5092 
5093   if (mDisplayPortExpiryTimer) {
5094     mDisplayPortExpiryTimer->Cancel();
5095     mDisplayPortExpiryTimer = nullptr;
5096   }
5097   if (mActivityExpirationState.IsTracked()) {
5098     gScrollFrameActivityTracker->RemoveObject(this);
5099   }
5100   if (gScrollFrameActivityTracker && gScrollFrameActivityTracker->IsEmpty()) {
5101     delete gScrollFrameActivityTracker;
5102     gScrollFrameActivityTracker = nullptr;
5103   }
5104 
5105   if (mScrollActivityTimer) {
5106     mScrollActivityTimer->Cancel();
5107     mScrollActivityTimer = nullptr;
5108   }
5109   RemoveObservers();
5110 }
5111 
RemoveObservers()5112 void ScrollFrameHelper::RemoveObservers() {
5113   if (mAsyncScroll) {
5114     mAsyncScroll->RemoveObserver();
5115     mAsyncScroll = nullptr;
5116   }
5117   if (mAsyncSmoothMSDScroll) {
5118     mAsyncSmoothMSDScroll->RemoveObserver();
5119     mAsyncSmoothMSDScroll = nullptr;
5120   }
5121 }
5122 
5123 /**
5124  * Called when we want to update the scrollbar position, either because
5125  * scrolling happened or the user moved the scrollbar position and we need to
5126  * undo that (e.g., when the user clicks to scroll and we're using smooth
5127  * scrolling, so we need to put the thumb back to its initial position for the
5128  * start of the smooth sequence).
5129  */
UpdateScrollbarPosition()5130 void ScrollFrameHelper::UpdateScrollbarPosition() {
5131   AutoWeakFrame weakFrame(mOuter);
5132   mFrameIsUpdatingScrollbar = true;
5133 
5134   nsPoint pt = GetScrollPosition();
5135   if (mVScrollbarBox) {
5136     SetCoordAttribute(mVScrollbarBox->GetContent()->AsElement(),
5137                       nsGkAtoms::curpos, pt.y - GetScrolledRect().y);
5138     if (!weakFrame.IsAlive()) {
5139       return;
5140     }
5141   }
5142   if (mHScrollbarBox) {
5143     SetCoordAttribute(mHScrollbarBox->GetContent()->AsElement(),
5144                       nsGkAtoms::curpos, pt.x - GetScrolledRect().x);
5145     if (!weakFrame.IsAlive()) {
5146       return;
5147     }
5148   }
5149 
5150   mFrameIsUpdatingScrollbar = false;
5151 }
5152 
CurPosAttributeChanged(nsIContent * aContent,bool aDoScroll)5153 void ScrollFrameHelper::CurPosAttributeChanged(nsIContent* aContent,
5154                                                bool aDoScroll) {
5155   NS_ASSERTION(aContent, "aContent must not be null");
5156   NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
5157                    (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
5158                "unexpected child");
5159   MOZ_ASSERT(aContent->IsElement());
5160 
5161   // Attribute changes on the scrollbars happen in one of three ways:
5162   // 1) The scrollbar changed the attribute in response to some user event
5163   // 2) We changed the attribute in response to a ScrollPositionDidChange
5164   // callback from the scrolling view
5165   // 3) We changed the attribute to adjust the scrollbars for the start
5166   // of a smooth scroll operation
5167   //
5168   // In cases 2 and 3 we do not need to scroll because we're just
5169   // updating our scrollbar.
5170   if (mFrameIsUpdatingScrollbar) return;
5171 
5172   nsRect scrolledRect = GetScrolledRect();
5173 
5174   nsPoint current = GetScrollPosition() - scrolledRect.TopLeft();
5175   nsPoint dest;
5176   nsRect allowedRange;
5177   dest.x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos, current.x,
5178                              &allowedRange.x, &allowedRange.width);
5179   dest.y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos, current.y,
5180                              &allowedRange.y, &allowedRange.height);
5181   current += scrolledRect.TopLeft();
5182   dest += scrolledRect.TopLeft();
5183   allowedRange += scrolledRect.TopLeft();
5184 
5185   // Don't try to scroll if we're already at an acceptable place.
5186   // Don't call Contains here since Contains returns false when the point is
5187   // on the bottom or right edge of the rectangle.
5188   if (allowedRange.ClampPoint(current) == current) {
5189     return;
5190   }
5191 
5192   if (mScrollbarActivity) {
5193     RefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
5194     scrollbarActivity->ActivityOccurred();
5195   }
5196 
5197   const bool isSmooth =
5198       aContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth);
5199   if (isSmooth) {
5200     // Make sure an attribute-setting callback occurs even if the view
5201     // didn't actually move yet.  We need to make sure other listeners
5202     // see that the scroll position is not (yet) what they thought it
5203     // was.
5204     AutoWeakFrame weakFrame(mOuter);
5205     UpdateScrollbarPosition();
5206     if (!weakFrame.IsAlive()) {
5207       return;
5208     }
5209   }
5210 
5211   if (aDoScroll) {
5212     ScrollToWithOrigin(dest,
5213                        isSmooth ? ScrollMode::Smooth : ScrollMode::Instant,
5214                        nsGkAtoms::scrollbars, &allowedRange);
5215   }
5216   // 'this' might be destroyed here
5217 }
5218 
5219 /* ============= Scroll events ========== */
5220 
ScrollEvent(ScrollFrameHelper * aHelper,bool aDelayed)5221 ScrollFrameHelper::ScrollEvent::ScrollEvent(ScrollFrameHelper* aHelper,
5222                                             bool aDelayed)
5223     : Runnable("ScrollFrameHelper::ScrollEvent"), mHelper(aHelper) {
5224   mHelper->mOuter->PresContext()->RefreshDriver()->PostScrollEvent(this,
5225                                                                    aDelayed);
5226 }
5227 
5228 NS_IMETHODIMP
Run()5229 ScrollFrameHelper::ScrollEvent::Run() {
5230   if (mHelper) {
5231     mHelper->FireScrollEvent();
5232   }
5233   return NS_OK;
5234 }
5235 
ScrollEndEvent(ScrollFrameHelper * aHelper)5236 ScrollFrameHelper::ScrollEndEvent::ScrollEndEvent(ScrollFrameHelper* aHelper)
5237     : Runnable("ScrollFrameHelper::ScrollEndEvent"), mHelper(aHelper) {
5238   mHelper->mOuter->PresContext()->RefreshDriver()->PostScrollEvent(this);
5239 }
5240 
5241 NS_IMETHODIMP
Run()5242 ScrollFrameHelper::ScrollEndEvent::Run() {
5243   if (mHelper) {
5244     mHelper->FireScrollEndEvent();
5245   }
5246   return NS_OK;
5247 }
5248 
FireScrollEvent()5249 void ScrollFrameHelper::FireScrollEvent() {
5250   nsIContent* content = mOuter->GetContent();
5251   nsPresContext* prescontext = mOuter->PresContext();
5252 #ifdef MOZ_GECKO_PROFILER
5253   nsCOMPtr<nsIDocShell> docShell = prescontext->GetDocShell();
5254   AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "FireScrollEvent", GRAPHICS,
5255                                         docShell);
5256 #endif
5257 
5258   MOZ_ASSERT(mScrollEvent);
5259   mScrollEvent->Revoke();
5260   mScrollEvent = nullptr;
5261 
5262   // If event handling is suppressed, keep posting the scroll event to the
5263   // refresh driver until it is unsuppressed. The event is marked as delayed so
5264   // that the refresh driver does not continue ticking.
5265   if (content->GetComposedDoc() &&
5266       content->GetComposedDoc()->EventHandlingSuppressed()) {
5267     content->GetComposedDoc()->SetHasDelayedRefreshEvent();
5268     PostScrollEvent(/* aDelayed = */ true);
5269     return;
5270   }
5271 
5272   bool oldProcessing = mProcessingScrollEvent;
5273   AutoWeakFrame weakFrame(mOuter);
5274   auto RestoreProcessingScrollEvent = mozilla::MakeScopeExit([&] {
5275     if (weakFrame.IsAlive()) {  // Otherwise `this` will be dead too.
5276       mProcessingScrollEvent = oldProcessing;
5277     }
5278   });
5279 
5280   mProcessingScrollEvent = true;
5281 
5282   ActiveLayerTracker::SetCurrentScrollHandlerFrame(mOuter);
5283   WidgetGUIEvent event(true, eScroll, nullptr);
5284   nsEventStatus status = nsEventStatus_eIgnore;
5285   // Fire viewport scroll events at the document (where they
5286   // will bubble to the window)
5287   mozilla::layers::ScrollLinkedEffectDetector detector(
5288       content->GetComposedDoc());
5289   if (mIsRoot) {
5290     if (Document* doc = content->GetUncomposedDoc()) {
5291       EventDispatcher::Dispatch(ToSupports(doc), prescontext, &event, nullptr,
5292                                 &status);
5293     }
5294   } else {
5295     // scroll events fired at elements don't bubble (although scroll events
5296     // fired at documents do, to the window)
5297     event.mFlags.mBubbles = false;
5298     EventDispatcher::Dispatch(content, prescontext, &event, nullptr, &status);
5299   }
5300   ActiveLayerTracker::SetCurrentScrollHandlerFrame(nullptr);
5301 }
5302 
PostScrollEvent(bool aDelayed)5303 void ScrollFrameHelper::PostScrollEvent(bool aDelayed) {
5304   if (mScrollEvent) {
5305     return;
5306   }
5307 
5308   // The ScrollEvent constructor registers itself with the refresh driver.
5309   mScrollEvent = new ScrollEvent(this, aDelayed);
5310 }
5311 
5312 NS_IMETHODIMP
Run()5313 ScrollFrameHelper::AsyncScrollPortEvent::Run() {
5314   return mHelper ? mHelper->FireScrollPortEvent() : NS_OK;
5315 }
5316 
AddHorizontalScrollbar(nsBoxLayoutState & aState,bool aOnBottom)5317 bool nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState,
5318                                               bool aOnBottom) {
5319   if (!mHelper.mHScrollbarBox) {
5320     return true;
5321   }
5322 
5323   return AddRemoveScrollbar(aState, aOnBottom, true, true);
5324 }
5325 
AddVerticalScrollbar(nsBoxLayoutState & aState,bool aOnRight)5326 bool nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState,
5327                                             bool aOnRight) {
5328   if (!mHelper.mVScrollbarBox) {
5329     return true;
5330   }
5331 
5332   return AddRemoveScrollbar(aState, aOnRight, false, true);
5333 }
5334 
RemoveHorizontalScrollbar(nsBoxLayoutState & aState,bool aOnBottom)5335 void nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState,
5336                                                  bool aOnBottom) {
5337   // removing a scrollbar should always fit
5338   DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnBottom, true, false);
5339   NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??");
5340 }
5341 
RemoveVerticalScrollbar(nsBoxLayoutState & aState,bool aOnRight)5342 void nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState,
5343                                                bool aOnRight) {
5344   // removing a scrollbar should always fit
5345   DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnRight, false, false);
5346   NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??");
5347 }
5348 
AddRemoveScrollbar(nsBoxLayoutState & aState,bool aOnRightOrBottom,bool aHorizontal,bool aAdd)5349 bool nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState,
5350                                           bool aOnRightOrBottom,
5351                                           bool aHorizontal, bool aAdd) {
5352   if (aHorizontal) {
5353     if (mHelper.mNeverHasHorizontalScrollbar || !mHelper.mHScrollbarBox) {
5354       return false;
5355     }
5356 
5357     nsSize hSize = mHelper.mHScrollbarBox->GetXULPrefSize(aState);
5358     nsIFrame::AddXULMargin(mHelper.mHScrollbarBox, hSize);
5359 
5360     ScrollFrameHelper::SetScrollbarVisibility(mHelper.mHScrollbarBox, aAdd);
5361 
5362     // We can't directly pass mHasHorizontalScrollbar as the bool outparam for
5363     // AddRemoveScrollbar() because it's a bool:1 bitfield. Hence this var:
5364     bool hasHorizontalScrollbar;
5365     bool fit = AddRemoveScrollbar(hasHorizontalScrollbar, mHelper.mScrollPort.y,
5366                                   mHelper.mScrollPort.height, hSize.height,
5367                                   aOnRightOrBottom, aAdd);
5368     mHelper.mHasHorizontalScrollbar = hasHorizontalScrollbar;
5369     if (!fit) {
5370       ScrollFrameHelper::SetScrollbarVisibility(mHelper.mHScrollbarBox, !aAdd);
5371     }
5372     return fit;
5373   } else {
5374     if (mHelper.mNeverHasVerticalScrollbar || !mHelper.mVScrollbarBox) {
5375       return false;
5376     }
5377 
5378     nsSize vSize = mHelper.mVScrollbarBox->GetXULPrefSize(aState);
5379     nsIFrame::AddXULMargin(mHelper.mVScrollbarBox, vSize);
5380 
5381     ScrollFrameHelper::SetScrollbarVisibility(mHelper.mVScrollbarBox, aAdd);
5382 
5383     // We can't directly pass mHasVerticalScrollbar as the bool outparam for
5384     // AddRemoveScrollbar() because it's a bool:1 bitfield. Hence this var:
5385     bool hasVerticalScrollbar;
5386     bool fit = AddRemoveScrollbar(hasVerticalScrollbar, mHelper.mScrollPort.x,
5387                                   mHelper.mScrollPort.width, vSize.width,
5388                                   aOnRightOrBottom, aAdd);
5389     mHelper.mHasVerticalScrollbar = hasVerticalScrollbar;
5390     if (!fit) {
5391       ScrollFrameHelper::SetScrollbarVisibility(mHelper.mVScrollbarBox, !aAdd);
5392     }
5393     return fit;
5394   }
5395 }
5396 
AddRemoveScrollbar(bool & aHasScrollbar,nscoord & aXY,nscoord & aSize,nscoord aSbSize,bool aOnRightOrBottom,bool aAdd)5397 bool nsXULScrollFrame::AddRemoveScrollbar(bool& aHasScrollbar, nscoord& aXY,
5398                                           nscoord& aSize, nscoord aSbSize,
5399                                           bool aOnRightOrBottom, bool aAdd) {
5400   nscoord size = aSize;
5401   nscoord xy = aXY;
5402 
5403   if (size != NS_UNCONSTRAINEDSIZE) {
5404     if (aAdd) {
5405       size -= aSbSize;
5406       if (!aOnRightOrBottom && size >= 0) xy += aSbSize;
5407     } else {
5408       size += aSbSize;
5409       if (!aOnRightOrBottom) xy -= aSbSize;
5410     }
5411   }
5412 
5413   // not enough room? Yes? Return true.
5414   if (size >= 0) {
5415     aHasScrollbar = aAdd;
5416     aSize = size;
5417     aXY = xy;
5418     return true;
5419   }
5420 
5421   aHasScrollbar = false;
5422   return false;
5423 }
5424 
LayoutScrollArea(nsBoxLayoutState & aState,const nsPoint & aScrollPosition)5425 void nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState,
5426                                         const nsPoint& aScrollPosition) {
5427   ReflowChildFlags oldflags = aState.LayoutFlags();
5428   nsRect childRect = nsRect(mHelper.mScrollPort.TopLeft() - aScrollPosition,
5429                             mHelper.mScrollPort.Size());
5430   ReflowChildFlags flags = ReflowChildFlags::NoMoveView;
5431 
5432   nsSize minSize = mHelper.mScrolledFrame->GetXULMinSize(aState);
5433 
5434   if (minSize.height > childRect.height) childRect.height = minSize.height;
5435 
5436   if (minSize.width > childRect.width) childRect.width = minSize.width;
5437 
5438   // TODO: Handle transformed children that inherit perspective
5439   // from this frame. See AdjustForPerspective for how we handle
5440   // this for HTML scroll frames.
5441 
5442   aState.SetLayoutFlags(flags);
5443   ClampAndSetBounds(aState, childRect, aScrollPosition);
5444   mHelper.mScrolledFrame->XULLayout(aState);
5445 
5446   childRect = mHelper.mScrolledFrame->GetRect();
5447 
5448   if (childRect.width < mHelper.mScrollPort.width ||
5449       childRect.height < mHelper.mScrollPort.height) {
5450     childRect.width = std::max(childRect.width, mHelper.mScrollPort.width);
5451     childRect.height = std::max(childRect.height, mHelper.mScrollPort.height);
5452 
5453     // remove overflow areas when we update the bounds,
5454     // because we've already accounted for it
5455     // REVIEW: Have we accounted for both?
5456     ClampAndSetBounds(aState, childRect, aScrollPosition, true);
5457   }
5458 
5459   aState.SetLayoutFlags(oldflags);
5460 }
5461 
PostOverflowEvent()5462 void ScrollFrameHelper::PostOverflowEvent() {
5463   if (mAsyncScrollPortEvent.IsPending()) {
5464     return;
5465   }
5466 
5467   // Keep this in sync with FireScrollPortEvent().
5468   nsSize scrollportSize = mScrollPort.Size();
5469   nsSize childSize = GetScrolledRect().Size();
5470 
5471   bool newVerticalOverflow = childSize.height > scrollportSize.height;
5472   bool vertChanged = mVerticalOverflow != newVerticalOverflow;
5473 
5474   bool newHorizontalOverflow = childSize.width > scrollportSize.width;
5475   bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
5476 
5477   if (!vertChanged && !horizChanged) {
5478     return;
5479   }
5480 
5481   nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext();
5482   if (!rpc) {
5483     return;
5484   }
5485 
5486   mAsyncScrollPortEvent = new AsyncScrollPortEvent(this);
5487   rpc->AddWillPaintObserver(mAsyncScrollPortEvent.get());
5488 }
5489 
GetFrameForStyle() const5490 nsIFrame* ScrollFrameHelper::GetFrameForStyle() const {
5491   nsIFrame* styleFrame = nullptr;
5492   if (mIsRoot) {
5493     if (const Element* rootElement =
5494             mOuter->PresContext()->Document()->GetRootElement()) {
5495       styleFrame = rootElement->GetPrimaryFrame();
5496     }
5497   } else {
5498     styleFrame = mOuter;
5499   }
5500 
5501   return styleFrame;
5502 }
5503 
NeedsScrollSnap() const5504 bool ScrollFrameHelper::NeedsScrollSnap() const {
5505   nsIFrame* scrollSnapFrame = GetFrameForStyle();
5506   if (!scrollSnapFrame) {
5507     return false;
5508   }
5509   return scrollSnapFrame->StyleDisplay()->mScrollSnapType.strictness !=
5510          StyleScrollSnapStrictness::None;
5511 }
5512 
IsScrollbarOnRight() const5513 bool ScrollFrameHelper::IsScrollbarOnRight() const {
5514   nsPresContext* presContext = mOuter->PresContext();
5515 
5516   // The position of the scrollbar in top-level windows depends on the pref
5517   // layout.scrollbar.side. For non-top-level elements, it depends only on the
5518   // directionaliy of the element (equivalent to a value of "1" for the pref).
5519   if (!mIsRoot) {
5520     return IsPhysicalLTR();
5521   }
5522   switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) {
5523     default:
5524     case 0:  // UI directionality
5525       return presContext->GetCachedIntPref(kPresContext_BidiDirection) ==
5526              IBMBIDI_TEXTDIRECTION_LTR;
5527     case 1:  // Document / content directionality
5528       return IsPhysicalLTR();
5529     case 2:  // Always right
5530       return true;
5531     case 3:  // Always left
5532       return false;
5533   }
5534 }
5535 
IsMaybeScrollingActive() const5536 bool ScrollFrameHelper::IsMaybeScrollingActive() const {
5537   const nsStyleDisplay* disp = mOuter->StyleDisplay();
5538   if (disp->mWillChange.bits & StyleWillChangeBits::SCROLL) {
5539     return true;
5540   }
5541 
5542   nsIContent* content = mOuter->GetContent();
5543   return mHasBeenScrolledRecently || IsAlwaysActive() ||
5544          nsLayoutUtils::HasDisplayPort(content) ||
5545          nsContentUtils::HasScrollgrab(content);
5546 }
5547 
IsScrollingActive(nsDisplayListBuilder * aBuilder) const5548 bool ScrollFrameHelper::IsScrollingActive(
5549     nsDisplayListBuilder* aBuilder) const {
5550   const nsStyleDisplay* disp = mOuter->StyleDisplay();
5551   if (disp->mWillChange.bits & StyleWillChangeBits::SCROLL &&
5552       aBuilder->IsInWillChangeBudget(mOuter, GetVisualViewportSize())) {
5553     return true;
5554   }
5555 
5556   nsIContent* content = mOuter->GetContent();
5557   return mHasBeenScrolledRecently || IsAlwaysActive() ||
5558          nsLayoutUtils::HasDisplayPort(content) ||
5559          nsContentUtils::HasScrollgrab(content);
5560 }
5561 
5562 /**
5563  * Reflow the scroll area if it needs it and return its size. Also determine if
5564  * the reflow will cause any of the scrollbars to need to be reflowed.
5565  */
XULLayout(nsBoxLayoutState & aState)5566 nsresult nsXULScrollFrame::XULLayout(nsBoxLayoutState& aState) {
5567   bool scrollbarRight = IsScrollbarOnRight();
5568   bool scrollbarBottom = true;
5569 
5570   // get the content rect
5571   nsRect clientRect(0, 0, 0, 0);
5572   GetXULClientRect(clientRect);
5573 
5574   nsRect oldScrollAreaBounds = mHelper.mScrollPort;
5575   nsPoint oldScrollPosition = mHelper.GetLogicalScrollPosition();
5576 
5577   // the scroll area size starts off as big as our content area
5578   mHelper.mScrollPort = clientRect;
5579 
5580   /**************
5581    Our basic strategy here is to first try laying out the content with
5582    the scrollbars in their current state. We're hoping that that will
5583    just "work"; the content will overflow wherever there's a scrollbar
5584    already visible. If that does work, then there's no need to lay out
5585    the scrollarea. Otherwise we fix up the scrollbars; first we add a
5586    vertical one to scroll the content if necessary, or remove it if
5587    it's not needed. Then we reflow the content if the scrollbar
5588    changed.  Then we add a horizontal scrollbar if necessary (or
5589    remove if not needed), and if that changed, we reflow the content
5590    again. At this point, any scrollbars that are needed to scroll the
5591    content have been added.
5592 
5593    In the second phase we check to see if any scrollbars are too small
5594    to display, and if so, we remove them. We check the horizontal
5595    scrollbar first; removing it might make room for the vertical
5596    scrollbar, and if we have room for just one scrollbar we'll save
5597    the vertical one.
5598 
5599    Finally we position and size the scrollbars and scrollcorner (the
5600    square that is needed in the corner of the window when two
5601    scrollbars are visible), and reflow any fixed position views
5602    (if we're the viewport and we added or removed a scrollbar).
5603    **************/
5604 
5605   ScrollStyles styles = GetScrollStyles();
5606 
5607   // Look at our style do we always have vertical or horizontal scrollbars?
5608   if (styles.mHorizontal == StyleOverflow::Scroll)
5609     mHelper.mHasHorizontalScrollbar = true;
5610   if (styles.mVertical == StyleOverflow::Scroll)
5611     mHelper.mHasVerticalScrollbar = true;
5612 
5613   if (mHelper.mHasHorizontalScrollbar)
5614     AddHorizontalScrollbar(aState, scrollbarBottom);
5615 
5616   if (mHelper.mHasVerticalScrollbar)
5617     AddVerticalScrollbar(aState, scrollbarRight);
5618 
5619   // layout our the scroll area
5620   LayoutScrollArea(aState, oldScrollPosition);
5621 
5622   // now look at the content area and see if we need scrollbars or not
5623   bool needsLayout = false;
5624 
5625   // if we have 'auto' scrollbars look at the vertical case
5626   if (styles.mVertical != StyleOverflow::Scroll) {
5627     // These are only good until the call to LayoutScrollArea.
5628     nsRect scrolledRect = mHelper.GetScrolledRect();
5629 
5630     // There are two cases to consider
5631     if (scrolledRect.height <= mHelper.mScrollPort.height ||
5632         styles.mVertical != StyleOverflow::Auto) {
5633       if (mHelper.mHasVerticalScrollbar) {
5634         // We left room for the vertical scrollbar, but it's not needed;
5635         // remove it.
5636         RemoveVerticalScrollbar(aState, scrollbarRight);
5637         needsLayout = true;
5638       }
5639     } else {
5640       if (!mHelper.mHasVerticalScrollbar) {
5641         // We didn't leave room for the vertical scrollbar, but it turns
5642         // out we needed it
5643         if (AddVerticalScrollbar(aState, scrollbarRight)) {
5644           needsLayout = true;
5645         }
5646       }
5647     }
5648 
5649     // ok layout at the right size
5650     if (needsLayout) {
5651       nsBoxLayoutState resizeState(aState);
5652       LayoutScrollArea(resizeState, oldScrollPosition);
5653       needsLayout = false;
5654     }
5655   }
5656 
5657   // if scrollbars are auto look at the horizontal case
5658   if (styles.mHorizontal != StyleOverflow::Scroll) {
5659     // These are only good until the call to LayoutScrollArea.
5660     nsRect scrolledRect = mHelper.GetScrolledRect();
5661 
5662     // if the child is wider that the scroll area
5663     // and we don't have a scrollbar add one.
5664     if ((scrolledRect.width > mHelper.mScrollPort.width) &&
5665         styles.mHorizontal == StyleOverflow::Auto) {
5666       if (!mHelper.mHasHorizontalScrollbar) {
5667         // no scrollbar?
5668         if (AddHorizontalScrollbar(aState, scrollbarBottom)) {
5669           // if we added a horizontal scrollbar and we did not have a vertical
5670           // there is a chance that by adding the horizontal scrollbar we will
5671           // suddenly need a vertical scrollbar. Is a special case but it's
5672           // important.
5673           //
5674           // But before we do that we need to relayout, since it's
5675           // possible that the contents will flex as a result of adding a
5676           // horizontal scrollbar and avoid the need for a vertical
5677           // scrollbar.
5678           //
5679           // So instead of setting needsLayout to true here, do the
5680           // layout immediately, and then consider whether to add the
5681           // vertical scrollbar (and then maybe layout again).
5682           {
5683             nsBoxLayoutState resizeState(aState);
5684             LayoutScrollArea(resizeState, oldScrollPosition);
5685             needsLayout = false;
5686           }
5687 
5688           // Refresh scrolledRect because we called LayoutScrollArea.
5689           scrolledRect = mHelper.GetScrolledRect();
5690 
5691           if (styles.mVertical == StyleOverflow::Auto &&
5692               !mHelper.mHasVerticalScrollbar &&
5693               scrolledRect.height > mHelper.mScrollPort.height) {
5694             if (AddVerticalScrollbar(aState, scrollbarRight)) {
5695               needsLayout = true;
5696             }
5697           }
5698         }
5699       }
5700     } else {
5701       // if the area is smaller or equal to and we have a scrollbar then
5702       // remove it.
5703       if (mHelper.mHasHorizontalScrollbar) {
5704         RemoveHorizontalScrollbar(aState, scrollbarBottom);
5705         needsLayout = true;
5706       }
5707     }
5708   }
5709 
5710   // we only need to set the rect. The inner child stays the same size.
5711   if (needsLayout) {
5712     nsBoxLayoutState resizeState(aState);
5713     LayoutScrollArea(resizeState, oldScrollPosition);
5714     needsLayout = false;
5715   }
5716 
5717   // get the preferred size of the scrollbars
5718   nsSize hMinSize(0, 0);
5719   if (mHelper.mHScrollbarBox && mHelper.mHasHorizontalScrollbar) {
5720     GetScrollbarMetrics(aState, mHelper.mHScrollbarBox, &hMinSize, nullptr);
5721   }
5722   nsSize vMinSize(0, 0);
5723   if (mHelper.mVScrollbarBox && mHelper.mHasVerticalScrollbar) {
5724     GetScrollbarMetrics(aState, mHelper.mVScrollbarBox, &vMinSize, nullptr);
5725   }
5726 
5727   // Disable scrollbars that are too small
5728   // Disable horizontal scrollbar first. If we have to disable only one
5729   // scrollbar, we'd rather keep the vertical scrollbar.
5730   // Note that we always give horizontal scrollbars their preferred height,
5731   // never their min-height. So check that there's room for the preferred
5732   // height.
5733   if (mHelper.mHasHorizontalScrollbar &&
5734       (hMinSize.width > clientRect.width - vMinSize.width ||
5735        hMinSize.height > clientRect.height)) {
5736     RemoveHorizontalScrollbar(aState, scrollbarBottom);
5737     needsLayout = true;
5738   }
5739   // Now disable vertical scrollbar if necessary
5740   if (mHelper.mHasVerticalScrollbar &&
5741       (vMinSize.height > clientRect.height - hMinSize.height ||
5742        vMinSize.width > clientRect.width)) {
5743     RemoveVerticalScrollbar(aState, scrollbarRight);
5744     needsLayout = true;
5745   }
5746 
5747   // we only need to set the rect. The inner child stays the same size.
5748   if (needsLayout) {
5749     nsBoxLayoutState resizeState(aState);
5750     LayoutScrollArea(resizeState, oldScrollPosition);
5751   }
5752 
5753   if (!mHelper.mSuppressScrollbarUpdate) {
5754     mHelper.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds);
5755   }
5756   if (!mHelper.mPostedReflowCallback) {
5757     // Make sure we'll try scrolling to restored position
5758     PresShell()->PostReflowCallback(&mHelper);
5759     mHelper.mPostedReflowCallback = true;
5760   }
5761   if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
5762     mHelper.mHadNonInitialReflow = true;
5763   }
5764 
5765   mHelper.UpdateSticky();
5766 
5767   // Set up overflow areas for block frames for the benefit of
5768   // text-overflow.
5769   nsIFrame* f = mHelper.mScrolledFrame->GetContentInsertionFrame();
5770   if (f && f->IsBlockFrameOrSubclass()) {
5771     nsRect origRect = f->GetRect();
5772     nsRect clippedRect = origRect;
5773     clippedRect.MoveBy(mHelper.mScrollPort.TopLeft());
5774     clippedRect.IntersectRect(clippedRect, mHelper.mScrollPort);
5775     nsOverflowAreas overflow = f->GetOverflowAreas();
5776     f->FinishAndStoreOverflow(overflow, clippedRect.Size());
5777     clippedRect.MoveTo(origRect.TopLeft());
5778     f->SetRect(clippedRect);
5779   }
5780 
5781   mHelper.UpdatePrevScrolledRect();
5782 
5783   mHelper.PostOverflowEvent();
5784   return NS_OK;
5785 }
5786 
FinishReflowForScrollbar(Element * aElement,nscoord aMinXY,nscoord aMaxXY,nscoord aCurPosXY,nscoord aPageIncrement,nscoord aIncrement)5787 void ScrollFrameHelper::FinishReflowForScrollbar(Element* aElement,
5788                                                  nscoord aMinXY, nscoord aMaxXY,
5789                                                  nscoord aCurPosXY,
5790                                                  nscoord aPageIncrement,
5791                                                  nscoord aIncrement) {
5792   // Scrollbars assume zero is the minimum position, so translate for them.
5793   SetCoordAttribute(aElement, nsGkAtoms::curpos, aCurPosXY - aMinXY);
5794   SetScrollbarEnabled(aElement, aMaxXY - aMinXY);
5795   SetCoordAttribute(aElement, nsGkAtoms::maxpos, aMaxXY - aMinXY);
5796   SetCoordAttribute(aElement, nsGkAtoms::pageincrement, aPageIncrement);
5797   SetCoordAttribute(aElement, nsGkAtoms::increment, aIncrement);
5798 }
5799 
5800 class MOZ_RAII AutoMinimumScaleSizeChangeDetector final {
5801  public:
AutoMinimumScaleSizeChangeDetector(ScrollFrameHelper * aScrollFrameHelper)5802   explicit AutoMinimumScaleSizeChangeDetector(
5803       ScrollFrameHelper* aScrollFrameHelper)
5804       : mHelper(aScrollFrameHelper) {
5805     MOZ_ASSERT(mHelper);
5806     MOZ_ASSERT(mHelper->mIsRoot);
5807 
5808     mPreviousMinimumScaleSize = aScrollFrameHelper->mMinimumScaleSize;
5809     mPreviousIsUsingMinimumScaleSize =
5810         aScrollFrameHelper->mIsUsingMinimumScaleSize;
5811   }
~AutoMinimumScaleSizeChangeDetector()5812   ~AutoMinimumScaleSizeChangeDetector() {
5813     if (mPreviousMinimumScaleSize != mHelper->mMinimumScaleSize ||
5814         mPreviousIsUsingMinimumScaleSize != mHelper->mIsUsingMinimumScaleSize) {
5815       mHelper->mMinimumScaleSizeChanged = true;
5816     }
5817   }
5818 
5819  private:
5820   ScrollFrameHelper* mHelper;
5821 
5822   nsSize mPreviousMinimumScaleSize;
5823   bool mPreviousIsUsingMinimumScaleSize;
5824 };
5825 
TrueOuterSize(nsDisplayListBuilder * aBuilder) const5826 nsSize ScrollFrameHelper::TrueOuterSize(nsDisplayListBuilder* aBuilder) const {
5827   if (RefPtr<MobileViewportManager> manager =
5828           mOuter->PresShell()->GetMobileViewportManager()) {
5829     LayoutDeviceIntSize displaySize = manager->DisplaySize();
5830 
5831     MOZ_ASSERT(aBuilder);
5832     // In case of WebRender, we expand the outer size to include the dynamic
5833     // toolbar area here.
5834     // In case of non WebRender, we expand the size dynamically in
5835     // MoveScrollbarForLayerMargin in AsyncCompositionManager.cpp.
5836     LayerManager* layerManager = aBuilder->GetWidgetLayerManager();
5837     if (layerManager &&
5838         layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
5839       displaySize.height += ViewAs<LayoutDevicePixel>(
5840           mOuter->PresContext()->GetDynamicToolbarMaxHeight(),
5841           PixelCastJustification::LayoutDeviceIsScreenForBounds);
5842     }
5843 
5844     return LayoutDeviceSize::ToAppUnits(
5845         displaySize, mOuter->PresContext()->AppUnitsPerDevPixel());
5846   }
5847   return mOuter->GetSize();
5848 }
5849 
UpdateMinimumScaleSize(const nsRect & aScrollableOverflow,const nsSize & aICBSize)5850 void ScrollFrameHelper::UpdateMinimumScaleSize(
5851     const nsRect& aScrollableOverflow, const nsSize& aICBSize) {
5852   MOZ_ASSERT(mIsRoot);
5853 
5854   AutoMinimumScaleSizeChangeDetector minimumScaleSizeChangeDetector(this);
5855 
5856   mIsUsingMinimumScaleSize = false;
5857 
5858   if (!mOuter->PresShell()->UsesMobileViewportSizing()) {
5859     return;
5860   }
5861 
5862   nsPresContext* pc = mOuter->PresContext();
5863   MOZ_ASSERT(pc->IsRootContentDocumentCrossProcess(),
5864              "The pres context should be for the root content document");
5865 
5866   RefPtr<MobileViewportManager> manager =
5867       mOuter->PresShell()->GetMobileViewportManager();
5868   MOZ_ASSERT(manager);
5869 
5870   ScreenIntSize displaySize = ViewAs<ScreenPixel>(
5871       manager->DisplaySize(),
5872       PixelCastJustification::LayoutDeviceIsScreenForBounds);
5873   if (displaySize.width == 0 || displaySize.height == 0) {
5874     return;
5875   }
5876   if (aScrollableOverflow.IsEmpty()) {
5877     // Bail if the scrollable overflow rect is empty, as we're going to be
5878     // dividing by it.
5879     return;
5880   }
5881 
5882   Document* doc = pc->Document();
5883   MOZ_ASSERT(doc, "The document should be valid");
5884   if (doc->GetFullscreenElement()) {
5885     // Don't use the minimum scale size in the case of fullscreen state.
5886     // FIXME: 1508177: We will no longer need this.
5887     return;
5888   }
5889 
5890   nsViewportInfo viewportInfo = doc->GetViewportInfo(displaySize);
5891   if (!viewportInfo.IsZoomAllowed()) {
5892     // Don't apply the minimum scale size if user-scalable=no is specified.
5893     return;
5894   }
5895 
5896   // The intrinsic minimum scale is the scale that fits the entire content
5897   // width into the visual viewport.
5898   CSSToScreenScale intrinsicMinScale(
5899       displaySize.width / CSSRect::FromAppUnits(aScrollableOverflow).XMost());
5900 
5901   // The scale used to compute the minimum-scale size is the larger of the
5902   // intrinsic minimum and the min-scale from the meta viewport tag.
5903   CSSToScreenScale minScale =
5904       std::max(intrinsicMinScale, viewportInfo.GetMinZoom());
5905 
5906   // The minimum-scale size is the size of the visual viewport when zoomed
5907   // to be the minimum scale.
5908   mMinimumScaleSize = CSSSize::ToAppUnits(ScreenSize(displaySize) / minScale);
5909 
5910   // Clamp the min-scale size so it's not taller than the content height.
5911   // TODO: Bug 1571599: Drop this check.
5912   if (!StaticPrefs::layout_viewport_contains_no_contents_area()) {
5913     mMinimumScaleSize =
5914         Min(mMinimumScaleSize,
5915             nsSize(aScrollableOverflow.XMost(), aScrollableOverflow.YMost()));
5916   }
5917 
5918   // Ensure the minimum-scale size is never smaller than the ICB size.
5919   // That could happen if a page has a meta viewport tag with large explicitly
5920   // specified viewport dimensions (making the ICB large) and also a large
5921   // minimum scale (making the min-scale size small).
5922   mMinimumScaleSize = Max(aICBSize, mMinimumScaleSize);
5923 
5924   mIsUsingMinimumScaleSize = true;
5925 }
5926 
ReflowFinished()5927 bool ScrollFrameHelper::ReflowFinished() {
5928   mPostedReflowCallback = false;
5929 
5930   if (mIsRoot && mMinimumScaleSizeChanged &&
5931       mOuter->PresShell()->UsesMobileViewportSizing() &&
5932       !mOuter->PresShell()->IsResolutionUpdatedByApz()) {
5933     PresShell* presShell = mOuter->PresShell();
5934     RefPtr<MobileViewportManager> manager =
5935         presShell->GetMobileViewportManager();
5936     MOZ_ASSERT(manager);
5937 
5938     ScreenIntSize displaySize = ViewAs<ScreenPixel>(
5939         manager->DisplaySize(),
5940         PixelCastJustification::LayoutDeviceIsScreenForBounds);
5941 
5942     Document* doc = presShell->GetDocument();
5943     MOZ_ASSERT(doc, "The document should be valid");
5944     nsViewportInfo viewportInfo = doc->GetViewportInfo(displaySize);
5945     manager->ShrinkToDisplaySizeIfNeeded(viewportInfo, displaySize);
5946     mMinimumScaleSizeChanged = false;
5947   }
5948 
5949   bool doScroll = true;
5950   if (NS_SUBTREE_DIRTY(mOuter)) {
5951     // We will get another call after the next reflow and scrolling
5952     // later is less janky.
5953     doScroll = false;
5954   }
5955 
5956   nsAutoScriptBlocker scriptBlocker;
5957 
5958   if (doScroll) {
5959     ScrollToRestoredPosition();
5960 
5961     // Clamp current scroll position to new bounds. Normally this won't
5962     // do anything.
5963     nsPoint currentScrollPos = GetScrollPosition();
5964     ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)));
5965     if (!mAsyncScroll && !mAsyncSmoothMSDScroll &&
5966         !mApzSmoothScrollDestination) {
5967       // We need to have mDestination track the current scroll position,
5968       // in case it falls outside the new reflow area. mDestination is used
5969       // by ScrollBy as its starting position.
5970       mDestination = GetScrollPosition();
5971     }
5972   }
5973 
5974   if (!mUpdateScrollbarAttributes) {
5975     return false;
5976   }
5977   mUpdateScrollbarAttributes = false;
5978 
5979   // Update scrollbar attributes.
5980   if (mMayHaveDirtyFixedChildren) {
5981     mMayHaveDirtyFixedChildren = false;
5982     nsIFrame* parentFrame = mOuter->GetParent();
5983     for (nsIFrame* fixedChild =
5984              parentFrame->GetChildList(nsIFrame::kFixedList).FirstChild();
5985          fixedChild; fixedChild = fixedChild->GetNextSibling()) {
5986       // force a reflow of the fixed child
5987       mOuter->PresShell()->FrameNeedsReflow(fixedChild, IntrinsicDirty::Resize,
5988                                             NS_FRAME_HAS_DIRTY_CHILDREN);
5989     }
5990   }
5991 
5992   nsRect scrolledContentRect = GetScrolledRect();
5993   nsSize scrollClampingScrollPort = GetVisualViewportSize();
5994   nscoord minX = scrolledContentRect.x;
5995   nscoord maxX = scrolledContentRect.XMost() - scrollClampingScrollPort.width;
5996   nscoord minY = scrolledContentRect.y;
5997   nscoord maxY = scrolledContentRect.YMost() - scrollClampingScrollPort.height;
5998 
5999   // Suppress handling of the curpos attribute changes we make here.
6000   NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here");
6001   mFrameIsUpdatingScrollbar = true;
6002 
6003   // FIXME(emilio): Why this instead of mHScrollbarContent / mVScrollbarContent?
6004   RefPtr<Element> vScroll =
6005       mVScrollbarBox ? mVScrollbarBox->GetContent()->AsElement() : nullptr;
6006   RefPtr<Element> hScroll =
6007       mHScrollbarBox ? mHScrollbarBox->GetContent()->AsElement() : nullptr;
6008 
6009   // Note, in some cases mOuter may get deleted while finishing reflow
6010   // for scrollbars. XXXmats is this still true now that we have a script
6011   // blocker in this scope? (if not, remove the weak frame checks below).
6012   if (vScroll || hScroll) {
6013     AutoWeakFrame weakFrame(mOuter);
6014     nsPoint scrollPos = GetScrollPosition();
6015     nsSize lineScrollAmount = GetLineScrollAmount();
6016     if (vScroll) {
6017       const double kScrollMultiplier =
6018           Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
6019                               NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
6020       nscoord increment = lineScrollAmount.height * kScrollMultiplier;
6021       // We normally use (scrollArea.height - increment) for height
6022       // of page scrolling.  However, it is too small when
6023       // increment is very large. (If increment is larger than
6024       // scrollArea.height, direction of scrolling will be opposite).
6025       // To avoid it, we use (float(scrollArea.height) * 0.8) as
6026       // lower bound value of height of page scrolling. (bug 383267)
6027       // XXX shouldn't we use GetPageScrollAmount here?
6028       nscoord pageincrement =
6029           nscoord(scrollClampingScrollPort.height - increment);
6030       nscoord pageincrementMin =
6031           nscoord(float(scrollClampingScrollPort.height) * 0.8);
6032       FinishReflowForScrollbar(vScroll, minY, maxY, scrollPos.y,
6033                                std::max(pageincrement, pageincrementMin),
6034                                increment);
6035     }
6036     if (hScroll) {
6037       const double kScrollMultiplier =
6038           Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
6039                               NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
6040       nscoord increment = lineScrollAmount.width * kScrollMultiplier;
6041       FinishReflowForScrollbar(
6042           hScroll, minX, maxX, scrollPos.x,
6043           nscoord(float(scrollClampingScrollPort.width) * 0.8), increment);
6044     }
6045     NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
6046   }
6047 
6048   mFrameIsUpdatingScrollbar = false;
6049   // We used to rely on the curpos attribute changes above to scroll the
6050   // view.  However, for scrolling to the left of the viewport, we
6051   // rescale the curpos attribute, which means that operations like
6052   // resizing the window while it is scrolled all the way to the left
6053   // hold the curpos attribute constant at 0 while still requiring
6054   // scrolling.  So we suppress the effect of the changes above with
6055   // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here.
6056   // (It actually even works some of the time without this, thanks to
6057   // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
6058   // we hide the scrollbar on a large size change, such as
6059   // maximization.)
6060   if (!mHScrollbarBox && !mVScrollbarBox) return false;
6061   CurPosAttributeChanged(mVScrollbarBox
6062                              ? mVScrollbarBox->GetContent()->AsElement()
6063                              : mHScrollbarBox->GetContent()->AsElement(),
6064                          doScroll);
6065   return doScroll;
6066 }
6067 
ReflowCallbackCanceled()6068 void ScrollFrameHelper::ReflowCallbackCanceled() {
6069   mPostedReflowCallback = false;
6070 }
6071 
ComputeCustomOverflow(nsOverflowAreas & aOverflowAreas)6072 bool ScrollFrameHelper::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) {
6073   nsIScrollableFrame* sf = do_QueryFrame(mOuter);
6074   ScrollStyles ss = sf->GetScrollStyles();
6075 
6076   // Reflow when the change in overflow leads to one of our scrollbars
6077   // changing or might require repositioning the scrolled content due to
6078   // reduced extents.
6079   nsRect scrolledRect = GetScrolledRect();
6080   uint32_t overflowChange = GetOverflowChange(scrolledRect, mPrevScrolledRect);
6081   mPrevScrolledRect = scrolledRect;
6082 
6083   bool needReflow = false;
6084   nsPoint scrollPosition = GetScrollPosition();
6085   if (overflowChange & nsIScrollableFrame::HORIZONTAL) {
6086     if (ss.mHorizontal != StyleOverflow::Hidden || scrollPosition.x) {
6087       needReflow = true;
6088     }
6089   }
6090   if (overflowChange & nsIScrollableFrame::VERTICAL) {
6091     if (ss.mVertical != StyleOverflow::Hidden || scrollPosition.y) {
6092       needReflow = true;
6093     }
6094   }
6095 
6096   if (needReflow) {
6097     // If there are scrollbars, or we're not at the beginning of the pane,
6098     // the scroll position may change. In this case, mark the frame as
6099     // needing reflow. Don't use NS_FRAME_IS_DIRTY as dirty as that means
6100     // we have to reflow the frame and all its descendants, and we don't
6101     // have to do that here. Only this frame needs to be reflowed.
6102     mOuter->PresShell()->FrameNeedsReflow(mOuter, IntrinsicDirty::Resize,
6103                                           NS_FRAME_HAS_DIRTY_CHILDREN);
6104     // Ensure that next time nsHTMLScrollFrame::Reflow runs, we don't skip
6105     // updating the scrollbars. (Because the overflow area of the scrolled
6106     // frame has probably just been updated, Reflow won't see it change.)
6107     mSkippedScrollbarLayout = true;
6108     return false;  // reflowing will update overflow
6109   }
6110   PostOverflowEvent();
6111   return mOuter->nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
6112 }
6113 
UpdateSticky()6114 void ScrollFrameHelper::UpdateSticky() {
6115   StickyScrollContainer* ssc =
6116       StickyScrollContainer::GetStickyScrollContainerForScrollFrame(mOuter);
6117   if (ssc) {
6118     nsIScrollableFrame* scrollFrame = do_QueryFrame(mOuter);
6119     ssc->UpdatePositions(scrollFrame->GetScrollPosition(), mOuter);
6120   }
6121 }
6122 
UpdatePrevScrolledRect()6123 void ScrollFrameHelper::UpdatePrevScrolledRect() {
6124   mPrevScrolledRect = GetScrolledRect();
6125 }
6126 
AdjustScrollbarRectForResizer(nsIFrame * aFrame,nsPresContext * aPresContext,nsRect & aRect,bool aHasResizer,ScrollDirection aDirection)6127 void ScrollFrameHelper::AdjustScrollbarRectForResizer(
6128     nsIFrame* aFrame, nsPresContext* aPresContext, nsRect& aRect,
6129     bool aHasResizer, ScrollDirection aDirection) {
6130   if ((aDirection == ScrollDirection::eVertical ? aRect.width : aRect.height) ==
6131       0) {
6132     return;
6133   }
6134 
6135   // if a content resizer is present, use its size. Otherwise, check if the
6136   // widget has a resizer.
6137   nsRect resizerRect;
6138   if (aHasResizer) {
6139     resizerRect = mResizerBox->GetRect();
6140   } else {
6141     nsPoint offset;
6142     nsIWidget* widget = aFrame->GetNearestWidget(offset);
6143     LayoutDeviceIntRect widgetRect;
6144     if (!widget || !widget->ShowsResizeIndicator(&widgetRect)) {
6145       return;
6146     }
6147 
6148     resizerRect =
6149         nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
6150                aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
6151                aPresContext->DevPixelsToAppUnits(widgetRect.width),
6152                aPresContext->DevPixelsToAppUnits(widgetRect.height));
6153   }
6154 
6155   if (resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1))) {
6156     switch (aDirection) {
6157       case ScrollDirection::eVertical:
6158         aRect.height = std::max(0, resizerRect.y - aRect.y);
6159         break;
6160       case ScrollDirection::eHorizontal:
6161         aRect.width = std::max(0, resizerRect.x - aRect.x);
6162         break;
6163     }
6164   } else if (resizerRect.Contains(aRect.BottomLeft() + nsPoint(1, -1))) {
6165     switch (aDirection) {
6166       case ScrollDirection::eVertical:
6167         aRect.height = std::max(0, resizerRect.y - aRect.y);
6168         break;
6169       case ScrollDirection::eHorizontal: {
6170         nscoord xmost = aRect.XMost();
6171         aRect.x = std::max(aRect.x, resizerRect.XMost());
6172         aRect.width = xmost - aRect.x;
6173         break;
6174       }
6175     }
6176   }
6177 }
6178 
AdjustOverlappingScrollbars(nsRect & aVRect,nsRect & aHRect)6179 static void AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect) {
6180   if (aVRect.IsEmpty() || aHRect.IsEmpty()) return;
6181 
6182   const nsRect oldVRect = aVRect;
6183   const nsRect oldHRect = aHRect;
6184   if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) {
6185     aHRect.width = std::max(0, oldVRect.x - oldHRect.x);
6186   } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) {
6187     nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x);
6188     aHRect.x += overlap;
6189     aHRect.width -= overlap;
6190   }
6191   if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) {
6192     aVRect.height = std::max(0, oldHRect.y - oldVRect.y);
6193   }
6194 }
6195 
LayoutScrollbars(nsBoxLayoutState & aState,const nsRect & aContentArea,const nsRect & aOldScrollArea)6196 void ScrollFrameHelper::LayoutScrollbars(nsBoxLayoutState& aState,
6197                                          const nsRect& aContentArea,
6198                                          const nsRect& aOldScrollArea) {
6199   NS_ASSERTION(!mSuppressScrollbarUpdate, "This should have been suppressed");
6200 
6201   PresShell* presShell = mOuter->PresShell();
6202 
6203   bool hasResizer = HasResizer();
6204   bool scrollbarOnLeft = !IsScrollbarOnRight();
6205   bool overlayScrollBarsWithZoom = UsesOverlayScrollbars() && mIsRoot &&
6206                                    presShell->IsVisualViewportSizeSet();
6207 
6208   nsSize scrollPortClampingSize = mScrollPort.Size();
6209   double res = 1.0;
6210   if (overlayScrollBarsWithZoom) {
6211     scrollPortClampingSize = presShell->GetVisualViewportSize();
6212     res = presShell->GetCumulativeResolution();
6213   }
6214 
6215   // place the scrollcorner
6216   if (mScrollCornerBox || mResizerBox) {
6217     MOZ_ASSERT(!mScrollCornerBox || mScrollCornerBox->IsXULBoxFrame(),
6218                "Must be a box frame!");
6219 
6220     nsRect r(0, 0, 0, 0);
6221     if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
6222       // scrollbar (if any) on left
6223       r.x = aContentArea.x;
6224       r.width = mScrollPort.x - aContentArea.x;
6225       NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
6226     } else {
6227       // scrollbar (if any) on right
6228       r.width = aContentArea.XMost() - mScrollPort.XMost();
6229       r.x = aContentArea.XMost() - r.width;
6230       NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
6231     }
6232     if (aContentArea.y != mScrollPort.y) {
6233       NS_ERROR("top scrollbars not supported");
6234     } else {
6235       // scrollbar (if any) on bottom
6236       r.height = aContentArea.YMost() - mScrollPort.YMost();
6237       r.y = aContentArea.YMost() - r.height;
6238       NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
6239     }
6240 
6241     if (mScrollCornerBox) {
6242       nsBoxFrame::LayoutChildAt(aState, mScrollCornerBox, r);
6243     }
6244 
6245     if (hasResizer) {
6246       // if a resizer is present, get its size. Assume a default size of 15
6247       // pixels.
6248       nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
6249       nsSize resizerMinSize = mResizerBox->GetXULMinSize(aState);
6250 
6251       nscoord vScrollbarWidth =
6252           mVScrollbarBox ? mVScrollbarBox->GetXULPrefSize(aState).width
6253                          : defaultSize;
6254       r.width =
6255           std::max(std::max(r.width, vScrollbarWidth), resizerMinSize.width);
6256       if (aContentArea.x == mScrollPort.x && !scrollbarOnLeft) {
6257         r.x = aContentArea.XMost() - r.width;
6258       }
6259 
6260       nscoord hScrollbarHeight =
6261           mHScrollbarBox ? mHScrollbarBox->GetXULPrefSize(aState).height
6262                          : defaultSize;
6263       r.height =
6264           std::max(std::max(r.height, hScrollbarHeight), resizerMinSize.height);
6265       if (aContentArea.y == mScrollPort.y) {
6266         r.y = aContentArea.YMost() - r.height;
6267       }
6268 
6269       nsBoxFrame::LayoutChildAt(aState, mResizerBox, r);
6270     } else if (mResizerBox) {
6271       // otherwise lay out the resizer with an empty rectangle
6272       nsBoxFrame::LayoutChildAt(aState, mResizerBox, nsRect());
6273     }
6274   }
6275 
6276   nsPresContext* presContext = mScrolledFrame->PresContext();
6277   nsRect vRect;
6278   if (mVScrollbarBox) {
6279     MOZ_ASSERT(mVScrollbarBox->IsXULBoxFrame(), "Must be a box frame!");
6280     vRect = mScrollPort;
6281     if (overlayScrollBarsWithZoom) {
6282       vRect.height = NSToCoordRound(res * scrollPortClampingSize.height);
6283     }
6284     vRect.width = aContentArea.width - mScrollPort.width;
6285     vRect.x = scrollbarOnLeft
6286                   ? aContentArea.x
6287                   : mScrollPort.x +
6288                         NSToCoordRound(res * scrollPortClampingSize.width);
6289     if (mHasVerticalScrollbar) {
6290       nsMargin margin;
6291       mVScrollbarBox->GetXULMargin(margin);
6292       vRect.Deflate(margin);
6293     }
6294     AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer,
6295                                   ScrollDirection::eVertical);
6296   }
6297 
6298   nsRect hRect;
6299   if (mHScrollbarBox) {
6300     MOZ_ASSERT(mHScrollbarBox->IsXULBoxFrame(), "Must be a box frame!");
6301     hRect = mScrollPort;
6302     if (overlayScrollBarsWithZoom) {
6303       hRect.width = NSToCoordRound(res * scrollPortClampingSize.width);
6304     }
6305     hRect.height = aContentArea.height - mScrollPort.height;
6306     hRect.y =
6307         mScrollPort.y + NSToCoordRound(res * scrollPortClampingSize.height);
6308     if (mHasHorizontalScrollbar) {
6309       nsMargin margin;
6310       mHScrollbarBox->GetXULMargin(margin);
6311       hRect.Deflate(margin);
6312     }
6313     AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer,
6314                                   ScrollDirection::eHorizontal);
6315   }
6316 
6317   if (!LookAndFeel::GetInt(LookAndFeel::eIntID_AllowOverlayScrollbarsOverlap)) {
6318     AdjustOverlappingScrollbars(vRect, hRect);
6319   }
6320   if (mVScrollbarBox) {
6321     nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect);
6322   }
6323   if (mHScrollbarBox) {
6324     nsBoxFrame::LayoutChildAt(aState, mHScrollbarBox, hRect);
6325   }
6326 
6327   // may need to update fixed position children of the viewport,
6328   // if the client area changed size because of an incremental
6329   // reflow of a descendant.  (If the outer frame is dirty, the fixed
6330   // children will be re-laid out anyway)
6331   if (aOldScrollArea.Size() != mScrollPort.Size() &&
6332       !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) && mIsRoot) {
6333     mMayHaveDirtyFixedChildren = true;
6334   }
6335 
6336   // post reflow callback to modify scrollbar attributes
6337   mUpdateScrollbarAttributes = true;
6338   if (!mPostedReflowCallback) {
6339     aState.PresShell()->PostReflowCallback(this);
6340     mPostedReflowCallback = true;
6341   }
6342 }
6343 
6344 #if DEBUG
ShellIsAlive(nsWeakPtr & aWeakPtr)6345 static bool ShellIsAlive(nsWeakPtr& aWeakPtr) {
6346   RefPtr<PresShell> presShell = do_QueryReferent(aWeakPtr);
6347   return !!presShell;
6348 }
6349 #endif
6350 
SetScrollbarEnabled(Element * aElement,nscoord aMaxPos)6351 void ScrollFrameHelper::SetScrollbarEnabled(Element* aElement,
6352                                             nscoord aMaxPos) {
6353   DebugOnly<nsWeakPtr> weakShell(do_GetWeakReference(mOuter->PresShell()));
6354   if (aMaxPos) {
6355     aElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
6356   } else {
6357     aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
6358                       NS_LITERAL_STRING("true"), true);
6359   }
6360   MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
6361 }
6362 
SetCoordAttribute(Element * aElement,nsAtom * aAtom,nscoord aSize)6363 void ScrollFrameHelper::SetCoordAttribute(Element* aElement, nsAtom* aAtom,
6364                                           nscoord aSize) {
6365   DebugOnly<nsWeakPtr> weakShell(do_GetWeakReference(mOuter->PresShell()));
6366   // convert to pixels
6367   int32_t pixelSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
6368 
6369   // only set the attribute if it changed.
6370 
6371   nsAutoString newValue;
6372   newValue.AppendInt(pixelSize);
6373 
6374   if (aElement->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters)) {
6375     return;
6376   }
6377 
6378   AutoWeakFrame weakFrame(mOuter);
6379   RefPtr<Element> kungFuDeathGrip = aElement;
6380   aElement->SetAttr(kNameSpaceID_None, aAtom, newValue, true);
6381   MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
6382   if (!weakFrame.IsAlive()) {
6383     return;
6384   }
6385 
6386   if (mScrollbarActivity) {
6387     RefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
6388     scrollbarActivity->ActivityOccurred();
6389   }
6390 }
6391 
ReduceRadii(nscoord aXBorder,nscoord aYBorder,nscoord & aXRadius,nscoord & aYRadius)6392 static void ReduceRadii(nscoord aXBorder, nscoord aYBorder, nscoord& aXRadius,
6393                         nscoord& aYRadius) {
6394   // In order to ensure that the inside edge of the border has no
6395   // curvature, we need at least one of its radii to be zero.
6396   if (aXRadius <= aXBorder || aYRadius <= aYBorder) return;
6397 
6398   // For any corner where we reduce the radii, preserve the corner's shape.
6399   double ratio =
6400       std::max(double(aXBorder) / aXRadius, double(aYBorder) / aYRadius);
6401   aXRadius *= ratio;
6402   aYRadius *= ratio;
6403 }
6404 
6405 /**
6406  * Implement an override for nsIFrame::GetBorderRadii to ensure that
6407  * the clipping region for the border radius does not clip the scrollbars.
6408  *
6409  * In other words, we require that the border radius be reduced until the
6410  * inner border radius at the inner edge of the border is 0 wherever we
6411  * have scrollbars.
6412  */
GetBorderRadii(const nsSize & aFrameSize,const nsSize & aBorderArea,Sides aSkipSides,nscoord aRadii[8]) const6413 bool ScrollFrameHelper::GetBorderRadii(const nsSize& aFrameSize,
6414                                        const nsSize& aBorderArea,
6415                                        Sides aSkipSides,
6416                                        nscoord aRadii[8]) const {
6417   if (!mOuter->nsContainerFrame::GetBorderRadii(aFrameSize, aBorderArea,
6418                                                 aSkipSides, aRadii)) {
6419     return false;
6420   }
6421 
6422   // Since we can use GetActualScrollbarSizes (rather than
6423   // GetDesiredScrollbarSizes) since this doesn't affect reflow, we
6424   // probably should.
6425   nsMargin sb = GetActualScrollbarSizes();
6426   nsMargin border = mOuter->GetUsedBorder();
6427 
6428   if (sb.left > 0 || sb.top > 0) {
6429     ReduceRadii(border.left, border.top, aRadii[eCornerTopLeftX],
6430                 aRadii[eCornerTopLeftY]);
6431   }
6432 
6433   if (sb.top > 0 || sb.right > 0) {
6434     ReduceRadii(border.right, border.top, aRadii[eCornerTopRightX],
6435                 aRadii[eCornerTopRightY]);
6436   }
6437 
6438   if (sb.right > 0 || sb.bottom > 0) {
6439     ReduceRadii(border.right, border.bottom, aRadii[eCornerBottomRightX],
6440                 aRadii[eCornerBottomRightY]);
6441   }
6442 
6443   if (sb.bottom > 0 || sb.left > 0) {
6444     ReduceRadii(border.left, border.bottom, aRadii[eCornerBottomLeftX],
6445                 aRadii[eCornerBottomLeftY]);
6446   }
6447 
6448   return true;
6449 }
6450 
SnapCoord(nscoord aCoord,double aRes,nscoord aAppUnitsPerPixel)6451 static nscoord SnapCoord(nscoord aCoord, double aRes,
6452                          nscoord aAppUnitsPerPixel) {
6453   double snappedToLayerPixels = NS_round((aRes * aCoord) / aAppUnitsPerPixel);
6454   return NSToCoordRoundWithClamp(snappedToLayerPixels * aAppUnitsPerPixel /
6455                                  aRes);
6456 }
6457 
GetScrolledRect() const6458 nsRect ScrollFrameHelper::GetScrolledRect() const {
6459   nsRect result = GetUnsnappedScrolledRectInternal(
6460       mScrolledFrame->GetScrollableOverflowRect(), mScrollPort.Size());
6461 
6462   if (result.width < mScrollPort.width) {
6463     NS_WARNING("Scrolled rect smaller than scrollport?");
6464   }
6465   if (result.height < mScrollPort.height) {
6466     NS_WARNING("Scrolled rect smaller than scrollport?");
6467   }
6468 
6469   // Expand / contract the result by up to half a layer pixel so that scrolling
6470   // to the right / bottom edge does not change the layer pixel alignment of
6471   // the scrolled contents.
6472 
6473   if (result.x == 0 && result.y == 0 && result.width == mScrollPort.width &&
6474       result.height == mScrollPort.height) {
6475     // The edges that we would snap are already aligned with the scroll port,
6476     // so we can skip all the work below.
6477     return result;
6478   }
6479 
6480   // For that, we first convert the scroll port and the scrolled rect to rects
6481   // relative to the reference frame, since that's the space where painting does
6482   // snapping.
6483   nsSize visualViewportSize = GetVisualViewportSize();
6484   const nsIFrame* referenceFrame =
6485       mReferenceFrameDuringPainting ? mReferenceFrameDuringPainting
6486                                     : nsLayoutUtils::GetReferenceFrame(mOuter);
6487   nsPoint toReferenceFrame = mOuter->GetOffsetToCrossDoc(referenceFrame);
6488   nsRect scrollPort(mScrollPort.TopLeft() + toReferenceFrame,
6489                     visualViewportSize);
6490   nsRect scrolledRect = result + scrollPort.TopLeft();
6491 
6492   if (scrollPort.Overflows() || scrolledRect.Overflows()) {
6493     return result;
6494   }
6495 
6496   // Now, snap the bottom right corner of both of these rects.
6497   // We snap to layer pixels, so we need to respect the layer's scale.
6498   nscoord appUnitsPerDevPixel =
6499       mScrolledFrame->PresContext()->AppUnitsPerDevPixel();
6500   gfxSize scale =
6501       FrameLayerBuilder::GetPaintedLayerScaleForFrame(mScrolledFrame);
6502   if (scale.IsEmpty()) {
6503     scale = gfxSize(1.0f, 1.0f);
6504   }
6505 
6506   // Compute bounds for the scroll position, and computed the snapped scrolled
6507   // rect from the scroll position bounds.
6508   nscoord snappedScrolledAreaBottom =
6509       SnapCoord(scrolledRect.YMost(), scale.height, appUnitsPerDevPixel);
6510   nscoord snappedScrollPortBottom =
6511       SnapCoord(scrollPort.YMost(), scale.height, appUnitsPerDevPixel);
6512   nscoord maximumScrollOffsetY =
6513       snappedScrolledAreaBottom - snappedScrollPortBottom;
6514   result.SetBottomEdge(scrollPort.height + maximumScrollOffsetY);
6515 
6516   if (GetScrolledFrameDir() == StyleDirection::Ltr) {
6517     nscoord snappedScrolledAreaRight =
6518         SnapCoord(scrolledRect.XMost(), scale.width, appUnitsPerDevPixel);
6519     nscoord snappedScrollPortRight =
6520         SnapCoord(scrollPort.XMost(), scale.width, appUnitsPerDevPixel);
6521     nscoord maximumScrollOffsetX =
6522         snappedScrolledAreaRight - snappedScrollPortRight;
6523     result.SetRightEdge(scrollPort.width + maximumScrollOffsetX);
6524   } else {
6525     // In RTL, the scrolled area's right edge is at scrollPort.XMost(),
6526     // and the scrolled area's x position is zero or negative. We want
6527     // the right edge to stay flush with the scroll port, so we snap the
6528     // left edge.
6529     nscoord snappedScrolledAreaLeft =
6530         SnapCoord(scrolledRect.x, scale.width, appUnitsPerDevPixel);
6531     nscoord snappedScrollPortLeft =
6532         SnapCoord(scrollPort.x, scale.width, appUnitsPerDevPixel);
6533     nscoord minimumScrollOffsetX =
6534         snappedScrolledAreaLeft - snappedScrollPortLeft;
6535     result.SetLeftEdge(minimumScrollOffsetX);
6536   }
6537 
6538   return result;
6539 }
6540 
GetScrolledFrameDir() const6541 StyleDirection ScrollFrameHelper::GetScrolledFrameDir() const {
6542   // If the scrolled frame has unicode-bidi: plaintext, the paragraph
6543   // direction set by the text content overrides the direction of the frame
6544   if (mScrolledFrame->StyleTextReset()->mUnicodeBidi &
6545       NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
6546     if (nsIFrame* child = mScrolledFrame->PrincipalChildList().FirstChild()) {
6547       return nsBidiPresUtils::ParagraphDirection(child) == NSBIDI_LTR
6548                  ? StyleDirection::Ltr
6549                  : StyleDirection::Rtl;
6550     }
6551   }
6552   return IsBidiLTR() ? StyleDirection::Ltr : StyleDirection::Rtl;
6553 }
6554 
GetUnsnappedScrolledRectInternal(const nsRect & aScrolledFrameOverflowArea,const nsSize & aScrollPortSize) const6555 nsRect ScrollFrameHelper::GetUnsnappedScrolledRectInternal(
6556     const nsRect& aScrolledFrameOverflowArea,
6557     const nsSize& aScrollPortSize) const {
6558   return nsLayoutUtils::GetScrolledRect(mScrolledFrame,
6559                                         aScrolledFrameOverflowArea,
6560                                         aScrollPortSize, GetScrolledFrameDir());
6561 }
6562 
GetActualScrollbarSizes() const6563 nsMargin ScrollFrameHelper::GetActualScrollbarSizes() const {
6564   nsRect r = mOuter->GetPaddingRectRelativeToSelf();
6565 
6566   return nsMargin(mScrollPort.y - r.y, r.XMost() - mScrollPort.XMost(),
6567                   r.YMost() - mScrollPort.YMost(), mScrollPort.x - r.x);
6568 }
6569 
SetScrollbarVisibility(nsIFrame * aScrollbar,bool aVisible)6570 void ScrollFrameHelper::SetScrollbarVisibility(nsIFrame* aScrollbar,
6571                                                bool aVisible) {
6572   nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar);
6573   if (scrollbar) {
6574     // See if we have a mediator.
6575     nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
6576     if (mediator) {
6577       // Inform the mediator of the visibility change.
6578       mediator->VisibilityChanged(aVisible);
6579     }
6580   }
6581 }
6582 
GetCoordAttribute(nsIFrame * aBox,nsAtom * aAtom,nscoord aDefaultValue,nscoord * aRangeStart,nscoord * aRangeLength)6583 nscoord ScrollFrameHelper::GetCoordAttribute(nsIFrame* aBox, nsAtom* aAtom,
6584                                              nscoord aDefaultValue,
6585                                              nscoord* aRangeStart,
6586                                              nscoord* aRangeLength) {
6587   if (aBox) {
6588     nsIContent* content = aBox->GetContent();
6589 
6590     nsAutoString value;
6591     if (content->IsElement()) {
6592       content->AsElement()->GetAttr(kNameSpaceID_None, aAtom, value);
6593     }
6594     if (!value.IsEmpty()) {
6595       nsresult error;
6596       // convert it to appunits
6597       nscoord result =
6598           nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
6599       nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
6600       // Any nscoord value that would round to the attribute value when
6601       // converted to CSS pixels is allowed.
6602       *aRangeStart = result - halfPixel;
6603       *aRangeLength = halfPixel * 2 - 1;
6604       return result;
6605     }
6606   }
6607 
6608   // Only this exact default value is allowed.
6609   *aRangeStart = aDefaultValue;
6610   *aRangeLength = 0;
6611   return aDefaultValue;
6612 }
6613 
SaveState() const6614 UniquePtr<PresState> ScrollFrameHelper::SaveState() const {
6615   nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
6616   if (mediator) {
6617     // child handles its own scroll state, so don't bother saving state here
6618     return nullptr;
6619   }
6620 
6621   // Don't store a scroll state if we never have been scrolled or restored
6622   // a previous scroll state, and we're not in the middle of a smooth scroll.
6623   bool isInSmoothScroll = IsProcessingAsyncScroll() || mLastSmoothScrollOrigin;
6624   if (!mHasBeenScrolled && !mDidHistoryRestore && !isInSmoothScroll) {
6625     return nullptr;
6626   }
6627 
6628   UniquePtr<PresState> state = NewPresState();
6629   bool allowScrollOriginDowngrade =
6630       !nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) ||
6631       mAllowScrollOriginDowngrade;
6632   // Save mRestorePos instead of our actual current scroll position, if it's
6633   // valid and we haven't moved since the last update of mLastPos (same check
6634   // that ScrollToRestoredPosition uses). This ensures if a reframe occurs
6635   // while we're in the process of loading content to scroll to a restored
6636   // position, we'll keep trying after the reframe. Similarly, if we're in the
6637   // middle of a smooth scroll, store the destination so that when we restore
6638   // we'll jump straight to the end of the scroll animation, rather than
6639   // effectively dropping it. Note that the mRestorePos will override the
6640   // smooth scroll destination if both are present.
6641   nsPoint pt = GetLogicalVisualViewportOffset();
6642   if (isInSmoothScroll) {
6643     pt = mDestination;
6644     allowScrollOriginDowngrade = false;
6645   }
6646   if (mRestorePos.y != -1 && pt == mLastPos) {
6647     pt = mRestorePos;
6648   }
6649   state->scrollState() = pt;
6650   state->allowScrollOriginDowngrade() = allowScrollOriginDowngrade;
6651   if (mIsRoot) {
6652     // Only save resolution properties for root scroll frames
6653     state->resolution() = mOuter->PresShell()->GetResolution();
6654   }
6655   return state;
6656 }
6657 
RestoreState(PresState * aState)6658 void ScrollFrameHelper::RestoreState(PresState* aState) {
6659   mRestorePos = aState->scrollState();
6660   MOZ_ASSERT(mLastScrollOrigin == nsGkAtoms::other);
6661   mAllowScrollOriginDowngrade = aState->allowScrollOriginDowngrade();
6662   mDidHistoryRestore = true;
6663   mLastPos = mScrolledFrame ? GetLogicalVisualViewportOffset() : nsPoint(0, 0);
6664 
6665   // Resolution properties should only exist on root scroll frames.
6666   MOZ_ASSERT(mIsRoot || aState->resolution() == 1.0);
6667 
6668   if (mIsRoot) {
6669     mOuter->PresShell()->SetResolutionAndScaleTo(
6670         aState->resolution(), ResolutionChangeOrigin::MainThreadRestore);
6671   }
6672 }
6673 
PostScrolledAreaEvent()6674 void ScrollFrameHelper::PostScrolledAreaEvent() {
6675   if (mScrolledAreaEvent.IsPending()) {
6676     return;
6677   }
6678   mScrolledAreaEvent = new ScrolledAreaEvent(this);
6679   nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get());
6680 }
6681 
6682 ////////////////////////////////////////////////////////////////////////////////
6683 // ScrolledArea change event dispatch
6684 
6685 NS_IMETHODIMP
Run()6686 ScrollFrameHelper::ScrolledAreaEvent::Run() {
6687   if (mHelper) {
6688     mHelper->FireScrolledAreaEvent();
6689   }
6690   return NS_OK;
6691 }
6692 
FireScrolledAreaEvent()6693 void ScrollFrameHelper::FireScrolledAreaEvent() {
6694   mScrolledAreaEvent.Forget();
6695 
6696   InternalScrollAreaEvent event(true, eScrolledAreaChanged, nullptr);
6697   nsPresContext* prescontext = mOuter->PresContext();
6698   nsIContent* content = mOuter->GetContent();
6699 
6700   event.mArea = mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
6701   if (Document* doc = content->GetUncomposedDoc()) {
6702     EventDispatcher::Dispatch(ToSupports(doc), prescontext, &event, nullptr);
6703   }
6704 }
6705 
GetAvailableScrollingDirections() const6706 uint32_t nsIScrollableFrame::GetAvailableScrollingDirections() const {
6707   nscoord oneDevPixel =
6708       GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
6709   uint32_t directions = 0;
6710   nsRect scrollRange = GetScrollRange();
6711   if (scrollRange.width >= oneDevPixel) {
6712     directions |= HORIZONTAL;
6713   }
6714   if (scrollRange.height >= oneDevPixel) {
6715     directions |= VERTICAL;
6716   }
6717   return directions;
6718 }
6719 
GetScrollRangeForUserInputEvents() const6720 nsRect ScrollFrameHelper::GetScrollRangeForUserInputEvents() const {
6721   // This function computes a scroll range based on a scrolled rect and scroll
6722   // port defined as follows:
6723   //   scrollable rect = overflow:hidden ? layout viewport : scrollable rect
6724   //   scroll port = have visual viewport ? visual viewport : layout viewport
6725   // The results in the same notion of scroll range that APZ uses (the combined
6726   // effect of FrameMetrics::CalculateScrollRange() and
6727   // nsLayoutUtils::CalculateScrollableRectForFrame).
6728 
6729   ScrollStyles ss = GetScrollStylesFromFrame();
6730 
6731   nsRect scrolledRect = GetScrolledRect();
6732   if (StyleOverflow::Hidden == ss.mHorizontal) {
6733     scrolledRect.width = mScrollPort.width;
6734   }
6735   if (StyleOverflow::Hidden == ss.mVertical) {
6736     scrolledRect.height = mScrollPort.height;
6737   }
6738 
6739   nsSize scrollPort = GetVisualViewportSize();
6740 
6741   nsRect scrollRange = scrolledRect;
6742   scrollRange.width = std::max(scrolledRect.width - scrollPort.width, 0);
6743   scrollRange.height = std::max(scrolledRect.height - scrollPort.height, 0);
6744 
6745   return scrollRange;
6746 }
6747 
GetAvailableScrollingDirectionsForUserInputEvents() const6748 uint32_t ScrollFrameHelper::GetAvailableScrollingDirectionsForUserInputEvents()
6749     const {
6750   nsRect scrollRange = GetScrollRangeForUserInputEvents();
6751 
6752   // We check if there is at least one half of a screen pixel of scroll range to
6753   // roughly match what apz does when it checks if the change in scroll position
6754   // in screen pixels round to zero or not.
6755   // (https://searchfox.org/mozilla-central/rev/2f09184ec781a2667feec87499d4b81b32b6c48e/gfx/layers/apz/src/AsyncPanZoomController.cpp#3210)
6756   // This isn't quite half a screen pixel, it doesn't take into account CSS
6757   // transforms, but should be good enough.
6758   float halfScreenPixel =
6759       GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel() /
6760       (mOuter->PresShell()->GetCumulativeResolution() * 2.f);
6761   uint32_t directions = 0;
6762   if (scrollRange.width >= halfScreenPixel) {
6763     directions |= nsIScrollableFrame::HORIZONTAL;
6764   }
6765   if (scrollRange.height >= halfScreenPixel) {
6766     directions |= nsIScrollableFrame::VERTICAL;
6767   }
6768   return directions;
6769 }
6770 
InflateByScrollMargin(const nsRect & aTargetRect,const nsMargin & aScrollMargin,const nsRect & aScrolledRect)6771 static nsRect InflateByScrollMargin(const nsRect& aTargetRect,
6772                                     const nsMargin& aScrollMargin,
6773                                     const nsRect& aScrolledRect) {
6774   // Inflate the rect by scroll-margin.
6775   nsRect result = aTargetRect;
6776   result.Inflate(aScrollMargin);
6777 
6778   // But don't be beyond the limit boundary.
6779   return result.Intersect(aScrolledRect);
6780 }
6781 
6782 /**
6783  * Append scroll positions for valid snap positions into |aSnapInfo| if
6784  * applicable.
6785  */
AppendScrollPositionsForSnap(const nsIFrame * aFrame,const nsIFrame * aScrolledFrame,const nsRect & aScrolledRect,const nsMargin & aScrollPadding,const Maybe<nsRect> & aSnapport,WritingMode aWritingModeOnScroller,ScrollSnapInfo & aSnapInfo)6786 static void AppendScrollPositionsForSnap(const nsIFrame* aFrame,
6787                                          const nsIFrame* aScrolledFrame,
6788                                          const nsRect& aScrolledRect,
6789                                          const nsMargin& aScrollPadding,
6790                                          const Maybe<nsRect>& aSnapport,
6791                                          WritingMode aWritingModeOnScroller,
6792                                          ScrollSnapInfo& aSnapInfo) {
6793   nsRect targetRect = nsLayoutUtils::TransformFrameRectToAncestor(
6794       aFrame, aFrame->GetRectRelativeToSelf(), aScrolledFrame);
6795 
6796   // The snap area contains scroll-margin values.
6797   // https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-area
6798   nsMargin scrollMargin = aFrame->StyleMargin()->GetScrollMargin();
6799   nsRect snapArea =
6800       InflateByScrollMargin(targetRect, scrollMargin, aScrolledRect);
6801 
6802   // Ignore elements outside of the snapport when we scroll to the given
6803   // destination.
6804   // https://drafts.csswg.org/css-scroll-snap-1/#snap-scope
6805   if (aSnapport && !aSnapport->Intersects(snapArea)) {
6806     return;
6807   }
6808 
6809   // These snap range shouldn't be involved with scroll-margin since we just
6810   // need the visible range of the target element.
6811   if (snapArea.width > aSnapInfo.mSnapportSize.width) {
6812     aSnapInfo.mXRangeWiderThanSnapport.AppendElement(
6813         ScrollSnapInfo::ScrollSnapRange(snapArea.X(), snapArea.XMost()));
6814   }
6815   if (snapArea.height > aSnapInfo.mSnapportSize.height) {
6816     aSnapInfo.mYRangeWiderThanSnapport.AppendElement(
6817         ScrollSnapInfo::ScrollSnapRange(snapArea.Y(), snapArea.YMost()));
6818   }
6819 
6820   // Shift target rect position by the scroll padding to get the padded
6821   // position thus we don't need to take account scroll-padding values in
6822   // ScrollSnapUtils::GetSnapPointForDestination() when it gets called from
6823   // the compositor thread.
6824   snapArea.y -= aScrollPadding.top;
6825   snapArea.x -= aScrollPadding.left;
6826 
6827   LogicalRect logicalTargetRect(aWritingModeOnScroller, snapArea,
6828                                 aSnapInfo.mSnapportSize);
6829   LogicalSize logicalSnapportRect(aWritingModeOnScroller,
6830                                   aSnapInfo.mSnapportSize);
6831 
6832   Maybe<nscoord> blockDirectionPosition;
6833   Maybe<nscoord> inlineDirectionPosition;
6834 
6835   const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
6836   nscoord containerBSize = logicalSnapportRect.BSize(aWritingModeOnScroller);
6837   switch (styleDisplay->mScrollSnapAlign.block) {
6838     case StyleScrollSnapAlignKeyword::None:
6839       break;
6840     case StyleScrollSnapAlignKeyword::Start:
6841       blockDirectionPosition.emplace(
6842           aWritingModeOnScroller.IsVerticalRL()
6843               ? -logicalTargetRect.BStart(aWritingModeOnScroller)
6844               : logicalTargetRect.BStart(aWritingModeOnScroller));
6845       break;
6846     case StyleScrollSnapAlignKeyword::End:
6847       if (aWritingModeOnScroller.IsVerticalRL()) {
6848         blockDirectionPosition.emplace(
6849             containerBSize - logicalTargetRect.BEnd(aWritingModeOnScroller));
6850       } else {
6851         // What we need here is the scroll position instead of the snap position
6852         // itself, so we need, for example, the top edge of the scroll port
6853         // on horizontal-tb when the frame is positioned at the bottom edge of
6854         // the scroll port. For this reason we subtract containerBSize from
6855         // BEnd of the target.
6856         blockDirectionPosition.emplace(
6857             logicalTargetRect.BEnd(aWritingModeOnScroller) - containerBSize);
6858       }
6859       break;
6860     case StyleScrollSnapAlignKeyword::Center: {
6861       nscoord targetCenter = (logicalTargetRect.BStart(aWritingModeOnScroller) +
6862                               logicalTargetRect.BEnd(aWritingModeOnScroller)) /
6863                              2;
6864       nscoord halfSnapportSize = containerBSize / 2;
6865       // Get the center of the target to align with the center of the snapport
6866       // depending on direction.
6867       if (aWritingModeOnScroller.IsVerticalRL()) {
6868         blockDirectionPosition.emplace(halfSnapportSize - targetCenter);
6869       } else {
6870         blockDirectionPosition.emplace(targetCenter - halfSnapportSize);
6871       }
6872       break;
6873     }
6874   }
6875 
6876   nscoord containerISize = logicalSnapportRect.ISize(aWritingModeOnScroller);
6877   switch (styleDisplay->mScrollSnapAlign.inline_) {
6878     case StyleScrollSnapAlignKeyword::None:
6879       break;
6880     case StyleScrollSnapAlignKeyword::Start:
6881       inlineDirectionPosition.emplace(
6882           aWritingModeOnScroller.IsInlineReversed()
6883               ? -logicalTargetRect.IStart(aWritingModeOnScroller)
6884               : logicalTargetRect.IStart(aWritingModeOnScroller));
6885       break;
6886     case StyleScrollSnapAlignKeyword::End:
6887       if (aWritingModeOnScroller.IsInlineReversed()) {
6888         inlineDirectionPosition.emplace(
6889             containerISize - logicalTargetRect.IEnd(aWritingModeOnScroller));
6890       } else {
6891         // Same as above BEnd case, we subtract containerISize.
6892         inlineDirectionPosition.emplace(
6893             logicalTargetRect.IEnd(aWritingModeOnScroller) - containerISize);
6894       }
6895       break;
6896     case StyleScrollSnapAlignKeyword::Center: {
6897       nscoord targetCenter = (logicalTargetRect.IStart(aWritingModeOnScroller) +
6898                               logicalTargetRect.IEnd(aWritingModeOnScroller)) /
6899                              2;
6900       nscoord halfSnapportSize = containerISize / 2;
6901       // Get the center of the target to align with the center of the snapport
6902       // depending on direction.
6903       if (aWritingModeOnScroller.IsInlineReversed()) {
6904         inlineDirectionPosition.emplace(halfSnapportSize - targetCenter);
6905       } else {
6906         inlineDirectionPosition.emplace(targetCenter - halfSnapportSize);
6907       }
6908       break;
6909     }
6910   }
6911 
6912   if (inlineDirectionPosition) {
6913     (aWritingModeOnScroller.IsVertical() ? aSnapInfo.mSnapPositionY
6914                                          : aSnapInfo.mSnapPositionX)
6915         .AppendElement(inlineDirectionPosition.value());
6916   }
6917 
6918   if (blockDirectionPosition) {
6919     (aWritingModeOnScroller.IsVertical() ? aSnapInfo.mSnapPositionX
6920                                          : aSnapInfo.mSnapPositionY)
6921         .AppendElement(blockDirectionPosition.value());
6922   }
6923 }
6924 
6925 /**
6926  * Collect the scroll positions corresponding to snap positions of frames in the
6927  * subtree rooted at |aFrame|, relative to |aScrolledFrame|, into |aSnapInfo|.
6928  * If |aSnapport| is given, elements outside of the range of |aSnapport| will be
6929  * ignored.
6930  */
CollectScrollPositionsForSnap(nsIFrame * aFrame,nsIFrame * aScrolledFrame,const nsRect & aScrolledRect,const nsMargin & aScrollPadding,const Maybe<nsRect> & aSnapport,WritingMode aWritingModeOnScroller,ScrollSnapInfo & aSnapInfo)6931 static void CollectScrollPositionsForSnap(
6932     nsIFrame* aFrame, nsIFrame* aScrolledFrame, const nsRect& aScrolledRect,
6933     const nsMargin& aScrollPadding, const Maybe<nsRect>& aSnapport,
6934     WritingMode aWritingModeOnScroller, ScrollSnapInfo& aSnapInfo) {
6935   // Snap positions only affect the nearest ancestor scroll container on the
6936   // element's containing block chain.
6937   nsIScrollableFrame* sf = do_QueryFrame(aFrame);
6938   if (sf) {
6939     return;
6940   }
6941 
6942   for (const auto& childList : aFrame->ChildLists()) {
6943     for (nsIFrame* f : childList.mList) {
6944       const nsStyleDisplay* styleDisplay = f->StyleDisplay();
6945       if (styleDisplay->mScrollSnapAlign.inline_ !=
6946               StyleScrollSnapAlignKeyword::None ||
6947           styleDisplay->mScrollSnapAlign.block !=
6948               StyleScrollSnapAlignKeyword::None) {
6949         AppendScrollPositionsForSnap(f, aScrolledFrame, aScrolledRect,
6950                                      aScrollPadding, aSnapport,
6951                                      aWritingModeOnScroller, aSnapInfo);
6952       }
6953       CollectScrollPositionsForSnap(f, aScrolledFrame, aScrolledRect,
6954                                     aScrollPadding, aSnapport,
6955                                     aWritingModeOnScroller, aSnapInfo);
6956     }
6957   }
6958 }
6959 
ResolveScrollPaddingStyleValue(const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto> & aScrollPaddingStyle,Side aSide,const nsSize & aScrollPortSize)6960 static nscoord ResolveScrollPaddingStyleValue(
6961     const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto>&
6962         aScrollPaddingStyle,
6963     Side aSide, const nsSize& aScrollPortSize) {
6964   if (aScrollPaddingStyle.Get(aSide).IsAuto()) {
6965     // https://drafts.csswg.org/css-scroll-snap-1/#valdef-scroll-padding-auto
6966     return 0;
6967   }
6968 
6969   nscoord percentageBasis;
6970   switch (aSide) {
6971     case eSideTop:
6972     case eSideBottom:
6973       percentageBasis = aScrollPortSize.height;
6974       break;
6975     case eSideLeft:
6976     case eSideRight:
6977       percentageBasis = aScrollPortSize.width;
6978       break;
6979   }
6980 
6981   return aScrollPaddingStyle.Get(aSide).AsLengthPercentage().Resolve(
6982       percentageBasis);
6983 }
6984 
ResolveScrollPaddingStyle(const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto> & aScrollPaddingStyle,const nsSize & aScrollPortSize)6985 static nsMargin ResolveScrollPaddingStyle(
6986     const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto>&
6987         aScrollPaddingStyle,
6988     const nsSize& aScrollPortSize) {
6989   return nsMargin(ResolveScrollPaddingStyleValue(aScrollPaddingStyle, eSideTop,
6990                                                  aScrollPortSize),
6991                   ResolveScrollPaddingStyleValue(aScrollPaddingStyle,
6992                                                  eSideRight, aScrollPortSize),
6993                   ResolveScrollPaddingStyleValue(aScrollPaddingStyle,
6994                                                  eSideBottom, aScrollPortSize),
6995                   ResolveScrollPaddingStyleValue(aScrollPaddingStyle, eSideLeft,
6996                                                  aScrollPortSize));
6997 }
6998 
GetScrollPadding() const6999 nsMargin ScrollFrameHelper::GetScrollPadding() const {
7000   nsIFrame* styleFrame = GetFrameForStyle();
7001   if (!styleFrame) {
7002     return nsMargin();
7003   }
7004 
7005   // The spec says percentage values are relative to the scroll port size.
7006   // https://drafts.csswg.org/css-scroll-snap-1/#scroll-padding
7007   return ResolveScrollPaddingStyle(styleFrame->StylePadding()->mScrollPadding,
7008                                    GetScrollPortRect().Size());
7009 }
7010 
ComputeScrollSnapInfo(const Maybe<nsPoint> & aDestination) const7011 layers::ScrollSnapInfo ScrollFrameHelper::ComputeScrollSnapInfo(
7012     const Maybe<nsPoint>& aDestination) const {
7013   ScrollSnapInfo result;
7014 
7015   nsIFrame* scrollSnapFrame = GetFrameForStyle();
7016   if (!scrollSnapFrame) {
7017     return result;
7018   }
7019 
7020   const nsStyleDisplay* disp = scrollSnapFrame->StyleDisplay();
7021   if (disp->mScrollSnapType.strictness == StyleScrollSnapStrictness::None) {
7022     // We won't be snapping, short-circuit the computation.
7023     return result;
7024   }
7025 
7026   WritingMode writingMode = mOuter->GetWritingMode();
7027   result.InitializeScrollSnapStrictness(writingMode, disp);
7028 
7029   nsRect snapport = GetScrollPortRect();
7030   nsMargin scrollPadding = GetScrollPadding();
7031 
7032   Maybe<nsRect> snapportOnDestination;
7033   if (aDestination) {
7034     snapport.MoveTo(aDestination.value());
7035     snapport.Deflate(scrollPadding);
7036     snapportOnDestination.emplace(snapport);
7037   } else {
7038     snapport.Deflate(scrollPadding);
7039   }
7040 
7041   result.mSnapportSize = snapport.Size();
7042   CollectScrollPositionsForSnap(mScrolledFrame, mScrolledFrame,
7043                                 GetScrolledRect(), scrollPadding,
7044                                 snapportOnDestination, writingMode, result);
7045   return result;
7046 }
7047 
GetScrollSnapInfo(const Maybe<nsPoint> & aDestination) const7048 layers::ScrollSnapInfo ScrollFrameHelper::GetScrollSnapInfo(
7049     const Maybe<nsPoint>& aDestination) const {
7050   // TODO(botond): Should we cache it?
7051   return ComputeScrollSnapInfo(aDestination);
7052 }
7053 
GetSnapPointForDestination(ScrollUnit aUnit,const nsPoint & aStartPos,nsPoint & aDestination)7054 bool ScrollFrameHelper::GetSnapPointForDestination(ScrollUnit aUnit,
7055                                                    const nsPoint& aStartPos,
7056                                                    nsPoint& aDestination) {
7057   Maybe<nsPoint> snapPoint = ScrollSnapUtils::GetSnapPointForDestination(
7058       GetScrollSnapInfo(Some(aDestination)), aUnit, GetLayoutScrollRange(),
7059       aStartPos, aDestination);
7060   if (snapPoint) {
7061     aDestination = snapPoint.ref();
7062     return true;
7063   }
7064   return false;
7065 }
7066 
UsesOverlayScrollbars() const7067 bool ScrollFrameHelper::UsesOverlayScrollbars() const {
7068   return Document::UseOverlayScrollbars(mOuter->PresShell()->GetDocument());
7069 }
7070 
DragScroll(WidgetEvent * aEvent)7071 bool ScrollFrameHelper::DragScroll(WidgetEvent* aEvent) {
7072   // Dragging is allowed while within a 20 pixel border. Note that device pixels
7073   // are used so that the same margin is used even when zoomed in or out.
7074   nscoord margin = 20 * mOuter->PresContext()->AppUnitsPerDevPixel();
7075 
7076   // Don't drag scroll for small scrollareas.
7077   if (mScrollPort.width < margin * 2 || mScrollPort.height < margin * 2) {
7078     return false;
7079   }
7080 
7081   // If willScroll is computed as false, then the frame is already scrolled as
7082   // far as it can go in both directions. Return false so that an ancestor
7083   // scrollframe can scroll instead.
7084   bool willScroll = false;
7085   nsPoint pnt =
7086       nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{mOuter});
7087   nsPoint scrollPoint = GetScrollPosition();
7088   nsRect rangeRect = GetLayoutScrollRange();
7089 
7090   // Only drag scroll when a scrollbar is present.
7091   nsPoint offset;
7092   if (mHasHorizontalScrollbar) {
7093     if (pnt.x >= mScrollPort.x && pnt.x <= mScrollPort.x + margin) {
7094       offset.x = -margin;
7095       if (scrollPoint.x > 0) {
7096         willScroll = true;
7097       }
7098     } else if (pnt.x >= mScrollPort.XMost() - margin &&
7099                pnt.x <= mScrollPort.XMost()) {
7100       offset.x = margin;
7101       if (scrollPoint.x < rangeRect.width) {
7102         willScroll = true;
7103       }
7104     }
7105   }
7106 
7107   if (mHasVerticalScrollbar) {
7108     if (pnt.y >= mScrollPort.y && pnt.y <= mScrollPort.y + margin) {
7109       offset.y = -margin;
7110       if (scrollPoint.y > 0) {
7111         willScroll = true;
7112       }
7113     } else if (pnt.y >= mScrollPort.YMost() - margin &&
7114                pnt.y <= mScrollPort.YMost()) {
7115       offset.y = margin;
7116       if (scrollPoint.y < rangeRect.height) {
7117         willScroll = true;
7118       }
7119     }
7120   }
7121 
7122   if (offset.x || offset.y) {
7123     ScrollTo(GetScrollPosition() + offset, ScrollMode::Normal,
7124              nsGkAtoms::other);
7125   }
7126 
7127   return willScroll;
7128 }
7129 
GetSliderFrame(nsIFrame * aScrollbarFrame)7130 static nsSliderFrame* GetSliderFrame(nsIFrame* aScrollbarFrame) {
7131   if (!aScrollbarFrame) {
7132     return nullptr;
7133   }
7134 
7135   for (const auto& childList : aScrollbarFrame->ChildLists()) {
7136     for (nsIFrame* frame : childList.mList) {
7137       if (nsSliderFrame* sliderFrame = do_QueryFrame(frame)) {
7138         return sliderFrame;
7139       }
7140     }
7141   }
7142   return nullptr;
7143 }
7144 
AsyncScrollbarDragInitiated(uint64_t aDragBlockId,nsIFrame * aScrollbar)7145 static void AsyncScrollbarDragInitiated(uint64_t aDragBlockId,
7146                                         nsIFrame* aScrollbar) {
7147   if (nsSliderFrame* sliderFrame = GetSliderFrame(aScrollbar)) {
7148     sliderFrame->AsyncScrollbarDragInitiated(aDragBlockId);
7149   }
7150 }
7151 
AsyncScrollbarDragInitiated(uint64_t aDragBlockId,ScrollDirection aDirection)7152 void ScrollFrameHelper::AsyncScrollbarDragInitiated(
7153     uint64_t aDragBlockId, ScrollDirection aDirection) {
7154   switch (aDirection) {
7155     case ScrollDirection::eVertical:
7156       ::AsyncScrollbarDragInitiated(aDragBlockId, mVScrollbarBox);
7157       break;
7158     case ScrollDirection::eHorizontal:
7159       ::AsyncScrollbarDragInitiated(aDragBlockId, mHScrollbarBox);
7160       break;
7161   }
7162 }
7163 
AsyncScrollbarDragRejected(nsIFrame * aScrollbar)7164 static void AsyncScrollbarDragRejected(nsIFrame* aScrollbar) {
7165   if (nsSliderFrame* sliderFrame = GetSliderFrame(aScrollbar)) {
7166     sliderFrame->AsyncScrollbarDragRejected();
7167   }
7168 }
7169 
AsyncScrollbarDragRejected()7170 void ScrollFrameHelper::AsyncScrollbarDragRejected() {
7171   // We don't get told which scrollbar requested the async drag,
7172   // so we notify both.
7173   ::AsyncScrollbarDragRejected(mHScrollbarBox);
7174   ::AsyncScrollbarDragRejected(mVScrollbarBox);
7175 }
7176 
ApzSmoothScrollTo(const nsPoint & aDestination,nsAtom * aOrigin)7177 void ScrollFrameHelper::ApzSmoothScrollTo(const nsPoint& aDestination,
7178                                           nsAtom* aOrigin) {
7179   if (mApzSmoothScrollDestination == Some(aDestination) &&
7180       mScrollGeneration == sScrollGenerationCounter) {
7181     // If we already sent APZ a smooth-scroll request to this
7182     // destination with this generation (i.e. it was the last request
7183     // we sent), then don't send another one because it is redundant.
7184     // This is to avoid a scenario where pages do repeated scrollBy
7185     // calls, incrementing the generation counter, and blocking APZ from
7186     // syncing the scroll offset back to the main thread.
7187     // Note that if we get two smooth-scroll requests to the same
7188     // destination with some other scroll in between,
7189     // mApzSmoothScrollDestination will get reset to Nothing() and so
7190     // we shouldn't have the problem where this check discards a
7191     // legitimate smooth-scroll.
7192     // Note: if there are two separate scrollframes both getting smooth
7193     // scrolled at the same time, sScrollGenerationCounter can get
7194     // incremented and this early-exit won't get taken. Bug 1231177 is
7195     // on file for this.
7196     return;
7197   }
7198 
7199   // The animation will be handled in the compositor, pass the
7200   // information needed to start the animation and skip the main-thread
7201   // animation for this scroll.
7202   mLastSmoothScrollOrigin = aOrigin;
7203   mApzSmoothScrollDestination = Some(aDestination);
7204   mScrollGeneration = ++sScrollGenerationCounter;
7205 
7206   if (!nsLayoutUtils::HasDisplayPort(mOuter->GetContent())) {
7207     // If this frame doesn't have a displayport then there won't be an
7208     // APZC instance for it and so there won't be anything to process
7209     // this smooth scroll request. We should set a displayport on this
7210     // frame to force an APZC which can handle the request.
7211     nsLayoutUtils::CalculateAndSetDisplayPortMargins(
7212         mOuter->GetScrollTargetFrame(), nsLayoutUtils::RepaintMode::Repaint);
7213     nsIFrame* frame = do_QueryFrame(mOuter->GetScrollTargetFrame());
7214     nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame);
7215   }
7216 
7217   // Schedule a paint to ensure that the frame metrics get updated on
7218   // the compositor thread.
7219   mOuter->SchedulePaint();
7220 }
7221 
SmoothScrollVisual(const nsPoint & aVisualViewportOffset,FrameMetrics::ScrollOffsetUpdateType aUpdateType)7222 bool ScrollFrameHelper::SmoothScrollVisual(
7223     const nsPoint& aVisualViewportOffset,
7224     FrameMetrics::ScrollOffsetUpdateType aUpdateType) {
7225   bool canDoApzSmoothScroll =
7226       StaticPrefs::layout_css_scroll_behavior_enabled() &&
7227       nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && WantAsyncScroll();
7228   if (!canDoApzSmoothScroll) {
7229     return false;
7230   }
7231 
7232   // Clamp the destination to the visual scroll range.
7233   // There is a potential issue here, where |mDestination| is usually
7234   // clamped to the layout scroll range, and so e.g. a subsequent
7235   // window.scrollBy() may have an undesired effect. However, as this function
7236   // is only called internally, this should not be a problem in practice.
7237   // If it turns out to be, the fix would be:
7238   //   - add a new "destination" field that doesn't have to be clamped to
7239   //     the layout scroll range
7240   //   - clamp mDestination to the layout scroll range here
7241   //   - make sure ComputeScrollMetadata() picks up the former as the
7242   //     smooth scroll destination to send to APZ.
7243   mDestination = GetVisualScrollRange().ClampPoint(aVisualViewportOffset);
7244 
7245   // Perform the scroll.
7246   ApzSmoothScrollTo(mDestination, aUpdateType == FrameMetrics::eRestore
7247                                       ? nsGkAtoms::restore
7248                                       : nsGkAtoms::other);
7249   return true;
7250 }
7251 
IsSmoothScroll(dom::ScrollBehavior aBehavior) const7252 bool ScrollFrameHelper::IsSmoothScroll(dom::ScrollBehavior aBehavior) const {
7253   if (aBehavior == dom::ScrollBehavior::Smooth) {
7254     return true;
7255   }
7256 
7257   nsIFrame* styleFrame = GetFrameForStyle();
7258   if (!styleFrame) {
7259     return false;
7260   }
7261   return (aBehavior == dom::ScrollBehavior::Auto &&
7262           styleFrame->StyleDisplay()->mScrollBehavior ==
7263               StyleScrollBehavior::Smooth);
7264 }
7265