|
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 } |