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, ©OfVisibleRect,
3745 ©OfDirtyRect,
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