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