layout/generic/nsGfxScrollFrame.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:35e3c3d441e0
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 "base/compiler_specific.h"
11 #include "DisplayItemClip.h"
12 #include "nsCOMPtr.h"
13 #include "nsPresContext.h"
14 #include "nsView.h"
15 #include "nsIScrollable.h"
16 #include "nsContainerFrame.h"
17 #include "nsGkAtoms.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsContentList.h"
20 #include "nsIDocumentInlines.h"
21 #include "nsFontMetrics.h"
22 #include "nsBoxLayoutState.h"
23 #include "nsINodeInfo.h"
24 #include "nsScrollbarFrame.h"
25 #include "nsIScrollbarMediator.h"
26 #include "nsITextControlFrame.h"
27 #include "nsIDOMHTMLTextAreaElement.h"
28 #include "nsNodeInfoManager.h"
29 #include "nsContentCreatorFunctions.h"
30 #include "nsAutoPtr.h"
31 #include "nsPresState.h"
32 #include "nsIHTMLDocument.h"
33 #include "nsContentUtils.h"
34 #include "nsLayoutUtils.h"
35 #include "nsBidiUtils.h"
36 #include "mozilla/ContentEvents.h"
37 #include "mozilla/EventDispatcher.h"
38 #include "mozilla/Preferences.h"
39 #include "mozilla/LookAndFeel.h"
40 #include "mozilla/dom/Element.h"
41 #include <stdint.h>
42 #include "mozilla/MathAlgorithms.h"
43 #include "FrameLayerBuilder.h"
44 #include "nsSMILKeySpline.h"
45 #include "nsSubDocumentFrame.h"
46 #include "nsSVGOuterSVGFrame.h"
47 #include "mozilla/Attributes.h"
48 #include "ScrollbarActivity.h"
49 #include "nsRefreshDriver.h"
50 #include "nsThemeConstants.h"
51 #include "nsSVGIntegrationUtils.h"
52 #include "nsIScrollPositionListener.h"
53 #include "StickyScrollContainer.h"
54 #include "nsIFrameInlines.h"
55 #include "gfxPrefs.h"
56 #include <algorithm>
57 #include <cstdlib> // for std::abs(int/long)
58 #include <cmath> // for std::abs(float/double)
59
60 using namespace mozilla;
61 using namespace mozilla::dom;
62 using namespace mozilla::layout;
63
64 //----------------------------------------------------------------------
65
66 //----------nsHTMLScrollFrame-------------------------------------------
67
68 nsIFrame*
69 NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot)
70 {
71 return new (aPresShell) nsHTMLScrollFrame(aPresShell, aContext, aIsRoot);
72 }
73
74 NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)
75
76 nsHTMLScrollFrame::nsHTMLScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, bool aIsRoot)
77 : nsContainerFrame(aContext),
78 mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot)
79 {
80 }
81
82 void
83 nsHTMLScrollFrame::ScrollbarActivityStarted() const
84 {
85 if (mHelper.mScrollbarActivity) {
86 mHelper.mScrollbarActivity->ActivityStarted();
87 }
88 }
89
90 void
91 nsHTMLScrollFrame::ScrollbarActivityStopped() const
92 {
93 if (mHelper.mScrollbarActivity) {
94 mHelper.mScrollbarActivity->ActivityStopped();
95 }
96 }
97
98 nsresult
99 nsHTMLScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
100 {
101 return mHelper.CreateAnonymousContent(aElements);
102 }
103
104 void
105 nsHTMLScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
106 uint32_t aFilter)
107 {
108 mHelper.AppendAnonymousContentTo(aElements, aFilter);
109 }
110
111 void
112 nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot)
113 {
114 DestroyAbsoluteFrames(aDestructRoot);
115 mHelper.Destroy();
116 nsContainerFrame::DestroyFrom(aDestructRoot);
117 }
118
119 nsresult
120 nsHTMLScrollFrame::SetInitialChildList(ChildListID aListID,
121 nsFrameList& aChildList)
122 {
123 nsresult rv = nsContainerFrame::SetInitialChildList(aListID, aChildList);
124 mHelper.ReloadChildFrames();
125 return rv;
126 }
127
128
129 nsresult
130 nsHTMLScrollFrame::AppendFrames(ChildListID aListID,
131 nsFrameList& aFrameList)
132 {
133 NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
134 mFrames.AppendFrames(nullptr, aFrameList);
135 mHelper.ReloadChildFrames();
136 return NS_OK;
137 }
138
139 nsresult
140 nsHTMLScrollFrame::InsertFrames(ChildListID aListID,
141 nsIFrame* aPrevFrame,
142 nsFrameList& aFrameList)
143 {
144 NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
145 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
146 "inserting after sibling frame with different parent");
147 mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
148 mHelper.ReloadChildFrames();
149 return NS_OK;
150 }
151
152 nsresult
153 nsHTMLScrollFrame::RemoveFrame(ChildListID aListID,
154 nsIFrame* aOldFrame)
155 {
156 NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
157 mFrames.DestroyFrame(aOldFrame);
158 mHelper.ReloadChildFrames();
159 return NS_OK;
160 }
161
162 nsSplittableType
163 nsHTMLScrollFrame::GetSplittableType() const
164 {
165 return NS_FRAME_NOT_SPLITTABLE;
166 }
167
168 nsIAtom*
169 nsHTMLScrollFrame::GetType() const
170 {
171 return nsGkAtoms::scrollFrame;
172 }
173
174 /**
175 HTML scrolling implementation
176
177 All other things being equal, we prefer layouts with fewer scrollbars showing.
178 */
179
180 struct MOZ_STACK_CLASS ScrollReflowState {
181 const nsHTMLReflowState& mReflowState;
182 nsBoxLayoutState mBoxState;
183 ScrollbarStyles mStyles;
184 nsMargin mComputedBorder;
185
186 // === Filled in by ReflowScrolledFrame ===
187 nsOverflowAreas mContentsOverflowAreas;
188 bool mReflowedContentsWithHScrollbar;
189 bool mReflowedContentsWithVScrollbar;
190
191 // === Filled in when TryLayout succeeds ===
192 // The size of the inside-border area
193 nsSize mInsideBorderSize;
194 // Whether we decided to show the horizontal scrollbar
195 bool mShowHScrollbar;
196 // Whether we decided to show the vertical scrollbar
197 bool mShowVScrollbar;
198
199 ScrollReflowState(nsIScrollableFrame* aFrame,
200 const nsHTMLReflowState& aState) :
201 mReflowState(aState),
202 // mBoxState is just used for scrollbars so we don't need to
203 // worry about the reflow depth here
204 mBoxState(aState.frame->PresContext(), aState.rendContext, 0),
205 mStyles(aFrame->GetScrollbarStyles()) {
206 }
207 };
208
209 // XXXldb Can this go away?
210 static nsSize ComputeInsideBorderSize(ScrollReflowState* aState,
211 const nsSize& aDesiredInsideBorderSize)
212 {
213 // aDesiredInsideBorderSize is the frame size; i.e., it includes
214 // borders and padding (but the scrolled child doesn't have
215 // borders). The scrolled child has the same padding as us.
216 nscoord contentWidth = aState->mReflowState.ComputedWidth();
217 if (contentWidth == NS_UNCONSTRAINEDSIZE) {
218 contentWidth = aDesiredInsideBorderSize.width -
219 aState->mReflowState.ComputedPhysicalPadding().LeftRight();
220 }
221 nscoord contentHeight = aState->mReflowState.ComputedHeight();
222 if (contentHeight == NS_UNCONSTRAINEDSIZE) {
223 contentHeight = aDesiredInsideBorderSize.height -
224 aState->mReflowState.ComputedPhysicalPadding().TopBottom();
225 }
226
227 contentWidth = aState->mReflowState.ApplyMinMaxWidth(contentWidth);
228 contentHeight = aState->mReflowState.ApplyMinMaxHeight(contentHeight);
229 return nsSize(contentWidth + aState->mReflowState.ComputedPhysicalPadding().LeftRight(),
230 contentHeight + aState->mReflowState.ComputedPhysicalPadding().TopBottom());
231 }
232
233 static void
234 GetScrollbarMetrics(nsBoxLayoutState& aState, nsIFrame* aBox, nsSize* aMin,
235 nsSize* aPref, bool aVertical)
236 {
237 NS_ASSERTION(aState.GetRenderingContext(),
238 "Must have rendering context in layout state for size "
239 "computations");
240
241 if (aMin) {
242 *aMin = aBox->GetMinSize(aState);
243 nsBox::AddMargin(aBox, *aMin);
244 if (aMin->width < 0) {
245 aMin->width = 0;
246 }
247 if (aMin->height < 0) {
248 aMin->height = 0;
249 }
250 }
251
252 if (aPref) {
253 *aPref = aBox->GetPrefSize(aState);
254 nsBox::AddMargin(aBox, *aPref);
255 if (aPref->width < 0) {
256 aPref->width = 0;
257 }
258 if (aPref->height < 0) {
259 aPref->height = 0;
260 }
261 }
262 }
263
264 /**
265 * Assuming that we know the metrics for our wrapped frame and
266 * whether the horizontal and/or vertical scrollbars are present,
267 * compute the resulting layout and return true if the layout is
268 * consistent. If the layout is consistent then we fill in the
269 * computed fields of the ScrollReflowState.
270 *
271 * The layout is consistent when both scrollbars are showing if and only
272 * if they should be showing. A horizontal scrollbar should be showing if all
273 * following conditions are met:
274 * 1) the style is not HIDDEN
275 * 2) our inside-border height is at least the scrollbar height (i.e., the
276 * scrollbar fits vertically)
277 * 3) our scrollport width (the inside-border width minus the width allocated for a
278 * vertical scrollbar, if showing) is at least the scrollbar's min-width
279 * (i.e., the scrollbar fits horizontally)
280 * 4) the style is SCROLL, or the kid's overflow-area XMost is
281 * greater than the scrollport width
282 *
283 * @param aForce if true, then we just assume the layout is consistent.
284 */
285 bool
286 nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState,
287 nsHTMLReflowMetrics* aKidMetrics,
288 bool aAssumeHScroll, bool aAssumeVScroll,
289 bool aForce, nsresult* aResult)
290 {
291 *aResult = NS_OK;
292
293 if ((aState->mStyles.mVertical == NS_STYLE_OVERFLOW_HIDDEN && aAssumeVScroll) ||
294 (aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && aAssumeHScroll)) {
295 NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
296 return false;
297 }
298
299 if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar ||
300 (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar &&
301 ScrolledContentDependsOnHeight(aState))) {
302 nsresult rv = ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll,
303 aKidMetrics, false);
304 if (NS_FAILED(rv)) {
305 *aResult = rv;
306 return false;
307 }
308 }
309
310 nsSize vScrollbarMinSize(0, 0);
311 nsSize vScrollbarPrefSize(0, 0);
312 if (mHelper.mVScrollbarBox) {
313 GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox,
314 &vScrollbarMinSize,
315 aAssumeVScroll ? &vScrollbarPrefSize : nullptr, true);
316 }
317 nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0;
318 nscoord vScrollbarMinHeight = aAssumeVScroll ? vScrollbarMinSize.height : 0;
319
320 nsSize hScrollbarMinSize(0, 0);
321 nsSize hScrollbarPrefSize(0, 0);
322 if (mHelper.mHScrollbarBox) {
323 GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox,
324 &hScrollbarMinSize,
325 aAssumeHScroll ? &hScrollbarPrefSize : nullptr, false);
326 }
327 nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0;
328 nscoord hScrollbarMinWidth = aAssumeHScroll ? hScrollbarMinSize.width : 0;
329
330 // First, compute our inside-border size and scrollport size
331 // XXXldb Can we depend more on ComputeSize here?
332 nsSize desiredInsideBorderSize;
333 desiredInsideBorderSize.width = vScrollbarDesiredWidth +
334 std::max(aKidMetrics->Width(), hScrollbarMinWidth);
335 desiredInsideBorderSize.height = hScrollbarDesiredHeight +
336 std::max(aKidMetrics->Height(), vScrollbarMinHeight);
337 aState->mInsideBorderSize =
338 ComputeInsideBorderSize(aState, desiredInsideBorderSize);
339 nsSize scrollPortSize = nsSize(std::max(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth),
340 std::max(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight));
341
342 if (!aForce) {
343 nsRect scrolledRect =
344 mHelper.GetScrolledRectInternal(aState->mContentsOverflowAreas.ScrollableOverflow(),
345 scrollPortSize);
346 nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1);
347
348 // If the style is HIDDEN then we already know that aAssumeHScroll is false
349 if (aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
350 bool wantHScrollbar =
351 aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL ||
352 scrolledRect.XMost() >= scrollPortSize.width + oneDevPixel ||
353 scrolledRect.x <= -oneDevPixel;
354 if (scrollPortSize.width < hScrollbarMinSize.width)
355 wantHScrollbar = false;
356 if (wantHScrollbar != aAssumeHScroll)
357 return false;
358 }
359
360 // If the style is HIDDEN then we already know that aAssumeVScroll is false
361 if (aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) {
362 bool wantVScrollbar =
363 aState->mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL ||
364 scrolledRect.YMost() >= scrollPortSize.height + oneDevPixel ||
365 scrolledRect.y <= -oneDevPixel;
366 if (scrollPortSize.height < vScrollbarMinSize.height)
367 wantVScrollbar = false;
368 if (wantVScrollbar != aAssumeVScroll)
369 return false;
370 }
371 }
372
373 nscoord vScrollbarActualWidth = aState->mInsideBorderSize.width - scrollPortSize.width;
374
375 aState->mShowHScrollbar = aAssumeHScroll;
376 aState->mShowVScrollbar = aAssumeVScroll;
377 nsPoint scrollPortOrigin(aState->mComputedBorder.left,
378 aState->mComputedBorder.top);
379 if (!mHelper.IsScrollbarOnRight()) {
380 scrollPortOrigin.x += vScrollbarActualWidth;
381 }
382 mHelper.mScrollPort = nsRect(scrollPortOrigin, scrollPortSize);
383 return true;
384 }
385
386 bool
387 nsHTMLScrollFrame::ScrolledContentDependsOnHeight(ScrollReflowState* aState)
388 {
389 // Return true if ReflowScrolledFrame is going to do something different
390 // based on the presence of a horizontal scrollbar.
391 return (mHelper.mScrolledFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) ||
392 aState->mReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE ||
393 aState->mReflowState.ComputedMinHeight() > 0 ||
394 aState->mReflowState.ComputedMaxHeight() != NS_UNCONSTRAINEDSIZE;
395 }
396
397 nsresult
398 nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState,
399 bool aAssumeHScroll,
400 bool aAssumeVScroll,
401 nsHTMLReflowMetrics* aMetrics,
402 bool aFirstPass)
403 {
404 // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should
405 // be OK
406 const nsMargin& padding = aState->mReflowState.ComputedPhysicalPadding();
407 nscoord availWidth = aState->mReflowState.ComputedWidth() + padding.LeftRight();
408
409 nscoord computedHeight = aState->mReflowState.ComputedHeight();
410 nscoord computedMinHeight = aState->mReflowState.ComputedMinHeight();
411 nscoord computedMaxHeight = aState->mReflowState.ComputedMaxHeight();
412 if (!ShouldPropagateComputedHeightToScrolledContent()) {
413 computedHeight = NS_UNCONSTRAINEDSIZE;
414 computedMinHeight = 0;
415 computedMaxHeight = NS_UNCONSTRAINEDSIZE;
416 }
417 if (aAssumeHScroll) {
418 nsSize hScrollbarPrefSize;
419 GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox,
420 nullptr, &hScrollbarPrefSize, false);
421 if (computedHeight != NS_UNCONSTRAINEDSIZE) {
422 computedHeight = std::max(0, computedHeight - hScrollbarPrefSize.height);
423 }
424 computedMinHeight = std::max(0, computedMinHeight - hScrollbarPrefSize.height);
425 if (computedMaxHeight != NS_UNCONSTRAINEDSIZE) {
426 computedMaxHeight = std::max(0, computedMaxHeight - hScrollbarPrefSize.height);
427 }
428 }
429
430 if (aAssumeVScroll) {
431 nsSize vScrollbarPrefSize;
432 GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox,
433 nullptr, &vScrollbarPrefSize, true);
434 availWidth = std::max(0, availWidth - vScrollbarPrefSize.width);
435 }
436
437 nsPresContext* presContext = PresContext();
438
439 // Pass false for aInit so we can pass in the correct padding.
440 nsHTMLReflowState kidReflowState(presContext, aState->mReflowState,
441 mHelper.mScrolledFrame,
442 nsSize(availWidth, NS_UNCONSTRAINEDSIZE),
443 -1, -1, nsHTMLReflowState::CALLER_WILL_INIT);
444 kidReflowState.Init(presContext, -1, -1, nullptr,
445 &padding);
446 kidReflowState.mFlags.mAssumingHScrollbar = aAssumeHScroll;
447 kidReflowState.mFlags.mAssumingVScrollbar = aAssumeVScroll;
448 kidReflowState.SetComputedHeight(computedHeight);
449 kidReflowState.ComputedMinHeight() = computedMinHeight;
450 kidReflowState.ComputedMaxHeight() = computedMaxHeight;
451
452 // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to
453 // reflect our assumptions while we reflow the child.
454 bool didHaveHorizontalScrollbar = mHelper.mHasHorizontalScrollbar;
455 bool didHaveVerticalScrollbar = mHelper.mHasVerticalScrollbar;
456 mHelper.mHasHorizontalScrollbar = aAssumeHScroll;
457 mHelper.mHasVerticalScrollbar = aAssumeVScroll;
458
459 nsReflowStatus status;
460 nsresult rv = ReflowChild(mHelper.mScrolledFrame, presContext, *aMetrics,
461 kidReflowState, 0, 0,
462 NS_FRAME_NO_MOVE_FRAME, status);
463
464 mHelper.mHasHorizontalScrollbar = didHaveHorizontalScrollbar;
465 mHelper.mHasVerticalScrollbar = didHaveVerticalScrollbar;
466
467 // Don't resize or position the view (if any) because we're going to resize
468 // it to the correct size anyway in PlaceScrollArea. Allowing it to
469 // resize here would size it to the natural height of the frame,
470 // which will usually be different from the scrollport height;
471 // invalidating the difference will cause unnecessary repainting.
472 FinishReflowChild(mHelper.mScrolledFrame, presContext,
473 *aMetrics, &kidReflowState, 0, 0,
474 NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW);
475
476 // XXX Some frames (e.g., nsObjectFrame, nsFrameFrame, nsTextFrame) don't bother
477 // setting their mOverflowArea. This is wrong because every frame should
478 // always set mOverflowArea. In fact nsObjectFrame and nsFrameFrame don't
479 // support the 'outline' property because of this. Rather than fix the world
480 // right now, just fix up the overflow area if necessary. Note that we don't
481 // check HasOverflowRect() because it could be set even though the
482 // overflow area doesn't include the frame bounds.
483 aMetrics->UnionOverflowAreasWithDesiredBounds();
484
485 if (MOZ_UNLIKELY(StyleDisplay()->mOverflowClipBox ==
486 NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) {
487 nsOverflowAreas childOverflow;
488 nsLayoutUtils::UnionChildOverflow(mHelper.mScrolledFrame, childOverflow);
489 nsRect childScrollableOverflow = childOverflow.ScrollableOverflow();
490 childScrollableOverflow.Inflate(padding);
491 nsRect contentArea = nsRect(0, 0, availWidth, computedHeight);
492 if (!contentArea.Contains(childScrollableOverflow)) {
493 aMetrics->mOverflowAreas.ScrollableOverflow() = childScrollableOverflow;
494 }
495 }
496
497 aState->mContentsOverflowAreas = aMetrics->mOverflowAreas;
498 aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
499 aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
500
501 return rv;
502 }
503
504 bool
505 nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowState& aState)
506 {
507 if (aState.mStyles.mHorizontal != NS_STYLE_OVERFLOW_AUTO)
508 // no guessing required
509 return aState.mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL;
510
511 return mHelper.mHasHorizontalScrollbar;
512 }
513
514 bool
515 nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowState& aState)
516 {
517 if (aState.mStyles.mVertical != NS_STYLE_OVERFLOW_AUTO)
518 // no guessing required
519 return aState.mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL;
520
521 // If we've had at least one non-initial reflow, then just assume
522 // the state of the vertical scrollbar will be what we determined
523 // last time.
524 if (mHelper.mHadNonInitialReflow) {
525 return mHelper.mHasVerticalScrollbar;
526 }
527
528 // If this is the initial reflow, guess false because usually
529 // we have very little content by then.
530 if (InInitialReflow())
531 return false;
532
533 if (mHelper.mIsRoot) {
534 nsIFrame *f = mHelper.mScrolledFrame->GetFirstPrincipalChild();
535 if (f && f->GetType() == nsGkAtoms::svgOuterSVGFrame &&
536 static_cast<nsSVGOuterSVGFrame*>(f)->VerticalScrollbarNotNeeded()) {
537 // Common SVG case - avoid a bad guess.
538 return false;
539 }
540 // Assume that there will be a scrollbar; it seems to me
541 // that 'most pages' do have a scrollbar, and anyway, it's cheaper
542 // to do an extra reflow for the pages that *don't* need a
543 // scrollbar (because on average they will have less content).
544 return true;
545 }
546
547 // For non-viewports, just guess that we don't need a scrollbar.
548 // XXX I wonder if statistically this is the right idea; I'm
549 // basically guessing that there are a lot of overflow:auto DIVs
550 // that get their intrinsic size and don't overflow
551 return false;
552 }
553
554 bool
555 nsHTMLScrollFrame::InInitialReflow() const
556 {
557 // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a
558 // root scrollframe. In that case we want to skip this clause altogether.
559 // The guess here is that there are lots of overflow:auto divs out there that
560 // end up auto-sizing so they don't overflow, and that the root basically
561 // always needs a scrollbar if it did last time we loaded this page (good
562 // assumption, because our initial reflow is no longer synchronous).
563 return !mHelper.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW);
564 }
565
566 nsresult
567 nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState,
568 const nsHTMLReflowMetrics& aDesiredSize)
569 {
570 nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.GetWritingMode(), aDesiredSize.mFlags);
571 nsresult rv = ReflowScrolledFrame(aState, GuessHScrollbarNeeded(*aState),
572 GuessVScrollbarNeeded(*aState), &kidDesiredSize, true);
573 NS_ENSURE_SUCCESS(rv, rv);
574
575 // There's an important special case ... if the child appears to fit
576 // in the inside-border rect (but overflows the scrollport), we
577 // should try laying it out without a vertical scrollbar. It will
578 // usually fit because making the available-width wider will not
579 // normally make the child taller. (The only situation I can think
580 // of is when you have a line containing %-width inline replaced
581 // elements whose percentages sum to more than 100%, so increasing
582 // the available width makes the line break where it was fitting
583 // before.) If we don't treat this case specially, then we will
584 // decide that showing scrollbars is OK because the content
585 // overflows when we're showing scrollbars and we won't try to
586 // remove the vertical scrollbar.
587
588 // Detecting when we enter this special case is important for when
589 // people design layouts that exactly fit the container "most of the
590 // time".
591
592 // XXX Is this check really sufficient to catch all the incremental cases
593 // where the ideal case doesn't have a scrollbar?
594 if ((aState->mReflowedContentsWithHScrollbar || aState->mReflowedContentsWithVScrollbar) &&
595 aState->mStyles.mVertical != NS_STYLE_OVERFLOW_SCROLL &&
596 aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) {
597 nsSize insideBorderSize =
598 ComputeInsideBorderSize(aState,
599 nsSize(kidDesiredSize.Width(), kidDesiredSize.Height()));
600 nsRect scrolledRect =
601 mHelper.GetScrolledRectInternal(kidDesiredSize.ScrollableOverflow(),
602 insideBorderSize);
603 if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
604 // Let's pretend we had no scrollbars coming in here
605 rv = ReflowScrolledFrame(aState, false, false,
606 &kidDesiredSize, false);
607 NS_ENSURE_SUCCESS(rv, rv);
608 }
609 }
610
611 // Try vertical scrollbar settings that leave the vertical scrollbar unchanged.
612 // Do this first because changing the vertical scrollbar setting is expensive,
613 // forcing a reflow always.
614
615 // Try leaving the horizontal scrollbar unchanged first. This will be more
616 // efficient.
617 if (TryLayout(aState, &kidDesiredSize, aState->mReflowedContentsWithHScrollbar,
618 aState->mReflowedContentsWithVScrollbar, false, &rv))
619 return NS_OK;
620 if (TryLayout(aState, &kidDesiredSize, !aState->mReflowedContentsWithHScrollbar,
621 aState->mReflowedContentsWithVScrollbar, false, &rv))
622 return NS_OK;
623
624 // OK, now try toggling the vertical scrollbar. The performance advantage
625 // of trying the status-quo horizontal scrollbar state
626 // does not exist here (we'll have to reflow due to the vertical scrollbar
627 // change), so always try no horizontal scrollbar first.
628 bool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar;
629 if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false, &rv))
630 return NS_OK;
631 if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false, &rv))
632 return NS_OK;
633
634 // OK, we're out of ideas. Try again enabling whatever scrollbars we can
635 // enable and force the layout to stick even if it's inconsistent.
636 // This just happens sometimes.
637 TryLayout(aState, &kidDesiredSize,
638 aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN,
639 aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN,
640 true, &rv);
641 return rv;
642 }
643
644 void
645 nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowState& aState,
646 const nsPoint& aScrollPosition)
647 {
648 nsIFrame *scrolledFrame = mHelper.mScrolledFrame;
649 // Set the x,y of the scrolled frame to the correct value
650 scrolledFrame->SetPosition(mHelper.mScrollPort.TopLeft() - aScrollPosition);
651
652 nsRect scrolledArea;
653 // Preserve the width or height of empty rects
654 nsSize portSize = mHelper.mScrollPort.Size();
655 nsRect scrolledRect =
656 mHelper.GetScrolledRectInternal(aState.mContentsOverflowAreas.ScrollableOverflow(),
657 portSize);
658 scrolledArea.UnionRectEdges(scrolledRect,
659 nsRect(nsPoint(0,0), portSize));
660
661 // Store the new overflow area. Note that this changes where an outline
662 // of the scrolled frame would be painted, but scrolled frames can't have
663 // outlines (the outline would go on this scrollframe instead).
664 // Using FinishAndStoreOverflow is needed so the overflow rect
665 // gets set correctly. It also messes with the overflow rect in the
666 // -moz-hidden-unscrollable case, but scrolled frames can't have
667 // 'overflow' either.
668 // This needs to happen before SyncFrameViewAfterReflow so
669 // HasOverflowRect() will return the correct value.
670 nsOverflowAreas overflow(scrolledArea, scrolledArea);
671 scrolledFrame->FinishAndStoreOverflow(overflow,
672 scrolledFrame->GetSize());
673
674 // Note that making the view *exactly* the size of the scrolled area
675 // is critical, since the view scrolling code uses the size of the
676 // scrolled view to clamp scroll requests.
677 // Normally the scrolledFrame won't have a view but in some cases it
678 // might create its own.
679 nsContainerFrame::SyncFrameViewAfterReflow(scrolledFrame->PresContext(),
680 scrolledFrame,
681 scrolledFrame->GetView(),
682 scrolledArea,
683 0);
684 }
685
686 nscoord
687 nsHTMLScrollFrame::GetIntrinsicVScrollbarWidth(nsRenderingContext *aRenderingContext)
688 {
689 ScrollbarStyles ss = GetScrollbarStyles();
690 if (ss.mVertical != NS_STYLE_OVERFLOW_SCROLL || !mHelper.mVScrollbarBox)
691 return 0;
692
693 // Don't need to worry about reflow depth here since it's
694 // just for scrollbars
695 nsBoxLayoutState bls(PresContext(), aRenderingContext, 0);
696 nsSize vScrollbarPrefSize(0, 0);
697 GetScrollbarMetrics(bls, mHelper.mVScrollbarBox,
698 nullptr, &vScrollbarPrefSize, true);
699 return vScrollbarPrefSize.width;
700 }
701
702 /* virtual */ nscoord
703 nsHTMLScrollFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
704 {
705 nscoord result = mHelper.mScrolledFrame->GetMinWidth(aRenderingContext);
706 DISPLAY_MIN_WIDTH(this, result);
707 return result + GetIntrinsicVScrollbarWidth(aRenderingContext);
708 }
709
710 /* virtual */ nscoord
711 nsHTMLScrollFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
712 {
713 nscoord result = mHelper.mScrolledFrame->GetPrefWidth(aRenderingContext);
714 DISPLAY_PREF_WIDTH(this, result);
715 return NSCoordSaturatingAdd(result, GetIntrinsicVScrollbarWidth(aRenderingContext));
716 }
717
718 nsresult
719 nsHTMLScrollFrame::GetPadding(nsMargin& aMargin)
720 {
721 // Our padding hangs out on the inside of the scrollframe, but XUL doesn't
722 // reaize that. If we're stuck inside a XUL box, we need to claim no
723 // padding.
724 // @see also nsXULScrollFrame::GetPadding.
725 aMargin.SizeTo(0,0,0,0);
726 return NS_OK;
727 }
728
729 bool
730 nsHTMLScrollFrame::IsCollapsed()
731 {
732 // We're never collapsed in the box sense.
733 return false;
734 }
735
736 // Return the <browser> if the scrollframe is for the root frame directly
737 // inside a <browser>.
738 static nsIContent*
739 GetBrowserRoot(nsIContent* aContent)
740 {
741 if (aContent) {
742 nsIDocument* doc = aContent->GetCurrentDoc();
743 nsPIDOMWindow* win = doc->GetWindow();
744 if (win) {
745 nsCOMPtr<nsIContent> frameContent =
746 do_QueryInterface(win->GetFrameElementInternal());
747 if (frameContent &&
748 frameContent->NodeInfo()->Equals(nsGkAtoms::browser, kNameSpaceID_XUL))
749 return frameContent;
750 }
751 }
752
753 return nullptr;
754 }
755
756 nsresult
757 nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext,
758 nsHTMLReflowMetrics& aDesiredSize,
759 const nsHTMLReflowState& aReflowState,
760 nsReflowStatus& aStatus)
761 {
762 DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
763 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
764
765 mHelper.HandleScrollbarStyleSwitching();
766
767 ScrollReflowState state(this, aReflowState);
768 // sanity check: ensure that if we have no scrollbar, we treat it
769 // as hidden.
770 if (!mHelper.mVScrollbarBox || mHelper.mNeverHasVerticalScrollbar)
771 state.mStyles.mVertical = NS_STYLE_OVERFLOW_HIDDEN;
772 if (!mHelper.mHScrollbarBox || mHelper.mNeverHasHorizontalScrollbar)
773 state.mStyles.mHorizontal = NS_STYLE_OVERFLOW_HIDDEN;
774
775 //------------ Handle Incremental Reflow -----------------
776 bool reflowHScrollbar = true;
777 bool reflowVScrollbar = true;
778 bool reflowScrollCorner = true;
779 if (!aReflowState.ShouldReflowAllKids()) {
780 #define NEEDS_REFLOW(frame_) \
781 ((frame_) && NS_SUBTREE_DIRTY(frame_))
782
783 reflowHScrollbar = NEEDS_REFLOW(mHelper.mHScrollbarBox);
784 reflowVScrollbar = NEEDS_REFLOW(mHelper.mVScrollbarBox);
785 reflowScrollCorner = NEEDS_REFLOW(mHelper.mScrollCornerBox) ||
786 NEEDS_REFLOW(mHelper.mResizerBox);
787
788 #undef NEEDS_REFLOW
789 }
790
791 if (mHelper.mIsRoot) {
792 mHelper.mCollapsedResizer = true;
793
794 nsIContent* browserRoot = GetBrowserRoot(mContent);
795 if (browserRoot) {
796 bool showResizer = browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer);
797 reflowScrollCorner = showResizer == mHelper.mCollapsedResizer;
798 mHelper.mCollapsedResizer = !showResizer;
799 }
800 }
801
802 nsRect oldScrollAreaBounds = mHelper.mScrollPort;
803 nsRect oldScrolledAreaBounds =
804 mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
805 nsPoint oldScrollPosition = mHelper.GetScrollPosition();
806
807 state.mComputedBorder = aReflowState.ComputedPhysicalBorderPadding() -
808 aReflowState.ComputedPhysicalPadding();
809
810 nsresult rv = ReflowContents(&state, aDesiredSize);
811 if (NS_FAILED(rv))
812 return rv;
813
814 // Restore the old scroll position, for now, even if that's not valid anymore
815 // because we changed size. We'll fix it up in a post-reflow callback, because
816 // our current size may only be temporary (e.g. we're compute XUL desired sizes).
817 PlaceScrollArea(state, oldScrollPosition);
818 if (!mHelper.mPostedReflowCallback) {
819 // Make sure we'll try scrolling to restored position
820 PresContext()->PresShell()->PostReflowCallback(&mHelper);
821 mHelper.mPostedReflowCallback = true;
822 }
823
824 bool didHaveHScrollbar = mHelper.mHasHorizontalScrollbar;
825 bool didHaveVScrollbar = mHelper.mHasVerticalScrollbar;
826 mHelper.mHasHorizontalScrollbar = state.mShowHScrollbar;
827 mHelper.mHasVerticalScrollbar = state.mShowVScrollbar;
828 nsRect newScrollAreaBounds = mHelper.mScrollPort;
829 nsRect newScrolledAreaBounds =
830 mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
831 if (mHelper.mSkippedScrollbarLayout ||
832 reflowHScrollbar || reflowVScrollbar || reflowScrollCorner ||
833 (GetStateBits() & NS_FRAME_IS_DIRTY) ||
834 didHaveHScrollbar != state.mShowHScrollbar ||
835 didHaveVScrollbar != state.mShowVScrollbar ||
836 !oldScrollAreaBounds.IsEqualEdges(newScrollAreaBounds) ||
837 !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
838 if (!mHelper.mSupppressScrollbarUpdate) {
839 mHelper.mSkippedScrollbarLayout = false;
840 mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, state.mShowHScrollbar);
841 mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, state.mShowVScrollbar);
842 // place and reflow scrollbars
843 nsRect insideBorderArea =
844 nsRect(nsPoint(state.mComputedBorder.left, state.mComputedBorder.top),
845 state.mInsideBorderSize);
846 mHelper.LayoutScrollbars(state.mBoxState, insideBorderArea,
847 oldScrollAreaBounds);
848 } else {
849 mHelper.mSkippedScrollbarLayout = true;
850 }
851 }
852
853 aDesiredSize.Width() = state.mInsideBorderSize.width +
854 state.mComputedBorder.LeftRight();
855 aDesiredSize.Height() = state.mInsideBorderSize.height +
856 state.mComputedBorder.TopBottom();
857
858 aDesiredSize.SetOverflowAreasToDesiredBounds();
859 if (mHelper.IsIgnoringViewportClipping()) {
860 aDesiredSize.mOverflowAreas.UnionWith(
861 state.mContentsOverflowAreas + mHelper.mScrolledFrame->GetPosition());
862 }
863
864 mHelper.UpdateSticky();
865 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus);
866
867 if (!InInitialReflow() && !mHelper.mHadNonInitialReflow) {
868 mHelper.mHadNonInitialReflow = true;
869 }
870
871 if (mHelper.mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
872 mHelper.PostScrolledAreaEvent();
873 }
874
875 aStatus = NS_FRAME_COMPLETE;
876 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
877 mHelper.PostOverflowEvent();
878 return rv;
879 }
880
881
882 ////////////////////////////////////////////////////////////////////////////////
883
884 #ifdef DEBUG_FRAME_DUMP
885 nsresult
886 nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const
887 {
888 return MakeFrameName(NS_LITERAL_STRING("HTMLScroll"), aResult);
889 }
890 #endif
891
892 #ifdef ACCESSIBILITY
893 a11y::AccType
894 nsHTMLScrollFrame::AccessibleType()
895 {
896 // Create an accessible regardless of focusable state because the state can be
897 // changed during frame life cycle without any notifications to accessibility.
898 if (mContent->IsRootOfNativeAnonymousSubtree() ||
899 GetScrollbarStyles() ==
900 ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN) ) {
901 return a11y::eNoType;
902 }
903
904 return a11y::eHyperTextType;
905 }
906 #endif
907
908 NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
909 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
910 NS_QUERYFRAME_ENTRY(nsIScrollbarOwner)
911 NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
912 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
913 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
914
915 //----------nsXULScrollFrame-------------------------------------------
916
917 nsIFrame*
918 NS_NewXULScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext,
919 bool aIsRoot, bool aClipAllDescendants)
920 {
921 return new (aPresShell) nsXULScrollFrame(aPresShell, aContext, aIsRoot,
922 aClipAllDescendants);
923 }
924
925 NS_IMPL_FRAMEARENA_HELPERS(nsXULScrollFrame)
926
927 nsXULScrollFrame::nsXULScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext,
928 bool aIsRoot, bool aClipAllDescendants)
929 : nsBoxFrame(aShell, aContext, aIsRoot),
930 mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot)
931 {
932 SetLayoutManager(nullptr);
933 mHelper.mClipAllDescendants = aClipAllDescendants;
934 }
935
936 void
937 nsXULScrollFrame::ScrollbarActivityStarted() const
938 {
939 if (mHelper.mScrollbarActivity) {
940 mHelper.mScrollbarActivity->ActivityStarted();
941 }
942 }
943
944 void
945 nsXULScrollFrame::ScrollbarActivityStopped() const
946 {
947 if (mHelper.mScrollbarActivity) {
948 mHelper.mScrollbarActivity->ActivityStopped();
949 }
950 }
951
952 nsMargin
953 ScrollFrameHelper::GetDesiredScrollbarSizes(nsBoxLayoutState* aState)
954 {
955 NS_ASSERTION(aState && aState->GetRenderingContext(),
956 "Must have rendering context in layout state for size "
957 "computations");
958
959 nsMargin result(0, 0, 0, 0);
960
961 if (mVScrollbarBox) {
962 nsSize size = mVScrollbarBox->GetPrefSize(*aState);
963 nsBox::AddMargin(mVScrollbarBox, size);
964 if (IsScrollbarOnRight())
965 result.left = size.width;
966 else
967 result.right = size.width;
968 }
969
970 if (mHScrollbarBox) {
971 nsSize size = mHScrollbarBox->GetPrefSize(*aState);
972 nsBox::AddMargin(mHScrollbarBox, size);
973 // We don't currently support any scripts that would require a scrollbar
974 // at the top. (Are there any?)
975 result.bottom = size.height;
976 }
977
978 return result;
979 }
980
981 nscoord
982 ScrollFrameHelper::GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState)
983 {
984 NS_ASSERTION(aState && aState->GetRenderingContext(),
985 "Must have rendering context in layout state for size "
986 "computations");
987
988 if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
989 // We're using overlay scrollbars, so we need to get the width that
990 // non-disappearing scrollbars would have.
991 nsITheme* theme = aState->PresContext()->GetTheme();
992 if (theme &&
993 theme->ThemeSupportsWidget(aState->PresContext(),
994 mVScrollbarBox,
995 NS_THEME_SCROLLBAR_NON_DISAPPEARING)) {
996 nsIntSize size;
997 nsRenderingContext* rendContext = aState->GetRenderingContext();
998 if (rendContext) {
999 bool canOverride = true;
1000 theme->GetMinimumWidgetSize(rendContext,
1001 mVScrollbarBox,
1002 NS_THEME_SCROLLBAR_NON_DISAPPEARING,
1003 &size,
1004 &canOverride);
1005 if (size.width) {
1006 return aState->PresContext()->DevPixelsToAppUnits(size.width);
1007 }
1008 }
1009 }
1010 }
1011
1012 return GetDesiredScrollbarSizes(aState).LeftRight();
1013 }
1014
1015 void
1016 ScrollFrameHelper::HandleScrollbarStyleSwitching()
1017 {
1018 // Check if we switched between scrollbar styles.
1019 if (mScrollbarActivity &&
1020 LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) == 0) {
1021 mScrollbarActivity->Destroy();
1022 mScrollbarActivity = nullptr;
1023 mOuter->PresContext()->ThemeChanged();
1024 }
1025 else if (!mScrollbarActivity &&
1026 LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
1027 mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(mOuter));
1028 mOuter->PresContext()->ThemeChanged();
1029 }
1030 }
1031
1032 static bool IsFocused(nsIContent* aContent)
1033 {
1034 // Some content elements, like the GetContent() of a scroll frame
1035 // for a text input field, are inside anonymous subtrees, but the focus
1036 // manager always reports a non-anonymous element as the focused one, so
1037 // walk up the tree until we reach a non-anonymous element.
1038 while (aContent && aContent->IsInAnonymousSubtree()) {
1039 aContent = aContent->GetParent();
1040 }
1041
1042 return aContent ? nsContentUtils::IsFocusedContent(aContent) : false;
1043 }
1044
1045 bool
1046 ScrollFrameHelper::WantAsyncScroll() const
1047 {
1048 nsRect scrollRange = GetScrollRange();
1049 ScrollbarStyles styles = GetScrollbarStylesFromFrame();
1050 bool isFocused = IsFocused(mOuter->GetContent());
1051 bool isVScrollable = (scrollRange.height > 0)
1052 && (styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
1053 bool isHScrollable = (scrollRange.width > 0)
1054 && (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN);
1055 // The check for scroll bars was added in bug 825692 to prevent layerization
1056 // of text inputs for performance reasons. However, if a text input is
1057 // focused we want to layerize it so we can async scroll it (bug 946408).
1058 bool isVAsyncScrollable = isVScrollable && (mVScrollbarBox || isFocused);
1059 bool isHAsyncScrollable = isHScrollable && (mHScrollbarBox || isFocused);
1060 return isVAsyncScrollable || isHAsyncScrollable;
1061 }
1062
1063 nsresult
1064 nsXULScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
1065 {
1066 return mHelper.CreateAnonymousContent(aElements);
1067 }
1068
1069 void
1070 nsXULScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
1071 uint32_t aFilter)
1072 {
1073 mHelper.AppendAnonymousContentTo(aElements, aFilter);
1074 }
1075
1076 void
1077 nsXULScrollFrame::DestroyFrom(nsIFrame* aDestructRoot)
1078 {
1079 mHelper.Destroy();
1080 nsBoxFrame::DestroyFrom(aDestructRoot);
1081 }
1082
1083 nsresult
1084 nsXULScrollFrame::SetInitialChildList(ChildListID aListID,
1085 nsFrameList& aChildList)
1086 {
1087 nsresult rv = nsBoxFrame::SetInitialChildList(aListID, aChildList);
1088 mHelper.ReloadChildFrames();
1089 return rv;
1090 }
1091
1092
1093 nsresult
1094 nsXULScrollFrame::AppendFrames(ChildListID aListID,
1095 nsFrameList& aFrameList)
1096 {
1097 nsresult rv = nsBoxFrame::AppendFrames(aListID, aFrameList);
1098 mHelper.ReloadChildFrames();
1099 return rv;
1100 }
1101
1102 nsresult
1103 nsXULScrollFrame::InsertFrames(ChildListID aListID,
1104 nsIFrame* aPrevFrame,
1105 nsFrameList& aFrameList)
1106 {
1107 nsresult rv = nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
1108 mHelper.ReloadChildFrames();
1109 return rv;
1110 }
1111
1112 nsresult
1113 nsXULScrollFrame::RemoveFrame(ChildListID aListID,
1114 nsIFrame* aOldFrame)
1115 {
1116 nsresult rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame);
1117 mHelper.ReloadChildFrames();
1118 return rv;
1119 }
1120
1121 nsSplittableType
1122 nsXULScrollFrame::GetSplittableType() const
1123 {
1124 return NS_FRAME_NOT_SPLITTABLE;
1125 }
1126
1127 nsresult
1128 nsXULScrollFrame::GetPadding(nsMargin& aMargin)
1129 {
1130 aMargin.SizeTo(0,0,0,0);
1131 return NS_OK;
1132 }
1133
1134 nsIAtom*
1135 nsXULScrollFrame::GetType() const
1136 {
1137 return nsGkAtoms::scrollFrame;
1138 }
1139
1140 nscoord
1141 nsXULScrollFrame::GetBoxAscent(nsBoxLayoutState& aState)
1142 {
1143 if (!mHelper.mScrolledFrame)
1144 return 0;
1145
1146 nscoord ascent = mHelper.mScrolledFrame->GetBoxAscent(aState);
1147 nsMargin m(0,0,0,0);
1148 GetBorderAndPadding(m);
1149 ascent += m.top;
1150 GetMargin(m);
1151 ascent += m.top;
1152
1153 return ascent;
1154 }
1155
1156 nsSize
1157 nsXULScrollFrame::GetPrefSize(nsBoxLayoutState& aState)
1158 {
1159 #ifdef DEBUG_LAYOUT
1160 PropagateDebug(aState);
1161 #endif
1162
1163 nsSize pref = mHelper.mScrolledFrame->GetPrefSize(aState);
1164
1165 ScrollbarStyles styles = GetScrollbarStyles();
1166
1167 // scrolled frames don't have their own margins
1168
1169 if (mHelper.mVScrollbarBox &&
1170 styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1171 nsSize vSize = mHelper.mVScrollbarBox->GetPrefSize(aState);
1172 nsBox::AddMargin(mHelper.mVScrollbarBox, vSize);
1173 pref.width += vSize.width;
1174 }
1175
1176 if (mHelper.mHScrollbarBox &&
1177 styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1178 nsSize hSize = mHelper.mHScrollbarBox->GetPrefSize(aState);
1179 nsBox::AddMargin(mHelper.mHScrollbarBox, hSize);
1180 pref.height += hSize.height;
1181 }
1182
1183 AddBorderAndPadding(pref);
1184 bool widthSet, heightSet;
1185 nsIFrame::AddCSSPrefSize(this, pref, widthSet, heightSet);
1186 return pref;
1187 }
1188
1189 nsSize
1190 nsXULScrollFrame::GetMinSize(nsBoxLayoutState& aState)
1191 {
1192 #ifdef DEBUG_LAYOUT
1193 PropagateDebug(aState);
1194 #endif
1195
1196 nsSize min = mHelper.mScrolledFrame->GetMinSizeForScrollArea(aState);
1197
1198 ScrollbarStyles styles = GetScrollbarStyles();
1199
1200 if (mHelper.mVScrollbarBox &&
1201 styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1202 nsSize vSize = mHelper.mVScrollbarBox->GetMinSize(aState);
1203 AddMargin(mHelper.mVScrollbarBox, vSize);
1204 min.width += vSize.width;
1205 if (min.height < vSize.height)
1206 min.height = vSize.height;
1207 }
1208
1209 if (mHelper.mHScrollbarBox &&
1210 styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1211 nsSize hSize = mHelper.mHScrollbarBox->GetMinSize(aState);
1212 AddMargin(mHelper.mHScrollbarBox, hSize);
1213 min.height += hSize.height;
1214 if (min.width < hSize.width)
1215 min.width = hSize.width;
1216 }
1217
1218 AddBorderAndPadding(min);
1219 bool widthSet, heightSet;
1220 nsIFrame::AddCSSMinSize(aState, this, min, widthSet, heightSet);
1221 return min;
1222 }
1223
1224 nsSize
1225 nsXULScrollFrame::GetMaxSize(nsBoxLayoutState& aState)
1226 {
1227 #ifdef DEBUG_LAYOUT
1228 PropagateDebug(aState);
1229 #endif
1230
1231 nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
1232
1233 AddBorderAndPadding(maxSize);
1234 bool widthSet, heightSet;
1235 nsIFrame::AddCSSMaxSize(this, maxSize, widthSet, heightSet);
1236 return maxSize;
1237 }
1238
1239 #ifdef DEBUG_FRAME_DUMP
1240 nsresult
1241 nsXULScrollFrame::GetFrameName(nsAString& aResult) const
1242 {
1243 return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult);
1244 }
1245 #endif
1246
1247 NS_IMETHODIMP
1248 nsXULScrollFrame::DoLayout(nsBoxLayoutState& aState)
1249 {
1250 uint32_t flags = aState.LayoutFlags();
1251 nsresult rv = Layout(aState);
1252 aState.SetLayoutFlags(flags);
1253
1254 nsBox::DoLayout(aState);
1255 return rv;
1256 }
1257
1258 NS_QUERYFRAME_HEAD(nsXULScrollFrame)
1259 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
1260 NS_QUERYFRAME_ENTRY(nsIScrollbarOwner)
1261 NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
1262 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
1263 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
1264
1265 //-------------------- Helper ----------------------
1266
1267 #define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll"
1268
1269 const double kCurrentVelocityWeighting = 0.25;
1270 const double kStopDecelerationWeighting = 0.4;
1271
1272 // AsyncScroll has ref counting.
1273 class ScrollFrameHelper::AsyncScroll MOZ_FINAL : public nsARefreshObserver {
1274 public:
1275 typedef mozilla::TimeStamp TimeStamp;
1276 typedef mozilla::TimeDuration TimeDuration;
1277
1278 AsyncScroll(nsPoint aStartPos)
1279 : mIsFirstIteration(true)
1280 , mStartPos(aStartPos)
1281 , mCallee(nullptr)
1282 {}
1283
1284 private:
1285 // Private destructor, to discourage deletion outside of Release():
1286 ~AsyncScroll() {
1287 RemoveObserver();
1288 }
1289
1290 public:
1291 nsPoint PositionAt(TimeStamp aTime);
1292 nsSize VelocityAt(TimeStamp aTime); // In nscoords per second
1293
1294 void InitSmoothScroll(TimeStamp aTime, nsPoint aDestination,
1295 nsIAtom *aOrigin, const nsRect& aRange);
1296 void Init(const nsRect& aRange) {
1297 mRange = aRange;
1298 }
1299
1300 bool IsFinished(TimeStamp aTime) {
1301 return aTime > mStartTime + mDuration; // XXX or if we've hit the wall
1302 }
1303
1304 TimeStamp mStartTime;
1305
1306 // mPrevEventTime holds previous 3 timestamps for intervals averaging (to
1307 // reduce duration fluctuations). When AsyncScroll is constructed and no
1308 // previous timestamps are available (indicated with mIsFirstIteration),
1309 // initialize mPrevEventTime using imaginary previous timestamps with maximum
1310 // relevant intervals between them.
1311 TimeStamp mPrevEventTime[3];
1312 bool mIsFirstIteration;
1313
1314 // Cached Preferences values to avoid re-reading them when extending an existing
1315 // animation for the same event origin (can be as frequent as every 10(!)ms for
1316 // a quick roll of the mouse wheel).
1317 // These values are minimum and maximum animation duration per event origin,
1318 // and a global ratio which defines how longer is the animation's duration
1319 // compared to the average recent events intervals (such that for a relatively
1320 // consistent events rate, the next event arrives before current animation ends)
1321 nsCOMPtr<nsIAtom> mOrigin;
1322 int32_t mOriginMinMS;
1323 int32_t mOriginMaxMS;
1324 double mIntervalRatio;
1325
1326 TimeDuration mDuration;
1327 nsPoint mStartPos;
1328 nsPoint mDestination;
1329 // Allowed destination positions around mDestination
1330 nsRect mRange;
1331 nsSMILKeySpline mTimingFunctionX;
1332 nsSMILKeySpline mTimingFunctionY;
1333 bool mIsSmoothScroll;
1334
1335 protected:
1336 double ProgressAt(TimeStamp aTime) {
1337 return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0);
1338 }
1339
1340 nscoord VelocityComponent(double aTimeProgress,
1341 nsSMILKeySpline& aTimingFunction,
1342 nscoord aStart, nscoord aDestination);
1343
1344 // Initializes the timing function in such a way that the current velocity is
1345 // preserved.
1346 void InitTimingFunction(nsSMILKeySpline& aTimingFunction,
1347 nscoord aCurrentPos, nscoord aCurrentVelocity,
1348 nscoord aDestination);
1349
1350 TimeDuration CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin);
1351
1352 // The next section is observer/callback management
1353 // Bodies of WillRefresh and RefreshDriver contain ScrollFrameHelper specific code.
1354 public:
1355 NS_INLINE_DECL_REFCOUNTING(AsyncScroll)
1356
1357 /*
1358 * Set a refresh observer for smooth scroll iterations (and start observing).
1359 * Should be used at most once during the lifetime of this object.
1360 * Return value: true on success, false otherwise.
1361 */
1362 bool SetRefreshObserver(ScrollFrameHelper *aCallee) {
1363 NS_ASSERTION(aCallee && !mCallee, "AsyncScroll::SetRefreshObserver - Invalid usage.");
1364
1365 if (!RefreshDriver(aCallee)->AddRefreshObserver(this, Flush_Style)) {
1366 return false;
1367 }
1368
1369 mCallee = aCallee;
1370 return true;
1371 }
1372
1373 virtual void WillRefresh(mozilla::TimeStamp aTime) MOZ_OVERRIDE {
1374 // The callback may release "this".
1375 // We don't access members after returning, so no need for KungFuDeathGrip.
1376 ScrollFrameHelper::AsyncScrollCallback(mCallee, aTime);
1377 }
1378
1379 private:
1380 ScrollFrameHelper *mCallee;
1381
1382 nsRefreshDriver* RefreshDriver(ScrollFrameHelper* aCallee) {
1383 return aCallee->mOuter->PresContext()->RefreshDriver();
1384 }
1385
1386 /*
1387 * The refresh driver doesn't hold a reference to its observers,
1388 * so releasing this object can (and is) used to remove the observer on DTOR.
1389 * Currently, this object is released once the scrolling ends.
1390 */
1391 void RemoveObserver() {
1392 if (mCallee) {
1393 RefreshDriver(mCallee)->RemoveRefreshObserver(this, Flush_Style);
1394 }
1395 }
1396 };
1397
1398 nsPoint
1399 ScrollFrameHelper::AsyncScroll::PositionAt(TimeStamp aTime) {
1400 double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
1401 double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
1402 return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x + progressX * mDestination.x),
1403 NSToCoordRound((1 - progressY) * mStartPos.y + progressY * mDestination.y));
1404 }
1405
1406 nsSize
1407 ScrollFrameHelper::AsyncScroll::VelocityAt(TimeStamp aTime) {
1408 double timeProgress = ProgressAt(aTime);
1409 return nsSize(VelocityComponent(timeProgress, mTimingFunctionX,
1410 mStartPos.x, mDestination.x),
1411 VelocityComponent(timeProgress, mTimingFunctionY,
1412 mStartPos.y, mDestination.y));
1413 }
1414
1415 /*
1416 * Calculate duration, possibly dynamically according to events rate and event origin.
1417 * (also maintain previous timestamps - which are only used here).
1418 */
1419 TimeDuration
1420 ScrollFrameHelper::
1421 AsyncScroll::CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin) {
1422 if (!aOrigin){
1423 aOrigin = nsGkAtoms::other;
1424 }
1425
1426 // Read preferences only on first iteration or for a different event origin.
1427 if (mIsFirstIteration || aOrigin != mOrigin) {
1428 mOrigin = aOrigin;
1429 mOriginMinMS = mOriginMaxMS = 0;
1430 bool isOriginSmoothnessEnabled = false;
1431 mIntervalRatio = 1;
1432
1433 // Default values for all preferences are defined in all.js
1434 static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150;
1435 static const bool kDefaultIsSmoothEnabled = true;
1436
1437 nsAutoCString originName;
1438 aOrigin->ToUTF8String(originName);
1439 nsAutoCString prefBase = NS_LITERAL_CSTRING("general.smoothScroll.") + originName;
1440
1441 isOriginSmoothnessEnabled = Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled);
1442 if (isOriginSmoothnessEnabled) {
1443 nsAutoCString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS");
1444 nsAutoCString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS");
1445 mOriginMinMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS);
1446 mOriginMaxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS);
1447
1448 static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000;
1449 mOriginMaxMS = clamped(mOriginMaxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS);
1450 mOriginMinMS = clamped(mOriginMinMS, 0, mOriginMaxMS);
1451 }
1452
1453 // Keep the animation duration longer than the average event intervals
1454 // (to "connect" consecutive scroll animations before the scroll comes to a stop).
1455 static const double kDefaultDurationToIntervalRatio = 2; // Duplicated at all.js
1456 mIntervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio",
1457 kDefaultDurationToIntervalRatio * 100) / 100.0;
1458
1459 // Duration should be at least as long as the intervals -> ratio is at least 1
1460 mIntervalRatio = std::max(1.0, mIntervalRatio);
1461
1462 if (mIsFirstIteration) {
1463 // Starting a new scroll (i.e. not when extending an existing scroll animation),
1464 // create imaginary prev timestamps with maximum relevant intervals between them.
1465
1466 // Longest relevant interval (which results in maximum duration)
1467 TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio);
1468 mPrevEventTime[0] = aTime - maxDelta;
1469 mPrevEventTime[1] = mPrevEventTime[0] - maxDelta;
1470 mPrevEventTime[2] = mPrevEventTime[1] - maxDelta;
1471 }
1472 }
1473
1474 // Average last 3 delta durations (rounding errors up to 2ms are negligible for us)
1475 int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3;
1476 mPrevEventTime[2] = mPrevEventTime[1];
1477 mPrevEventTime[1] = mPrevEventTime[0];
1478 mPrevEventTime[0] = aTime;
1479
1480 // Modulate duration according to events rate (quicker events -> shorter durations).
1481 // The desired effect is to use longer duration when scrolling slowly, such that
1482 // it's easier to follow, but reduce the duration to make it feel more snappy when
1483 // scrolling quickly. To reduce fluctuations of the duration, we average event
1484 // intervals using the recent 4 timestamps (now + three prev -> 3 intervals).
1485 int32_t durationMS = clamped<int32_t>(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS);
1486
1487 return TimeDuration::FromMilliseconds(durationMS);
1488 }
1489
1490 void
1491 ScrollFrameHelper::AsyncScroll::InitSmoothScroll(TimeStamp aTime,
1492 nsPoint aDestination,
1493 nsIAtom *aOrigin,
1494 const nsRect& aRange) {
1495 mRange = aRange;
1496 TimeDuration duration = CalcDurationForEventTime(aTime, aOrigin);
1497 nsSize currentVelocity(0, 0);
1498 if (!mIsFirstIteration) {
1499 // If an additional event has not changed the destination, then do not let
1500 // another minimum duration reset slow things down. If it would then
1501 // instead continue with the existing timing function.
1502 if (aDestination == mDestination &&
1503 aTime + duration > mStartTime + mDuration)
1504 return;
1505
1506 currentVelocity = VelocityAt(aTime);
1507 mStartPos = PositionAt(aTime);
1508 }
1509 mStartTime = aTime;
1510 mDuration = duration;
1511 mDestination = aDestination;
1512 InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width,
1513 aDestination.x);
1514 InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height,
1515 aDestination.y);
1516 mIsFirstIteration = false;
1517 }
1518
1519
1520 nscoord
1521 ScrollFrameHelper::AsyncScroll::VelocityComponent(double aTimeProgress,
1522 nsSMILKeySpline& aTimingFunction,
1523 nscoord aStart,
1524 nscoord aDestination)
1525 {
1526 double dt, dxy;
1527 aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy);
1528 if (dt == 0)
1529 return dxy >= 0 ? nscoord_MAX : nscoord_MIN;
1530
1531 const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
1532 double slope = dxy / dt;
1533 return NSToCoordRound(slope * (aDestination - aStart) / (mDuration / oneSecond));
1534 }
1535
1536 void
1537 ScrollFrameHelper::AsyncScroll::InitTimingFunction(nsSMILKeySpline& aTimingFunction,
1538 nscoord aCurrentPos,
1539 nscoord aCurrentVelocity,
1540 nscoord aDestination)
1541 {
1542 if (aDestination == aCurrentPos || kCurrentVelocityWeighting == 0) {
1543 aTimingFunction.Init(0, 0, 1 - kStopDecelerationWeighting, 1);
1544 return;
1545 }
1546
1547 const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
1548 double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
1549 double normalization = sqrt(1.0 + slope * slope);
1550 double dt = 1.0 / normalization * kCurrentVelocityWeighting;
1551 double dxy = slope / normalization * kCurrentVelocityWeighting;
1552 aTimingFunction.Init(dt, dxy, 1 - kStopDecelerationWeighting, 1);
1553 }
1554
1555 static bool
1556 IsSmoothScrollingEnabled()
1557 {
1558 return Preferences::GetBool(SMOOTH_SCROLL_PREF_NAME, false);
1559 }
1560
1561 class ScrollFrameActivityTracker MOZ_FINAL : public nsExpirationTracker<ScrollFrameHelper,4> {
1562 public:
1563 // Wait for 3-4s between scrolls before we remove our layers.
1564 // That's 4 generations of 1s each.
1565 enum { TIMEOUT_MS = 1000 };
1566 ScrollFrameActivityTracker()
1567 : nsExpirationTracker<ScrollFrameHelper,4>(TIMEOUT_MS) {}
1568 ~ScrollFrameActivityTracker() {
1569 AgeAllGenerations();
1570 }
1571
1572 virtual void NotifyExpired(ScrollFrameHelper *aObject) {
1573 RemoveObject(aObject);
1574 aObject->MarkInactive();
1575 }
1576 };
1577
1578 static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nullptr;
1579
1580 // There are situations when a scroll frame is destroyed and then re-created
1581 // for the same content element. In this case we want to increment the scroll
1582 // generation between the old and new scrollframes. If the new one knew about
1583 // the old one then it could steal the old generation counter and increment it
1584 // but it doesn't have that reference so instead we use a static global to
1585 // ensure the new one gets a fresh value.
1586 static uint32_t sScrollGenerationCounter = 0;
1587
1588 ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
1589 bool aIsRoot)
1590 : mHScrollbarBox(nullptr)
1591 , mVScrollbarBox(nullptr)
1592 , mScrolledFrame(nullptr)
1593 , mScrollCornerBox(nullptr)
1594 , mResizerBox(nullptr)
1595 , mOuter(aOuter)
1596 , mAsyncScroll(nullptr)
1597 , mOriginOfLastScroll(nsGkAtoms::other)
1598 , mScrollGeneration(++sScrollGenerationCounter)
1599 , mDestination(0, 0)
1600 , mScrollPosAtLastPaint(0, 0)
1601 , mRestorePos(-1, -1)
1602 , mLastPos(-1, -1)
1603 , mResolution(1.0, 1.0)
1604 , mScrollPosForLayerPixelAlignment(-1, -1)
1605 , mLastUpdateImagesPos(-1, -1)
1606 , mNeverHasVerticalScrollbar(false)
1607 , mNeverHasHorizontalScrollbar(false)
1608 , mHasVerticalScrollbar(false)
1609 , mHasHorizontalScrollbar(false)
1610 , mFrameIsUpdatingScrollbar(false)
1611 , mDidHistoryRestore(false)
1612 , mIsRoot(aIsRoot)
1613 , mClipAllDescendants(aIsRoot)
1614 , mSupppressScrollbarUpdate(false)
1615 , mSkippedScrollbarLayout(false)
1616 , mHadNonInitialReflow(false)
1617 , mHorizontalOverflow(false)
1618 , mVerticalOverflow(false)
1619 , mPostedReflowCallback(false)
1620 , mMayHaveDirtyFixedChildren(false)
1621 , mUpdateScrollbarAttributes(false)
1622 , mCollapsedResizer(false)
1623 , mShouldBuildScrollableLayer(false)
1624 , mHasBeenScrolled(false)
1625 , mIsResolutionSet(false)
1626 {
1627 mScrollingActive = IsAlwaysActive();
1628
1629 if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
1630 mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
1631 }
1632
1633 EnsureImageVisPrefsCached();
1634 }
1635
1636 ScrollFrameHelper::~ScrollFrameHelper()
1637 {
1638 if (mActivityExpirationState.IsTracked()) {
1639 gScrollFrameActivityTracker->RemoveObject(this);
1640 }
1641 if (gScrollFrameActivityTracker &&
1642 gScrollFrameActivityTracker->IsEmpty()) {
1643 delete gScrollFrameActivityTracker;
1644 gScrollFrameActivityTracker = nullptr;
1645 }
1646
1647 if (mScrollActivityTimer) {
1648 mScrollActivityTimer->Cancel();
1649 mScrollActivityTimer = nullptr;
1650 }
1651 }
1652
1653 /*
1654 * Callback function from AsyncScroll, used in ScrollFrameHelper::ScrollTo
1655 */
1656 void
1657 ScrollFrameHelper::AsyncScrollCallback(void* anInstance, mozilla::TimeStamp aTime)
1658 {
1659 ScrollFrameHelper* self = static_cast<ScrollFrameHelper*>(anInstance);
1660 if (!self || !self->mAsyncScroll)
1661 return;
1662
1663 nsRect range = self->mAsyncScroll->mRange;
1664 if (self->mAsyncScroll->mIsSmoothScroll) {
1665 if (!self->mAsyncScroll->IsFinished(aTime)) {
1666 nsPoint destination = self->mAsyncScroll->PositionAt(aTime);
1667 // Allow this scroll operation to land on any pixel boundary between the
1668 // current position and the final allowed range. (We don't want
1669 // intermediate steps to be more constrained than the final step!)
1670 nsRect intermediateRange =
1671 nsRect(self->GetScrollPosition(), nsSize()).UnionEdges(range);
1672 self->ScrollToImpl(destination, intermediateRange);
1673 // 'self' might be destroyed here
1674 return;
1675 }
1676 }
1677
1678 // Apply desired destination range since this is the last step of scrolling.
1679 self->mAsyncScroll = nullptr;
1680 nsWeakFrame weakFrame(self->mOuter);
1681 self->ScrollToImpl(self->mDestination, range);
1682 if (!weakFrame.IsAlive()) {
1683 return;
1684 }
1685 // We are done scrolling, set our destination to wherever we actually ended
1686 // up scrolling to.
1687 self->mDestination = self->GetScrollPosition();
1688 }
1689
1690 void
1691 ScrollFrameHelper::ScrollToCSSPixels(const CSSIntPoint& aScrollPosition)
1692 {
1693 nsPoint current = GetScrollPosition();
1694 CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels();
1695 nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
1696 nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
1697 nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2*halfPixel - 1, 2*halfPixel - 1);
1698 // XXX I don't think the following blocks are needed anymore, now that
1699 // ScrollToImpl simply tries to scroll an integer number of layer
1700 // pixels from the current position
1701 if (currentCSSPixels.x == aScrollPosition.x) {
1702 pt.x = current.x;
1703 range.x = pt.x;
1704 range.width = 0;
1705 }
1706 if (currentCSSPixels.y == aScrollPosition.y) {
1707 pt.y = current.y;
1708 range.y = pt.y;
1709 range.height = 0;
1710 }
1711 ScrollTo(pt, nsIScrollableFrame::INSTANT, &range);
1712 // 'this' might be destroyed here
1713 }
1714
1715 void
1716 ScrollFrameHelper::ScrollToCSSPixelsApproximate(const CSSPoint& aScrollPosition,
1717 nsIAtom *aOrigin)
1718 {
1719 nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
1720 nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000);
1721 nsRect range(pt.x - halfRange, pt.y - halfRange, 2*halfRange - 1, 2*halfRange - 1);
1722 ScrollToWithOrigin(pt, nsIScrollableFrame::INSTANT, aOrigin, &range);
1723 // 'this' might be destroyed here
1724 }
1725
1726 CSSIntPoint
1727 ScrollFrameHelper::GetScrollPositionCSSPixels()
1728 {
1729 return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition());
1730 }
1731
1732 /*
1733 * this method wraps calls to ScrollToImpl(), either in one shot or incrementally,
1734 * based on the setting of the smoothness scroll pref
1735 */
1736 void
1737 ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition,
1738 nsIScrollableFrame::ScrollMode aMode,
1739 nsIAtom *aOrigin,
1740 const nsRect* aRange)
1741 {
1742 nsRect scrollRange = GetScrollRangeForClamping();
1743 mDestination = scrollRange.ClampPoint(aScrollPosition);
1744
1745 nsRect range = aRange ? *aRange : nsRect(aScrollPosition, nsSize(0, 0));
1746
1747 if (aMode == nsIScrollableFrame::INSTANT) {
1748 // Asynchronous scrolling is not allowed, so we'll kill any existing
1749 // async-scrolling process and do an instant scroll.
1750 mAsyncScroll = nullptr;
1751 nsWeakFrame weakFrame(mOuter);
1752 ScrollToImpl(mDestination, range, aOrigin);
1753 if (!weakFrame.IsAlive()) {
1754 return;
1755 }
1756 // We are done scrolling, set our destination to wherever we actually ended
1757 // up scrolling to.
1758 mDestination = GetScrollPosition();
1759 return;
1760 }
1761
1762 TimeStamp now = TimeStamp::Now();
1763 bool isSmoothScroll = (aMode == nsIScrollableFrame::SMOOTH) &&
1764 IsSmoothScrollingEnabled();
1765
1766 if (!mAsyncScroll) {
1767 mAsyncScroll = new AsyncScroll(GetScrollPosition());
1768 if (!mAsyncScroll->SetRefreshObserver(this)) {
1769 mAsyncScroll = nullptr;
1770 // Observer setup failed. Scroll the normal way.
1771 nsWeakFrame weakFrame(mOuter);
1772 ScrollToImpl(mDestination, range, aOrigin);
1773 if (!weakFrame.IsAlive()) {
1774 return;
1775 }
1776 // We are done scrolling, set our destination to wherever we actually
1777 // ended up scrolling to.
1778 mDestination = GetScrollPosition();
1779 return;
1780 }
1781 }
1782
1783 mAsyncScroll->mIsSmoothScroll = isSmoothScroll;
1784
1785 if (isSmoothScroll) {
1786 mAsyncScroll->InitSmoothScroll(now, mDestination, aOrigin, range);
1787 } else {
1788 mAsyncScroll->Init(range);
1789 }
1790 }
1791
1792 // We can't use nsContainerFrame::PositionChildViews here because
1793 // we don't want to invalidate views that have moved.
1794 static void AdjustViews(nsIFrame* aFrame)
1795 {
1796 nsView* view = aFrame->GetView();
1797 if (view) {
1798 nsPoint pt;
1799 aFrame->GetParent()->GetClosestView(&pt);
1800 pt += aFrame->GetPosition();
1801 view->SetPosition(pt.x, pt.y);
1802
1803 return;
1804 }
1805
1806 if (!(aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
1807 return;
1808 }
1809
1810 // Call AdjustViews recursively for all child frames except the popup list as
1811 // the views for popups are not scrolled.
1812 nsIFrame::ChildListIterator lists(aFrame);
1813 for (; !lists.IsDone(); lists.Next()) {
1814 if (lists.CurrentID() == nsIFrame::kPopupList) {
1815 continue;
1816 }
1817 nsFrameList::Enumerator childFrames(lists.CurrentList());
1818 for (; !childFrames.AtEnd(); childFrames.Next()) {
1819 AdjustViews(childFrames.get());
1820 }
1821 }
1822 }
1823
1824 static bool
1825 CanScrollWithBlitting(nsIFrame* aFrame)
1826 {
1827 if (aFrame->GetStateBits() & NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL)
1828 return false;
1829
1830 for (nsIFrame* f = aFrame; f;
1831 f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
1832 if (nsSVGIntegrationUtils::UsingEffectsForFrame(f) ||
1833 f->IsFrameOfType(nsIFrame::eSVG) ||
1834 f->GetStateBits() & NS_FRAME_NO_COMPONENT_ALPHA) {
1835 return false;
1836 }
1837 if (nsLayoutUtils::IsPopup(f))
1838 break;
1839 }
1840 return true;
1841 }
1842
1843 bool ScrollFrameHelper::IsIgnoringViewportClipping() const
1844 {
1845 if (!mIsRoot)
1846 return false;
1847 nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*>
1848 (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame()));
1849 return subdocFrame && !subdocFrame->ShouldClipSubdocument();
1850 }
1851
1852 bool ScrollFrameHelper::ShouldClampScrollPosition() const
1853 {
1854 if (!mIsRoot)
1855 return true;
1856 nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*>
1857 (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame()));
1858 return !subdocFrame || subdocFrame->ShouldClampScrollPosition();
1859 }
1860
1861 bool ScrollFrameHelper::IsAlwaysActive() const
1862 {
1863 if (nsDisplayItem::ForceActiveLayers()) {
1864 return true;
1865 }
1866
1867 const nsStyleDisplay* disp = mOuter->StyleDisplay();
1868 if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL)) {
1869 return true;
1870 }
1871
1872 // Unless this is the root scrollframe for a non-chrome document
1873 // which is the direct child of a chrome document, we default to not
1874 // being "active".
1875 if (!(mIsRoot && mOuter->PresContext()->IsRootContentDocument())) {
1876 return false;
1877 }
1878
1879 // If we have scrolled before, then we should stay active.
1880 if (mHasBeenScrolled) {
1881 return true;
1882 }
1883
1884 // If we're overflow:hidden, then start as inactive until
1885 // we get scrolled manually.
1886 ScrollbarStyles styles = GetScrollbarStylesFromFrame();
1887 return (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN &&
1888 styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
1889 }
1890
1891 void ScrollFrameHelper::MarkInactive()
1892 {
1893 if (IsAlwaysActive() || !mScrollingActive)
1894 return;
1895
1896 mScrollingActive = false;
1897 mOuter->InvalidateFrameSubtree();
1898 }
1899
1900 void ScrollFrameHelper::MarkActive()
1901 {
1902 mScrollingActive = true;
1903 if (IsAlwaysActive())
1904 return;
1905
1906 if (mActivityExpirationState.IsTracked()) {
1907 gScrollFrameActivityTracker->MarkUsed(this);
1908 } else {
1909 if (!gScrollFrameActivityTracker) {
1910 gScrollFrameActivityTracker = new ScrollFrameActivityTracker();
1911 }
1912 gScrollFrameActivityTracker->AddObject(this);
1913 }
1914 }
1915
1916 void ScrollFrameHelper::ScrollVisual(nsPoint aOldScrolledFramePos)
1917 {
1918 // Mark this frame as having been scrolled. If this is the root
1919 // scroll frame of a content document, then IsAlwaysActive()
1920 // will return true from now on and MarkInactive() won't
1921 // have any effect.
1922 mHasBeenScrolled = true;
1923
1924 AdjustViews(mScrolledFrame);
1925 // We need to call this after fixing up the view positions
1926 // to be consistent with the frame hierarchy.
1927 bool canScrollWithBlitting = CanScrollWithBlitting(mOuter);
1928 mOuter->RemoveStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL);
1929 if (IsScrollingActive()) {
1930 if (!canScrollWithBlitting) {
1931 MarkInactive();
1932 }
1933 }
1934 if (canScrollWithBlitting) {
1935 MarkActive();
1936 }
1937
1938 mOuter->SchedulePaint();
1939 }
1940
1941 /**
1942 * Clamp desired scroll position aDesired and range [aDestLower, aDestUpper]
1943 * to [aBoundLower, aBoundUpper] and then select the appunit value from among
1944 * aBoundLower, aBoundUpper and those such that (aDesired - aCurrent) *
1945 * aRes/aAppUnitsPerPixel is an integer (or as close as we can get
1946 * modulo rounding to appunits) that is in [aDestLower, aDestUpper] and
1947 * closest to aDesired. If no such value exists, return the nearest in
1948 * [aDestLower, aDestUpper].
1949 */
1950 static nscoord
1951 ClampAndAlignWithPixels(nscoord aDesired,
1952 nscoord aBoundLower, nscoord aBoundUpper,
1953 nscoord aDestLower, nscoord aDestUpper,
1954 nscoord aAppUnitsPerPixel, double aRes,
1955 nscoord aCurrent)
1956 {
1957 // Intersect scroll range with allowed range, by clamping the ends
1958 // of aRange to be within bounds
1959 nscoord destLower = clamped(aDestLower, aBoundLower, aBoundUpper);
1960 nscoord destUpper = clamped(aDestUpper, aBoundLower, aBoundUpper);
1961
1962 nscoord desired = clamped(aDesired, destLower, destUpper);
1963
1964 double currentLayerVal = (aRes*aCurrent)/aAppUnitsPerPixel;
1965 double desiredLayerVal = (aRes*desired)/aAppUnitsPerPixel;
1966 double delta = desiredLayerVal - currentLayerVal;
1967 double nearestLayerVal = NS_round(delta) + currentLayerVal;
1968
1969 // Convert back from ThebesLayer space to appunits relative to the top-left
1970 // of the scrolled frame.
1971 nscoord aligned =
1972 NSToCoordRoundWithClamp(nearestLayerVal*aAppUnitsPerPixel/aRes);
1973
1974 // Use a bound if it is within the allowed range and closer to desired than
1975 // the nearest pixel-aligned value.
1976 if (aBoundUpper == destUpper &&
1977 static_cast<decltype(Abs(desired))>(aBoundUpper - desired) <
1978 Abs(desired - aligned))
1979 return aBoundUpper;
1980
1981 if (aBoundLower == destLower &&
1982 static_cast<decltype(Abs(desired))>(desired - aBoundLower) <
1983 Abs(aligned - desired))
1984 return aBoundLower;
1985
1986 // Accept the nearest pixel-aligned value if it is within the allowed range.
1987 if (aligned >= destLower && aligned <= destUpper)
1988 return aligned;
1989
1990 // Check if opposite pixel boundary fits into allowed range.
1991 double oppositeLayerVal =
1992 nearestLayerVal + ((nearestLayerVal < desiredLayerVal) ? 1.0 : -1.0);
1993 nscoord opposite =
1994 NSToCoordRoundWithClamp(oppositeLayerVal*aAppUnitsPerPixel/aRes);
1995 if (opposite >= destLower && opposite <= destUpper) {
1996 return opposite;
1997 }
1998
1999 // No alignment available.
2000 return desired;
2001 }
2002
2003 /**
2004 * Clamp desired scroll position aPt to aBounds and then snap
2005 * it to the same layer pixel edges as aCurrent, keeping it within aRange
2006 * during snapping. aCurrent is the current scroll position.
2007 */
2008 static nsPoint
2009 ClampAndAlignWithLayerPixels(const nsPoint& aPt,
2010 const nsRect& aBounds,
2011 const nsRect& aRange,
2012 const nsPoint& aCurrent,
2013 nscoord aAppUnitsPerPixel,
2014 const gfxSize& aScale)
2015 {
2016 return nsPoint(ClampAndAlignWithPixels(aPt.x, aBounds.x, aBounds.XMost(),
2017 aRange.x, aRange.XMost(),
2018 aAppUnitsPerPixel, aScale.width,
2019 aCurrent.x),
2020 ClampAndAlignWithPixels(aPt.y, aBounds.y, aBounds.YMost(),
2021 aRange.y, aRange.YMost(),
2022 aAppUnitsPerPixel, aScale.height,
2023 aCurrent.y));
2024 }
2025
2026 /* static */ void
2027 ScrollFrameHelper::ScrollActivityCallback(nsITimer *aTimer, void* anInstance)
2028 {
2029 ScrollFrameHelper* self = static_cast<ScrollFrameHelper*>(anInstance);
2030
2031 // Fire the synth mouse move.
2032 self->mScrollActivityTimer->Cancel();
2033 self->mScrollActivityTimer = nullptr;
2034 self->mOuter->PresContext()->PresShell()->SynthesizeMouseMove(true);
2035 }
2036
2037
2038 void
2039 ScrollFrameHelper::ScheduleSyntheticMouseMove()
2040 {
2041 if (!mScrollActivityTimer) {
2042 mScrollActivityTimer = do_CreateInstance("@mozilla.org/timer;1");
2043 if (!mScrollActivityTimer)
2044 return;
2045 }
2046
2047 mScrollActivityTimer->InitWithFuncCallback(
2048 ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT);
2049 }
2050
2051 void
2052 ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOrigin)
2053 {
2054 if (aOrigin == nullptr) {
2055 // If no origin was specified, we still want to set it to something that's
2056 // non-null, so that we can use nullness to distinguish if the frame was scrolled
2057 // at all. Default it to some generic placeholder.
2058 aOrigin = nsGkAtoms::other;
2059 }
2060
2061 nsPresContext* presContext = mOuter->PresContext();
2062 nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
2063 // 'scale' is our estimate of the scale factor that will be applied
2064 // when rendering the scrolled content to its own ThebesLayer.
2065 gfxSize scale = FrameLayerBuilder::GetThebesLayerScaleForFrame(mScrolledFrame);
2066 nsPoint curPos = GetScrollPosition();
2067 nsPoint alignWithPos = mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)
2068 ? curPos : mScrollPosForLayerPixelAlignment;
2069 // Try to align aPt with curPos so they have an integer number of layer
2070 // pixels between them. This gives us the best chance of scrolling without
2071 // having to invalidate due to changes in subpixel rendering.
2072 // Note that when we actually draw into a ThebesLayer, the coordinates
2073 // that get mapped onto the layer buffer pixels are from the display list,
2074 // which are relative to the display root frame's top-left increasing down,
2075 // whereas here our coordinates are scroll positions which increase upward
2076 // and are relative to the scrollport top-left. This difference doesn't actually
2077 // matter since all we are about is that there be an integer number of
2078 // layer pixels between pt and curPos.
2079 nsPoint pt =
2080 ClampAndAlignWithLayerPixels(aPt,
2081 GetScrollRangeForClamping(),
2082 aRange,
2083 alignWithPos,
2084 appUnitsPerDevPixel,
2085 scale);
2086 if (pt == curPos) {
2087 return;
2088 }
2089
2090 bool needImageVisibilityUpdate = (mLastUpdateImagesPos == nsPoint(-1,-1));
2091
2092 nsPoint dist(std::abs(pt.x - mLastUpdateImagesPos.x),
2093 std::abs(pt.y - mLastUpdateImagesPos.y));
2094 nsSize scrollPortSize = GetScrollPositionClampingScrollPortSize();
2095 nscoord horzAllowance = std::max(scrollPortSize.width / std::max(sHorzScrollFraction, 1),
2096 nsPresContext::AppUnitsPerCSSPixel());
2097 nscoord vertAllowance = std::max(scrollPortSize.height / std::max(sVertScrollFraction, 1),
2098 nsPresContext::AppUnitsPerCSSPixel());
2099 if (dist.x >= horzAllowance || dist.y >= vertAllowance) {
2100 needImageVisibilityUpdate = true;
2101 }
2102
2103 if (needImageVisibilityUpdate) {
2104 presContext->PresShell()->ScheduleImageVisibilityUpdate();
2105 }
2106
2107 // notify the listeners.
2108 for (uint32_t i = 0; i < mListeners.Length(); i++) {
2109 mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
2110 }
2111
2112 nsPoint oldScrollFramePos = mScrolledFrame->GetPosition();
2113 // Update frame position for scrolling
2114 mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
2115 mOriginOfLastScroll = aOrigin;
2116 mScrollGeneration = ++sScrollGenerationCounter;
2117
2118 // We pass in the amount to move visually
2119 ScrollVisual(oldScrollFramePos);
2120
2121 ScheduleSyntheticMouseMove();
2122 nsWeakFrame weakFrame(mOuter);
2123 UpdateScrollbarPosition();
2124 if (!weakFrame.IsAlive()) {
2125 return;
2126 }
2127 PostScrollEvent();
2128
2129 // notify the listeners.
2130 for (uint32_t i = 0; i < mListeners.Length(); i++) {
2131 mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
2132 }
2133
2134 nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell();
2135 if (docShell) {
2136 docShell->NotifyScrollObservers();
2137 }
2138 }
2139
2140 static int32_t
2141 MaxZIndexInList(nsDisplayList* aList, nsDisplayListBuilder* aBuilder)
2142 {
2143 int32_t maxZIndex = 0;
2144 for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) {
2145 maxZIndex = std::max(maxZIndex, item->ZIndex());
2146 }
2147 return maxZIndex;
2148 }
2149
2150 static void
2151 AppendToTop(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists,
2152 nsDisplayList* aSource, nsIFrame* aSourceFrame, bool aOwnLayer,
2153 uint32_t aFlags, mozilla::layers::FrameMetrics::ViewID aScrollTargetId,
2154 bool aPositioned)
2155 {
2156 if (aSource->IsEmpty())
2157 return;
2158
2159 nsDisplayWrapList* newItem = aOwnLayer?
2160 new (aBuilder) nsDisplayOwnLayer(aBuilder, aSourceFrame, aSource,
2161 aFlags, aScrollTargetId) :
2162 new (aBuilder) nsDisplayWrapList(aBuilder, aSourceFrame, aSource);
2163
2164 if (aPositioned) {
2165 // We want overlay scrollbars to always be on top of the scrolled content,
2166 // but we don't want them to unnecessarily cover overlapping elements from
2167 // outside our scroll frame.
2168 nsDisplayList* positionedDescendants = aLists.PositionedDescendants();
2169 if (!positionedDescendants->IsEmpty()) {
2170 newItem->SetOverrideZIndex(MaxZIndexInList(positionedDescendants, aBuilder));
2171 positionedDescendants->AppendNewToTop(newItem);
2172 } else {
2173 aLists.Outlines()->AppendNewToTop(newItem);
2174 }
2175 } else {
2176 aLists.BorderBackground()->AppendNewToTop(newItem);
2177 }
2178 }
2179
2180 struct HoveredStateComparator
2181 {
2182 bool Equals(nsIFrame* A, nsIFrame* B) const {
2183 bool aHovered = A->GetContent()->HasAttr(kNameSpaceID_None,
2184 nsGkAtoms::hover);
2185 bool bHovered = B->GetContent()->HasAttr(kNameSpaceID_None,
2186 nsGkAtoms::hover);
2187 return aHovered == bHovered;
2188 }
2189 bool LessThan(nsIFrame* A, nsIFrame* B) const {
2190 bool aHovered = A->GetContent()->HasAttr(kNameSpaceID_None,
2191 nsGkAtoms::hover);
2192 bool bHovered = B->GetContent()->HasAttr(kNameSpaceID_None,
2193 nsGkAtoms::hover);
2194 return !aHovered && bHovered;
2195 }
2196 };
2197
2198 void
2199 ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder,
2200 const nsRect& aDirtyRect,
2201 const nsDisplayListSet& aLists,
2202 bool& aCreateLayer,
2203 bool aPositioned)
2204 {
2205 nsITheme* theme = mOuter->PresContext()->GetTheme();
2206 if (theme &&
2207 theme->ShouldHideScrollbars()) {
2208 return;
2209 }
2210
2211 bool overlayScrollbars =
2212 LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
2213
2214 nsAutoTArray<nsIFrame*, 3> scrollParts;
2215 for (nsIFrame* kid = mOuter->GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) {
2216 if (kid == mScrolledFrame ||
2217 (kid->IsPositioned() || overlayScrollbars) != aPositioned)
2218 continue;
2219
2220 scrollParts.AppendElement(kid);
2221 }
2222
2223 mozilla::layers::FrameMetrics::ViewID scrollTargetId = aCreateLayer
2224 ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
2225 : mozilla::layers::FrameMetrics::NULL_SCROLL_ID;
2226
2227 scrollParts.Sort(HoveredStateComparator());
2228
2229 for (uint32_t i = 0; i < scrollParts.Length(); ++i) {
2230 nsDisplayListCollection partList;
2231 mOuter->BuildDisplayListForChild(
2232 aBuilder, scrollParts[i], aDirtyRect, partList,
2233 nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
2234
2235 uint32_t flags = 0;
2236 if (scrollParts[i] == mVScrollbarBox) {
2237 flags |= nsDisplayOwnLayer::VERTICAL_SCROLLBAR;
2238 }
2239 if (scrollParts[i] == mHScrollbarBox) {
2240 flags |= nsDisplayOwnLayer::HORIZONTAL_SCROLLBAR;
2241 }
2242
2243 // DISPLAY_CHILD_FORCE_STACKING_CONTEXT put everything into
2244 // partList.PositionedDescendants().
2245 ::AppendToTop(aBuilder, aLists,
2246 partList.PositionedDescendants(), scrollParts[i],
2247 aCreateLayer, flags, scrollTargetId, aPositioned);
2248 }
2249 }
2250
2251 class ScrollLayerWrapper : public nsDisplayWrapper
2252 {
2253 public:
2254 ScrollLayerWrapper(nsIFrame* aScrollFrame, nsIFrame* aScrolledFrame)
2255 : mCount(0)
2256 , mProps(aScrolledFrame->Properties())
2257 , mScrollFrame(aScrollFrame)
2258 , mScrolledFrame(aScrolledFrame)
2259 {
2260 SetCount(0);
2261 }
2262
2263 virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
2264 nsIFrame* aFrame,
2265 nsDisplayList* aList) MOZ_OVERRIDE {
2266 SetCount(++mCount);
2267 return new (aBuilder) nsDisplayScrollLayer(aBuilder, aList, mScrolledFrame, mScrolledFrame, mScrollFrame);
2268 }
2269
2270 virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
2271 nsDisplayItem* aItem) MOZ_OVERRIDE {
2272
2273 SetCount(++mCount);
2274 return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->Frame(), mScrolledFrame, mScrollFrame);
2275 }
2276
2277 protected:
2278 void SetCount(intptr_t aCount) {
2279 mProps.Set(nsIFrame::ScrollLayerCount(), reinterpret_cast<void*>(aCount));
2280 }
2281
2282 intptr_t mCount;
2283 FrameProperties mProps;
2284 nsIFrame* mScrollFrame;
2285 nsIFrame* mScrolledFrame;
2286 };
2287
2288 /* static */ bool ScrollFrameHelper::sImageVisPrefsCached = false;
2289 /* static */ uint32_t ScrollFrameHelper::sHorzExpandScrollPort = 0;
2290 /* static */ uint32_t ScrollFrameHelper::sVertExpandScrollPort = 1;
2291 /* static */ int32_t ScrollFrameHelper::sHorzScrollFraction = 2;
2292 /* static */ int32_t ScrollFrameHelper::sVertScrollFraction = 2;
2293
2294 /* static */ void
2295 ScrollFrameHelper::EnsureImageVisPrefsCached()
2296 {
2297 if (!sImageVisPrefsCached) {
2298 Preferences::AddUintVarCache(&sHorzExpandScrollPort,
2299 "layout.imagevisibility.numscrollportwidths", (uint32_t)0);
2300 Preferences::AddUintVarCache(&sVertExpandScrollPort,
2301 "layout.imagevisibility.numscrollportheights", 1);
2302
2303 Preferences::AddIntVarCache(&sHorzScrollFraction,
2304 "layout.imagevisibility.amountscrollbeforeupdatehorizontal", 2);
2305 Preferences::AddIntVarCache(&sVertScrollFraction,
2306 "layout.imagevisibility.amountscrollbeforeupdatevertical", 2);
2307
2308 sImageVisPrefsCached = true;
2309 }
2310 }
2311
2312 nsRect
2313 ScrollFrameHelper::ExpandRect(const nsRect& aRect) const
2314 {
2315 // We don't want to expand a rect in a direction that we can't scroll, so we
2316 // check the scroll range.
2317 nsRect scrollRange = GetScrollRangeForClamping();
2318 nsPoint scrollPos = GetScrollPosition();
2319 nsMargin expand(0, 0, 0, 0);
2320
2321 nscoord vertShift = sVertExpandScrollPort * aRect.height;
2322 if (scrollRange.y < scrollPos.y) {
2323 expand.top = vertShift;
2324 }
2325 if (scrollPos.y < scrollRange.YMost()) {
2326 expand.bottom = vertShift;
2327 }
2328
2329 nscoord horzShift = sHorzExpandScrollPort * aRect.width;
2330 if (scrollRange.x < scrollPos.x) {
2331 expand.left = horzShift;
2332 }
2333 if (scrollPos.x < scrollRange.XMost()) {
2334 expand.right = horzShift;
2335 }
2336
2337 nsRect rect = aRect;
2338 rect.Inflate(expand);
2339 return rect;
2340 }
2341
2342 static bool
2343 ShouldBeClippedByFrame(nsIFrame* aClipFrame, nsIFrame* aClippedFrame)
2344 {
2345 return nsLayoutUtils::IsProperAncestorFrame(aClipFrame, aClippedFrame);
2346 }
2347
2348 static void
2349 ClipItemsExceptCaret(nsDisplayList* aList, nsDisplayListBuilder* aBuilder,
2350 nsIFrame* aClipFrame, const DisplayItemClip& aClip)
2351 {
2352 nsDisplayItem* i = aList->GetBottom();
2353 for (; i; i = i->GetAbove()) {
2354 if (!::ShouldBeClippedByFrame(aClipFrame, i->Frame())) {
2355 continue;
2356 }
2357
2358 bool unused;
2359 nsRect bounds = i->GetBounds(aBuilder, &unused);
2360 bool isAffectedByClip = aClip.IsRectAffectedByClip(bounds);
2361 if (isAffectedByClip && nsDisplayItem::TYPE_CARET == i->GetType()) {
2362 // Don't clip the caret if it overflows vertically only, and by half
2363 // its height at most. This is to avoid clipping it when the line-height
2364 // is small.
2365 auto half = bounds.height / 2;
2366 bounds.y += half;
2367 bounds.height -= half;
2368 isAffectedByClip = aClip.IsRectAffectedByClip(bounds);
2369 if (isAffectedByClip) {
2370 // Don't clip the caret if it's just outside on the right side.
2371 nsRect rightSide(bounds.x - 1, bounds.y, 1, bounds.height);
2372 isAffectedByClip = aClip.IsRectAffectedByClip(rightSide);
2373 // Also, avoid clipping it in a zero-height line box (heuristic only).
2374 if (isAffectedByClip) {
2375 isAffectedByClip = i->Frame()->GetRect().height != 0;
2376 }
2377 }
2378 }
2379 if (isAffectedByClip) {
2380 DisplayItemClip newClip;
2381 newClip.IntersectWith(i->GetClip());
2382 newClip.IntersectWith(aClip);
2383 i->SetClip(aBuilder, newClip);
2384 }
2385 nsDisplayList* children = i->GetSameCoordinateSystemChildren();
2386 if (children) {
2387 ClipItemsExceptCaret(children, aBuilder, aClipFrame, aClip);
2388 }
2389 }
2390 }
2391
2392 static void
2393 ClipListsExceptCaret(nsDisplayListCollection* aLists,
2394 nsDisplayListBuilder* aBuilder,
2395 nsIFrame* aClipFrame,
2396 const DisplayItemClip& aClip)
2397 {
2398 ::ClipItemsExceptCaret(aLists->BorderBackground(), aBuilder, aClipFrame, aClip);
2399 ::ClipItemsExceptCaret(aLists->BlockBorderBackgrounds(), aBuilder, aClipFrame, aClip);
2400 ::ClipItemsExceptCaret(aLists->Floats(), aBuilder, aClipFrame, aClip);
2401 ::ClipItemsExceptCaret(aLists->PositionedDescendants(), aBuilder, aClipFrame, aClip);
2402 ::ClipItemsExceptCaret(aLists->Outlines(), aBuilder, aClipFrame, aClip);
2403 ::ClipItemsExceptCaret(aLists->Content(), aBuilder, aClipFrame, aClip);
2404 }
2405
2406 static bool
2407 DisplayportExceedsMaxTextureSize(nsPresContext* aPresContext, const nsRect& aDisplayPort)
2408 {
2409 #ifdef MOZ_WIDGET_GONK
2410 // On B2G we actively run into max texture size limits because the displayport-sizing code
2411 // in the AsyncPanZoomController code is slightly busted (bug 957668 will fix it properly).
2412 // We sometimes end up requesting displayports for which the corresponding layer will be
2413 // larger than the max GL texture size (which we assume to be 4096 here).
2414 // If we run into this case, we should just not use the displayport at all and fall back to
2415 // just making a ScrollInfoLayer so that we use the APZC's synchronous scrolling fallback
2416 // mechanism.
2417 // Note also that if we don't do this here, it is quite likely that the parent B2G process
2418 // will kill this child process to prevent OOMs (see the patch that landed as part of bug
2419 // 965945 for details).
2420 gfxSize resolution = aPresContext->PresShell()->GetCumulativeResolution();
2421 return (aPresContext->AppUnitsToDevPixels(aDisplayPort.width) * resolution.width > 4096) ||
2422 (aPresContext->AppUnitsToDevPixels(aDisplayPort.height) * resolution.height > 4096);
2423 #else
2424 return false;
2425 #endif
2426 }
2427
2428 void
2429 ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
2430 const nsRect& aDirtyRect,
2431 const nsDisplayListSet& aLists)
2432 {
2433 if (aBuilder->IsForImageVisibility()) {
2434 mLastUpdateImagesPos = GetScrollPosition();
2435 }
2436
2437 mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
2438
2439 if (aBuilder->IsPaintingToWindow()) {
2440 mScrollPosAtLastPaint = GetScrollPosition();
2441 if (IsScrollingActive() && !CanScrollWithBlitting(mOuter)) {
2442 MarkInactive();
2443 }
2444 if (IsScrollingActive()) {
2445 if (mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)) {
2446 mScrollPosForLayerPixelAlignment = mScrollPosAtLastPaint;
2447 }
2448 } else {
2449 mScrollPosForLayerPixelAlignment = nsPoint(-1,-1);
2450 }
2451 }
2452
2453 // We put scrollbars in their own layers when this is the root scroll
2454 // frame and we are a toplevel content document. In this situation, the
2455 // scrollbar(s) would normally be assigned their own layer anyway, since
2456 // they're not scrolled with the rest of the document. But when both
2457 // scrollbars are visible, the layer's visible rectangle would be the size
2458 // of the viewport, so most layer implementations would create a layer buffer
2459 // that's much larger than necessary. Creating independent layers for each
2460 // scrollbar works around the problem.
2461 bool createLayersForScrollbars = mIsRoot &&
2462 mOuter->PresContext()->IsRootContentDocument();
2463
2464 if (aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping()) {
2465
2466 // If we are a root scroll frame that has a display port we want to add
2467 // scrollbars, they will be children of the scrollable layer, but they get
2468 // adjusted by the APZC automatically.
2469 bool addScrollBars = mIsRoot &&
2470 nsLayoutUtils::GetDisplayPort(mOuter->GetContent()) &&
2471 !aBuilder->IsForEventDelivery();
2472 // For now, don't add them for display root documents, cause we've never
2473 // had them there.
2474 if (aBuilder->RootReferenceFrame()->PresContext() == mOuter->PresContext()) {
2475 addScrollBars = false;
2476 }
2477
2478 if (addScrollBars) {
2479 // Add classic scrollbars.
2480 AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars,
2481 false);
2482 }
2483
2484 // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
2485 // The scrolled frame shouldn't have its own background/border, so we
2486 // can just pass aLists directly.
2487 mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame,
2488 aDirtyRect, aLists);
2489
2490 #ifdef MOZ_WIDGET_GONK
2491 // TODO: only layerize the overlay scrollbars if this scrollframe can be
2492 // panned asynchronously. For now just always layerize on B2G because.
2493 // that's where we want the layerized scrollbars
2494 createLayersForScrollbars = true;
2495 #endif
2496 if (addScrollBars) {
2497 // Add overlay scrollbars.
2498 AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars,
2499 true);
2500 }
2501
2502 return;
2503 }
2504
2505 // Now display the scrollbars and scrollcorner. These parts are drawn
2506 // in the border-background layer, on top of our own background and
2507 // borders and underneath borders and backgrounds of later elements
2508 // in the tree.
2509 // Note that this does not apply for overlay scrollbars; those are drawn
2510 // in the positioned-elements layer on top of everything else by the call
2511 // to AppendScrollPartsTo(..., true) further down.
2512 AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars,
2513 false);
2514
2515 // Overflow clipping can never clip frames outside our subtree, so there
2516 // is no need to worry about whether we are a moving frame that might clip
2517 // non-moving frames.
2518 // Not all our descendants will be clipped by overflow clipping, but all
2519 // the ones that aren't clipped will be out of flow frames that have already
2520 // had dirty rects saved for them by their parent frames calling
2521 // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
2522 // dirty rect here.
2523 nsRect dirtyRect = aDirtyRect.Intersect(mScrollPort);
2524
2525 nsRect displayPort;
2526 bool usingDisplayport = false;
2527 if (!aBuilder->IsForEventDelivery()) {
2528 if (!mIsRoot) {
2529 // For a non-root scroll frame, override the value of the display port
2530 // base rect, and possibly create a display port if there isn't one
2531 // already. For root scroll frame, nsLayoutUtils::PaintFrame or
2532 // nsSubDocumentFrame::BuildDisplayList takes care of this.
2533 nsRect displayportBase = dirtyRect;
2534 usingDisplayport = nsLayoutUtils::GetOrMaybeCreateDisplayPort(
2535 *aBuilder, mOuter, displayportBase, &displayPort);
2536 } else {
2537 // For a root frmae, just get the value of the existing of the display
2538 // port, if any.
2539 usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort);
2540 }
2541
2542 if (usingDisplayport && DisplayportExceedsMaxTextureSize(mOuter->PresContext(), displayPort)) {
2543 usingDisplayport = false;
2544 }
2545
2546 // Override the dirty rectangle if the displayport has been set.
2547 if (usingDisplayport) {
2548 dirtyRect = displayPort;
2549 }
2550 }
2551
2552 if (aBuilder->IsForImageVisibility()) {
2553 // We expand the dirty rect to catch images just outside of the scroll port.
2554 // We use the dirty rect instead of the whole scroll port to prevent
2555 // too much expansion in the presence of very large (bigger than the
2556 // viewport) scroll ports.
2557 dirtyRect = ExpandRect(dirtyRect);
2558 }
2559
2560 // Since making new layers is expensive, only use nsDisplayScrollLayer
2561 // if the area is scrollable and we're the content process (unless we're on
2562 // B2G, where we support async scrolling for scrollable elements in the
2563 // parent process as well).
2564 // When a displayport is being used, force building of a layer so that
2565 // CompositorParent can always find the scrollable layer for the root content
2566 // document.
2567 // If the element is marked 'scrollgrab', also force building of a layer
2568 // so that APZ can implement scroll grabbing.
2569 mShouldBuildScrollableLayer = usingDisplayport || nsContentUtils::HasScrollgrab(mOuter->GetContent());
2570 bool shouldBuildLayer = false;
2571 if (mShouldBuildScrollableLayer) {
2572 shouldBuildLayer = true;
2573 } else {
2574 shouldBuildLayer =
2575 nsLayoutUtils::WantSubAPZC() &&
2576 WantAsyncScroll() &&
2577 // If we are the root scroll frame for the display root then we don't need a scroll
2578 // info layer to make a RecordFrameMetrics call for us as
2579 // nsDisplayList::PaintForFrame already calls RecordFrameMetrics for us.
2580 (!mIsRoot || aBuilder->RootReferenceFrame()->PresContext() != mOuter->PresContext());
2581 }
2582
2583 nsDisplayListCollection scrolledContent;
2584 {
2585 // Note that setting the current scroll parent id here means that positioned children
2586 // of this scroll info layer will pick up the scroll info layer as their scroll handoff
2587 // parent. This is intentional because that is what happens for positioned children
2588 // of scroll layers, and we want to maintain consistent behaviour between scroll layers
2589 // and scroll info layers.
2590 nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(
2591 aBuilder,
2592 shouldBuildLayer && mScrolledFrame->GetContent()
2593 ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
2594 : aBuilder->GetCurrentScrollParentId());
2595 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
2596
2597 if (usingDisplayport) {
2598 nsRect clip = displayPort + aBuilder->ToReferenceFrame(mOuter);
2599
2600 // If we are using a display port, then ignore any pre-existing clip
2601 // passed down from our parents, and use only the clip computed here
2602 // based on the display port. The pre-existing clip would just defeat
2603 // the purpose of a display port which is to paint regions that are not
2604 // currently visible so that they can be brought into view asynchronously.
2605 // Notes:
2606 // - The pre-existing clip state will be restored when the
2607 // AutoSaveRestore goes out of scope, so there is no permanent change
2608 // to this clip state.
2609 // - We still set a clip to the scroll port further below where we
2610 // build the scroll wrapper. This doesn't prevent us from painting
2611 // the entire displayport, but it lets the compositor know to
2612 // clip to the scroll port after compositing.
2613 clipState.Clear();
2614
2615 if (mClipAllDescendants) {
2616 clipState.ClipContentDescendants(clip);
2617 } else {
2618 clipState.ClipContainingBlockDescendants(clip, nullptr);
2619 }
2620 } else {
2621 nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
2622 nscoord radii[8];
2623 bool haveRadii = mOuter->GetPaddingBoxBorderRadii(radii);
2624 // Our override of GetBorderRadii ensures we never have a radius at
2625 // the corners where we have a scrollbar.
2626 if (mClipAllDescendants) {
2627 clipState.ClipContentDescendants(clip, haveRadii ? radii : nullptr);
2628 } else {
2629 clipState.ClipContainingBlockDescendants(clip, haveRadii ? radii : nullptr);
2630 }
2631 }
2632
2633 mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, scrolledContent);
2634 }
2635
2636 if (MOZ_UNLIKELY(mOuter->StyleDisplay()->mOverflowClipBox ==
2637 NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) {
2638 // We only clip if there is *scrollable* overflow, to avoid clipping
2639 // *visual* overflow unnecessarily.
2640 nsRect clipRect = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
2641 nsRect so = mScrolledFrame->GetScrollableOverflowRect();
2642 if (clipRect.width != so.width || clipRect.height != so.height ||
2643 so.x < 0 || so.y < 0) {
2644 // The 'scrolledContent' items are clipped to the padding-box at this point.
2645 // Now clip them again to the content-box, except the nsDisplayCaret item
2646 // which we allow to overflow the content-box in various situations --
2647 // see ::ClipItemsExceptCaret.
2648 clipRect.Deflate(mOuter->GetUsedPadding());
2649 DisplayItemClip clip;
2650 clip.SetTo(clipRect);
2651 ::ClipListsExceptCaret(&scrolledContent, aBuilder, mScrolledFrame, clip);
2652 }
2653 }
2654
2655 if (shouldBuildLayer) {
2656 // ScrollLayerWrapper must always be created because it initializes the
2657 // scroll layer count. The display lists depend on this.
2658 ScrollLayerWrapper wrapper(mOuter, mScrolledFrame);
2659
2660 if (mShouldBuildScrollableLayer) {
2661 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
2662
2663 // For root scrollframes in documents where the CSS viewport has been
2664 // modified, the CSS viewport no longer corresponds to what is visible,
2665 // so we don't want to clip the content to it. For root scrollframes
2666 // in documents where the CSS viewport is NOT modified, the mScrollPort
2667 // is the same as the CSS viewport, modulo scrollbars.
2668 if (!(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden())) {
2669 nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
2670 if (mClipAllDescendants) {
2671 clipState.ClipContentDescendants(clip);
2672 } else {
2673 clipState.ClipContainingBlockDescendants(clip);
2674 }
2675 }
2676
2677 // Once a displayport is set, assume that scrolling needs to be fast
2678 // so create a layer with all the content inside. The compositor
2679 // process will be able to scroll the content asynchronously.
2680 wrapper.WrapListsInPlace(aBuilder, mOuter, scrolledContent);
2681 }
2682
2683 // In case we are not using displayport or the nsDisplayScrollLayers are
2684 // flattened during visibility computation, we still need to export the
2685 // metadata about this scroll box to the compositor process.
2686 nsDisplayScrollInfoLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer(
2687 aBuilder, mScrolledFrame, mOuter);
2688 scrolledContent.BorderBackground()->AppendNewToBottom(layerItem);
2689 }
2690 // Now display overlay scrollbars and the resizer, if we have one.
2691 #ifdef MOZ_WIDGET_GONK
2692 // TODO: only layerize the overlay scrollbars if this scrollframe can be
2693 // panned asynchronously. For now just always layerize on B2G because.
2694 // that's where we want the layerized scrollbars
2695 createLayersForScrollbars = true;
2696 #endif
2697 AppendScrollPartsTo(aBuilder, aDirtyRect, scrolledContent,
2698 createLayersForScrollbars, true);
2699 scrolledContent.MoveTo(aLists);
2700 }
2701
2702 bool
2703 ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const
2704 {
2705 // Use the right rect depending on if a display port is set.
2706 nsRect displayPort;
2707 bool usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort);
2708 return aRect.Intersects(ExpandRect(usingDisplayport ? displayPort : mScrollPort));
2709 }
2710
2711 static void HandleScrollPref(nsIScrollable *aScrollable, int32_t aOrientation,
2712 uint8_t& aValue)
2713 {
2714 int32_t pref;
2715 aScrollable->GetDefaultScrollbarPreferences(aOrientation, &pref);
2716 switch (pref) {
2717 case nsIScrollable::Scrollbar_Auto:
2718 // leave |aValue| untouched
2719 break;
2720 case nsIScrollable::Scrollbar_Never:
2721 aValue = NS_STYLE_OVERFLOW_HIDDEN;
2722 break;
2723 case nsIScrollable::Scrollbar_Always:
2724 aValue = NS_STYLE_OVERFLOW_SCROLL;
2725 break;
2726 }
2727 }
2728
2729 ScrollbarStyles
2730 ScrollFrameHelper::GetScrollbarStylesFromFrame() const
2731 {
2732 nsPresContext* presContext = mOuter->PresContext();
2733 if (!presContext->IsDynamic() &&
2734 !(mIsRoot && presContext->HasPaginatedScrolling())) {
2735 return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN);
2736 }
2737
2738 if (!mIsRoot) {
2739 const nsStyleDisplay* disp = mOuter->StyleDisplay();
2740 return ScrollbarStyles(disp->mOverflowX, disp->mOverflowY);
2741 }
2742
2743 ScrollbarStyles result = presContext->GetViewportOverflowOverride();
2744 nsCOMPtr<nsISupports> container = presContext->GetContainerWeak();
2745 nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(container);
2746 if (scrollable) {
2747 HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_X,
2748 result.mHorizontal);
2749 HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_Y,
2750 result.mVertical);
2751 }
2752 return result;
2753 }
2754
2755 nsRect
2756 ScrollFrameHelper::GetScrollRange() const
2757 {
2758 return GetScrollRange(mScrollPort.width, mScrollPort.height);
2759 }
2760
2761 nsRect
2762 ScrollFrameHelper::GetScrollRange(nscoord aWidth, nscoord aHeight) const
2763 {
2764 nsRect range = GetScrolledRect();
2765 range.width = std::max(range.width - aWidth, 0);
2766 range.height = std::max(range.height - aHeight, 0);
2767 return range;
2768 }
2769
2770 nsRect
2771 ScrollFrameHelper::GetScrollRangeForClamping() const
2772 {
2773 if (!ShouldClampScrollPosition()) {
2774 return nsRect(nscoord_MIN/2, nscoord_MIN/2,
2775 nscoord_MAX - nscoord_MIN/2, nscoord_MAX - nscoord_MIN/2);
2776 }
2777 nsSize scrollPortSize = GetScrollPositionClampingScrollPortSize();
2778 return GetScrollRange(scrollPortSize.width, scrollPortSize.height);
2779 }
2780
2781 nsSize
2782 ScrollFrameHelper::GetScrollPositionClampingScrollPortSize() const
2783 {
2784 nsIPresShell* presShell = mOuter->PresContext()->PresShell();
2785 if (mIsRoot && presShell->IsScrollPositionClampingScrollPortSizeSet()) {
2786 return presShell->GetScrollPositionClampingScrollPortSize();
2787 }
2788 return mScrollPort.Size();
2789 }
2790
2791 gfxSize
2792 ScrollFrameHelper::GetResolution() const
2793 {
2794 return mResolution;
2795 }
2796
2797 void
2798 ScrollFrameHelper::SetResolution(const gfxSize& aResolution)
2799 {
2800 mResolution = aResolution;
2801 mIsResolutionSet = true;
2802 }
2803
2804 static void
2805 AdjustForWholeDelta(int32_t aDelta, nscoord* aCoord)
2806 {
2807 if (aDelta < 0) {
2808 *aCoord = nscoord_MIN;
2809 } else if (aDelta > 0) {
2810 *aCoord = nscoord_MAX;
2811 }
2812 }
2813
2814 /**
2815 * Calculate lower/upper scrollBy range in given direction.
2816 * @param aDelta specifies scrollBy direction, if 0 then range will be 0 size
2817 * @param aPos desired destination in AppUnits
2818 * @param aNeg/PosTolerance defines relative range distance
2819 * below and above of aPos point
2820 * @param aMultiplier used for conversion of tolerance into appUnis
2821 */
2822 static void
2823 CalcRangeForScrollBy(int32_t aDelta, nscoord aPos,
2824 float aNegTolerance,
2825 float aPosTolerance,
2826 nscoord aMultiplier,
2827 nscoord* aLower, nscoord* aUpper)
2828 {
2829 if (!aDelta) {
2830 *aLower = *aUpper = aPos;
2831 return;
2832 }
2833 *aLower = aPos - NSToCoordRound(aMultiplier * (aDelta > 0 ? aNegTolerance : aPosTolerance));
2834 *aUpper = aPos + NSToCoordRound(aMultiplier * (aDelta > 0 ? aPosTolerance : aNegTolerance));
2835 }
2836
2837 void
2838 ScrollFrameHelper::ScrollBy(nsIntPoint aDelta,
2839 nsIScrollableFrame::ScrollUnit aUnit,
2840 nsIScrollableFrame::ScrollMode aMode,
2841 nsIntPoint* aOverflow,
2842 nsIAtom *aOrigin)
2843 {
2844 nsSize deltaMultiplier;
2845 float negativeTolerance;
2846 float positiveTolerance;
2847 if (!aOrigin){
2848 aOrigin = nsGkAtoms::other;
2849 }
2850 bool isGenericOrigin = (aOrigin == nsGkAtoms::other);
2851 switch (aUnit) {
2852 case nsIScrollableFrame::DEVICE_PIXELS: {
2853 nscoord appUnitsPerDevPixel =
2854 mOuter->PresContext()->AppUnitsPerDevPixel();
2855 deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
2856 if (isGenericOrigin){
2857 aOrigin = nsGkAtoms::pixels;
2858 }
2859 negativeTolerance = positiveTolerance = 0.5f;
2860 break;
2861 }
2862 case nsIScrollableFrame::LINES: {
2863 deltaMultiplier = GetLineScrollAmount();
2864 if (isGenericOrigin){
2865 aOrigin = nsGkAtoms::lines;
2866 }
2867 negativeTolerance = positiveTolerance = 0.1f;
2868 break;
2869 }
2870 case nsIScrollableFrame::PAGES: {
2871 deltaMultiplier = GetPageScrollAmount();
2872 if (isGenericOrigin){
2873 aOrigin = nsGkAtoms::pages;
2874 }
2875 negativeTolerance = 0.05f;
2876 positiveTolerance = 0;
2877 break;
2878 }
2879 case nsIScrollableFrame::WHOLE: {
2880 nsPoint pos = GetScrollPosition();
2881 AdjustForWholeDelta(aDelta.x, &pos.x);
2882 AdjustForWholeDelta(aDelta.y, &pos.y);
2883 ScrollTo(pos, aMode);
2884 // 'this' might be destroyed here
2885 if (aOverflow) {
2886 *aOverflow = nsIntPoint(0, 0);
2887 }
2888 return;
2889 }
2890 default:
2891 NS_ERROR("Invalid scroll mode");
2892 return;
2893 }
2894
2895 nsPoint newPos = mDestination +
2896 nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height);
2897 // Calculate desired range values.
2898 nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY;
2899 CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance,
2900 deltaMultiplier.width, &rangeLowerX, &rangeUpperX);
2901 CalcRangeForScrollBy(aDelta.y, newPos.y, negativeTolerance, positiveTolerance,
2902 deltaMultiplier.height, &rangeLowerY, &rangeUpperY);
2903 nsRect range(rangeLowerX,
2904 rangeLowerY,
2905 rangeUpperX - rangeLowerX,
2906 rangeUpperY - rangeLowerY);
2907 nsWeakFrame weakFrame(mOuter);
2908 ScrollToWithOrigin(newPos, aMode, aOrigin, &range);
2909 if (!weakFrame.IsAlive()) {
2910 return;
2911 }
2912
2913 if (aOverflow) {
2914 nsPoint clampAmount = newPos - mDestination;
2915 float appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
2916 *aOverflow = nsIntPoint(
2917 NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel),
2918 NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel));
2919 }
2920 }
2921
2922 nsSize
2923 ScrollFrameHelper::GetLineScrollAmount() const
2924 {
2925 nsRefPtr<nsFontMetrics> fm;
2926 nsLayoutUtils::GetFontMetricsForFrame(mOuter, getter_AddRefs(fm),
2927 nsLayoutUtils::FontSizeInflationFor(mOuter));
2928 NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
2929 static nscoord sMinLineScrollAmountInPixels = -1;
2930 if (sMinLineScrollAmountInPixels < 0) {
2931 Preferences::AddIntVarCache(&sMinLineScrollAmountInPixels,
2932 "mousewheel.min_line_scroll_amount", 1);
2933 }
2934 int32_t appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
2935 nscoord minScrollAmountInAppUnits =
2936 std::max(1, sMinLineScrollAmountInPixels) * appUnitsPerDevPixel;
2937 nscoord horizontalAmount = fm ? fm->AveCharWidth() : 0;
2938 nscoord verticalAmount = fm ? fm->MaxHeight() : 0;
2939 return nsSize(std::max(horizontalAmount, minScrollAmountInAppUnits),
2940 std::max(verticalAmount, minScrollAmountInAppUnits));
2941 }
2942
2943 /**
2944 * Compute the scrollport size excluding any fixed-pos headers and
2945 * footers. A header or footer is an box that spans that entire width
2946 * of the viewport and touches the top (or bottom, respectively) of the
2947 * viewport. We also want to consider fixed elements that stack or overlap
2948 * to effectively create a larger header or footer. Headers and footers that
2949 * cover more than a third of the the viewport are ignored since they
2950 * probably aren't true headers and footers and we don't want to restrict
2951 * scrolling too much in such cases. This is a bit conservative --- some
2952 * pages use elements as headers or footers that don't span the entire width
2953 * of the viewport --- but it should be a good start.
2954 */
2955 struct TopAndBottom
2956 {
2957 TopAndBottom(nscoord aTop, nscoord aBottom) : top(aTop), bottom(aBottom) {}
2958
2959 nscoord top, bottom;
2960 };
2961 struct TopComparator
2962 {
2963 bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
2964 return A.top == B.top;
2965 }
2966 bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
2967 return A.top < B.top;
2968 }
2969 };
2970 struct ReverseBottomComparator
2971 {
2972 bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
2973 return A.bottom == B.bottom;
2974 }
2975 bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
2976 return A.bottom > B.bottom;
2977 }
2978 };
2979 static nsSize
2980 GetScrollPortSizeExcludingHeadersAndFooters(nsIFrame* aViewportFrame,
2981 const nsRect& aScrollPort)
2982 {
2983 nsTArray<TopAndBottom> list;
2984 nsFrameList fixedFrames = aViewportFrame->GetChildList(nsIFrame::kFixedList);
2985 for (nsFrameList::Enumerator iterator(fixedFrames); !iterator.AtEnd();
2986 iterator.Next()) {
2987 nsIFrame* f = iterator.get();
2988 nsRect r = f->GetRect().Intersect(aScrollPort);
2989 if (r.x == 0 && r.width == aScrollPort.width &&
2990 r.height <= aScrollPort.height/3) {
2991 list.AppendElement(TopAndBottom(r.y, r.YMost()));
2992 }
2993 }
2994
2995 list.Sort(TopComparator());
2996 nscoord headerBottom = 0;
2997 for (uint32_t i = 0; i < list.Length(); ++i) {
2998 if (list[i].top <= headerBottom) {
2999 headerBottom = std::max(headerBottom, list[i].bottom);
3000 }
3001 }
3002
3003 list.Sort(ReverseBottomComparator());
3004 nscoord footerTop = aScrollPort.height;
3005 for (uint32_t i = 0; i < list.Length(); ++i) {
3006 if (list[i].bottom >= footerTop) {
3007 footerTop = std::min(footerTop, list[i].top);
3008 }
3009 }
3010
3011 headerBottom = std::min(aScrollPort.height/3, headerBottom);
3012 footerTop = std::max(aScrollPort.height - aScrollPort.height/3, footerTop);
3013
3014 return nsSize(aScrollPort.width, footerTop - headerBottom);
3015 }
3016
3017 nsSize
3018 ScrollFrameHelper::GetPageScrollAmount() const
3019 {
3020 nsSize lineScrollAmount = GetLineScrollAmount();
3021 nsSize effectiveScrollPortSize;
3022 if (mIsRoot) {
3023 // Reduce effective scrollport height by the height of any fixed-pos
3024 // headers or footers
3025 nsIFrame* root = mOuter->PresContext()->PresShell()->GetRootFrame();
3026 effectiveScrollPortSize =
3027 GetScrollPortSizeExcludingHeadersAndFooters(root, mScrollPort);
3028 } else {
3029 effectiveScrollPortSize = mScrollPort.Size();
3030 }
3031 // The page increment is the size of the page, minus the smaller of
3032 // 10% of the size or 2 lines.
3033 return nsSize(
3034 effectiveScrollPortSize.width -
3035 std::min(effectiveScrollPortSize.width/10, 2*lineScrollAmount.width),
3036 effectiveScrollPortSize.height -
3037 std::min(effectiveScrollPortSize.height/10, 2*lineScrollAmount.height));
3038 }
3039
3040 /**
3041 * this code is resposible for restoring the scroll position back to some
3042 * saved position. if the user has not moved the scroll position manually
3043 * we keep scrolling down until we get to our original position. keep in
3044 * mind that content could incrementally be coming in. we only want to stop
3045 * when we reach our new position.
3046 */
3047 void
3048 ScrollFrameHelper::ScrollToRestoredPosition()
3049 {
3050 if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
3051 return;
3052 }
3053 // make sure our scroll position did not change for where we last put
3054 // it. if it does then the user must have moved it, and we no longer
3055 // need to restore.
3056 //
3057 // In the RTL case, we check whether the scroll position changed using the
3058 // logical scroll position, but we scroll to the physical scroll position in
3059 // all cases
3060
3061 // if we didn't move, we still need to restore
3062 if (GetLogicalScrollPosition() == mLastPos) {
3063 // if our desired position is different to the scroll position, scroll.
3064 // remember that we could be incrementally loading so we may enter
3065 // and scroll many times.
3066 if (mRestorePos != mLastPos /* GetLogicalScrollPosition() */) {
3067 nsPoint scrollToPos = mRestorePos;
3068 if (!IsLTR())
3069 // convert from logical to physical scroll position
3070 scrollToPos.x = mScrollPort.x -
3071 (mScrollPort.XMost() - scrollToPos.x - mScrolledFrame->GetRect().width);
3072 nsWeakFrame weakFrame(mOuter);
3073 ScrollTo(scrollToPos, nsIScrollableFrame::INSTANT);
3074 if (!weakFrame.IsAlive()) {
3075 return;
3076 }
3077 // Re-get the scroll position, it might not be exactly equal to
3078 // mRestorePos due to rounding and clamping.
3079 mLastPos = GetLogicalScrollPosition();
3080 } else {
3081 // if we reached the position then stop
3082 mRestorePos.y = -1;
3083 mLastPos.x = -1;
3084 mLastPos.y = -1;
3085 }
3086 } else {
3087 // user moved the position, so we won't need to restore
3088 mLastPos.x = -1;
3089 mLastPos.y = -1;
3090 }
3091 }
3092
3093 nsresult
3094 ScrollFrameHelper::FireScrollPortEvent()
3095 {
3096 mAsyncScrollPortEvent.Forget();
3097
3098 // Keep this in sync with PostOverflowEvent().
3099 nsSize scrollportSize = mScrollPort.Size();
3100 nsSize childSize = GetScrolledRect().Size();
3101
3102 bool newVerticalOverflow = childSize.height > scrollportSize.height;
3103 bool vertChanged = mVerticalOverflow != newVerticalOverflow;
3104
3105 bool newHorizontalOverflow = childSize.width > scrollportSize.width;
3106 bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
3107
3108 if (!vertChanged && !horizChanged) {
3109 return NS_OK;
3110 }
3111
3112 // If both either overflowed or underflowed then we dispatch only one
3113 // DOM event.
3114 bool both = vertChanged && horizChanged &&
3115 newVerticalOverflow == newHorizontalOverflow;
3116 InternalScrollPortEvent::orientType orient;
3117 if (both) {
3118 orient = InternalScrollPortEvent::both;
3119 mHorizontalOverflow = newHorizontalOverflow;
3120 mVerticalOverflow = newVerticalOverflow;
3121 }
3122 else if (vertChanged) {
3123 orient = InternalScrollPortEvent::vertical;
3124 mVerticalOverflow = newVerticalOverflow;
3125 if (horizChanged) {
3126 // We need to dispatch a separate horizontal DOM event. Do that the next
3127 // time around since dispatching the vertical DOM event might destroy
3128 // the frame.
3129 PostOverflowEvent();
3130 }
3131 }
3132 else {
3133 orient = InternalScrollPortEvent::horizontal;
3134 mHorizontalOverflow = newHorizontalOverflow;
3135 }
3136
3137 InternalScrollPortEvent event(true,
3138 (orient == InternalScrollPortEvent::horizontal ? mHorizontalOverflow :
3139 mVerticalOverflow) ?
3140 NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW, nullptr);
3141 event.orient = orient;
3142 return EventDispatcher::Dispatch(mOuter->GetContent(),
3143 mOuter->PresContext(), &event);
3144 }
3145
3146 void
3147 ScrollFrameHelper::ReloadChildFrames()
3148 {
3149 mScrolledFrame = nullptr;
3150 mHScrollbarBox = nullptr;
3151 mVScrollbarBox = nullptr;
3152 mScrollCornerBox = nullptr;
3153 mResizerBox = nullptr;
3154
3155 nsIFrame* frame = mOuter->GetFirstPrincipalChild();
3156 while (frame) {
3157 nsIContent* content = frame->GetContent();
3158 if (content == mOuter->GetContent()) {
3159 NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
3160 mScrolledFrame = frame;
3161 } else {
3162 nsAutoString value;
3163 content->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, value);
3164 if (!value.IsEmpty()) {
3165 // probably a scrollbar then
3166 if (value.LowerCaseEqualsLiteral("horizontal")) {
3167 NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?");
3168 mHScrollbarBox = frame;
3169 } else {
3170 NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
3171 mVScrollbarBox = frame;
3172 }
3173 } else if (content->Tag() == nsGkAtoms::resizer) {
3174 NS_ASSERTION(!mResizerBox, "Found multiple resizers");
3175 mResizerBox = frame;
3176 } else if (content->Tag() == nsGkAtoms::scrollcorner) {
3177 // probably a scrollcorner
3178 NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
3179 mScrollCornerBox = frame;
3180 }
3181 }
3182
3183 frame = frame->GetNextSibling();
3184 }
3185 }
3186
3187 nsresult
3188 ScrollFrameHelper::CreateAnonymousContent(
3189 nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements)
3190 {
3191 nsPresContext* presContext = mOuter->PresContext();
3192 nsIFrame* parent = mOuter->GetParent();
3193
3194 // Don't create scrollbars if we're an SVG document being used as an image,
3195 // or if we're printing/print previewing.
3196 // (In the printing case, we allow scrollbars if this is the child of the
3197 // viewport & paginated scrolling is enabled, because then we must be the
3198 // scroll frame for the print preview window, & that does need scrollbars.)
3199 if (presContext->Document()->IsBeingUsedAsImage() ||
3200 (!presContext->IsDynamic() &&
3201 !(mIsRoot && presContext->HasPaginatedScrolling()))) {
3202 mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
3203 return NS_OK;
3204 }
3205
3206 // Check if the frame is resizable.
3207 int8_t resizeStyle = mOuter->StyleDisplay()->mResize;
3208 bool isResizable = resizeStyle != NS_STYLE_RESIZE_NONE;
3209
3210 nsIScrollableFrame *scrollable = do_QueryFrame(mOuter);
3211
3212 // If we're the scrollframe for the root, then we want to construct
3213 // our scrollbar frames no matter what. That way later dynamic
3214 // changes to propagated overflow styles will show or hide
3215 // scrollbars on the viewport without requiring frame reconstruction
3216 // of the viewport (good!).
3217 bool canHaveHorizontal;
3218 bool canHaveVertical;
3219 if (!mIsRoot) {
3220 ScrollbarStyles styles = scrollable->GetScrollbarStyles();
3221 canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
3222 canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
3223 if (!canHaveHorizontal && !canHaveVertical && !isResizable) {
3224 // Nothing to do.
3225 return NS_OK;
3226 }
3227 } else {
3228 canHaveHorizontal = true;
3229 canHaveVertical = true;
3230 }
3231
3232 // The anonymous <div> used by <inputs> never gets scrollbars.
3233 nsITextControlFrame* textFrame = do_QueryFrame(parent);
3234 if (textFrame) {
3235 // Make sure we are not a text area.
3236 nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement(do_QueryInterface(parent->GetContent()));
3237 if (!textAreaElement) {
3238 mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
3239 return NS_OK;
3240 }
3241 }
3242
3243 nsNodeInfoManager *nodeInfoManager =
3244 presContext->Document()->NodeInfoManager();
3245 nsCOMPtr<nsINodeInfo> nodeInfo;
3246 nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbar, nullptr,
3247 kNameSpaceID_XUL,
3248 nsIDOMNode::ELEMENT_NODE);
3249 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
3250
3251 if (canHaveHorizontal) {
3252 nsCOMPtr<nsINodeInfo> ni = nodeInfo;
3253 NS_TrustedNewXULElement(getter_AddRefs(mHScrollbarContent), ni.forget());
3254 mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
3255 NS_LITERAL_STRING("horizontal"), false);
3256 mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
3257 NS_LITERAL_STRING("always"), false);
3258 if (mIsRoot) {
3259 mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_,
3260 NS_LITERAL_STRING("true"), false);
3261 }
3262 if (!aElements.AppendElement(mHScrollbarContent))
3263 return NS_ERROR_OUT_OF_MEMORY;
3264 }
3265
3266 if (canHaveVertical) {
3267 nsCOMPtr<nsINodeInfo> ni = nodeInfo;
3268 NS_TrustedNewXULElement(getter_AddRefs(mVScrollbarContent), ni.forget());
3269 mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
3270 NS_LITERAL_STRING("vertical"), false);
3271 mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
3272 NS_LITERAL_STRING("always"), false);
3273 if (mIsRoot) {
3274 mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_,
3275 NS_LITERAL_STRING("true"), false);
3276 }
3277 if (!aElements.AppendElement(mVScrollbarContent))
3278 return NS_ERROR_OUT_OF_MEMORY;
3279 }
3280
3281 if (isResizable) {
3282 nsCOMPtr<nsINodeInfo> nodeInfo;
3283 nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nullptr,
3284 kNameSpaceID_XUL,
3285 nsIDOMNode::ELEMENT_NODE);
3286 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
3287
3288 NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget());
3289
3290 nsAutoString dir;
3291 switch (resizeStyle) {
3292 case NS_STYLE_RESIZE_HORIZONTAL:
3293 if (IsScrollbarOnRight()) {
3294 dir.AssignLiteral("right");
3295 }
3296 else {
3297 dir.AssignLiteral("left");
3298 }
3299 break;
3300 case NS_STYLE_RESIZE_VERTICAL:
3301 dir.AssignLiteral("bottom");
3302 break;
3303 case NS_STYLE_RESIZE_BOTH:
3304 dir.AssignLiteral("bottomend");
3305 break;
3306 default:
3307 NS_WARNING("only resizable types should have resizers");
3308 }
3309 mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false);
3310
3311 if (mIsRoot) {
3312 nsIContent* browserRoot = GetBrowserRoot(mOuter->GetContent());
3313 mCollapsedResizer = !(browserRoot &&
3314 browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer));
3315 }
3316 else {
3317 mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element,
3318 NS_LITERAL_STRING("_parent"), false);
3319 }
3320
3321 mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
3322 NS_LITERAL_STRING("always"), false);
3323
3324 if (!aElements.AppendElement(mResizerContent))
3325 return NS_ERROR_OUT_OF_MEMORY;
3326 }
3327
3328 if (canHaveHorizontal && canHaveVertical) {
3329 nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nullptr,
3330 kNameSpaceID_XUL,
3331 nsIDOMNode::ELEMENT_NODE);
3332 NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent), nodeInfo.forget());
3333 if (!aElements.AppendElement(mScrollCornerContent))
3334 return NS_ERROR_OUT_OF_MEMORY;
3335 }
3336
3337 return NS_OK;
3338 }
3339
3340 void
3341 ScrollFrameHelper::AppendAnonymousContentTo(nsBaseContentList& aElements,
3342 uint32_t aFilter)
3343 {
3344 aElements.MaybeAppendElement(mHScrollbarContent);
3345 aElements.MaybeAppendElement(mVScrollbarContent);
3346 aElements.MaybeAppendElement(mScrollCornerContent);
3347 aElements.MaybeAppendElement(mResizerContent);
3348 }
3349
3350 void
3351 ScrollFrameHelper::Destroy()
3352 {
3353 if (mScrollbarActivity) {
3354 mScrollbarActivity->Destroy();
3355 mScrollbarActivity = nullptr;
3356 }
3357
3358 // Unbind any content created in CreateAnonymousContent from the tree
3359 nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent);
3360 nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent);
3361 nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent);
3362 nsContentUtils::DestroyAnonymousContent(&mResizerContent);
3363
3364 if (mPostedReflowCallback) {
3365 mOuter->PresContext()->PresShell()->CancelReflowCallback(this);
3366 mPostedReflowCallback = false;
3367 }
3368 }
3369
3370 /**
3371 * Called when we want to update the scrollbar position, either because scrolling happened
3372 * or the user moved the scrollbar position and we need to undo that (e.g., when the user
3373 * clicks to scroll and we're using smooth scrolling, so we need to put the thumb back
3374 * to its initial position for the start of the smooth sequence).
3375 */
3376 void
3377 ScrollFrameHelper::UpdateScrollbarPosition()
3378 {
3379 nsWeakFrame weakFrame(mOuter);
3380 mFrameIsUpdatingScrollbar = true;
3381
3382 nsPoint pt = GetScrollPosition();
3383 if (mVScrollbarBox) {
3384 SetCoordAttribute(mVScrollbarBox->GetContent(), nsGkAtoms::curpos,
3385 pt.y - GetScrolledRect().y);
3386 if (!weakFrame.IsAlive()) {
3387 return;
3388 }
3389 }
3390 if (mHScrollbarBox) {
3391 SetCoordAttribute(mHScrollbarBox->GetContent(), nsGkAtoms::curpos,
3392 pt.x - GetScrolledRect().x);
3393 if (!weakFrame.IsAlive()) {
3394 return;
3395 }
3396 }
3397
3398 mFrameIsUpdatingScrollbar = false;
3399 }
3400
3401 void ScrollFrameHelper::CurPosAttributeChanged(nsIContent* aContent)
3402 {
3403 NS_ASSERTION(aContent, "aContent must not be null");
3404 NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
3405 (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
3406 "unexpected child");
3407
3408 // Attribute changes on the scrollbars happen in one of three ways:
3409 // 1) The scrollbar changed the attribute in response to some user event
3410 // 2) We changed the attribute in response to a ScrollPositionDidChange
3411 // callback from the scrolling view
3412 // 3) We changed the attribute to adjust the scrollbars for the start
3413 // of a smooth scroll operation
3414 //
3415 // In cases 2 and 3 we do not need to scroll because we're just
3416 // updating our scrollbar.
3417 if (mFrameIsUpdatingScrollbar)
3418 return;
3419
3420 nsRect scrolledRect = GetScrolledRect();
3421
3422 nsPoint current = GetScrollPosition() - scrolledRect.TopLeft();
3423 nsPoint dest;
3424 nsRect allowedRange;
3425 dest.x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos, current.x,
3426 &allowedRange.x, &allowedRange.width);
3427 dest.y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos, current.y,
3428 &allowedRange.y, &allowedRange.height);
3429 current += scrolledRect.TopLeft();
3430 dest += scrolledRect.TopLeft();
3431 allowedRange += scrolledRect.TopLeft();
3432
3433 // Don't try to scroll if we're already at an acceptable place.
3434 // Don't call Contains here since Contains returns false when the point is
3435 // on the bottom or right edge of the rectangle.
3436 if (allowedRange.ClampPoint(current) == current) {
3437 return;
3438 }
3439
3440 if (mScrollbarActivity) {
3441 nsRefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
3442 scrollbarActivity->ActivityOccurred();
3443 }
3444
3445 bool isSmooth = aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth);
3446 if (isSmooth) {
3447 // Make sure an attribute-setting callback occurs even if the view
3448 // didn't actually move yet. We need to make sure other listeners
3449 // see that the scroll position is not (yet) what they thought it
3450 // was.
3451 nsWeakFrame weakFrame(mOuter);
3452 UpdateScrollbarPosition();
3453 if (!weakFrame.IsAlive()) {
3454 return;
3455 }
3456 }
3457 ScrollToWithOrigin(dest,
3458 isSmooth ? nsIScrollableFrame::SMOOTH : nsIScrollableFrame::INSTANT,
3459 nsGkAtoms::scrollbars, &allowedRange);
3460 // 'this' might be destroyed here
3461 }
3462
3463 /* ============= Scroll events ========== */
3464
3465 NS_IMETHODIMP
3466 ScrollFrameHelper::ScrollEvent::Run()
3467 {
3468 if (mHelper)
3469 mHelper->FireScrollEvent();
3470 return NS_OK;
3471 }
3472
3473 void
3474 ScrollFrameHelper::FireScrollEvent()
3475 {
3476 mScrollEvent.Forget();
3477
3478 WidgetGUIEvent event(true, NS_SCROLL_EVENT, nullptr);
3479 nsEventStatus status = nsEventStatus_eIgnore;
3480 nsIContent* content = mOuter->GetContent();
3481 nsPresContext* prescontext = mOuter->PresContext();
3482 // Fire viewport scroll events at the document (where they
3483 // will bubble to the window)
3484 if (mIsRoot) {
3485 nsIDocument* doc = content->GetCurrentDoc();
3486 if (doc) {
3487 EventDispatcher::Dispatch(doc, prescontext, &event, nullptr, &status);
3488 }
3489 } else {
3490 // scroll events fired at elements don't bubble (although scroll events
3491 // fired at documents do, to the window)
3492 event.mFlags.mBubbles = false;
3493 EventDispatcher::Dispatch(content, prescontext, &event, nullptr, &status);
3494 }
3495 }
3496
3497 void
3498 ScrollFrameHelper::PostScrollEvent()
3499 {
3500 if (mScrollEvent.IsPending())
3501 return;
3502
3503 nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext();
3504 if (!rpc)
3505 return;
3506 mScrollEvent = new ScrollEvent(this);
3507 rpc->AddWillPaintObserver(mScrollEvent.get());
3508 }
3509
3510 NS_IMETHODIMP
3511 ScrollFrameHelper::AsyncScrollPortEvent::Run()
3512 {
3513 if (mHelper) {
3514 mHelper->mOuter->PresContext()->GetPresShell()->
3515 FlushPendingNotifications(Flush_InterruptibleLayout);
3516 }
3517 return mHelper ? mHelper->FireScrollPortEvent() : NS_OK;
3518 }
3519
3520 bool
3521 nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom)
3522 {
3523 if (!mHelper.mHScrollbarBox)
3524 return true;
3525
3526 return AddRemoveScrollbar(aState, aOnBottom, true, true);
3527 }
3528
3529 bool
3530 nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight)
3531 {
3532 if (!mHelper.mVScrollbarBox)
3533 return true;
3534
3535 return AddRemoveScrollbar(aState, aOnRight, false, true);
3536 }
3537
3538 void
3539 nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom)
3540 {
3541 // removing a scrollbar should always fit
3542 DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnBottom, true, false);
3543 NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??");
3544 }
3545
3546 void
3547 nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight)
3548 {
3549 // removing a scrollbar should always fit
3550 DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnRight, false, false);
3551 NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??");
3552 }
3553
3554 bool
3555 nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState,
3556 bool aOnRightOrBottom, bool aHorizontal, bool aAdd)
3557 {
3558 if (aHorizontal) {
3559 if (mHelper.mNeverHasHorizontalScrollbar || !mHelper.mHScrollbarBox)
3560 return false;
3561
3562 nsSize hSize = mHelper.mHScrollbarBox->GetPrefSize(aState);
3563 nsBox::AddMargin(mHelper.mHScrollbarBox, hSize);
3564
3565 mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, aAdd);
3566
3567 bool hasHorizontalScrollbar;
3568 bool fit = AddRemoveScrollbar(hasHorizontalScrollbar,
3569 mHelper.mScrollPort.y,
3570 mHelper.mScrollPort.height,
3571 hSize.height, aOnRightOrBottom, aAdd);
3572 mHelper.mHasHorizontalScrollbar = hasHorizontalScrollbar; // because mHasHorizontalScrollbar is a bool
3573 if (!fit)
3574 mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, !aAdd);
3575
3576 return fit;
3577 } else {
3578 if (mHelper.mNeverHasVerticalScrollbar || !mHelper.mVScrollbarBox)
3579 return false;
3580
3581 nsSize vSize = mHelper.mVScrollbarBox->GetPrefSize(aState);
3582 nsBox::AddMargin(mHelper.mVScrollbarBox, vSize);
3583
3584 mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, aAdd);
3585
3586 bool hasVerticalScrollbar;
3587 bool fit = AddRemoveScrollbar(hasVerticalScrollbar,
3588 mHelper.mScrollPort.x,
3589 mHelper.mScrollPort.width,
3590 vSize.width, aOnRightOrBottom, aAdd);
3591 mHelper.mHasVerticalScrollbar = hasVerticalScrollbar; // because mHasVerticalScrollbar is a bool
3592 if (!fit)
3593 mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, !aAdd);
3594
3595 return fit;
3596 }
3597 }
3598
3599 bool
3600 nsXULScrollFrame::AddRemoveScrollbar(bool& aHasScrollbar, nscoord& aXY,
3601 nscoord& aSize, nscoord aSbSize,
3602 bool aOnRightOrBottom, bool aAdd)
3603 {
3604 nscoord size = aSize;
3605 nscoord xy = aXY;
3606
3607 if (size != NS_INTRINSICSIZE) {
3608 if (aAdd) {
3609 size -= aSbSize;
3610 if (!aOnRightOrBottom && size >= 0)
3611 xy += aSbSize;
3612 } else {
3613 size += aSbSize;
3614 if (!aOnRightOrBottom)
3615 xy -= aSbSize;
3616 }
3617 }
3618
3619 // not enough room? Yes? Return true.
3620 if (size >= 0) {
3621 aHasScrollbar = aAdd;
3622 aSize = size;
3623 aXY = xy;
3624 return true;
3625 }
3626
3627 aHasScrollbar = false;
3628 return false;
3629 }
3630
3631 void
3632 nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState,
3633 const nsPoint& aScrollPosition)
3634 {
3635 uint32_t oldflags = aState.LayoutFlags();
3636 nsRect childRect = nsRect(mHelper.mScrollPort.TopLeft() - aScrollPosition,
3637 mHelper.mScrollPort.Size());
3638 int32_t flags = NS_FRAME_NO_MOVE_VIEW;
3639
3640 nsSize minSize = mHelper.mScrolledFrame->GetMinSize(aState);
3641
3642 if (minSize.height > childRect.height)
3643 childRect.height = minSize.height;
3644
3645 if (minSize.width > childRect.width)
3646 childRect.width = minSize.width;
3647
3648 aState.SetLayoutFlags(flags);
3649 ClampAndSetBounds(aState, childRect, aScrollPosition);
3650 mHelper.mScrolledFrame->Layout(aState);
3651
3652 childRect = mHelper.mScrolledFrame->GetRect();
3653
3654 if (childRect.width < mHelper.mScrollPort.width ||
3655 childRect.height < mHelper.mScrollPort.height)
3656 {
3657 childRect.width = std::max(childRect.width, mHelper.mScrollPort.width);
3658 childRect.height = std::max(childRect.height, mHelper.mScrollPort.height);
3659
3660 // remove overflow areas when we update the bounds,
3661 // because we've already accounted for it
3662 // REVIEW: Have we accounted for both?
3663 ClampAndSetBounds(aState, childRect, aScrollPosition, true);
3664 }
3665
3666 aState.SetLayoutFlags(oldflags);
3667
3668 }
3669
3670 void ScrollFrameHelper::PostOverflowEvent()
3671 {
3672 if (mAsyncScrollPortEvent.IsPending())
3673 return;
3674
3675 // Keep this in sync with FireScrollPortEvent().
3676 nsSize scrollportSize = mScrollPort.Size();
3677 nsSize childSize = GetScrolledRect().Size();
3678
3679 bool newVerticalOverflow = childSize.height > scrollportSize.height;
3680 bool vertChanged = mVerticalOverflow != newVerticalOverflow;
3681
3682 bool newHorizontalOverflow = childSize.width > scrollportSize.width;
3683 bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
3684
3685 if (!vertChanged && !horizChanged) {
3686 return;
3687 }
3688
3689 nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext();
3690 if (!rpc)
3691 return;
3692
3693 mAsyncScrollPortEvent = new AsyncScrollPortEvent(this);
3694 rpc->AddWillPaintObserver(mAsyncScrollPortEvent.get());
3695 }
3696
3697 bool
3698 ScrollFrameHelper::IsLTR() const
3699 {
3700 //TODO make bidi code set these from preferences
3701
3702 nsIFrame *frame = mOuter;
3703 // XXX This is a bit on the slow side.
3704 if (mIsRoot) {
3705 // If we're the root scrollframe, we need the root element's style data.
3706 nsPresContext *presContext = mOuter->PresContext();
3707 nsIDocument *document = presContext->Document();
3708 Element *root = document->GetRootElement();
3709
3710 // But for HTML and XHTML we want the body element.
3711 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
3712 if (htmlDoc) {
3713 Element *bodyElement = document->GetBodyElement();
3714 if (bodyElement)
3715 root = bodyElement; // we can trust the document to hold on to it
3716 }
3717
3718 if (root) {
3719 nsIFrame *rootsFrame = root->GetPrimaryFrame();
3720 if (rootsFrame)
3721 frame = rootsFrame;
3722 }
3723 }
3724
3725 return frame->StyleVisibility()->mDirection != NS_STYLE_DIRECTION_RTL;
3726 }
3727
3728 bool
3729 ScrollFrameHelper::IsScrollbarOnRight() const
3730 {
3731 nsPresContext *presContext = mOuter->PresContext();
3732
3733 // The position of the scrollbar in top-level windows depends on the pref
3734 // layout.scrollbar.side. For non-top-level elements, it depends only on the
3735 // directionaliy of the element (equivalent to a value of "1" for the pref).
3736 if (!mIsRoot)
3737 return IsLTR();
3738 switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) {
3739 default:
3740 case 0: // UI directionality
3741 return presContext->GetCachedIntPref(kPresContext_BidiDirection)
3742 == IBMBIDI_TEXTDIRECTION_LTR;
3743 case 1: // Document / content directionality
3744 return IsLTR();
3745 case 2: // Always right
3746 return true;
3747 case 3: // Always left
3748 return false;
3749 }
3750 }
3751
3752 /**
3753 * Reflow the scroll area if it needs it and return its size. Also determine if the reflow will
3754 * cause any of the scrollbars to need to be reflowed.
3755 */
3756 nsresult
3757 nsXULScrollFrame::Layout(nsBoxLayoutState& aState)
3758 {
3759 bool scrollbarRight = mHelper.IsScrollbarOnRight();
3760 bool scrollbarBottom = true;
3761
3762 // get the content rect
3763 nsRect clientRect(0,0,0,0);
3764 GetClientRect(clientRect);
3765
3766 nsRect oldScrollAreaBounds = mHelper.mScrollPort;
3767 nsPoint oldScrollPosition = mHelper.GetLogicalScrollPosition();
3768
3769 // the scroll area size starts off as big as our content area
3770 mHelper.mScrollPort = clientRect;
3771
3772 /**************
3773 Our basic strategy here is to first try laying out the content with
3774 the scrollbars in their current state. We're hoping that that will
3775 just "work"; the content will overflow wherever there's a scrollbar
3776 already visible. If that does work, then there's no need to lay out
3777 the scrollarea. Otherwise we fix up the scrollbars; first we add a
3778 vertical one to scroll the content if necessary, or remove it if
3779 it's not needed. Then we reflow the content if the scrollbar
3780 changed. Then we add a horizontal scrollbar if necessary (or
3781 remove if not needed), and if that changed, we reflow the content
3782 again. At this point, any scrollbars that are needed to scroll the
3783 content have been added.
3784
3785 In the second phase we check to see if any scrollbars are too small
3786 to display, and if so, we remove them. We check the horizontal
3787 scrollbar first; removing it might make room for the vertical
3788 scrollbar, and if we have room for just one scrollbar we'll save
3789 the vertical one.
3790
3791 Finally we position and size the scrollbars and scrollcorner (the
3792 square that is needed in the corner of the window when two
3793 scrollbars are visible), and reflow any fixed position views
3794 (if we're the viewport and we added or removed a scrollbar).
3795 **************/
3796
3797 ScrollbarStyles styles = GetScrollbarStyles();
3798
3799 // Look at our style do we always have vertical or horizontal scrollbars?
3800 if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL)
3801 mHelper.mHasHorizontalScrollbar = true;
3802 if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL)
3803 mHelper.mHasVerticalScrollbar = true;
3804
3805 if (mHelper.mHasHorizontalScrollbar)
3806 AddHorizontalScrollbar(aState, scrollbarBottom);
3807
3808 if (mHelper.mHasVerticalScrollbar)
3809 AddVerticalScrollbar(aState, scrollbarRight);
3810
3811 // layout our the scroll area
3812 LayoutScrollArea(aState, oldScrollPosition);
3813
3814 // now look at the content area and see if we need scrollbars or not
3815 bool needsLayout = false;
3816
3817 // if we have 'auto' scrollbars look at the vertical case
3818 if (styles.mVertical != NS_STYLE_OVERFLOW_SCROLL) {
3819 // These are only good until the call to LayoutScrollArea.
3820 nsRect scrolledRect = mHelper.GetScrolledRect();
3821
3822 // There are two cases to consider
3823 if (scrolledRect.height <= mHelper.mScrollPort.height
3824 || styles.mVertical != NS_STYLE_OVERFLOW_AUTO) {
3825 if (mHelper.mHasVerticalScrollbar) {
3826 // We left room for the vertical scrollbar, but it's not needed;
3827 // remove it.
3828 RemoveVerticalScrollbar(aState, scrollbarRight);
3829 needsLayout = true;
3830 }
3831 } else {
3832 if (!mHelper.mHasVerticalScrollbar) {
3833 // We didn't leave room for the vertical scrollbar, but it turns
3834 // out we needed it
3835 if (AddVerticalScrollbar(aState, scrollbarRight))
3836 needsLayout = true;
3837 }
3838 }
3839
3840 // ok layout at the right size
3841 if (needsLayout) {
3842 nsBoxLayoutState resizeState(aState);
3843 LayoutScrollArea(resizeState, oldScrollPosition);
3844 needsLayout = false;
3845 }
3846 }
3847
3848
3849 // if scrollbars are auto look at the horizontal case
3850 if (styles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL)
3851 {
3852 // These are only good until the call to LayoutScrollArea.
3853 nsRect scrolledRect = mHelper.GetScrolledRect();
3854
3855 // if the child is wider that the scroll area
3856 // and we don't have a scrollbar add one.
3857 if ((scrolledRect.width > mHelper.mScrollPort.width)
3858 && styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) {
3859
3860 if (!mHelper.mHasHorizontalScrollbar) {
3861 // no scrollbar?
3862 if (AddHorizontalScrollbar(aState, scrollbarBottom))
3863 needsLayout = true;
3864
3865 // if we added a horizontal scrollbar and we did not have a vertical
3866 // there is a chance that by adding the horizontal scrollbar we will
3867 // suddenly need a vertical scrollbar. Is a special case but its
3868 // important.
3869 //if (!mHasVerticalScrollbar && scrolledRect.height > scrollAreaRect.height - sbSize.height)
3870 // printf("****Gfx Scrollbar Special case hit!!*****\n");
3871
3872 }
3873 } else {
3874 // if the area is smaller or equal to and we have a scrollbar then
3875 // remove it.
3876 if (mHelper.mHasHorizontalScrollbar) {
3877 RemoveHorizontalScrollbar(aState, scrollbarBottom);
3878 needsLayout = true;
3879 }
3880 }
3881 }
3882
3883 // we only need to set the rect. The inner child stays the same size.
3884 if (needsLayout) {
3885 nsBoxLayoutState resizeState(aState);
3886 LayoutScrollArea(resizeState, oldScrollPosition);
3887 needsLayout = false;
3888 }
3889
3890 // get the preferred size of the scrollbars
3891 nsSize hMinSize(0, 0);
3892 if (mHelper.mHScrollbarBox && mHelper.mHasHorizontalScrollbar) {
3893 GetScrollbarMetrics(aState, mHelper.mHScrollbarBox, &hMinSize, nullptr, false);
3894 }
3895 nsSize vMinSize(0, 0);
3896 if (mHelper.mVScrollbarBox && mHelper.mHasVerticalScrollbar) {
3897 GetScrollbarMetrics(aState, mHelper.mVScrollbarBox, &vMinSize, nullptr, true);
3898 }
3899
3900 // Disable scrollbars that are too small
3901 // Disable horizontal scrollbar first. If we have to disable only one
3902 // scrollbar, we'd rather keep the vertical scrollbar.
3903 // Note that we always give horizontal scrollbars their preferred height,
3904 // never their min-height. So check that there's room for the preferred height.
3905 if (mHelper.mHasHorizontalScrollbar &&
3906 (hMinSize.width > clientRect.width - vMinSize.width
3907 || hMinSize.height > clientRect.height)) {
3908 RemoveHorizontalScrollbar(aState, scrollbarBottom);
3909 needsLayout = true;
3910 }
3911 // Now disable vertical scrollbar if necessary
3912 if (mHelper.mHasVerticalScrollbar &&
3913 (vMinSize.height > clientRect.height - hMinSize.height
3914 || vMinSize.width > clientRect.width)) {
3915 RemoveVerticalScrollbar(aState, scrollbarRight);
3916 needsLayout = true;
3917 }
3918
3919 // we only need to set the rect. The inner child stays the same size.
3920 if (needsLayout) {
3921 nsBoxLayoutState resizeState(aState);
3922 LayoutScrollArea(resizeState, oldScrollPosition);
3923 }
3924
3925 if (!mHelper.mSupppressScrollbarUpdate) {
3926 mHelper.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds);
3927 }
3928 if (!mHelper.mPostedReflowCallback) {
3929 // Make sure we'll try scrolling to restored position
3930 PresContext()->PresShell()->PostReflowCallback(&mHelper);
3931 mHelper.mPostedReflowCallback = true;
3932 }
3933 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
3934 mHelper.mHadNonInitialReflow = true;
3935 }
3936
3937 mHelper.UpdateSticky();
3938
3939 // Set up overflow areas for block frames for the benefit of
3940 // text-overflow.
3941 nsIFrame* f = mHelper.mScrolledFrame->GetContentInsertionFrame();
3942 if (nsLayoutUtils::GetAsBlock(f)) {
3943 nsRect origRect = f->GetRect();
3944 nsRect clippedRect = origRect;
3945 clippedRect.MoveBy(mHelper.mScrollPort.TopLeft());
3946 clippedRect.IntersectRect(clippedRect, mHelper.mScrollPort);
3947 nsOverflowAreas overflow = f->GetOverflowAreas();
3948 f->FinishAndStoreOverflow(overflow, clippedRect.Size());
3949 clippedRect.MoveTo(origRect.TopLeft());
3950 f->SetRect(clippedRect);
3951 }
3952
3953 mHelper.PostOverflowEvent();
3954 return NS_OK;
3955 }
3956
3957 void
3958 ScrollFrameHelper::FinishReflowForScrollbar(nsIContent* aContent,
3959 nscoord aMinXY, nscoord aMaxXY,
3960 nscoord aCurPosXY,
3961 nscoord aPageIncrement,
3962 nscoord aIncrement)
3963 {
3964 // Scrollbars assume zero is the minimum position, so translate for them.
3965 SetCoordAttribute(aContent, nsGkAtoms::curpos, aCurPosXY - aMinXY);
3966 SetScrollbarEnabled(aContent, aMaxXY - aMinXY);
3967 SetCoordAttribute(aContent, nsGkAtoms::maxpos, aMaxXY - aMinXY);
3968 SetCoordAttribute(aContent, nsGkAtoms::pageincrement, aPageIncrement);
3969 SetCoordAttribute(aContent, nsGkAtoms::increment, aIncrement);
3970 }
3971
3972 bool
3973 ScrollFrameHelper::ReflowFinished()
3974 {
3975 nsAutoScriptBlocker scriptBlocker;
3976 mPostedReflowCallback = false;
3977
3978 ScrollToRestoredPosition();
3979
3980 // Clamp current scroll position to new bounds. Normally this won't
3981 // do anything.
3982 nsPoint currentScrollPos = GetScrollPosition();
3983 ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)));
3984 if (!mAsyncScroll) {
3985 // We need to have mDestination track the current scroll position,
3986 // in case it falls outside the new reflow area. mDestination is used
3987 // by ScrollBy as its starting position.
3988 mDestination = GetScrollPosition();
3989 }
3990
3991 if (NS_SUBTREE_DIRTY(mOuter) || !mUpdateScrollbarAttributes)
3992 return false;
3993
3994 mUpdateScrollbarAttributes = false;
3995
3996 // Update scrollbar attributes.
3997 nsPresContext* presContext = mOuter->PresContext();
3998
3999 if (mMayHaveDirtyFixedChildren) {
4000 mMayHaveDirtyFixedChildren = false;
4001 nsIFrame* parentFrame = mOuter->GetParent();
4002 for (nsIFrame* fixedChild =
4003 parentFrame->GetFirstChild(nsIFrame::kFixedList);
4004 fixedChild; fixedChild = fixedChild->GetNextSibling()) {
4005 // force a reflow of the fixed child
4006 presContext->PresShell()->
4007 FrameNeedsReflow(fixedChild, nsIPresShell::eResize,
4008 NS_FRAME_HAS_DIRTY_CHILDREN);
4009 }
4010 }
4011
4012 nsRect scrolledContentRect = GetScrolledRect();
4013 nscoord minX = scrolledContentRect.x;
4014 nscoord maxX = scrolledContentRect.XMost() - mScrollPort.width;
4015 nscoord minY = scrolledContentRect.y;
4016 nscoord maxY = scrolledContentRect.YMost() - mScrollPort.height;
4017
4018 // Suppress handling of the curpos attribute changes we make here.
4019 NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here");
4020 mFrameIsUpdatingScrollbar = true;
4021
4022 nsCOMPtr<nsIContent> vScroll =
4023 mVScrollbarBox ? mVScrollbarBox->GetContent() : nullptr;
4024 nsCOMPtr<nsIContent> hScroll =
4025 mHScrollbarBox ? mHScrollbarBox->GetContent() : nullptr;
4026
4027 // Note, in some cases mOuter may get deleted while finishing reflow
4028 // for scrollbars. XXXmats is this still true now that we have a script
4029 // blocker in this scope? (if not, remove the weak frame checks below).
4030 if (vScroll || hScroll) {
4031 nsWeakFrame weakFrame(mOuter);
4032 nsPoint scrollPos = GetScrollPosition();
4033 nsSize lineScrollAmount = GetLineScrollAmount();
4034 if (vScroll) {
4035 const double kScrollMultiplier =
4036 Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
4037 NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
4038 nscoord increment = lineScrollAmount.height * kScrollMultiplier;
4039 // We normally use (scrollArea.height - increment) for height
4040 // of page scrolling. However, it is too small when
4041 // increment is very large. (If increment is larger than
4042 // scrollArea.height, direction of scrolling will be opposite).
4043 // To avoid it, we use (float(scrollArea.height) * 0.8) as
4044 // lower bound value of height of page scrolling. (bug 383267)
4045 // XXX shouldn't we use GetPageScrollAmount here?
4046 nscoord pageincrement = nscoord(mScrollPort.height - increment);
4047 nscoord pageincrementMin = nscoord(float(mScrollPort.height) * 0.8);
4048 FinishReflowForScrollbar(vScroll, minY, maxY, scrollPos.y,
4049 std::max(pageincrement, pageincrementMin),
4050 increment);
4051 }
4052 if (hScroll) {
4053 const double kScrollMultiplier =
4054 Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
4055 NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
4056 nscoord increment = lineScrollAmount.width * kScrollMultiplier;
4057 FinishReflowForScrollbar(hScroll, minX, maxX, scrollPos.x,
4058 nscoord(float(mScrollPort.width) * 0.8),
4059 increment);
4060 }
4061 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
4062 }
4063
4064 mFrameIsUpdatingScrollbar = false;
4065 // We used to rely on the curpos attribute changes above to scroll the
4066 // view. However, for scrolling to the left of the viewport, we
4067 // rescale the curpos attribute, which means that operations like
4068 // resizing the window while it is scrolled all the way to the left
4069 // hold the curpos attribute constant at 0 while still requiring
4070 // scrolling. So we suppress the effect of the changes above with
4071 // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here.
4072 // (It actually even works some of the time without this, thanks to
4073 // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
4074 // we hide the scrollbar on a large size change, such as
4075 // maximization.)
4076 if (!mHScrollbarBox && !mVScrollbarBox)
4077 return false;
4078 CurPosAttributeChanged(mVScrollbarBox ? mVScrollbarBox->GetContent()
4079 : mHScrollbarBox->GetContent());
4080 return true;
4081 }
4082
4083 void
4084 ScrollFrameHelper::ReflowCallbackCanceled()
4085 {
4086 mPostedReflowCallback = false;
4087 }
4088
4089 bool
4090 ScrollFrameHelper::UpdateOverflow()
4091 {
4092 nsIScrollableFrame* sf = do_QueryFrame(mOuter);
4093 ScrollbarStyles ss = sf->GetScrollbarStyles();
4094
4095 if (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN ||
4096 ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN ||
4097 GetScrollPosition() != nsPoint()) {
4098 // If there are scrollbars, or we're not at the beginning of the pane,
4099 // the scroll position may change. In this case, mark the frame as
4100 // needing reflow. Don't use NS_FRAME_IS_DIRTY as dirty as that means
4101 // we have to reflow the frame and all its descendants, and we don't
4102 // have to do that here. Only this frame needs to be reflowed.
4103 mOuter->PresContext()->PresShell()->FrameNeedsReflow(
4104 mOuter, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
4105 // Ensure that next time nsHTMLScrollFrame::Reflow runs, we don't skip
4106 // updating the scrollbars. (Because the overflow area of the scrolled
4107 // frame has probably just been updated, Reflow won't see it change.)
4108 mSkippedScrollbarLayout = true;
4109 return false; // reflowing will update overflow
4110 }
4111 PostOverflowEvent();
4112 return mOuter->nsContainerFrame::UpdateOverflow();
4113 }
4114
4115 void
4116 ScrollFrameHelper::UpdateSticky()
4117 {
4118 StickyScrollContainer* ssc = StickyScrollContainer::
4119 GetStickyScrollContainerForScrollFrame(mOuter);
4120 if (ssc) {
4121 nsIScrollableFrame* scrollFrame = do_QueryFrame(mOuter);
4122 ssc->UpdatePositions(scrollFrame->GetScrollPosition(), mOuter);
4123 }
4124 }
4125
4126 void
4127 ScrollFrameHelper::AdjustScrollbarRectForResizer(
4128 nsIFrame* aFrame, nsPresContext* aPresContext,
4129 nsRect& aRect, bool aHasResizer, bool aVertical)
4130 {
4131 if ((aVertical ? aRect.width : aRect.height) == 0)
4132 return;
4133
4134 // if a content resizer is present, use its size. Otherwise, check if the
4135 // widget has a resizer.
4136 nsRect resizerRect;
4137 if (aHasResizer) {
4138 resizerRect = mResizerBox->GetRect();
4139 }
4140 else {
4141 nsPoint offset;
4142 nsIWidget* widget = aFrame->GetNearestWidget(offset);
4143 nsIntRect widgetRect;
4144 if (!widget || !widget->ShowsResizeIndicator(&widgetRect))
4145 return;
4146
4147 resizerRect = nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
4148 aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
4149 aPresContext->DevPixelsToAppUnits(widgetRect.width),
4150 aPresContext->DevPixelsToAppUnits(widgetRect.height));
4151 }
4152
4153 if (!resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1)))
4154 return;
4155
4156 if (aVertical)
4157 aRect.height = std::max(0, resizerRect.y - aRect.y);
4158 else
4159 aRect.width = std::max(0, resizerRect.x - aRect.x);
4160 }
4161
4162 static void
4163 AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect)
4164 {
4165 if (aVRect.IsEmpty() || aHRect.IsEmpty())
4166 return;
4167
4168 const nsRect oldVRect = aVRect;
4169 const nsRect oldHRect = aHRect;
4170 if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) {
4171 aHRect.width = std::max(0, oldVRect.x - oldHRect.x);
4172 } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) {
4173 nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x);
4174 aHRect.x += overlap;
4175 aHRect.width -= overlap;
4176 }
4177 if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) {
4178 aVRect.height = std::max(0, oldHRect.y - oldVRect.y);
4179 }
4180 }
4181
4182 void
4183 ScrollFrameHelper::LayoutScrollbars(nsBoxLayoutState& aState,
4184 const nsRect& aContentArea,
4185 const nsRect& aOldScrollArea)
4186 {
4187 NS_ASSERTION(!mSupppressScrollbarUpdate,
4188 "This should have been suppressed");
4189
4190 bool hasResizer = HasResizer();
4191 bool scrollbarOnLeft = !IsScrollbarOnRight();
4192
4193 // place the scrollcorner
4194 if (mScrollCornerBox || mResizerBox) {
4195 NS_PRECONDITION(!mScrollCornerBox || mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
4196
4197 nsRect r(0, 0, 0, 0);
4198 if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
4199 // scrollbar (if any) on left
4200 r.x = aContentArea.x;
4201 r.width = mScrollPort.x - aContentArea.x;
4202 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
4203 } else {
4204 // scrollbar (if any) on right
4205 r.width = aContentArea.XMost() - mScrollPort.XMost();
4206 r.x = aContentArea.XMost() - r.width;
4207 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
4208 }
4209 if (aContentArea.y != mScrollPort.y) {
4210 NS_ERROR("top scrollbars not supported");
4211 } else {
4212 // scrollbar (if any) on bottom
4213 r.height = aContentArea.YMost() - mScrollPort.YMost();
4214 r.y = aContentArea.YMost() - r.height;
4215 NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
4216 }
4217
4218 if (mScrollCornerBox) {
4219 nsBoxFrame::LayoutChildAt(aState, mScrollCornerBox, r);
4220 }
4221
4222 if (hasResizer) {
4223 // if a resizer is present, get its size. Assume a default size of 15 pixels.
4224 nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
4225 nsSize resizerMinSize = mResizerBox->GetMinSize(aState);
4226
4227 nscoord vScrollbarWidth = mVScrollbarBox ?
4228 mVScrollbarBox->GetPrefSize(aState).width : defaultSize;
4229 r.width = std::max(std::max(r.width, vScrollbarWidth), resizerMinSize.width);
4230 if (aContentArea.x == mScrollPort.x && !scrollbarOnLeft) {
4231 r.x = aContentArea.XMost() - r.width;
4232 }
4233
4234 nscoord hScrollbarHeight = mHScrollbarBox ?
4235 mHScrollbarBox->GetPrefSize(aState).height : defaultSize;
4236 r.height = std::max(std::max(r.height, hScrollbarHeight), resizerMinSize.height);
4237 if (aContentArea.y == mScrollPort.y) {
4238 r.y = aContentArea.YMost() - r.height;
4239 }
4240
4241 nsBoxFrame::LayoutChildAt(aState, mResizerBox, r);
4242 }
4243 else if (mResizerBox) {
4244 // otherwise lay out the resizer with an empty rectangle
4245 nsBoxFrame::LayoutChildAt(aState, mResizerBox, nsRect());
4246 }
4247 }
4248
4249 nsPresContext* presContext = mScrolledFrame->PresContext();
4250 nsRect vRect;
4251 if (mVScrollbarBox) {
4252 NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
4253 vRect = mScrollPort;
4254 vRect.width = aContentArea.width - mScrollPort.width;
4255 vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost();
4256 if (mHasVerticalScrollbar) {
4257 nsMargin margin;
4258 mVScrollbarBox->GetMargin(margin);
4259 vRect.Deflate(margin);
4260 }
4261 AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, true);
4262 }
4263
4264 nsRect hRect;
4265 if (mHScrollbarBox) {
4266 NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!");
4267 hRect = mScrollPort;
4268 hRect.height = aContentArea.height - mScrollPort.height;
4269 hRect.y = true ? mScrollPort.YMost() : aContentArea.y;
4270 if (mHasHorizontalScrollbar) {
4271 nsMargin margin;
4272 mHScrollbarBox->GetMargin(margin);
4273 hRect.Deflate(margin);
4274 }
4275 AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false);
4276 }
4277
4278 if (!LookAndFeel::GetInt(LookAndFeel::eIntID_AllowOverlayScrollbarsOverlap)) {
4279 AdjustOverlappingScrollbars(vRect, hRect);
4280 }
4281 if (mVScrollbarBox) {
4282 nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect);
4283 }
4284 if (mHScrollbarBox) {
4285 nsBoxFrame::LayoutChildAt(aState, mHScrollbarBox, hRect);
4286 }
4287
4288 // may need to update fixed position children of the viewport,
4289 // if the client area changed size because of an incremental
4290 // reflow of a descendant. (If the outer frame is dirty, the fixed
4291 // children will be re-laid out anyway)
4292 if (aOldScrollArea.Size() != mScrollPort.Size() &&
4293 !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) &&
4294 mIsRoot) {
4295 mMayHaveDirtyFixedChildren = true;
4296 }
4297
4298 // post reflow callback to modify scrollbar attributes
4299 mUpdateScrollbarAttributes = true;
4300 if (!mPostedReflowCallback) {
4301 aState.PresContext()->PresShell()->PostReflowCallback(this);
4302 mPostedReflowCallback = true;
4303 }
4304 }
4305
4306 #if DEBUG
4307 static bool ShellIsAlive(nsWeakPtr& aWeakPtr)
4308 {
4309 nsCOMPtr<nsIPresShell> shell(do_QueryReferent(aWeakPtr));
4310 return !!shell;
4311 }
4312 #endif
4313
4314 void
4315 ScrollFrameHelper::SetScrollbarEnabled(nsIContent* aContent, nscoord aMaxPos)
4316 {
4317 DebugOnly<nsWeakPtr> weakShell(
4318 do_GetWeakReference(mOuter->PresContext()->PresShell()));
4319 if (aMaxPos) {
4320 aContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
4321 } else {
4322 aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
4323 NS_LITERAL_STRING("true"), true);
4324 }
4325 MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
4326 }
4327
4328 void
4329 ScrollFrameHelper::SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom,
4330 nscoord aSize)
4331 {
4332 DebugOnly<nsWeakPtr> weakShell(
4333 do_GetWeakReference(mOuter->PresContext()->PresShell()));
4334 // convert to pixels
4335 aSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
4336
4337 // only set the attribute if it changed.
4338
4339 nsAutoString newValue;
4340 newValue.AppendInt(aSize);
4341
4342 if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters))
4343 return;
4344
4345 nsWeakFrame weakFrame(mOuter);
4346 nsCOMPtr<nsIContent> kungFuDeathGrip = aContent;
4347 aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, true);
4348 MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
4349 if (!weakFrame.IsAlive()) {
4350 return;
4351 }
4352
4353 if (mScrollbarActivity) {
4354 nsRefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
4355 scrollbarActivity->ActivityOccurred();
4356 }
4357 }
4358
4359 static void
4360 ReduceRadii(nscoord aXBorder, nscoord aYBorder,
4361 nscoord& aXRadius, nscoord& aYRadius)
4362 {
4363 // In order to ensure that the inside edge of the border has no
4364 // curvature, we need at least one of its radii to be zero.
4365 if (aXRadius <= aXBorder || aYRadius <= aYBorder)
4366 return;
4367
4368 // For any corner where we reduce the radii, preserve the corner's shape.
4369 double ratio = std::max(double(aXBorder) / aXRadius,
4370 double(aYBorder) / aYRadius);
4371 aXRadius *= ratio;
4372 aYRadius *= ratio;
4373 }
4374
4375 /**
4376 * Implement an override for nsIFrame::GetBorderRadii to ensure that
4377 * the clipping region for the border radius does not clip the scrollbars.
4378 *
4379 * In other words, we require that the border radius be reduced until the
4380 * inner border radius at the inner edge of the border is 0 wherever we
4381 * have scrollbars.
4382 */
4383 bool
4384 ScrollFrameHelper::GetBorderRadii(nscoord aRadii[8]) const
4385 {
4386 if (!mOuter->nsContainerFrame::GetBorderRadii(aRadii))
4387 return false;
4388
4389 // Since we can use GetActualScrollbarSizes (rather than
4390 // GetDesiredScrollbarSizes) since this doesn't affect reflow, we
4391 // probably should.
4392 nsMargin sb = GetActualScrollbarSizes();
4393 nsMargin border = mOuter->GetUsedBorder();
4394
4395 if (sb.left > 0 || sb.top > 0) {
4396 ReduceRadii(border.left, border.top,
4397 aRadii[NS_CORNER_TOP_LEFT_X],
4398 aRadii[NS_CORNER_TOP_LEFT_Y]);
4399 }
4400
4401 if (sb.top > 0 || sb.right > 0) {
4402 ReduceRadii(border.right, border.top,
4403 aRadii[NS_CORNER_TOP_RIGHT_X],
4404 aRadii[NS_CORNER_TOP_RIGHT_Y]);
4405 }
4406
4407 if (sb.right > 0 || sb.bottom > 0) {
4408 ReduceRadii(border.right, border.bottom,
4409 aRadii[NS_CORNER_BOTTOM_RIGHT_X],
4410 aRadii[NS_CORNER_BOTTOM_RIGHT_Y]);
4411 }
4412
4413 if (sb.bottom > 0 || sb.left > 0) {
4414 ReduceRadii(border.left, border.bottom,
4415 aRadii[NS_CORNER_BOTTOM_LEFT_X],
4416 aRadii[NS_CORNER_BOTTOM_LEFT_Y]);
4417 }
4418
4419 return true;
4420 }
4421
4422 nsRect
4423 ScrollFrameHelper::GetScrolledRect() const
4424 {
4425 nsRect result =
4426 GetScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(),
4427 mScrollPort.Size());
4428
4429 if (result.width < mScrollPort.width) {
4430 NS_WARNING("Scrolled rect smaller than scrollport?");
4431 }
4432 if (result.height < mScrollPort.height) {
4433 NS_WARNING("Scrolled rect smaller than scrollport?");
4434 }
4435 return result;
4436 }
4437
4438 nsRect
4439 ScrollFrameHelper::GetScrolledRectInternal(const nsRect& aScrolledFrameOverflowArea,
4440 const nsSize& aScrollPortSize) const
4441 {
4442 return nsLayoutUtils::GetScrolledRect(mScrolledFrame,
4443 aScrolledFrameOverflowArea, aScrollPortSize,
4444 IsLTR() ? NS_STYLE_DIRECTION_LTR : NS_STYLE_DIRECTION_RTL);
4445 }
4446
4447 nsMargin
4448 ScrollFrameHelper::GetActualScrollbarSizes() const
4449 {
4450 nsRect r = mOuter->GetPaddingRect() - mOuter->GetPosition();
4451
4452 return nsMargin(mScrollPort.y - r.y,
4453 r.XMost() - mScrollPort.XMost(),
4454 r.YMost() - mScrollPort.YMost(),
4455 mScrollPort.x - r.x);
4456 }
4457
4458 void
4459 ScrollFrameHelper::SetScrollbarVisibility(nsIFrame* aScrollbar, bool aVisible)
4460 {
4461 nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar);
4462 if (scrollbar) {
4463 // See if we have a mediator.
4464 nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
4465 if (mediator) {
4466 // Inform the mediator of the visibility change.
4467 mediator->VisibilityChanged(aVisible);
4468 }
4469 }
4470 }
4471
4472 nscoord
4473 ScrollFrameHelper::GetCoordAttribute(nsIFrame* aBox, nsIAtom* aAtom,
4474 nscoord aDefaultValue,
4475 nscoord* aRangeStart,
4476 nscoord* aRangeLength)
4477 {
4478 if (aBox) {
4479 nsIContent* content = aBox->GetContent();
4480
4481 nsAutoString value;
4482 content->GetAttr(kNameSpaceID_None, aAtom, value);
4483 if (!value.IsEmpty())
4484 {
4485 nsresult error;
4486 // convert it to appunits
4487 nscoord result = nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
4488 nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
4489 // Any nscoord value that would round to the attribute value when converted
4490 // to CSS pixels is allowed.
4491 *aRangeStart = result - halfPixel;
4492 *aRangeLength = halfPixel*2 - 1;
4493 return result;
4494 }
4495 }
4496
4497 // Only this exact default value is allowed.
4498 *aRangeStart = aDefaultValue;
4499 *aRangeLength = 0;
4500 return aDefaultValue;
4501 }
4502
4503 nsPresState*
4504 ScrollFrameHelper::SaveState() const
4505 {
4506 nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
4507 if (mediator) {
4508 // child handles its own scroll state, so don't bother saving state here
4509 return nullptr;
4510 }
4511
4512 // Don't store a scroll state if we never have been scrolled or restored
4513 // a previous scroll state.
4514 if (!mHasBeenScrolled && !mDidHistoryRestore) {
4515 return nullptr;
4516 }
4517
4518 nsPresState* state = new nsPresState();
4519 // Save mRestorePos instead of our actual current scroll position, if it's
4520 // valid and we haven't moved since the last update of mLastPos (same check
4521 // that ScrollToRestoredPosition uses). This ensures if a reframe occurs
4522 // while we're in the process of loading content to scroll to a restored
4523 // position, we'll keep trying after the reframe.
4524 nsPoint pt = GetLogicalScrollPosition();
4525 if (mRestorePos.y != -1 && pt == mLastPos) {
4526 pt = mRestorePos;
4527 }
4528 state->SetScrollState(pt);
4529 state->SetResolution(mResolution);
4530 return state;
4531 }
4532
4533 void
4534 ScrollFrameHelper::RestoreState(nsPresState* aState)
4535 {
4536 mRestorePos = aState->GetScrollState();
4537 mDidHistoryRestore = true;
4538 mLastPos = mScrolledFrame ? GetLogicalScrollPosition() : nsPoint(0,0);
4539 mResolution = aState->GetResolution();
4540 mIsResolutionSet = true;
4541
4542 if (mIsRoot) {
4543 mOuter->PresContext()->PresShell()->SetResolution(mResolution.width, mResolution.height);
4544 }
4545 }
4546
4547 void
4548 ScrollFrameHelper::PostScrolledAreaEvent()
4549 {
4550 if (mScrolledAreaEvent.IsPending()) {
4551 return;
4552 }
4553 mScrolledAreaEvent = new ScrolledAreaEvent(this);
4554 nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get());
4555 }
4556
4557 ////////////////////////////////////////////////////////////////////////////////
4558 // ScrolledArea change event dispatch
4559
4560 NS_IMETHODIMP
4561 ScrollFrameHelper::ScrolledAreaEvent::Run()
4562 {
4563 if (mHelper) {
4564 mHelper->FireScrolledAreaEvent();
4565 }
4566 return NS_OK;
4567 }
4568
4569 void
4570 ScrollFrameHelper::FireScrolledAreaEvent()
4571 {
4572 mScrolledAreaEvent.Forget();
4573
4574 InternalScrollAreaEvent event(true, NS_SCROLLEDAREACHANGED, nullptr);
4575 nsPresContext *prescontext = mOuter->PresContext();
4576 nsIContent* content = mOuter->GetContent();
4577
4578 event.mArea = mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
4579
4580 nsIDocument *doc = content->GetCurrentDoc();
4581 if (doc) {
4582 EventDispatcher::Dispatch(doc, prescontext, &event, nullptr);
4583 }
4584 }
4585
4586 uint32_t
4587 nsIScrollableFrame::GetPerceivedScrollingDirections() const
4588 {
4589 nscoord oneDevPixel = GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
4590 uint32_t directions = GetScrollbarVisibility();
4591 nsRect scrollRange = GetScrollRange();
4592 if (scrollRange.width >= oneDevPixel) {
4593 directions |= HORIZONTAL;
4594 }
4595 if (scrollRange.height >= oneDevPixel) {
4596 directions |= VERTICAL;
4597 }
4598 return directions;
4599 }

mercurial