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