1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Pierre Phaneuf <pp@ludusdesign.com>
24 : * Mats Palmgren <matspal@gmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : /* rendering object to wrap rendering objects that should be scrollable */
41 :
42 : #include "nsCOMPtr.h"
43 : #include "nsHTMLParts.h"
44 : #include "nsPresContext.h"
45 : #include "nsIServiceManager.h"
46 : #include "nsIView.h"
47 : #include "nsIScrollable.h"
48 : #include "nsIViewManager.h"
49 : #include "nsContainerFrame.h"
50 : #include "nsGfxScrollFrame.h"
51 : #include "nsGkAtoms.h"
52 : #include "nsINameSpaceManager.h"
53 : #include "nsIDocument.h"
54 : #include "nsFontMetrics.h"
55 : #include "nsIDocumentObserver.h"
56 : #include "nsBoxLayoutState.h"
57 : #include "nsINodeInfo.h"
58 : #include "nsScrollbarFrame.h"
59 : #include "nsIScrollbarMediator.h"
60 : #include "nsITextControlFrame.h"
61 : #include "nsIDOMHTMLTextAreaElement.h"
62 : #include "nsNodeInfoManager.h"
63 : #include "nsIURI.h"
64 : #include "nsGUIEvent.h"
65 : #include "nsContentCreatorFunctions.h"
66 : #include "nsISupportsPrimitives.h"
67 : #include "nsAutoPtr.h"
68 : #include "nsPresState.h"
69 : #include "nsDocShellCID.h"
70 : #include "nsIHTMLDocument.h"
71 : #include "nsEventDispatcher.h"
72 : #include "nsContentUtils.h"
73 : #include "nsLayoutUtils.h"
74 : #ifdef ACCESSIBILITY
75 : #include "nsAccessibilityService.h"
76 : #endif
77 : #include "nsBidiUtils.h"
78 : #include "nsFrameManager.h"
79 : #include "mozilla/Preferences.h"
80 : #include "mozilla/LookAndFeel.h"
81 : #include "mozilla/dom/Element.h"
82 : #include "FrameLayerBuilder.h"
83 : #include "nsSMILKeySpline.h"
84 : #include "nsSubDocumentFrame.h"
85 :
86 : using namespace mozilla;
87 : using namespace mozilla::dom;
88 :
89 : //----------------------------------------------------------------------
90 :
91 : //----------nsHTMLScrollFrame-------------------------------------------
92 :
93 : nsIFrame*
94 0 : NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot)
95 : {
96 0 : return new (aPresShell) nsHTMLScrollFrame(aPresShell, aContext, aIsRoot);
97 : }
98 :
99 0 : NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)
100 :
101 0 : nsHTMLScrollFrame::nsHTMLScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, bool aIsRoot)
102 : : nsContainerFrame(aContext),
103 0 : mInner(this, aIsRoot)
104 : {
105 0 : }
106 :
107 : nsresult
108 0 : nsHTMLScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
109 : {
110 0 : return mInner.CreateAnonymousContent(aElements);
111 : }
112 :
113 : void
114 0 : nsHTMLScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
115 : PRUint32 aFilter)
116 : {
117 0 : mInner.AppendAnonymousContentTo(aElements, aFilter);
118 0 : }
119 :
120 : void
121 0 : nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot)
122 : {
123 0 : mInner.Destroy();
124 0 : DestroyAbsoluteFrames(aDestructRoot);
125 0 : nsContainerFrame::DestroyFrom(aDestructRoot);
126 0 : }
127 :
128 : NS_IMETHODIMP
129 0 : nsHTMLScrollFrame::SetInitialChildList(ChildListID aListID,
130 : nsFrameList& aChildList)
131 : {
132 0 : nsresult rv = nsContainerFrame::SetInitialChildList(aListID, aChildList);
133 0 : mInner.ReloadChildFrames();
134 0 : return rv;
135 : }
136 :
137 :
138 : NS_IMETHODIMP
139 0 : nsHTMLScrollFrame::AppendFrames(ChildListID aListID,
140 : nsFrameList& aFrameList)
141 : {
142 0 : NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
143 0 : mFrames.AppendFrames(nsnull, aFrameList);
144 0 : mInner.ReloadChildFrames();
145 0 : return NS_OK;
146 : }
147 :
148 : NS_IMETHODIMP
149 0 : nsHTMLScrollFrame::InsertFrames(ChildListID aListID,
150 : nsIFrame* aPrevFrame,
151 : nsFrameList& aFrameList)
152 : {
153 0 : NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
154 0 : NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
155 : "inserting after sibling frame with different parent");
156 0 : mFrames.InsertFrames(nsnull, aPrevFrame, aFrameList);
157 0 : mInner.ReloadChildFrames();
158 0 : return NS_OK;
159 : }
160 :
161 : NS_IMETHODIMP
162 0 : nsHTMLScrollFrame::RemoveFrame(ChildListID aListID,
163 : nsIFrame* aOldFrame)
164 : {
165 0 : NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
166 0 : mFrames.DestroyFrame(aOldFrame);
167 0 : mInner.ReloadChildFrames();
168 0 : return NS_OK;
169 : }
170 :
171 : nsSplittableType
172 0 : nsHTMLScrollFrame::GetSplittableType() const
173 : {
174 0 : return NS_FRAME_NOT_SPLITTABLE;
175 : }
176 :
177 : PRIntn
178 0 : nsHTMLScrollFrame::GetSkipSides() const
179 : {
180 0 : return 0;
181 : }
182 :
183 : nsIAtom*
184 0 : nsHTMLScrollFrame::GetType() const
185 : {
186 0 : return nsGkAtoms::scrollFrame;
187 : }
188 :
189 : void
190 0 : nsHTMLScrollFrame::InvalidateInternal(const nsRect& aDamageRect,
191 : nscoord aX, nscoord aY, nsIFrame* aForChild,
192 : PRUint32 aFlags)
193 : {
194 0 : if (aForChild) {
195 0 : if (aForChild == mInner.mScrolledFrame) {
196 0 : nsRect damage = aDamageRect + nsPoint(aX, aY);
197 : // This is the damage rect that we're going to pass up to our parent.
198 0 : nsRect parentDamage;
199 0 : if (mInner.IsIgnoringViewportClipping()) {
200 0 : parentDamage = damage;
201 : } else {
202 : // If we're using a displayport, we might be displaying an area
203 : // different than our scroll port and the damage needs to be
204 : // clipped to that instead.
205 0 : nsRect displayport;
206 : bool usingDisplayport = nsLayoutUtils::GetDisplayPort(GetContent(),
207 0 : &displayport);
208 0 : if (usingDisplayport) {
209 0 : parentDamage.IntersectRect(damage, displayport);
210 : } else {
211 0 : parentDamage.IntersectRect(damage, mInner.mScrollPort);
212 : }
213 : }
214 :
215 0 : if (IsScrollingActive()) {
216 : // This is the damage rect that we're going to pass up and
217 : // only request invalidation of ThebesLayers for.
218 : // damage is now in our coordinate system, which means it was
219 : // translated using the current scroll position. Adjust it to
220 : // reflect the scroll position at last paint, since that's what
221 : // the ThebesLayers are currently set up for.
222 : // This should not be clipped to the scrollport since ThebesLayers
223 : // can contain content outside the scrollport that may need to be
224 : // invalidated.
225 0 : nsRect thebesLayerDamage = damage + GetScrollPosition() - mInner.mScrollPosAtLastPaint;
226 0 : if (parentDamage.IsEqualInterior(thebesLayerDamage)) {
227 : // This single call will take care of both rects
228 0 : nsContainerFrame::InvalidateInternal(parentDamage, 0, 0, aForChild, aFlags);
229 : } else {
230 : // Invalidate rects separately
231 0 : if (!(aFlags & INVALIDATE_NO_THEBES_LAYERS)) {
232 : nsContainerFrame::InvalidateInternal(thebesLayerDamage, 0, 0, aForChild,
233 0 : aFlags | INVALIDATE_ONLY_THEBES_LAYERS);
234 : }
235 0 : if (!(aFlags & INVALIDATE_ONLY_THEBES_LAYERS) && !parentDamage.IsEmpty()) {
236 : nsContainerFrame::InvalidateInternal(parentDamage, 0, 0, aForChild,
237 0 : aFlags | INVALIDATE_NO_THEBES_LAYERS);
238 : }
239 : }
240 : } else {
241 0 : if (!parentDamage.IsEmpty()) {
242 0 : nsContainerFrame::InvalidateInternal(parentDamage, 0, 0, aForChild, aFlags);
243 : }
244 : }
245 :
246 0 : if (mInner.mIsRoot && !parentDamage.IsEqualInterior(damage)) {
247 : // Make sure we notify our prescontext about invalidations outside
248 : // viewport clipping.
249 : // This is important for things that are snapshotting the viewport,
250 : // possibly outside the scrolled bounds.
251 : // We don't need to propagate this any further up, though. Anyone who
252 : // cares about scrolled-out-of-view invalidates had better be listening
253 : // to our window directly.
254 0 : PresContext()->NotifyInvalidation(damage, aFlags);
255 : }
256 : return;
257 0 : } else if (aForChild == mInner.mHScrollbarBox) {
258 0 : if (!mInner.mHasHorizontalScrollbar) {
259 : // Our scrollbars may send up invalidations even when they're collapsed,
260 : // because we just size a collapsed scrollbar to empty and some
261 : // descendants may be non-empty. Suppress that invalidation here.
262 0 : return;
263 : }
264 0 : } else if (aForChild == mInner.mVScrollbarBox) {
265 0 : if (!mInner.mHasVerticalScrollbar) {
266 : // Our scrollbars may send up invalidations even when they're collapsed,
267 : // because we just size a collapsed scrollbar to empty and some
268 : // descendants may be non-empty. Suppress that invalidation here.
269 0 : return;
270 : }
271 : }
272 : }
273 :
274 0 : nsContainerFrame::InvalidateInternal(aDamageRect, aX, aY, aForChild, aFlags);
275 : }
276 :
277 : /**
278 : HTML scrolling implementation
279 :
280 : All other things being equal, we prefer layouts with fewer scrollbars showing.
281 : */
282 :
283 0 : struct ScrollReflowState {
284 : const nsHTMLReflowState& mReflowState;
285 : nsBoxLayoutState mBoxState;
286 : nsGfxScrollFrameInner::ScrollbarStyles mStyles;
287 : nsMargin mComputedBorder;
288 :
289 : // === Filled in by ReflowScrolledFrame ===
290 : nsOverflowAreas mContentsOverflowAreas;
291 : bool mReflowedContentsWithHScrollbar;
292 : bool mReflowedContentsWithVScrollbar;
293 :
294 : // === Filled in when TryLayout succeeds ===
295 : // The size of the inside-border area
296 : nsSize mInsideBorderSize;
297 : // Whether we decided to show the horizontal scrollbar
298 : bool mShowHScrollbar;
299 : // Whether we decided to show the vertical scrollbar
300 : bool mShowVScrollbar;
301 :
302 0 : ScrollReflowState(nsIScrollableFrame* aFrame,
303 : const nsHTMLReflowState& aState) :
304 : mReflowState(aState),
305 : // mBoxState is just used for scrollbars so we don't need to
306 : // worry about the reflow depth here
307 : mBoxState(aState.frame->PresContext(), aState.rendContext, 0),
308 0 : mStyles(aFrame->GetScrollbarStyles()) {
309 0 : }
310 : };
311 :
312 : // XXXldb Can this go away?
313 0 : static nsSize ComputeInsideBorderSize(ScrollReflowState* aState,
314 : const nsSize& aDesiredInsideBorderSize)
315 : {
316 : // aDesiredInsideBorderSize is the frame size; i.e., it includes
317 : // borders and padding (but the scrolled child doesn't have
318 : // borders). The scrolled child has the same padding as us.
319 0 : nscoord contentWidth = aState->mReflowState.ComputedWidth();
320 0 : if (contentWidth == NS_UNCONSTRAINEDSIZE) {
321 : contentWidth = aDesiredInsideBorderSize.width -
322 0 : aState->mReflowState.mComputedPadding.LeftRight();
323 : }
324 0 : nscoord contentHeight = aState->mReflowState.ComputedHeight();
325 0 : if (contentHeight == NS_UNCONSTRAINEDSIZE) {
326 : contentHeight = aDesiredInsideBorderSize.height -
327 0 : aState->mReflowState.mComputedPadding.TopBottom();
328 : }
329 :
330 0 : aState->mReflowState.ApplyMinMaxConstraints(&contentWidth, &contentHeight);
331 0 : return nsSize(contentWidth + aState->mReflowState.mComputedPadding.LeftRight(),
332 0 : contentHeight + aState->mReflowState.mComputedPadding.TopBottom());
333 : }
334 :
335 : static void
336 0 : GetScrollbarMetrics(nsBoxLayoutState& aState, nsIBox* aBox, nsSize* aMin,
337 : nsSize* aPref, bool aVertical)
338 : {
339 0 : NS_ASSERTION(aState.GetRenderingContext(),
340 : "Must have rendering context in layout state for size "
341 : "computations");
342 :
343 0 : if (aMin) {
344 0 : *aMin = aBox->GetMinSize(aState);
345 0 : nsBox::AddMargin(aBox, *aMin);
346 : }
347 :
348 0 : if (aPref) {
349 0 : *aPref = aBox->GetPrefSize(aState);
350 0 : nsBox::AddMargin(aBox, *aPref);
351 : }
352 0 : }
353 :
354 : /**
355 : * Assuming that we know the metrics for our wrapped frame and
356 : * whether the horizontal and/or vertical scrollbars are present,
357 : * compute the resulting layout and return true if the layout is
358 : * consistent. If the layout is consistent then we fill in the
359 : * computed fields of the ScrollReflowState.
360 : *
361 : * The layout is consistent when both scrollbars are showing if and only
362 : * if they should be showing. A horizontal scrollbar should be showing if all
363 : * following conditions are met:
364 : * 1) the style is not HIDDEN
365 : * 2) our inside-border height is at least the scrollbar height (i.e., the
366 : * scrollbar fits vertically)
367 : * 3) our scrollport width (the inside-border width minus the width allocated for a
368 : * vertical scrollbar, if showing) is at least the scrollbar's min-width
369 : * (i.e., the scrollbar fits horizontally)
370 : * 4) the style is SCROLL, or the kid's overflow-area XMost is
371 : * greater than the scrollport width
372 : *
373 : * @param aForce if true, then we just assume the layout is consistent.
374 : */
375 : bool
376 0 : nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState,
377 : nsHTMLReflowMetrics* aKidMetrics,
378 : bool aAssumeHScroll, bool aAssumeVScroll,
379 : bool aForce, nsresult* aResult)
380 : {
381 0 : *aResult = NS_OK;
382 :
383 0 : if ((aState->mStyles.mVertical == NS_STYLE_OVERFLOW_HIDDEN && aAssumeVScroll) ||
384 : (aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && aAssumeHScroll)) {
385 0 : NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
386 0 : return false;
387 : }
388 :
389 0 : if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar ||
390 : (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar &&
391 0 : ScrolledContentDependsOnHeight(aState))) {
392 : nsresult rv = ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll,
393 0 : aKidMetrics, false);
394 0 : if (NS_FAILED(rv)) {
395 0 : *aResult = rv;
396 0 : return false;
397 : }
398 : }
399 :
400 0 : nsSize vScrollbarMinSize(0, 0);
401 0 : nsSize vScrollbarPrefSize(0, 0);
402 0 : if (mInner.mVScrollbarBox) {
403 : GetScrollbarMetrics(aState->mBoxState, mInner.mVScrollbarBox,
404 : &vScrollbarMinSize,
405 0 : aAssumeVScroll ? &vScrollbarPrefSize : nsnull, true);
406 : }
407 0 : nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0;
408 0 : nscoord vScrollbarMinHeight = aAssumeVScroll ? vScrollbarMinSize.height : 0;
409 :
410 0 : nsSize hScrollbarMinSize(0, 0);
411 0 : nsSize hScrollbarPrefSize(0, 0);
412 0 : if (mInner.mHScrollbarBox) {
413 : GetScrollbarMetrics(aState->mBoxState, mInner.mHScrollbarBox,
414 : &hScrollbarMinSize,
415 0 : aAssumeHScroll ? &hScrollbarPrefSize : nsnull, false);
416 : }
417 0 : nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0;
418 0 : nscoord hScrollbarMinWidth = aAssumeHScroll ? hScrollbarMinSize.width : 0;
419 :
420 : // First, compute our inside-border size and scrollport size
421 : // XXXldb Can we depend more on ComputeSize here?
422 0 : nsSize desiredInsideBorderSize;
423 : desiredInsideBorderSize.width = vScrollbarDesiredWidth +
424 0 : NS_MAX(aKidMetrics->width, hScrollbarMinWidth);
425 : desiredInsideBorderSize.height = hScrollbarDesiredHeight +
426 0 : NS_MAX(aKidMetrics->height, vScrollbarMinHeight);
427 : aState->mInsideBorderSize =
428 0 : ComputeInsideBorderSize(aState, desiredInsideBorderSize);
429 0 : nsSize scrollPortSize = nsSize(NS_MAX(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth),
430 0 : NS_MAX(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight));
431 :
432 0 : if (!aForce) {
433 : nsRect scrolledRect =
434 0 : mInner.GetScrolledRectInternal(aState->mContentsOverflowAreas.ScrollableOverflow(),
435 0 : scrollPortSize);
436 0 : nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1);
437 :
438 : // If the style is HIDDEN then we already know that aAssumeHScroll is false
439 0 : if (aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
440 : bool wantHScrollbar =
441 : aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL ||
442 0 : scrolledRect.XMost() >= scrollPortSize.width + oneDevPixel ||
443 0 : scrolledRect.x <= -oneDevPixel;
444 0 : if (scrollPortSize.width < hScrollbarMinSize.width)
445 0 : wantHScrollbar = false;
446 0 : if (wantHScrollbar != aAssumeHScroll)
447 0 : return false;
448 : }
449 :
450 : // If the style is HIDDEN then we already know that aAssumeVScroll is false
451 0 : if (aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) {
452 : bool wantVScrollbar =
453 : aState->mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL ||
454 0 : scrolledRect.YMost() >= scrollPortSize.height + oneDevPixel ||
455 0 : scrolledRect.y <= -oneDevPixel;
456 0 : if (scrollPortSize.height < vScrollbarMinSize.height)
457 0 : wantVScrollbar = false;
458 0 : if (wantVScrollbar != aAssumeVScroll)
459 0 : return false;
460 : }
461 : }
462 :
463 0 : nscoord vScrollbarActualWidth = aState->mInsideBorderSize.width - scrollPortSize.width;
464 :
465 0 : aState->mShowHScrollbar = aAssumeHScroll;
466 0 : aState->mShowVScrollbar = aAssumeVScroll;
467 : nsPoint scrollPortOrigin(aState->mComputedBorder.left,
468 0 : aState->mComputedBorder.top);
469 0 : if (!mInner.IsScrollbarOnRight()) {
470 0 : scrollPortOrigin.x += vScrollbarActualWidth;
471 : }
472 0 : mInner.mScrollPort = nsRect(scrollPortOrigin, scrollPortSize);
473 0 : return true;
474 : }
475 :
476 : bool
477 0 : nsHTMLScrollFrame::ScrolledContentDependsOnHeight(ScrollReflowState* aState)
478 : {
479 : // Return true if ReflowScrolledFrame is going to do something different
480 : // based on the presence of a horizontal scrollbar.
481 0 : return (mInner.mScrolledFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) ||
482 0 : aState->mReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE ||
483 : aState->mReflowState.mComputedMinHeight > 0 ||
484 0 : aState->mReflowState.mComputedMaxHeight != NS_UNCONSTRAINEDSIZE;
485 : }
486 :
487 : nsresult
488 0 : nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState,
489 : bool aAssumeHScroll,
490 : bool aAssumeVScroll,
491 : nsHTMLReflowMetrics* aMetrics,
492 : bool aFirstPass)
493 : {
494 : // these could be NS_UNCONSTRAINEDSIZE ... NS_MIN arithmetic should
495 : // be OK
496 0 : nscoord paddingLR = aState->mReflowState.mComputedPadding.LeftRight();
497 :
498 0 : nscoord availWidth = aState->mReflowState.ComputedWidth() + paddingLR;
499 :
500 0 : nscoord computedHeight = aState->mReflowState.ComputedHeight();
501 0 : nscoord computedMinHeight = aState->mReflowState.mComputedMinHeight;
502 0 : nscoord computedMaxHeight = aState->mReflowState.mComputedMaxHeight;
503 0 : if (!ShouldPropagateComputedHeightToScrolledContent()) {
504 0 : computedHeight = NS_UNCONSTRAINEDSIZE;
505 0 : computedMinHeight = 0;
506 0 : computedMaxHeight = NS_UNCONSTRAINEDSIZE;
507 : }
508 0 : if (aAssumeHScroll) {
509 : nsSize hScrollbarPrefSize =
510 0 : mInner.mHScrollbarBox->GetPrefSize(const_cast<nsBoxLayoutState&>(aState->mBoxState));
511 0 : if (computedHeight != NS_UNCONSTRAINEDSIZE)
512 0 : computedHeight = NS_MAX(0, computedHeight - hScrollbarPrefSize.height);
513 0 : computedMinHeight = NS_MAX(0, computedMinHeight - hScrollbarPrefSize.height);
514 0 : if (computedMaxHeight != NS_UNCONSTRAINEDSIZE)
515 0 : computedMaxHeight = NS_MAX(0, computedMaxHeight - hScrollbarPrefSize.height);
516 : }
517 :
518 0 : if (aAssumeVScroll) {
519 : nsSize vScrollbarPrefSize =
520 0 : mInner.mVScrollbarBox->GetPrefSize(const_cast<nsBoxLayoutState&>(aState->mBoxState));
521 0 : availWidth = NS_MAX(0, availWidth - vScrollbarPrefSize.width);
522 : }
523 :
524 0 : nsPresContext* presContext = PresContext();
525 :
526 : // Pass false for aInit so we can pass in the correct padding.
527 : nsHTMLReflowState kidReflowState(presContext, aState->mReflowState,
528 : mInner.mScrolledFrame,
529 : nsSize(availWidth, NS_UNCONSTRAINEDSIZE),
530 0 : -1, -1, false);
531 : kidReflowState.Init(presContext, -1, -1, nsnull,
532 0 : &aState->mReflowState.mComputedPadding);
533 0 : kidReflowState.mFlags.mAssumingHScrollbar = aAssumeHScroll;
534 0 : kidReflowState.mFlags.mAssumingVScrollbar = aAssumeVScroll;
535 0 : kidReflowState.SetComputedHeight(computedHeight);
536 0 : kidReflowState.mComputedMinHeight = computedMinHeight;
537 0 : kidReflowState.mComputedMaxHeight = computedMaxHeight;
538 :
539 : // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to
540 : // reflect our assumptions while we reflow the child.
541 0 : bool didHaveHorizontalScrollbar = mInner.mHasHorizontalScrollbar;
542 0 : bool didHaveVerticalScrollbar = mInner.mHasVerticalScrollbar;
543 0 : mInner.mHasHorizontalScrollbar = aAssumeHScroll;
544 0 : mInner.mHasVerticalScrollbar = aAssumeVScroll;
545 :
546 : nsReflowStatus status;
547 : nsresult rv = ReflowChild(mInner.mScrolledFrame, presContext, *aMetrics,
548 : kidReflowState, 0, 0,
549 0 : NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_MOVE_VIEW, status);
550 :
551 0 : mInner.mHasHorizontalScrollbar = didHaveHorizontalScrollbar;
552 0 : mInner.mHasVerticalScrollbar = didHaveVerticalScrollbar;
553 :
554 : // Don't resize or position the view (if any) because we're going to resize
555 : // it to the correct size anyway in PlaceScrollArea. Allowing it to
556 : // resize here would size it to the natural height of the frame,
557 : // which will usually be different from the scrollport height;
558 : // invalidating the difference will cause unnecessary repainting.
559 : FinishReflowChild(mInner.mScrolledFrame, presContext,
560 : &kidReflowState, *aMetrics, 0, 0,
561 0 : NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_SIZE_VIEW);
562 :
563 : // XXX Some frames (e.g., nsObjectFrame, nsFrameFrame, nsTextFrame) don't bother
564 : // setting their mOverflowArea. This is wrong because every frame should
565 : // always set mOverflowArea. In fact nsObjectFrame and nsFrameFrame don't
566 : // support the 'outline' property because of this. Rather than fix the world
567 : // right now, just fix up the overflow area if necessary. Note that we don't
568 : // check HasOverflowRect() because it could be set even though the
569 : // overflow area doesn't include the frame bounds.
570 0 : aMetrics->UnionOverflowAreasWithDesiredBounds();
571 :
572 0 : aState->mContentsOverflowAreas = aMetrics->mOverflowAreas;
573 0 : aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
574 0 : aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
575 :
576 0 : return rv;
577 : }
578 :
579 : bool
580 0 : nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowState& aState)
581 : {
582 0 : if (aState.mStyles.mHorizontal != NS_STYLE_OVERFLOW_AUTO)
583 : // no guessing required
584 0 : return aState.mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL;
585 :
586 0 : return mInner.mHasHorizontalScrollbar;
587 : }
588 :
589 : bool
590 0 : nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowState& aState)
591 : {
592 0 : if (aState.mStyles.mVertical != NS_STYLE_OVERFLOW_AUTO)
593 : // no guessing required
594 0 : return aState.mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL;
595 :
596 : // If we've had at least one non-initial reflow, then just assume
597 : // the state of the vertical scrollbar will be what we determined
598 : // last time.
599 0 : if (mInner.mHadNonInitialReflow) {
600 0 : return mInner.mHasVerticalScrollbar;
601 : }
602 :
603 : // If this is the initial reflow, guess false because usually
604 : // we have very little content by then.
605 0 : if (InInitialReflow())
606 0 : return false;
607 :
608 0 : if (mInner.mIsRoot) {
609 : // Assume that there will be a scrollbar; it seems to me
610 : // that 'most pages' do have a scrollbar, and anyway, it's cheaper
611 : // to do an extra reflow for the pages that *don't* need a
612 : // scrollbar (because on average they will have less content).
613 0 : return true;
614 : }
615 :
616 : // For non-viewports, just guess that we don't need a scrollbar.
617 : // XXX I wonder if statistically this is the right idea; I'm
618 : // basically guessing that there are a lot of overflow:auto DIVs
619 : // that get their intrinsic size and don't overflow
620 0 : return false;
621 : }
622 :
623 : bool
624 0 : nsHTMLScrollFrame::InInitialReflow() const
625 : {
626 : // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a
627 : // root scrollframe. In that case we want to skip this clause altogether.
628 : // The guess here is that there are lots of overflow:auto divs out there that
629 : // end up auto-sizing so they don't overflow, and that the root basically
630 : // always needs a scrollbar if it did last time we loaded this page (good
631 : // assumption, because our initial reflow is no longer synchronous).
632 0 : return !mInner.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW);
633 : }
634 :
635 : nsresult
636 0 : nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState,
637 : const nsHTMLReflowMetrics& aDesiredSize)
638 : {
639 0 : nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mFlags);
640 0 : nsresult rv = ReflowScrolledFrame(aState, GuessHScrollbarNeeded(*aState),
641 0 : GuessVScrollbarNeeded(*aState), &kidDesiredSize, true);
642 0 : NS_ENSURE_SUCCESS(rv, rv);
643 :
644 : // There's an important special case ... if the child appears to fit
645 : // in the inside-border rect (but overflows the scrollport), we
646 : // should try laying it out without a vertical scrollbar. It will
647 : // usually fit because making the available-width wider will not
648 : // normally make the child taller. (The only situation I can think
649 : // of is when you have a line containing %-width inline replaced
650 : // elements whose percentages sum to more than 100%, so increasing
651 : // the available width makes the line break where it was fitting
652 : // before.) If we don't treat this case specially, then we will
653 : // decide that showing scrollbars is OK because the content
654 : // overflows when we're showing scrollbars and we won't try to
655 : // remove the vertical scrollbar.
656 :
657 : // Detecting when we enter this special case is important for when
658 : // people design layouts that exactly fit the container "most of the
659 : // time".
660 :
661 : // XXX Is this check really sufficient to catch all the incremental cases
662 : // where the ideal case doesn't have a scrollbar?
663 0 : if ((aState->mReflowedContentsWithHScrollbar || aState->mReflowedContentsWithVScrollbar) &&
664 : aState->mStyles.mVertical != NS_STYLE_OVERFLOW_SCROLL &&
665 : aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) {
666 : nsSize insideBorderSize =
667 : ComputeInsideBorderSize(aState,
668 0 : nsSize(kidDesiredSize.width, kidDesiredSize.height));
669 : nsRect scrolledRect =
670 0 : mInner.GetScrolledRectInternal(kidDesiredSize.ScrollableOverflow(),
671 0 : insideBorderSize);
672 0 : if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
673 : // Let's pretend we had no scrollbars coming in here
674 : rv = ReflowScrolledFrame(aState, false, false,
675 0 : &kidDesiredSize, false);
676 0 : NS_ENSURE_SUCCESS(rv, rv);
677 : }
678 : }
679 :
680 : // Try vertical scrollbar settings that leave the vertical scrollbar unchanged.
681 : // Do this first because changing the vertical scrollbar setting is expensive,
682 : // forcing a reflow always.
683 :
684 : // Try leaving the horizontal scrollbar unchanged first. This will be more
685 : // efficient.
686 0 : if (TryLayout(aState, &kidDesiredSize, aState->mReflowedContentsWithHScrollbar,
687 0 : aState->mReflowedContentsWithVScrollbar, false, &rv))
688 0 : return NS_OK;
689 0 : if (TryLayout(aState, &kidDesiredSize, !aState->mReflowedContentsWithHScrollbar,
690 0 : aState->mReflowedContentsWithVScrollbar, false, &rv))
691 0 : return NS_OK;
692 :
693 : // OK, now try toggling the vertical scrollbar. The performance advantage
694 : // of trying the status-quo horizontal scrollbar state
695 : // does not exist here (we'll have to reflow due to the vertical scrollbar
696 : // change), so always try no horizontal scrollbar first.
697 0 : bool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar;
698 0 : if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false, &rv))
699 0 : return NS_OK;
700 0 : if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false, &rv))
701 0 : return NS_OK;
702 :
703 : // OK, we're out of ideas. Try again enabling whatever scrollbars we can
704 : // enable and force the layout to stick even if it's inconsistent.
705 : // This just happens sometimes.
706 : TryLayout(aState, &kidDesiredSize,
707 : aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN,
708 : aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN,
709 0 : true, &rv);
710 0 : return rv;
711 : }
712 :
713 : void
714 0 : nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowState& aState,
715 : const nsPoint& aScrollPosition)
716 : {
717 0 : nsIFrame *scrolledFrame = mInner.mScrolledFrame;
718 : // Set the x,y of the scrolled frame to the correct value
719 0 : scrolledFrame->SetPosition(mInner.mScrollPort.TopLeft() - aScrollPosition);
720 :
721 0 : nsRect scrolledArea;
722 : // Preserve the width or height of empty rects
723 0 : nsSize portSize = mInner.mScrollPort.Size();
724 : nsRect scrolledRect =
725 0 : mInner.GetScrolledRectInternal(aState.mContentsOverflowAreas.ScrollableOverflow(),
726 0 : portSize);
727 : scrolledArea.UnionRectEdges(scrolledRect,
728 0 : nsRect(nsPoint(0,0), portSize));
729 :
730 : // Store the new overflow area. Note that this changes where an outline
731 : // of the scrolled frame would be painted, but scrolled frames can't have
732 : // outlines (the outline would go on this scrollframe instead).
733 : // Using FinishAndStoreOverflow is needed so the overflow rect
734 : // gets set correctly. It also messes with the overflow rect in the
735 : // -moz-hidden-unscrollable case, but scrolled frames can't have
736 : // 'overflow' either.
737 : // This needs to happen before SyncFrameViewAfterReflow so
738 : // HasOverflowRect() will return the correct value.
739 0 : nsOverflowAreas overflow(scrolledArea, scrolledArea);
740 : scrolledFrame->FinishAndStoreOverflow(overflow,
741 0 : scrolledFrame->GetSize());
742 :
743 : // Note that making the view *exactly* the size of the scrolled area
744 : // is critical, since the view scrolling code uses the size of the
745 : // scrolled view to clamp scroll requests.
746 : // Normally the scrolledFrame won't have a view but in some cases it
747 : // might create its own.
748 : nsContainerFrame::SyncFrameViewAfterReflow(scrolledFrame->PresContext(),
749 : scrolledFrame,
750 : scrolledFrame->GetView(),
751 : scrolledArea,
752 0 : 0);
753 0 : }
754 :
755 : nscoord
756 0 : nsHTMLScrollFrame::GetIntrinsicVScrollbarWidth(nsRenderingContext *aRenderingContext)
757 : {
758 0 : nsGfxScrollFrameInner::ScrollbarStyles ss = GetScrollbarStyles();
759 0 : if (ss.mVertical != NS_STYLE_OVERFLOW_SCROLL || !mInner.mVScrollbarBox)
760 0 : return 0;
761 :
762 : // Don't need to worry about reflow depth here since it's
763 : // just for scrollbars
764 0 : nsBoxLayoutState bls(PresContext(), aRenderingContext, 0);
765 0 : nsSize vScrollbarPrefSize(0, 0);
766 : GetScrollbarMetrics(bls, mInner.mVScrollbarBox,
767 0 : nsnull, &vScrollbarPrefSize, true);
768 0 : return vScrollbarPrefSize.width;
769 : }
770 :
771 : /* virtual */ nscoord
772 0 : nsHTMLScrollFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
773 : {
774 0 : nscoord result = mInner.mScrolledFrame->GetMinWidth(aRenderingContext);
775 0 : DISPLAY_MIN_WIDTH(this, result);
776 0 : return result + GetIntrinsicVScrollbarWidth(aRenderingContext);
777 : }
778 :
779 : /* virtual */ nscoord
780 0 : nsHTMLScrollFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
781 : {
782 0 : nscoord result = mInner.mScrolledFrame->GetPrefWidth(aRenderingContext);
783 0 : DISPLAY_PREF_WIDTH(this, result);
784 0 : return NSCoordSaturatingAdd(result, GetIntrinsicVScrollbarWidth(aRenderingContext));
785 : }
786 :
787 : NS_IMETHODIMP
788 0 : nsHTMLScrollFrame::GetPadding(nsMargin& aMargin)
789 : {
790 : // Our padding hangs out on the inside of the scrollframe, but XUL doesn't
791 : // reaize that. If we're stuck inside a XUL box, we need to claim no
792 : // padding.
793 : // @see also nsXULScrollFrame::GetPadding.
794 0 : aMargin.SizeTo(0,0,0,0);
795 0 : return NS_OK;
796 : }
797 :
798 : bool
799 0 : nsHTMLScrollFrame::IsCollapsed()
800 : {
801 : // We're never collapsed in the box sense.
802 0 : return false;
803 : }
804 :
805 : // Return the <browser> if the scrollframe is for the root frame directly
806 : // inside a <browser>.
807 : static nsIContent*
808 0 : GetBrowserRoot(nsIContent* aContent)
809 : {
810 0 : if (aContent) {
811 0 : nsIDocument* doc = aContent->GetCurrentDoc();
812 0 : nsPIDOMWindow* win = doc->GetWindow();
813 0 : if (win) {
814 : nsCOMPtr<nsIContent> frameContent =
815 0 : do_QueryInterface(win->GetFrameElementInternal());
816 0 : if (frameContent &&
817 0 : frameContent->NodeInfo()->Equals(nsGkAtoms::browser, kNameSpaceID_XUL))
818 0 : return frameContent;
819 : }
820 : }
821 :
822 0 : return nsnull;
823 : }
824 :
825 :
826 : NS_IMETHODIMP
827 0 : nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext,
828 : nsHTMLReflowMetrics& aDesiredSize,
829 : const nsHTMLReflowState& aReflowState,
830 : nsReflowStatus& aStatus)
831 : {
832 0 : DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
833 0 : DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
834 :
835 0 : ScrollReflowState state(this, aReflowState);
836 : // sanity check: ensure that if we have no scrollbar, we treat it
837 : // as hidden.
838 0 : if (!mInner.mVScrollbarBox || mInner.mNeverHasVerticalScrollbar)
839 0 : state.mStyles.mVertical = NS_STYLE_OVERFLOW_HIDDEN;
840 0 : if (!mInner.mHScrollbarBox || mInner.mNeverHasHorizontalScrollbar)
841 0 : state.mStyles.mHorizontal = NS_STYLE_OVERFLOW_HIDDEN;
842 :
843 : //------------ Handle Incremental Reflow -----------------
844 0 : bool reflowHScrollbar = true;
845 0 : bool reflowVScrollbar = true;
846 0 : bool reflowScrollCorner = true;
847 0 : if (!aReflowState.ShouldReflowAllKids()) {
848 : #define NEEDS_REFLOW(frame_) \
849 : ((frame_) && NS_SUBTREE_DIRTY(frame_))
850 :
851 0 : reflowHScrollbar = NEEDS_REFLOW(mInner.mHScrollbarBox);
852 0 : reflowVScrollbar = NEEDS_REFLOW(mInner.mVScrollbarBox);
853 0 : reflowScrollCorner = NEEDS_REFLOW(mInner.mScrollCornerBox) ||
854 0 : NEEDS_REFLOW(mInner.mResizerBox);
855 :
856 : #undef NEEDS_REFLOW
857 : }
858 :
859 0 : if (mInner.mIsRoot) {
860 0 : mInner.mCollapsedResizer = true;
861 :
862 0 : nsIContent* browserRoot = GetBrowserRoot(mContent);
863 0 : if (browserRoot) {
864 0 : bool showResizer = browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer);
865 0 : reflowScrollCorner = showResizer == mInner.mCollapsedResizer;
866 0 : mInner.mCollapsedResizer = !showResizer;
867 : }
868 : }
869 :
870 0 : nsRect oldScrollAreaBounds = mInner.mScrollPort;
871 : nsRect oldScrolledAreaBounds =
872 0 : mInner.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
873 : // Adjust to a multiple of device pixels to restore the invariant that
874 : // oldScrollPosition is a multiple of device pixels. This could have been
875 : // thrown out by a zoom change.
876 0 : nsIntPoint ptDevPx;
877 0 : nsPoint oldScrollPosition = mInner.GetScrollPosition();
878 :
879 : state.mComputedBorder = aReflowState.mComputedBorderPadding -
880 0 : aReflowState.mComputedPadding;
881 :
882 0 : nsresult rv = ReflowContents(&state, aDesiredSize);
883 0 : if (NS_FAILED(rv))
884 0 : return rv;
885 :
886 : // Restore the old scroll position, for now, even if that's not valid anymore
887 : // because we changed size. We'll fix it up in a post-reflow callback, because
888 : // our current size may only be temporary (e.g. we're compute XUL desired sizes).
889 0 : PlaceScrollArea(state, oldScrollPosition);
890 0 : if (!mInner.mPostedReflowCallback) {
891 : // Make sure we'll try scrolling to restored position
892 0 : PresContext()->PresShell()->PostReflowCallback(&mInner);
893 0 : mInner.mPostedReflowCallback = true;
894 : }
895 :
896 0 : bool didHaveHScrollbar = mInner.mHasHorizontalScrollbar;
897 0 : bool didHaveVScrollbar = mInner.mHasVerticalScrollbar;
898 0 : mInner.mHasHorizontalScrollbar = state.mShowHScrollbar;
899 0 : mInner.mHasVerticalScrollbar = state.mShowVScrollbar;
900 0 : nsRect newScrollAreaBounds = mInner.mScrollPort;
901 : nsRect newScrolledAreaBounds =
902 0 : mInner.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
903 0 : if (mInner.mSkippedScrollbarLayout ||
904 : reflowHScrollbar || reflowVScrollbar || reflowScrollCorner ||
905 0 : (GetStateBits() & NS_FRAME_IS_DIRTY) ||
906 : didHaveHScrollbar != state.mShowHScrollbar ||
907 : didHaveVScrollbar != state.mShowVScrollbar ||
908 0 : !oldScrollAreaBounds.IsEqualEdges(newScrollAreaBounds) ||
909 0 : !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
910 0 : if (!mInner.mSupppressScrollbarUpdate) {
911 0 : mInner.mSkippedScrollbarLayout = false;
912 0 : mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, state.mShowHScrollbar);
913 0 : mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, state.mShowVScrollbar);
914 : // place and reflow scrollbars
915 : nsRect insideBorderArea =
916 : nsRect(nsPoint(state.mComputedBorder.left, state.mComputedBorder.top),
917 0 : state.mInsideBorderSize);
918 : mInner.LayoutScrollbars(state.mBoxState, insideBorderArea,
919 0 : oldScrollAreaBounds);
920 : } else {
921 0 : mInner.mSkippedScrollbarLayout = true;
922 : }
923 : }
924 :
925 : aDesiredSize.width = state.mInsideBorderSize.width +
926 0 : state.mComputedBorder.LeftRight();
927 : aDesiredSize.height = state.mInsideBorderSize.height +
928 0 : state.mComputedBorder.TopBottom();
929 :
930 0 : aDesiredSize.SetOverflowAreasToDesiredBounds();
931 0 : if (mInner.IsIgnoringViewportClipping()) {
932 : aDesiredSize.mOverflowAreas.UnionWith(
933 0 : state.mContentsOverflowAreas + mInner.mScrolledFrame->GetPosition());
934 : }
935 :
936 0 : CheckInvalidateSizeChange(aDesiredSize);
937 :
938 0 : FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus);
939 :
940 0 : if (!InInitialReflow() && !mInner.mHadNonInitialReflow) {
941 0 : mInner.mHadNonInitialReflow = true;
942 : }
943 :
944 0 : if (mInner.mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
945 0 : mInner.PostScrolledAreaEvent();
946 : }
947 :
948 0 : aStatus = NS_FRAME_COMPLETE;
949 0 : NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
950 0 : mInner.PostOverflowEvent();
951 0 : return rv;
952 : }
953 :
954 :
955 : ////////////////////////////////////////////////////////////////////////////////
956 :
957 : #ifdef NS_DEBUG
958 : NS_IMETHODIMP
959 0 : nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const
960 : {
961 0 : return MakeFrameName(NS_LITERAL_STRING("HTMLScroll"), aResult);
962 : }
963 : #endif
964 :
965 : #ifdef ACCESSIBILITY
966 : already_AddRefed<nsAccessible>
967 0 : nsHTMLScrollFrame::CreateAccessible()
968 : {
969 : // Create an accessible regardless of focusable state because the state can be
970 : // changed during frame life cycle without any notifications to accessibility.
971 0 : if (mContent->IsRootOfNativeAnonymousSubtree() ||
972 0 : GetScrollbarStyles() == nsIScrollableFrame::
973 0 : ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN) ) {
974 0 : return nsnull;
975 : }
976 :
977 0 : nsAccessibilityService* accService = nsIPresShell::AccService();
978 0 : if (accService) {
979 : return accService->CreateHyperTextAccessible(mContent,
980 0 : PresContext()->PresShell());
981 : }
982 :
983 0 : return nsnull;
984 : }
985 : #endif
986 :
987 0 : NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
988 0 : NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
989 0 : NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
990 0 : NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
991 0 : NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
992 :
993 : //----------nsXULScrollFrame-------------------------------------------
994 :
995 : nsIFrame*
996 0 : NS_NewXULScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot)
997 : {
998 0 : return new (aPresShell) nsXULScrollFrame(aPresShell, aContext, aIsRoot);
999 : }
1000 :
1001 0 : NS_IMPL_FRAMEARENA_HELPERS(nsXULScrollFrame)
1002 :
1003 0 : nsXULScrollFrame::nsXULScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, bool aIsRoot)
1004 : : nsBoxFrame(aShell, aContext, aIsRoot),
1005 0 : mInner(this, aIsRoot)
1006 : {
1007 0 : SetLayoutManager(nsnull);
1008 0 : }
1009 :
1010 0 : nsMargin nsGfxScrollFrameInner::GetDesiredScrollbarSizes(nsBoxLayoutState* aState) {
1011 0 : NS_ASSERTION(aState && aState->GetRenderingContext(),
1012 : "Must have rendering context in layout state for size "
1013 : "computations");
1014 :
1015 0 : nsMargin result(0, 0, 0, 0);
1016 :
1017 0 : if (mVScrollbarBox) {
1018 0 : nsSize size = mVScrollbarBox->GetPrefSize(*aState);
1019 0 : nsBox::AddMargin(mVScrollbarBox, size);
1020 0 : if (IsScrollbarOnRight())
1021 0 : result.left = size.width;
1022 : else
1023 0 : result.right = size.width;
1024 : }
1025 :
1026 0 : if (mHScrollbarBox) {
1027 0 : nsSize size = mHScrollbarBox->GetPrefSize(*aState);
1028 0 : nsBox::AddMargin(mHScrollbarBox, size);
1029 : // We don't currently support any scripts that would require a scrollbar
1030 : // at the top. (Are there any?)
1031 0 : result.bottom = size.height;
1032 : }
1033 :
1034 : return result;
1035 : }
1036 :
1037 : nsresult
1038 0 : nsXULScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
1039 : {
1040 0 : return mInner.CreateAnonymousContent(aElements);
1041 : }
1042 :
1043 : void
1044 0 : nsXULScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
1045 : PRUint32 aFilter)
1046 : {
1047 0 : mInner.AppendAnonymousContentTo(aElements, aFilter);
1048 0 : }
1049 :
1050 : void
1051 0 : nsXULScrollFrame::DestroyFrom(nsIFrame* aDestructRoot)
1052 : {
1053 0 : mInner.Destroy();
1054 0 : nsBoxFrame::DestroyFrom(aDestructRoot);
1055 0 : }
1056 :
1057 : NS_IMETHODIMP
1058 0 : nsXULScrollFrame::SetInitialChildList(ChildListID aListID,
1059 : nsFrameList& aChildList)
1060 : {
1061 0 : nsresult rv = nsBoxFrame::SetInitialChildList(aListID, aChildList);
1062 0 : mInner.ReloadChildFrames();
1063 0 : return rv;
1064 : }
1065 :
1066 :
1067 : NS_IMETHODIMP
1068 0 : nsXULScrollFrame::AppendFrames(ChildListID aListID,
1069 : nsFrameList& aFrameList)
1070 : {
1071 0 : nsresult rv = nsBoxFrame::AppendFrames(aListID, aFrameList);
1072 0 : mInner.ReloadChildFrames();
1073 0 : return rv;
1074 : }
1075 :
1076 : NS_IMETHODIMP
1077 0 : nsXULScrollFrame::InsertFrames(ChildListID aListID,
1078 : nsIFrame* aPrevFrame,
1079 : nsFrameList& aFrameList)
1080 : {
1081 0 : nsresult rv = nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
1082 0 : mInner.ReloadChildFrames();
1083 0 : return rv;
1084 : }
1085 :
1086 : NS_IMETHODIMP
1087 0 : nsXULScrollFrame::RemoveFrame(ChildListID aListID,
1088 : nsIFrame* aOldFrame)
1089 : {
1090 0 : nsresult rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame);
1091 0 : mInner.ReloadChildFrames();
1092 0 : return rv;
1093 : }
1094 :
1095 : nsSplittableType
1096 0 : nsXULScrollFrame::GetSplittableType() const
1097 : {
1098 0 : return NS_FRAME_NOT_SPLITTABLE;
1099 : }
1100 :
1101 : NS_IMETHODIMP
1102 0 : nsXULScrollFrame::GetPadding(nsMargin& aMargin)
1103 : {
1104 0 : aMargin.SizeTo(0,0,0,0);
1105 0 : return NS_OK;
1106 : }
1107 :
1108 : PRIntn
1109 0 : nsXULScrollFrame::GetSkipSides() const
1110 : {
1111 0 : return 0;
1112 : }
1113 :
1114 : nsIAtom*
1115 0 : nsXULScrollFrame::GetType() const
1116 : {
1117 0 : return nsGkAtoms::scrollFrame;
1118 : }
1119 :
1120 : void
1121 0 : nsXULScrollFrame::InvalidateInternal(const nsRect& aDamageRect,
1122 : nscoord aX, nscoord aY, nsIFrame* aForChild,
1123 : PRUint32 aFlags)
1124 : {
1125 0 : if (aForChild == mInner.mScrolledFrame) {
1126 0 : nsRect damage = aDamageRect + nsPoint(aX, aY);
1127 : // This is the damage rect that we're going to pass up to our parent.
1128 0 : nsRect parentDamage;
1129 : // If we're using a displayport, we might be displaying an area
1130 : // different than our scroll port and the damage needs to be
1131 : // clipped to that instead.
1132 0 : nsRect displayport;
1133 : bool usingDisplayport = nsLayoutUtils::GetDisplayPort(GetContent(),
1134 0 : &displayport);
1135 0 : if (usingDisplayport) {
1136 0 : parentDamage.IntersectRect(damage, displayport);
1137 : } else {
1138 0 : parentDamage.IntersectRect(damage, mInner.mScrollPort);
1139 : }
1140 :
1141 0 : if (IsScrollingActive()) {
1142 : // This is the damage rect that we're going to pass up and
1143 : // only request invalidation of ThebesLayers for.
1144 : // damage is now in our coordinate system, which means it was
1145 : // translated using the current scroll position. Adjust it to
1146 : // reflect the scroll position at last paint, since that's what
1147 : // the ThebesLayers are currently set up for.
1148 : // This should not be clipped to the scrollport since ThebesLayers
1149 : // can contain content outside the scrollport that may need to be
1150 : // invalidated.
1151 0 : nsRect thebesLayerDamage = damage + GetScrollPosition() - mInner.mScrollPosAtLastPaint;
1152 0 : if (parentDamage.IsEqualInterior(thebesLayerDamage)) {
1153 : // This single call will take care of both rects
1154 0 : nsBoxFrame::InvalidateInternal(parentDamage, 0, 0, aForChild, aFlags);
1155 : } else {
1156 : // Invalidate rects separately
1157 0 : if (!(aFlags & INVALIDATE_NO_THEBES_LAYERS)) {
1158 : nsBoxFrame::InvalidateInternal(thebesLayerDamage, 0, 0, aForChild,
1159 0 : aFlags | INVALIDATE_ONLY_THEBES_LAYERS);
1160 : }
1161 0 : if (!(aFlags & INVALIDATE_ONLY_THEBES_LAYERS) && !parentDamage.IsEmpty()) {
1162 : nsBoxFrame::InvalidateInternal(parentDamage, 0, 0, aForChild,
1163 0 : aFlags | INVALIDATE_NO_THEBES_LAYERS);
1164 : }
1165 : }
1166 : } else {
1167 0 : if (!parentDamage.IsEmpty()) {
1168 0 : nsBoxFrame::InvalidateInternal(parentDamage, 0, 0, aForChild, aFlags);
1169 : }
1170 : }
1171 : return;
1172 : }
1173 :
1174 0 : nsBoxFrame::InvalidateInternal(aDamageRect, aX, aY, aForChild, aFlags);
1175 : }
1176 :
1177 : nscoord
1178 0 : nsXULScrollFrame::GetBoxAscent(nsBoxLayoutState& aState)
1179 : {
1180 0 : if (!mInner.mScrolledFrame)
1181 0 : return 0;
1182 :
1183 0 : nscoord ascent = mInner.mScrolledFrame->GetBoxAscent(aState);
1184 0 : nsMargin m(0,0,0,0);
1185 0 : GetBorderAndPadding(m);
1186 0 : ascent += m.top;
1187 0 : GetMargin(m);
1188 0 : ascent += m.top;
1189 :
1190 0 : return ascent;
1191 : }
1192 :
1193 : nsSize
1194 0 : nsXULScrollFrame::GetPrefSize(nsBoxLayoutState& aState)
1195 : {
1196 : #ifdef DEBUG_LAYOUT
1197 : PropagateDebug(aState);
1198 : #endif
1199 :
1200 0 : nsSize pref = mInner.mScrolledFrame->GetPrefSize(aState);
1201 :
1202 0 : nsGfxScrollFrameInner::ScrollbarStyles styles = GetScrollbarStyles();
1203 :
1204 : // scrolled frames don't have their own margins
1205 :
1206 0 : if (mInner.mVScrollbarBox &&
1207 : styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1208 0 : nsSize vSize = mInner.mVScrollbarBox->GetPrefSize(aState);
1209 0 : nsBox::AddMargin(mInner.mVScrollbarBox, vSize);
1210 0 : pref.width += vSize.width;
1211 : }
1212 :
1213 0 : if (mInner.mHScrollbarBox &&
1214 : styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1215 0 : nsSize hSize = mInner.mHScrollbarBox->GetPrefSize(aState);
1216 0 : nsBox::AddMargin(mInner.mHScrollbarBox, hSize);
1217 0 : pref.height += hSize.height;
1218 : }
1219 :
1220 0 : AddBorderAndPadding(pref);
1221 : bool widthSet, heightSet;
1222 0 : nsIBox::AddCSSPrefSize(this, pref, widthSet, heightSet);
1223 : return pref;
1224 : }
1225 :
1226 : nsSize
1227 0 : nsXULScrollFrame::GetMinSize(nsBoxLayoutState& aState)
1228 : {
1229 : #ifdef DEBUG_LAYOUT
1230 : PropagateDebug(aState);
1231 : #endif
1232 :
1233 0 : nsSize min = mInner.mScrolledFrame->GetMinSizeForScrollArea(aState);
1234 :
1235 0 : nsGfxScrollFrameInner::ScrollbarStyles styles = GetScrollbarStyles();
1236 :
1237 0 : if (mInner.mVScrollbarBox &&
1238 : styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1239 0 : nsSize vSize = mInner.mVScrollbarBox->GetMinSize(aState);
1240 0 : AddMargin(mInner.mVScrollbarBox, vSize);
1241 0 : min.width += vSize.width;
1242 0 : if (min.height < vSize.height)
1243 0 : min.height = vSize.height;
1244 : }
1245 :
1246 0 : if (mInner.mHScrollbarBox &&
1247 : styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1248 0 : nsSize hSize = mInner.mHScrollbarBox->GetMinSize(aState);
1249 0 : AddMargin(mInner.mHScrollbarBox, hSize);
1250 0 : min.height += hSize.height;
1251 0 : if (min.width < hSize.width)
1252 0 : min.width = hSize.width;
1253 : }
1254 :
1255 0 : AddBorderAndPadding(min);
1256 : bool widthSet, heightSet;
1257 0 : nsIBox::AddCSSMinSize(aState, this, min, widthSet, heightSet);
1258 : return min;
1259 : }
1260 :
1261 : nsSize
1262 0 : nsXULScrollFrame::GetMaxSize(nsBoxLayoutState& aState)
1263 : {
1264 : #ifdef DEBUG_LAYOUT
1265 : PropagateDebug(aState);
1266 : #endif
1267 :
1268 0 : nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
1269 :
1270 0 : AddBorderAndPadding(maxSize);
1271 : bool widthSet, heightSet;
1272 0 : nsIBox::AddCSSMaxSize(this, maxSize, widthSet, heightSet);
1273 : return maxSize;
1274 : }
1275 :
1276 : #ifdef NS_DEBUG
1277 : NS_IMETHODIMP
1278 0 : nsXULScrollFrame::GetFrameName(nsAString& aResult) const
1279 : {
1280 0 : return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult);
1281 : }
1282 : #endif
1283 :
1284 : NS_IMETHODIMP
1285 0 : nsXULScrollFrame::DoLayout(nsBoxLayoutState& aState)
1286 : {
1287 0 : PRUint32 flags = aState.LayoutFlags();
1288 0 : nsresult rv = Layout(aState);
1289 0 : aState.SetLayoutFlags(flags);
1290 :
1291 0 : nsBox::DoLayout(aState);
1292 0 : return rv;
1293 : }
1294 :
1295 0 : NS_QUERYFRAME_HEAD(nsXULScrollFrame)
1296 0 : NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
1297 0 : NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
1298 0 : NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
1299 0 : NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
1300 :
1301 : //-------------------- Inner ----------------------
1302 :
1303 : #define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll"
1304 :
1305 : const double kCurrentVelocityWeighting = 0.25;
1306 : const double kStopDecelerationWeighting = 0.4;
1307 :
1308 : class nsGfxScrollFrameInner::AsyncScroll {
1309 : public:
1310 : typedef mozilla::TimeStamp TimeStamp;
1311 : typedef mozilla::TimeDuration TimeDuration;
1312 :
1313 0 : AsyncScroll():
1314 0 : mIsFirstIteration(true)
1315 0 : {}
1316 :
1317 0 : ~AsyncScroll() {
1318 0 : if (mScrollTimer) mScrollTimer->Cancel();
1319 0 : }
1320 :
1321 : nsPoint PositionAt(TimeStamp aTime);
1322 : nsSize VelocityAt(TimeStamp aTime); // In nscoords per second
1323 :
1324 : void InitSmoothScroll(TimeStamp aTime, nsPoint aCurrentPos,
1325 : nsSize aCurrentVelocity, nsPoint aDestination,
1326 : nsIAtom *aOrigin);
1327 :
1328 0 : bool IsFinished(TimeStamp aTime) {
1329 0 : return aTime > mStartTime + mDuration; // XXX or if we've hit the wall
1330 : }
1331 :
1332 : nsCOMPtr<nsITimer> mScrollTimer;
1333 : TimeStamp mStartTime;
1334 :
1335 : // mPrevStartTime holds previous 3 timestamps for intervals averaging (to
1336 : // reduce duration fluctuations). When AsyncScroll is constructed and no
1337 : // previous timestamps are available (indicated with mIsFirstIteration),
1338 : // initialize mPrevStartTime using imaginary previous timestamps with maximum
1339 : // relevant intervals between them.
1340 : TimeStamp mPrevStartTime[3];
1341 : bool mIsFirstIteration;
1342 :
1343 : // Cached Preferences values to avoid re-reading them when extending an existing
1344 : // animation for the same event origin (can be as frequent as every 10(!)ms for
1345 : // a quick roll of the mouse wheel).
1346 : // These values are minimum and maximum animation duration per event origin,
1347 : // and a global ratio which defines how longer is the animation's duration
1348 : // compared to the average recent events intervals (such that for a relatively
1349 : // consistent events rate, the next event arrives before current animation ends)
1350 : nsCOMPtr<nsIAtom> mOrigin;
1351 : PRInt32 mOriginMinMS;
1352 : PRInt32 mOriginMaxMS;
1353 : double mIntervalRatio;
1354 :
1355 : TimeDuration mDuration;
1356 : nsPoint mStartPos;
1357 : nsPoint mDestination;
1358 : nsSMILKeySpline mTimingFunctionX;
1359 : nsSMILKeySpline mTimingFunctionY;
1360 : bool mIsSmoothScroll;
1361 :
1362 : protected:
1363 0 : double ProgressAt(TimeStamp aTime) {
1364 0 : return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0);
1365 : }
1366 :
1367 : nscoord VelocityComponent(double aTimeProgress,
1368 : nsSMILKeySpline& aTimingFunction,
1369 : nscoord aStart, nscoord aDestination);
1370 :
1371 : // Initializes the timing function in such a way that the current velocity is
1372 : // preserved.
1373 : void InitTimingFunction(nsSMILKeySpline& aTimingFunction,
1374 : nscoord aCurrentPos, nscoord aCurrentVelocity,
1375 : nscoord aDestination);
1376 :
1377 : void InitDuration(nsIAtom *aOrigin);
1378 : };
1379 :
1380 : nsPoint
1381 0 : nsGfxScrollFrameInner::AsyncScroll::PositionAt(TimeStamp aTime) {
1382 0 : double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
1383 0 : double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
1384 : return nsPoint((1 - progressX) * mStartPos.x + progressX * mDestination.x,
1385 0 : (1 - progressY) * mStartPos.y + progressY * mDestination.y);
1386 : }
1387 :
1388 : nsSize
1389 0 : nsGfxScrollFrameInner::AsyncScroll::VelocityAt(TimeStamp aTime) {
1390 0 : double timeProgress = ProgressAt(aTime);
1391 : return nsSize(VelocityComponent(timeProgress, mTimingFunctionX,
1392 : mStartPos.x, mDestination.x),
1393 : VelocityComponent(timeProgress, mTimingFunctionY,
1394 0 : mStartPos.y, mDestination.y));
1395 : }
1396 :
1397 : /*
1398 : * Calculate/update mDuration, possibly dynamically according to events rate and event origin.
1399 : * (also maintain previous timestamps - which are only used here).
1400 : */
1401 : void
1402 0 : nsGfxScrollFrameInner::AsyncScroll::InitDuration(nsIAtom *aOrigin) {
1403 0 : if (!aOrigin){
1404 0 : aOrigin = nsGkAtoms::other;
1405 : }
1406 :
1407 : // Read preferences only on first iteration or for a different event origin.
1408 0 : if (mIsFirstIteration || aOrigin != mOrigin) {
1409 0 : mOrigin = aOrigin;
1410 0 : mOriginMinMS = mOriginMaxMS = 0;
1411 0 : bool isOriginSmoothnessEnabled = false;
1412 0 : mIntervalRatio = 1;
1413 :
1414 : // Default values for all preferences are defined in all.js
1415 : static const PRInt32 kDefaultMinMS = 150, kDefaultMaxMS = 150;
1416 : static const bool kDefaultIsSmoothEnabled = true;
1417 :
1418 0 : nsCAutoString originName;
1419 0 : aOrigin->ToUTF8String(originName);
1420 0 : nsCAutoString prefBase = NS_LITERAL_CSTRING("general.smoothScroll.") + originName;
1421 :
1422 0 : isOriginSmoothnessEnabled = Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled);
1423 0 : if (isOriginSmoothnessEnabled) {
1424 0 : nsCAutoString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS");
1425 0 : nsCAutoString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS");
1426 0 : mOriginMinMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS);
1427 0 : mOriginMaxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS);
1428 :
1429 : static const PRInt32 kSmoothScrollMaxAllowedAnimationDurationMS = 10000;
1430 0 : mOriginMaxMS = clamped(mOriginMaxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS);
1431 0 : mOriginMinMS = clamped(mOriginMinMS, 0, mOriginMaxMS);
1432 : }
1433 :
1434 : // Keep the animation duration longer than the average event intervals
1435 : // (to "connect" consecutive scroll animations before the scroll comes to a stop).
1436 : static const double kDefaultDurationToIntervalRatio = 2; // Duplicated at all.js
1437 : mIntervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio",
1438 0 : kDefaultDurationToIntervalRatio * 100) / 100.0;
1439 :
1440 : // Duration should be at least as long as the intervals -> ratio is at least 1
1441 0 : mIntervalRatio = NS_MAX(1.0, mIntervalRatio);
1442 :
1443 0 : if (mIsFirstIteration) {
1444 : // Starting a new scroll (i.e. not when extending an existing scroll animation),
1445 : // create imaginary prev timestamps with maximum relevant intervals between them.
1446 0 : mIsFirstIteration = false;
1447 :
1448 : // Longest relevant interval (which results in maximum duration)
1449 0 : TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio);
1450 0 : mPrevStartTime[0] = mStartTime - maxDelta;
1451 0 : mPrevStartTime[1] = mPrevStartTime[0] - maxDelta;
1452 0 : mPrevStartTime[2] = mPrevStartTime[1] - maxDelta;
1453 : }
1454 : }
1455 :
1456 : // Average last 3 delta durations (rounding errors up to 2ms are negligible for us)
1457 0 : PRInt32 eventsDeltaMs = (mStartTime - mPrevStartTime[2]).ToMilliseconds() / 3;
1458 0 : mPrevStartTime[2] = mPrevStartTime[1];
1459 0 : mPrevStartTime[1] = mPrevStartTime[0];
1460 0 : mPrevStartTime[0] = mStartTime;
1461 :
1462 : // Modulate duration according to events rate (quicker events -> shorter durations).
1463 : // The desired effect is to use longer duration when scrolling slowly, such that
1464 : // it's easier to follow, but reduce the duration to make it feel more snappy when
1465 : // scrolling quickly. To reduce fluctuations of the duration, we average event
1466 : // intervals using the recent 4 timestamps (now + three prev -> 3 intervals).
1467 0 : PRInt32 durationMS = clamped<PRInt32>(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS);
1468 :
1469 0 : mDuration = TimeDuration::FromMilliseconds(durationMS);
1470 0 : }
1471 :
1472 : void
1473 0 : nsGfxScrollFrameInner::AsyncScroll::InitSmoothScroll(TimeStamp aTime,
1474 : nsPoint aCurrentPos,
1475 : nsSize aCurrentVelocity,
1476 : nsPoint aDestination,
1477 : nsIAtom *aOrigin) {
1478 0 : mStartTime = aTime;
1479 0 : mStartPos = aCurrentPos;
1480 0 : mDestination = aDestination;
1481 0 : InitDuration(aOrigin);
1482 0 : InitTimingFunction(mTimingFunctionX, mStartPos.x, aCurrentVelocity.width, aDestination.x);
1483 0 : InitTimingFunction(mTimingFunctionY, mStartPos.y, aCurrentVelocity.height, aDestination.y);
1484 0 : }
1485 :
1486 :
1487 : nscoord
1488 0 : nsGfxScrollFrameInner::AsyncScroll::VelocityComponent(double aTimeProgress,
1489 : nsSMILKeySpline& aTimingFunction,
1490 : nscoord aStart,
1491 : nscoord aDestination)
1492 : {
1493 : double dt, dxy;
1494 0 : aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy);
1495 0 : if (dt == 0)
1496 0 : return dxy >= 0 ? nscoord_MAX : nscoord_MIN;
1497 :
1498 0 : const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
1499 0 : double slope = dxy / dt;
1500 0 : return (slope * (aDestination - aStart) / (mDuration / oneSecond));
1501 : }
1502 :
1503 : void
1504 0 : nsGfxScrollFrameInner::AsyncScroll::InitTimingFunction(nsSMILKeySpline& aTimingFunction,
1505 : nscoord aCurrentPos,
1506 : nscoord aCurrentVelocity,
1507 : nscoord aDestination)
1508 : {
1509 0 : if (aDestination == aCurrentPos || kCurrentVelocityWeighting == 0) {
1510 0 : aTimingFunction.Init(0, 0, 1 - kStopDecelerationWeighting, 1);
1511 0 : return;
1512 : }
1513 :
1514 0 : const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
1515 0 : double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
1516 0 : double normalization = sqrt(1.0 + slope * slope);
1517 0 : double dt = 1.0 / normalization * kCurrentVelocityWeighting;
1518 0 : double dxy = slope / normalization * kCurrentVelocityWeighting;
1519 0 : aTimingFunction.Init(dt, dxy, 1 - kStopDecelerationWeighting, 1);
1520 : }
1521 :
1522 : static bool
1523 0 : IsSmoothScrollingEnabled()
1524 : {
1525 0 : return Preferences::GetBool(SMOOTH_SCROLL_PREF_NAME, false);
1526 : }
1527 :
1528 : class ScrollFrameActivityTracker : public nsExpirationTracker<nsGfxScrollFrameInner,4> {
1529 : public:
1530 : // Wait for 3-4s between scrolls before we remove our layers.
1531 : // That's 4 generations of 1s each.
1532 : enum { TIMEOUT_MS = 1000 };
1533 0 : ScrollFrameActivityTracker()
1534 0 : : nsExpirationTracker<nsGfxScrollFrameInner,4>(TIMEOUT_MS) {}
1535 0 : ~ScrollFrameActivityTracker() {
1536 0 : AgeAllGenerations();
1537 0 : }
1538 :
1539 0 : virtual void NotifyExpired(nsGfxScrollFrameInner *aObject) {
1540 0 : RemoveObject(aObject);
1541 0 : aObject->MarkInactive();
1542 0 : }
1543 : };
1544 :
1545 : static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nsnull;
1546 :
1547 0 : nsGfxScrollFrameInner::nsGfxScrollFrameInner(nsContainerFrame* aOuter,
1548 : bool aIsRoot)
1549 : : mHScrollbarBox(nsnull)
1550 : , mVScrollbarBox(nsnull)
1551 : , mScrolledFrame(nsnull)
1552 : , mScrollCornerBox(nsnull)
1553 : , mResizerBox(nsnull)
1554 : , mOuter(aOuter)
1555 : , mAsyncScroll(nsnull)
1556 : , mDestination(0, 0)
1557 : , mScrollPosAtLastPaint(0, 0)
1558 : , mRestorePos(-1, -1)
1559 : , mLastPos(-1, -1)
1560 : , mNeverHasVerticalScrollbar(false)
1561 : , mNeverHasHorizontalScrollbar(false)
1562 : , mHasVerticalScrollbar(false)
1563 : , mHasHorizontalScrollbar(false)
1564 : , mFrameIsUpdatingScrollbar(false)
1565 : , mDidHistoryRestore(false)
1566 : , mIsRoot(aIsRoot)
1567 : , mSupppressScrollbarUpdate(false)
1568 : , mSkippedScrollbarLayout(false)
1569 : , mHadNonInitialReflow(false)
1570 : , mHorizontalOverflow(false)
1571 : , mVerticalOverflow(false)
1572 : , mPostedReflowCallback(false)
1573 : , mMayHaveDirtyFixedChildren(false)
1574 : , mUpdateScrollbarAttributes(false)
1575 : , mCollapsedResizer(false)
1576 0 : , mShouldBuildLayer(false)
1577 : {
1578 0 : mScrollingActive = IsAlwaysActive();
1579 0 : }
1580 :
1581 0 : nsGfxScrollFrameInner::~nsGfxScrollFrameInner()
1582 : {
1583 0 : if (mActivityExpirationState.IsTracked()) {
1584 0 : gScrollFrameActivityTracker->RemoveObject(this);
1585 : }
1586 0 : if (gScrollFrameActivityTracker &&
1587 0 : gScrollFrameActivityTracker->IsEmpty()) {
1588 0 : delete gScrollFrameActivityTracker;
1589 0 : gScrollFrameActivityTracker = nsnull;
1590 : }
1591 0 : delete mAsyncScroll;
1592 :
1593 0 : if (mScrollActivityTimer) {
1594 0 : mScrollActivityTimer->Cancel();
1595 0 : mScrollActivityTimer = nsnull;
1596 : }
1597 0 : }
1598 :
1599 : static nscoord
1600 0 : Clamp(nscoord aLower, nscoord aVal, nscoord aUpper)
1601 : {
1602 0 : if (aVal < aLower)
1603 0 : return aLower;
1604 0 : if (aVal > aUpper)
1605 0 : return aUpper;
1606 0 : return aVal;
1607 : }
1608 :
1609 : nsPoint
1610 0 : nsGfxScrollFrameInner::ClampScrollPosition(const nsPoint& aPt) const
1611 : {
1612 0 : nsRect range = GetScrollRange();
1613 : return nsPoint(Clamp(range.x, aPt.x, range.XMost()),
1614 0 : Clamp(range.y, aPt.y, range.YMost()));
1615 : }
1616 :
1617 : /*
1618 : * Callback function from timer used in nsGfxScrollFrameInner::ScrollTo
1619 : */
1620 : void
1621 0 : nsGfxScrollFrameInner::AsyncScrollCallback(nsITimer *aTimer, void* anInstance)
1622 : {
1623 0 : nsGfxScrollFrameInner* self = static_cast<nsGfxScrollFrameInner*>(anInstance);
1624 0 : if (!self || !self->mAsyncScroll)
1625 0 : return;
1626 :
1627 0 : if (self->mAsyncScroll->mIsSmoothScroll) {
1628 0 : TimeStamp now = TimeStamp::Now();
1629 0 : nsPoint destination = self->mAsyncScroll->PositionAt(now);
1630 0 : if (self->mAsyncScroll->IsFinished(now)) {
1631 0 : delete self->mAsyncScroll;
1632 0 : self->mAsyncScroll = nsnull;
1633 : }
1634 :
1635 0 : self->ScrollToImpl(destination);
1636 : } else {
1637 0 : delete self->mAsyncScroll;
1638 0 : self->mAsyncScroll = nsnull;
1639 :
1640 0 : self->ScrollToImpl(self->mDestination);
1641 : }
1642 : }
1643 :
1644 : /*
1645 : * this method wraps calls to ScrollToImpl(), either in one shot or incrementally,
1646 : * based on the setting of the smoothness scroll pref
1647 : */
1648 : void
1649 0 : nsGfxScrollFrameInner::ScrollToWithOrigin(nsPoint aScrollPosition,
1650 : nsIScrollableFrame::ScrollMode aMode,
1651 : nsIAtom *aOrigin)
1652 : {
1653 0 : if (ShouldClampScrollPosition()) {
1654 0 : mDestination = ClampScrollPosition(aScrollPosition);
1655 : } else {
1656 0 : mDestination = aScrollPosition;
1657 : }
1658 :
1659 0 : if (aMode == nsIScrollableFrame::INSTANT) {
1660 : // Asynchronous scrolling is not allowed, so we'll kill any existing
1661 : // async-scrolling process and do an instant scroll
1662 0 : delete mAsyncScroll;
1663 0 : mAsyncScroll = nsnull;
1664 0 : ScrollToImpl(mDestination);
1665 0 : return;
1666 : }
1667 :
1668 0 : TimeStamp now = TimeStamp::Now();
1669 0 : nsPoint currentPosition = GetScrollPosition();
1670 0 : nsSize currentVelocity(0, 0);
1671 : bool isSmoothScroll = (aMode == nsIScrollableFrame::SMOOTH) &&
1672 0 : IsSmoothScrollingEnabled();
1673 :
1674 0 : if (mAsyncScroll) {
1675 0 : if (mAsyncScroll->mIsSmoothScroll) {
1676 0 : currentPosition = mAsyncScroll->PositionAt(now);
1677 0 : currentVelocity = mAsyncScroll->VelocityAt(now);
1678 : }
1679 : } else {
1680 0 : mAsyncScroll = new AsyncScroll;
1681 0 : mAsyncScroll->mScrollTimer = do_CreateInstance("@mozilla.org/timer;1");
1682 0 : if (!mAsyncScroll->mScrollTimer) {
1683 0 : delete mAsyncScroll;
1684 0 : mAsyncScroll = nsnull;
1685 : // allocation failed. Scroll the normal way.
1686 0 : ScrollToImpl(mDestination);
1687 0 : return;
1688 : }
1689 0 : if (isSmoothScroll) {
1690 0 : mAsyncScroll->mScrollTimer->InitWithFuncCallback(
1691 : AsyncScrollCallback, this, 1000 / 60,
1692 0 : nsITimer::TYPE_REPEATING_SLACK);
1693 : } else {
1694 0 : mAsyncScroll->mScrollTimer->InitWithFuncCallback(
1695 0 : AsyncScrollCallback, this, 0, nsITimer::TYPE_ONE_SHOT);
1696 : }
1697 : }
1698 :
1699 0 : mAsyncScroll->mIsSmoothScroll = isSmoothScroll;
1700 :
1701 0 : if (isSmoothScroll) {
1702 : mAsyncScroll->InitSmoothScroll(now, currentPosition, currentVelocity,
1703 0 : mDestination, aOrigin);
1704 : }
1705 : }
1706 :
1707 : // We can't use nsContainerFrame::PositionChildViews here because
1708 : // we don't want to invalidate views that have moved.
1709 0 : static void AdjustViews(nsIFrame* aFrame)
1710 : {
1711 0 : nsIView* view = aFrame->GetView();
1712 0 : if (view) {
1713 0 : nsPoint pt;
1714 0 : aFrame->GetParent()->GetClosestView(&pt);
1715 0 : pt += aFrame->GetPosition();
1716 0 : view->SetPosition(pt.x, pt.y);
1717 :
1718 0 : return;
1719 : }
1720 :
1721 0 : if (!(aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
1722 0 : return;
1723 : }
1724 :
1725 : // Call AdjustViews recursively for all child frames except the popup list as
1726 : // the views for popups are not scrolled.
1727 0 : nsIFrame::ChildListIterator lists(aFrame);
1728 0 : for (; !lists.IsDone(); lists.Next()) {
1729 0 : if (lists.CurrentID() == nsIFrame::kPopupList) {
1730 0 : continue;
1731 : }
1732 0 : nsFrameList::Enumerator childFrames(lists.CurrentList());
1733 0 : for (; !childFrames.AtEnd(); childFrames.Next()) {
1734 0 : AdjustViews(childFrames.get());
1735 : }
1736 : }
1737 : }
1738 :
1739 : static bool
1740 0 : CanScrollWithBlitting(nsIFrame* aFrame)
1741 : {
1742 0 : if (aFrame->GetStateBits() & NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL)
1743 0 : return false;
1744 :
1745 0 : for (nsIFrame* f = aFrame; f;
1746 : f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
1747 0 : if (nsSVGIntegrationUtils::UsingEffectsForFrame(f) ||
1748 0 : f->IsFrameOfType(nsIFrame::eSVG)) {
1749 0 : return false;
1750 : }
1751 0 : nsIScrollableFrame* sf = do_QueryFrame(f);
1752 0 : if ((sf || f->IsFrameOfType(nsIFrame::eReplaced)) &&
1753 0 : nsLayoutUtils::HasNonZeroCorner(f->GetStyleBorder()->mBorderRadius))
1754 0 : return false;
1755 0 : if (nsLayoutUtils::IsPopup(f))
1756 0 : break;
1757 : }
1758 0 : return true;
1759 : }
1760 :
1761 : static void
1762 0 : InvalidateFixedBackgroundFramesFromList(nsDisplayListBuilder* aBuilder,
1763 : nsIFrame* aMovingFrame,
1764 : const nsDisplayList& aList)
1765 : {
1766 0 : for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) {
1767 0 : nsDisplayList* sublist = item->GetList();
1768 0 : if (sublist) {
1769 0 : InvalidateFixedBackgroundFramesFromList(aBuilder, aMovingFrame, *sublist);
1770 0 : continue;
1771 : }
1772 0 : nsIFrame* f = item->GetUnderlyingFrame();
1773 0 : if (f &&
1774 0 : item->IsVaryingRelativeToMovingFrame(aBuilder, aMovingFrame)) {
1775 0 : if (FrameLayerBuilder::NeedToInvalidateFixedDisplayItem(aBuilder, item)) {
1776 : // FrameLayerBuilder does not take care of scrolling this one
1777 0 : f->Invalidate(item->GetVisibleRect() - item->ToReferenceFrame());
1778 : }
1779 : }
1780 : }
1781 0 : }
1782 :
1783 : static void
1784 0 : InvalidateFixedBackgroundFrames(nsIFrame* aRootFrame,
1785 : nsIFrame* aMovingFrame,
1786 : const nsRect& aUpdateRect)
1787 : {
1788 0 : if (!aMovingFrame->PresContext()->MayHaveFixedBackgroundFrames())
1789 0 : return;
1790 :
1791 0 : NS_ASSERTION(aRootFrame != aMovingFrame,
1792 : "The root frame shouldn't be the one that's moving, that makes no sense");
1793 :
1794 : // Build the 'after' display list over the whole area of interest.
1795 0 : nsDisplayListBuilder builder(aRootFrame, nsDisplayListBuilder::OTHER, true);
1796 0 : builder.EnterPresShell(aRootFrame, aUpdateRect);
1797 0 : nsDisplayList list;
1798 : nsresult rv =
1799 0 : aRootFrame->BuildDisplayListForStackingContext(&builder, aUpdateRect, &list);
1800 0 : builder.LeavePresShell(aRootFrame, aUpdateRect);
1801 0 : if (NS_FAILED(rv))
1802 : return;
1803 :
1804 0 : nsRegion visibleRegion(aUpdateRect);
1805 0 : list.ComputeVisibilityForRoot(&builder, &visibleRegion);
1806 :
1807 0 : InvalidateFixedBackgroundFramesFromList(&builder, aMovingFrame, list);
1808 0 : list.DeleteAll();
1809 : }
1810 :
1811 0 : bool nsGfxScrollFrameInner::IsIgnoringViewportClipping() const
1812 : {
1813 0 : if (!mIsRoot)
1814 0 : return false;
1815 : nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*>
1816 0 : (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame()));
1817 0 : return subdocFrame && !subdocFrame->ShouldClipSubdocument();
1818 : }
1819 :
1820 0 : bool nsGfxScrollFrameInner::ShouldClampScrollPosition() const
1821 : {
1822 0 : if (!mIsRoot)
1823 0 : return true;
1824 : nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*>
1825 0 : (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame()));
1826 0 : return !subdocFrame || subdocFrame->ShouldClampScrollPosition();
1827 : }
1828 :
1829 0 : bool nsGfxScrollFrameInner::IsAlwaysActive() const
1830 : {
1831 : // The root scrollframe for a non-chrome document which is the direct
1832 : // child of a chrome document is always treated as "active".
1833 : // XXX maybe we should extend this so that IFRAMEs which are fill the
1834 : // entire viewport (like GMail!) are always active
1835 0 : return mIsRoot && mOuter->PresContext()->IsRootContentDocument();
1836 : }
1837 :
1838 0 : void nsGfxScrollFrameInner::MarkInactive()
1839 : {
1840 0 : if (IsAlwaysActive() || !mScrollingActive)
1841 0 : return;
1842 :
1843 0 : mScrollingActive = false;
1844 0 : mOuter->InvalidateFrameSubtree();
1845 : }
1846 :
1847 0 : void nsGfxScrollFrameInner::MarkActive()
1848 : {
1849 0 : if (IsAlwaysActive())
1850 0 : return;
1851 :
1852 0 : mScrollingActive = true;
1853 0 : if (mActivityExpirationState.IsTracked()) {
1854 0 : gScrollFrameActivityTracker->MarkUsed(this);
1855 : } else {
1856 0 : if (!gScrollFrameActivityTracker) {
1857 0 : gScrollFrameActivityTracker = new ScrollFrameActivityTracker();
1858 : }
1859 0 : gScrollFrameActivityTracker->AddObject(this);
1860 : }
1861 : }
1862 :
1863 0 : void nsGfxScrollFrameInner::ScrollVisual(nsPoint aOldScrolledFramePos)
1864 : {
1865 0 : nsRootPresContext* rootPresContext = mOuter->PresContext()->GetRootPresContext();
1866 0 : if (!rootPresContext) {
1867 0 : return;
1868 : }
1869 :
1870 0 : rootPresContext->RequestUpdatePluginGeometry(mOuter);
1871 :
1872 0 : AdjustViews(mScrolledFrame);
1873 : // We need to call this after fixing up the view positions
1874 : // to be consistent with the frame hierarchy.
1875 0 : PRUint32 flags = nsIFrame::INVALIDATE_REASON_SCROLL_REPAINT;
1876 0 : bool canScrollWithBlitting = CanScrollWithBlitting(mOuter);
1877 0 : mOuter->RemoveStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL);
1878 0 : if (IsScrollingActive()) {
1879 0 : if (!canScrollWithBlitting) {
1880 0 : MarkInactive();
1881 : } else {
1882 0 : flags |= nsIFrame::INVALIDATE_NO_THEBES_LAYERS;
1883 : }
1884 : }
1885 0 : if (canScrollWithBlitting) {
1886 0 : MarkActive();
1887 : }
1888 :
1889 0 : nsRect invalidateRect, displayport;
1890 0 : if (IsIgnoringViewportClipping()) {
1891 0 : nsRect visualOverflow = mScrolledFrame->GetVisualOverflowRect();
1892 0 : invalidateRect.UnionRect(visualOverflow + mScrolledFrame->GetPosition(),
1893 0 : visualOverflow + aOldScrolledFramePos);
1894 : } else {
1895 : invalidateRect =
1896 0 : (nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayport)) ?
1897 0 : displayport : mScrollPort;
1898 : }
1899 :
1900 0 : mOuter->InvalidateWithFlags(invalidateRect, flags);
1901 :
1902 0 : if (flags & nsIFrame::INVALIDATE_NO_THEBES_LAYERS) {
1903 0 : nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(mOuter);
1904 : nsRect update =
1905 0 : GetScrollPortRect() + mOuter->GetOffsetToCrossDoc(displayRoot);
1906 : update = update.ConvertAppUnitsRoundOut(
1907 0 : mOuter->PresContext()->AppUnitsPerDevPixel(),
1908 0 : displayRoot->PresContext()->AppUnitsPerDevPixel());
1909 0 : InvalidateFixedBackgroundFrames(displayRoot, mScrolledFrame, update);
1910 : }
1911 : }
1912 :
1913 : static PRInt32
1914 0 : ClampInt(nscoord aLower, nscoord aVal, nscoord aUpper, nscoord aAppUnitsPerPixel)
1915 : {
1916 0 : PRInt32 low = NSToIntCeil(float(aLower)/aAppUnitsPerPixel);
1917 0 : PRInt32 high = NSToIntFloor(float(aUpper)/aAppUnitsPerPixel);
1918 0 : PRInt32 v = NSToIntRound(float(aVal)/aAppUnitsPerPixel);
1919 0 : NS_ASSERTION(low <= high, "No integers in range; 0 is supposed to be in range");
1920 0 : if (v < low)
1921 0 : return low;
1922 0 : if (v > high)
1923 0 : return high;
1924 0 : return v;
1925 : }
1926 :
1927 : nsPoint
1928 0 : nsGfxScrollFrameInner::RestrictToDevPixels(const nsPoint& aPt,
1929 : nsIntPoint* aPtDevPx,
1930 : bool aShouldClamp) const
1931 : {
1932 0 : nsPresContext* presContext = mOuter->PresContext();
1933 0 : nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
1934 : // Convert to device pixels so we scroll to an integer offset of device
1935 : // pixels. But we also need to make sure that our position remains
1936 : // inside the allowed region.
1937 0 : if (aShouldClamp) {
1938 0 : nsRect scrollRange = GetScrollRange();
1939 : *aPtDevPx = nsIntPoint(ClampInt(scrollRange.x, aPt.x, scrollRange.XMost(), appUnitsPerDevPixel),
1940 0 : ClampInt(scrollRange.y, aPt.y, scrollRange.YMost(), appUnitsPerDevPixel));
1941 : } else {
1942 : *aPtDevPx = nsIntPoint(NSAppUnitsToIntPixels(aPt.x, appUnitsPerDevPixel),
1943 0 : NSAppUnitsToIntPixels(aPt.y, appUnitsPerDevPixel));
1944 : }
1945 : return nsPoint(NSIntPixelsToAppUnits(aPtDevPx->x, appUnitsPerDevPixel),
1946 0 : NSIntPixelsToAppUnits(aPtDevPx->y, appUnitsPerDevPixel));
1947 : }
1948 :
1949 : /* static */ void
1950 0 : nsGfxScrollFrameInner::ScrollActivityCallback(nsITimer *aTimer, void* anInstance)
1951 : {
1952 0 : nsGfxScrollFrameInner* self = static_cast<nsGfxScrollFrameInner*>(anInstance);
1953 :
1954 : // Fire the synth mouse move.
1955 0 : self->mScrollActivityTimer->Cancel();
1956 0 : self->mScrollActivityTimer = nsnull;
1957 0 : self->mOuter->PresContext()->PresShell()->SynthesizeMouseMove(true);
1958 0 : }
1959 :
1960 :
1961 : void
1962 0 : nsGfxScrollFrameInner::ScheduleSyntheticMouseMove()
1963 : {
1964 0 : if (!mScrollActivityTimer) {
1965 0 : mScrollActivityTimer = do_CreateInstance("@mozilla.org/timer;1");
1966 0 : if (!mScrollActivityTimer)
1967 0 : return;
1968 : }
1969 :
1970 0 : mScrollActivityTimer->InitWithFuncCallback(
1971 0 : ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT);
1972 : }
1973 :
1974 : void
1975 0 : nsGfxScrollFrameInner::ScrollToImpl(nsPoint aPt)
1976 : {
1977 0 : nsPresContext* presContext = mOuter->PresContext();
1978 0 : nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
1979 0 : nsIntPoint ptDevPx;
1980 :
1981 0 : nsPoint pt = RestrictToDevPixels(aPt, &ptDevPx, ShouldClampScrollPosition());
1982 :
1983 0 : nsPoint curPos = GetScrollPosition();
1984 0 : if (pt == curPos) {
1985 0 : return;
1986 : }
1987 : nsIntPoint curPosDevPx(NSAppUnitsToIntPixels(curPos.x, appUnitsPerDevPixel),
1988 0 : NSAppUnitsToIntPixels(curPos.y, appUnitsPerDevPixel));
1989 : // We maintain the invariant that the scroll position is a multiple of device
1990 : // pixels.
1991 0 : NS_ASSERTION(curPosDevPx.x*appUnitsPerDevPixel == curPos.x,
1992 : "curPos.x not a multiple of device pixels");
1993 0 : NS_ASSERTION(curPosDevPx.y*appUnitsPerDevPixel == curPos.y,
1994 : "curPos.y not a multiple of device pixels");
1995 :
1996 : // notify the listeners.
1997 0 : for (PRUint32 i = 0; i < mListeners.Length(); i++) {
1998 0 : mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
1999 : }
2000 :
2001 0 : nsPoint oldScrollFramePos = mScrolledFrame->GetPosition();
2002 : // Update frame position for scrolling
2003 0 : mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
2004 :
2005 : // We pass in the amount to move visually
2006 0 : ScrollVisual(oldScrollFramePos);
2007 :
2008 0 : ScheduleSyntheticMouseMove();
2009 0 : UpdateScrollbarPosition();
2010 0 : PostScrollEvent();
2011 :
2012 : // notify the listeners.
2013 0 : for (PRUint32 i = 0; i < mListeners.Length(); i++) {
2014 0 : mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
2015 : }
2016 : }
2017 :
2018 : static void
2019 0 : AppendToTop(nsDisplayListBuilder* aBuilder, nsDisplayList* aDest,
2020 : nsDisplayList* aSource, nsIFrame* aSourceFrame, bool aOwnLayer)
2021 : {
2022 0 : if (aSource->IsEmpty())
2023 0 : return;
2024 0 : if (aOwnLayer) {
2025 : aDest->AppendNewToTop(
2026 0 : new (aBuilder) nsDisplayOwnLayer(aBuilder, aSourceFrame, aSource));
2027 : } else {
2028 0 : aDest->AppendToTop(aSource);
2029 : }
2030 : }
2031 :
2032 : void
2033 0 : nsGfxScrollFrameInner::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder,
2034 : const nsRect& aDirtyRect,
2035 : const nsDisplayListSet& aLists,
2036 : bool& aCreateLayer,
2037 : bool aPositioned)
2038 : {
2039 0 : for (nsIFrame* kid = mOuter->GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) {
2040 0 : if (kid == mScrolledFrame ||
2041 0 : (kid->GetStyleDisplay()->IsPositioned() != aPositioned))
2042 0 : continue;
2043 :
2044 0 : nsDisplayListCollection partList;
2045 : mOuter->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, partList,
2046 0 : nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
2047 :
2048 : // Don't append textarea resizers to the positioned descendants because
2049 : // we don't want them to float on top of overlapping elements.
2050 0 : bool appendToPositioned = aPositioned && !(kid == mResizerBox && !mIsRoot);
2051 :
2052 : nsDisplayList* dest = appendToPositioned ?
2053 0 : aLists.PositionedDescendants() : aLists.BorderBackground();
2054 :
2055 : // DISPLAY_CHILD_FORCE_STACKING_CONTEXT put everything into
2056 : // partList.PositionedDescendants().
2057 : ::AppendToTop(aBuilder, dest,
2058 : partList.PositionedDescendants(), kid,
2059 0 : aCreateLayer);
2060 : }
2061 0 : }
2062 :
2063 : bool
2064 0 : nsGfxScrollFrameInner::ShouldBuildLayer() const
2065 : {
2066 0 : return mShouldBuildLayer;
2067 : }
2068 :
2069 : class ScrollLayerWrapper : public nsDisplayWrapper
2070 : {
2071 : public:
2072 0 : ScrollLayerWrapper(nsIFrame* aScrollFrame, nsIFrame* aScrolledFrame)
2073 : : mCount(0)
2074 : , mProps(aScrolledFrame->Properties())
2075 : , mScrollFrame(aScrollFrame)
2076 0 : , mScrolledFrame(aScrolledFrame)
2077 : {
2078 0 : SetCount(0);
2079 0 : }
2080 :
2081 0 : virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
2082 : nsIFrame* aFrame,
2083 : nsDisplayList* aList) {
2084 0 : SetCount(++mCount);
2085 0 : return new (aBuilder) nsDisplayScrollLayer(aBuilder, aList, mScrolledFrame, mScrolledFrame, mScrollFrame);
2086 : }
2087 :
2088 0 : virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
2089 : nsDisplayItem* aItem) {
2090 :
2091 0 : SetCount(++mCount);
2092 0 : return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->GetUnderlyingFrame(), mScrolledFrame, mScrollFrame);
2093 : }
2094 :
2095 : protected:
2096 0 : void SetCount(PRWord aCount) {
2097 0 : mProps.Set(nsIFrame::ScrollLayerCount(), reinterpret_cast<void*>(aCount));
2098 0 : }
2099 :
2100 : PRWord mCount;
2101 : FrameProperties mProps;
2102 : nsIFrame* mScrollFrame;
2103 : nsIFrame* mScrolledFrame;
2104 : };
2105 :
2106 : nsresult
2107 0 : nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
2108 : const nsRect& aDirtyRect,
2109 : const nsDisplayListSet& aLists)
2110 : {
2111 0 : nsresult rv = mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
2112 0 : NS_ENSURE_SUCCESS(rv, rv);
2113 :
2114 0 : if (aBuilder->IsPaintingToWindow()) {
2115 0 : mScrollPosAtLastPaint = GetScrollPosition();
2116 0 : if (IsScrollingActive() && !CanScrollWithBlitting(mOuter)) {
2117 0 : MarkInactive();
2118 : }
2119 : }
2120 :
2121 0 : if (aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping()) {
2122 : // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
2123 : // The scrolled frame shouldn't have its own background/border, so we
2124 : // can just pass aLists directly.
2125 : return mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame,
2126 0 : aDirtyRect, aLists);
2127 : }
2128 :
2129 : // We put scrollbars in their own layers when this is the root scroll
2130 : // frame and we are a toplevel content document. In this situation, the
2131 : // scrollbar(s) would normally be assigned their own layer anyway, since
2132 : // they're not scrolled with the rest of the document. But when both
2133 : // scrollbars are visible, the layer's visible rectangle would be the size
2134 : // of the viewport, so most layer implementations would create a layer buffer
2135 : // that's much larger than necessary. Creating independent layers for each
2136 : // scrollbar works around the problem.
2137 : bool createLayersForScrollbars = mIsRoot &&
2138 0 : mOuter->PresContext()->IsRootContentDocument();
2139 :
2140 : // Now display the scrollbars and scrollcorner. These parts are drawn
2141 : // in the border-background layer, on top of our own background and
2142 : // borders and underneath borders and backgrounds of later elements
2143 : // in the tree.
2144 : // Note that this does not apply for overlay scrollbars; those are drawn
2145 : // in the positioned-elements layer on top of everything else by the call
2146 : // to AppendScrollPartsTo(..., true) further down.
2147 : AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars,
2148 0 : false);
2149 :
2150 : // Overflow clipping can never clip frames outside our subtree, so there
2151 : // is no need to worry about whether we are a moving frame that might clip
2152 : // non-moving frames.
2153 0 : nsRect dirtyRect;
2154 : // Not all our descendants will be clipped by overflow clipping, but all
2155 : // the ones that aren't clipped will be out of flow frames that have already
2156 : // had dirty rects saved for them by their parent frames calling
2157 : // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
2158 : // dirty rect here.
2159 0 : dirtyRect.IntersectRect(aDirtyRect, mScrollPort);
2160 :
2161 : // Override the dirty rectangle if the displayport has been set.
2162 : bool usingDisplayport =
2163 0 : nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &dirtyRect);
2164 :
2165 0 : nsDisplayListCollection set;
2166 0 : rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
2167 0 : NS_ENSURE_SUCCESS(rv, rv);
2168 :
2169 : // Since making new layers is expensive, only use nsDisplayScrollLayer
2170 : // if the area is scrollable.
2171 0 : nsRect scrollRange = GetScrollRange();
2172 0 : ScrollbarStyles styles = GetScrollbarStylesFromFrame();
2173 : mShouldBuildLayer =
2174 0 : (XRE_GetProcessType() == GeckoProcessType_Content &&
2175 : (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN ||
2176 : styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) &&
2177 : (scrollRange.width > 0 ||
2178 : scrollRange.height > 0) &&
2179 0 : (!mIsRoot || !mOuter->PresContext()->IsRootContentDocument()));
2180 :
2181 0 : if (ShouldBuildLayer()) {
2182 : // ScrollLayerWrapper must always be created because it initializes the
2183 : // scroll layer count. The display lists depend on this.
2184 0 : ScrollLayerWrapper wrapper(mOuter, mScrolledFrame);
2185 :
2186 0 : if (usingDisplayport) {
2187 : // Once a displayport is set, assume that scrolling needs to be fast
2188 : // so create a layer with all the content inside. The compositor
2189 : // process will be able to scroll the content asynchronously.
2190 0 : wrapper.WrapListsInPlace(aBuilder, mOuter, set);
2191 : }
2192 :
2193 : // In case we are not using displayport or the nsDisplayScrollLayers are
2194 : // flattened during visibility computation, we still need to export the
2195 : // metadata about this scroll box to the compositor process.
2196 : nsDisplayScrollInfoLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer(
2197 0 : aBuilder, mScrolledFrame, mOuter);
2198 0 : set.BorderBackground()->AppendNewToBottom(layerItem);
2199 : }
2200 :
2201 0 : nsRect clip;
2202 0 : clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
2203 :
2204 : nscoord radii[8];
2205 : // Our override of GetBorderRadii ensures we never have a radius at
2206 : // the corners where we have a scrollbar.
2207 0 : mOuter->GetPaddingBoxBorderRadii(radii);
2208 :
2209 : // mScrolledFrame may have given us a background, e.g., the scrolled canvas
2210 : // frame below the viewport. If so, we want it to be clipped. We also want
2211 : // to end up on our BorderBackground list.
2212 : // If we are the viewport scrollframe, then clip all our descendants (to ensure
2213 : // that fixed-pos elements get clipped by us).
2214 : rv = mOuter->OverflowClip(aBuilder, set, aLists, clip, radii,
2215 0 : true, mIsRoot);
2216 0 : NS_ENSURE_SUCCESS(rv, rv);
2217 :
2218 : // Now display overlay scrollbars and the resizer, if we have one.
2219 : AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars,
2220 0 : true);
2221 :
2222 0 : return NS_OK;
2223 : }
2224 :
2225 0 : static void HandleScrollPref(nsIScrollable *aScrollable, PRInt32 aOrientation,
2226 : PRUint8& aValue)
2227 : {
2228 : PRInt32 pref;
2229 0 : aScrollable->GetDefaultScrollbarPreferences(aOrientation, &pref);
2230 0 : switch (pref) {
2231 : case nsIScrollable::Scrollbar_Auto:
2232 : // leave |aValue| untouched
2233 0 : break;
2234 : case nsIScrollable::Scrollbar_Never:
2235 0 : aValue = NS_STYLE_OVERFLOW_HIDDEN;
2236 0 : break;
2237 : case nsIScrollable::Scrollbar_Always:
2238 0 : aValue = NS_STYLE_OVERFLOW_SCROLL;
2239 0 : break;
2240 : }
2241 0 : }
2242 :
2243 : nsGfxScrollFrameInner::ScrollbarStyles
2244 0 : nsGfxScrollFrameInner::GetScrollbarStylesFromFrame() const
2245 : {
2246 0 : ScrollbarStyles result;
2247 :
2248 0 : nsPresContext* presContext = mOuter->PresContext();
2249 0 : if (!presContext->IsDynamic() &&
2250 0 : !(mIsRoot && presContext->HasPaginatedScrolling())) {
2251 0 : return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN);
2252 : }
2253 :
2254 0 : if (mIsRoot) {
2255 0 : result = presContext->GetViewportOverflowOverride();
2256 :
2257 0 : nsCOMPtr<nsISupports> container = presContext->GetContainer();
2258 0 : nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(container);
2259 0 : if (scrollable) {
2260 : HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_X,
2261 0 : result.mHorizontal);
2262 : HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_Y,
2263 0 : result.mVertical);
2264 : // XXX EVIL COMPILER BUG BE CAREFUL WHEN CHANGING
2265 : // There is a bug in the Android compiler :(
2266 : // It seems that the compiler optimizes something out and uses
2267 : // a bad value for result if we don't directly return here.
2268 0 : return result;
2269 : }
2270 : } else {
2271 0 : const nsStyleDisplay *disp = mOuter->GetStyleDisplay();
2272 0 : result.mHorizontal = disp->mOverflowX;
2273 0 : result.mVertical = disp->mOverflowY;
2274 : }
2275 :
2276 0 : return result;
2277 : }
2278 :
2279 : static nscoord
2280 0 : AlignToDevPixelRoundingToZero(nscoord aVal, PRInt32 aAppUnitsPerDevPixel)
2281 : {
2282 0 : return (aVal/aAppUnitsPerDevPixel)*aAppUnitsPerDevPixel;
2283 : }
2284 :
2285 : nsRect
2286 0 : nsGfxScrollFrameInner::GetScrollRange() const
2287 : {
2288 0 : nsRect range = GetScrolledRect();
2289 0 : range.width -= mScrollPort.width;
2290 0 : range.height -= mScrollPort.height;
2291 :
2292 0 : nsPresContext* presContext = mOuter->PresContext();
2293 0 : PRInt32 appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
2294 : range.width =
2295 0 : AlignToDevPixelRoundingToZero(range.XMost(), appUnitsPerDevPixel) - range.x;
2296 : range.height =
2297 0 : AlignToDevPixelRoundingToZero(range.YMost(), appUnitsPerDevPixel) - range.y;
2298 0 : range.x = AlignToDevPixelRoundingToZero(range.x, appUnitsPerDevPixel);
2299 0 : range.y = AlignToDevPixelRoundingToZero(range.y, appUnitsPerDevPixel);
2300 : return range;
2301 : }
2302 :
2303 : static void
2304 0 : AdjustForWholeDelta(PRInt32 aDelta, nscoord* aCoord)
2305 : {
2306 0 : if (aDelta < 0) {
2307 0 : *aCoord = nscoord_MIN;
2308 0 : } else if (aDelta > 0) {
2309 0 : *aCoord = nscoord_MAX;
2310 : }
2311 0 : }
2312 :
2313 : void
2314 0 : nsGfxScrollFrameInner::ScrollBy(nsIntPoint aDelta,
2315 : nsIScrollableFrame::ScrollUnit aUnit,
2316 : nsIScrollableFrame::ScrollMode aMode,
2317 : nsIntPoint* aOverflow,
2318 : nsIAtom *aOrigin)
2319 : {
2320 0 : nsSize deltaMultiplier;
2321 0 : if (!aOrigin){
2322 0 : aOrigin = nsGkAtoms::other;
2323 : }
2324 0 : bool isGenericOrigin = (aOrigin == nsGkAtoms::other);
2325 0 : switch (aUnit) {
2326 : case nsIScrollableFrame::DEVICE_PIXELS: {
2327 : nscoord appUnitsPerDevPixel =
2328 0 : mOuter->PresContext()->AppUnitsPerDevPixel();
2329 0 : deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
2330 0 : if (isGenericOrigin){
2331 0 : aOrigin = nsGkAtoms::pixels;
2332 : }
2333 0 : break;
2334 : }
2335 : case nsIScrollableFrame::LINES: {
2336 0 : deltaMultiplier = GetLineScrollAmount();
2337 0 : if (isGenericOrigin){
2338 0 : aOrigin = nsGkAtoms::lines;
2339 : }
2340 0 : break;
2341 : }
2342 : case nsIScrollableFrame::PAGES: {
2343 0 : deltaMultiplier = GetPageScrollAmount();
2344 0 : if (isGenericOrigin){
2345 0 : aOrigin = nsGkAtoms::pages;
2346 : }
2347 0 : break;
2348 : }
2349 : case nsIScrollableFrame::WHOLE: {
2350 0 : nsPoint pos = GetScrollPosition();
2351 0 : AdjustForWholeDelta(aDelta.x, &pos.x);
2352 0 : AdjustForWholeDelta(aDelta.y, &pos.y);
2353 0 : ScrollTo(pos, aMode);
2354 0 : if (aOverflow) {
2355 0 : *aOverflow = nsIntPoint(0, 0);
2356 : }
2357 0 : return;
2358 : }
2359 : default:
2360 0 : NS_ERROR("Invalid scroll mode");
2361 0 : return;
2362 : }
2363 :
2364 : nsPoint newPos = mDestination +
2365 0 : nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height);
2366 0 : ScrollToWithOrigin(newPos, aMode, aOrigin);
2367 :
2368 0 : if (aOverflow) {
2369 0 : nsPoint clampAmount = mDestination - newPos;
2370 0 : float appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
2371 : *aOverflow = nsIntPoint(
2372 : NSAppUnitsToIntPixels(NS_ABS(clampAmount.x), appUnitsPerDevPixel),
2373 0 : NSAppUnitsToIntPixels(NS_ABS(clampAmount.y), appUnitsPerDevPixel));
2374 : }
2375 : }
2376 :
2377 : nsSize
2378 0 : nsGfxScrollFrameInner::GetLineScrollAmount() const
2379 : {
2380 0 : nsRefPtr<nsFontMetrics> fm;
2381 : nsLayoutUtils::GetFontMetricsForFrame(mOuter, getter_AddRefs(fm),
2382 0 : nsLayoutUtils::FontSizeInflationFor(mOuter, nsLayoutUtils::eNotInReflow));
2383 0 : NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
2384 0 : nscoord fontHeight = 1;
2385 0 : if (fm) {
2386 0 : fontHeight = fm->MaxHeight();
2387 : }
2388 :
2389 0 : return nsSize(fontHeight, fontHeight);
2390 : }
2391 :
2392 : nsSize
2393 0 : nsGfxScrollFrameInner::GetPageScrollAmount() const
2394 : {
2395 0 : nsSize lineScrollAmount = GetLineScrollAmount();
2396 : // The page increment is the size of the page, minus the smaller of
2397 : // 10% of the size or 2 lines.
2398 : return nsSize(
2399 0 : mScrollPort.width - NS_MIN(mScrollPort.width/10, 2*lineScrollAmount.width),
2400 0 : mScrollPort.height - NS_MIN(mScrollPort.height/10, 2*lineScrollAmount.height));
2401 : }
2402 :
2403 : /**
2404 : * this code is resposible for restoring the scroll position back to some
2405 : * saved position. if the user has not moved the scroll position manually
2406 : * we keep scrolling down until we get to our original position. keep in
2407 : * mind that content could incrementally be coming in. we only want to stop
2408 : * when we reach our new position.
2409 : */
2410 : void
2411 0 : nsGfxScrollFrameInner::ScrollToRestoredPosition()
2412 : {
2413 0 : if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
2414 0 : return;
2415 : }
2416 : // make sure our scroll position did not change for where we last put
2417 : // it. if it does then the user must have moved it, and we no longer
2418 : // need to restore.
2419 : //
2420 : // In the RTL case, we check whether the scroll position changed using the
2421 : // logical scroll position, but we scroll to the physical scroll position in
2422 : // all cases
2423 :
2424 : // if we didn't move, we still need to restore
2425 0 : if (GetLogicalScrollPosition() == mLastPos) {
2426 : // if our desired position is different to the scroll position, scroll.
2427 : // remember that we could be incrementally loading so we may enter
2428 : // and scroll many times.
2429 0 : if (mRestorePos != mLastPos /* GetLogicalScrollPosition() */) {
2430 0 : nsPoint scrollToPos = mRestorePos;
2431 0 : if (!IsLTR())
2432 : // convert from logical to physical scroll position
2433 : scrollToPos.x = mScrollPort.x -
2434 0 : (mScrollPort.XMost() - scrollToPos.x - mScrolledFrame->GetRect().width);
2435 0 : ScrollTo(scrollToPos, nsIScrollableFrame::INSTANT);
2436 : // Re-get the scroll position, it might not be exactly equal to
2437 : // mRestorePos due to rounding and clamping.
2438 0 : mLastPos = GetLogicalScrollPosition();
2439 : } else {
2440 : // if we reached the position then stop
2441 0 : mRestorePos.y = -1;
2442 0 : mLastPos.x = -1;
2443 0 : mLastPos.y = -1;
2444 : }
2445 : } else {
2446 : // user moved the position, so we won't need to restore
2447 0 : mLastPos.x = -1;
2448 0 : mLastPos.y = -1;
2449 : }
2450 : }
2451 :
2452 : nsresult
2453 0 : nsGfxScrollFrameInner::FireScrollPortEvent()
2454 : {
2455 0 : mAsyncScrollPortEvent.Forget();
2456 :
2457 : // Keep this in sync with PostOverflowEvent().
2458 0 : nsSize scrollportSize = mScrollPort.Size();
2459 0 : nsSize childSize = GetScrolledRect().Size();
2460 :
2461 0 : bool newVerticalOverflow = childSize.height > scrollportSize.height;
2462 0 : bool vertChanged = mVerticalOverflow != newVerticalOverflow;
2463 :
2464 0 : bool newHorizontalOverflow = childSize.width > scrollportSize.width;
2465 0 : bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
2466 :
2467 0 : if (!vertChanged && !horizChanged) {
2468 0 : return NS_OK;
2469 : }
2470 :
2471 : // If both either overflowed or underflowed then we dispatch only one
2472 : // DOM event.
2473 : bool both = vertChanged && horizChanged &&
2474 0 : newVerticalOverflow == newHorizontalOverflow;
2475 : nsScrollPortEvent::orientType orient;
2476 0 : if (both) {
2477 0 : orient = nsScrollPortEvent::both;
2478 0 : mHorizontalOverflow = newHorizontalOverflow;
2479 0 : mVerticalOverflow = newVerticalOverflow;
2480 : }
2481 0 : else if (vertChanged) {
2482 0 : orient = nsScrollPortEvent::vertical;
2483 0 : mVerticalOverflow = newVerticalOverflow;
2484 0 : if (horizChanged) {
2485 : // We need to dispatch a separate horizontal DOM event. Do that the next
2486 : // time around since dispatching the vertical DOM event might destroy
2487 : // the frame.
2488 0 : PostOverflowEvent();
2489 : }
2490 : }
2491 : else {
2492 0 : orient = nsScrollPortEvent::horizontal;
2493 0 : mHorizontalOverflow = newHorizontalOverflow;
2494 : }
2495 :
2496 : nsScrollPortEvent event(true,
2497 : (orient == nsScrollPortEvent::horizontal ?
2498 : mHorizontalOverflow : mVerticalOverflow) ?
2499 : NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW,
2500 0 : nsnull);
2501 0 : event.orient = orient;
2502 0 : return nsEventDispatcher::Dispatch(mOuter->GetContent(),
2503 0 : mOuter->PresContext(), &event);
2504 : }
2505 :
2506 : void
2507 0 : nsGfxScrollFrameInner::ReloadChildFrames()
2508 : {
2509 0 : mScrolledFrame = nsnull;
2510 0 : mHScrollbarBox = nsnull;
2511 0 : mVScrollbarBox = nsnull;
2512 0 : mScrollCornerBox = nsnull;
2513 0 : mResizerBox = nsnull;
2514 :
2515 0 : nsIFrame* frame = mOuter->GetFirstPrincipalChild();
2516 0 : while (frame) {
2517 0 : nsIContent* content = frame->GetContent();
2518 0 : if (content == mOuter->GetContent()) {
2519 0 : NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
2520 0 : mScrolledFrame = frame;
2521 : } else {
2522 0 : nsAutoString value;
2523 0 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, value);
2524 0 : if (!value.IsEmpty()) {
2525 : // probably a scrollbar then
2526 0 : if (value.LowerCaseEqualsLiteral("horizontal")) {
2527 0 : NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?");
2528 0 : mHScrollbarBox = frame;
2529 : } else {
2530 0 : NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
2531 0 : mVScrollbarBox = frame;
2532 : }
2533 0 : } else if (content->Tag() == nsGkAtoms::resizer) {
2534 0 : NS_ASSERTION(!mResizerBox, "Found multiple resizers");
2535 0 : mResizerBox = frame;
2536 : } else {
2537 : // probably a scrollcorner
2538 0 : NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
2539 0 : mScrollCornerBox = frame;
2540 : }
2541 : }
2542 :
2543 0 : frame = frame->GetNextSibling();
2544 : }
2545 0 : }
2546 :
2547 : nsresult
2548 0 : nsGfxScrollFrameInner::CreateAnonymousContent(
2549 : nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements)
2550 : {
2551 0 : nsPresContext* presContext = mOuter->PresContext();
2552 0 : nsIFrame* parent = mOuter->GetParent();
2553 :
2554 : // Don't create scrollbars if we're an SVG document being used as an image,
2555 : // or if we're printing/print previewing.
2556 : // (In the printing case, we allow scrollbars if this is the child of the
2557 : // viewport & paginated scrolling is enabled, because then we must be the
2558 : // scroll frame for the print preview window, & that does need scrollbars.)
2559 0 : if (presContext->Document()->IsBeingUsedAsImage() ||
2560 0 : (!presContext->IsDynamic() &&
2561 0 : !(mIsRoot && presContext->HasPaginatedScrolling()))) {
2562 0 : mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
2563 0 : return NS_OK;
2564 : }
2565 :
2566 : // Check if the frame is resizable.
2567 0 : PRInt8 resizeStyle = mOuter->GetStyleDisplay()->mResize;
2568 0 : bool isResizable = resizeStyle != NS_STYLE_RESIZE_NONE;
2569 :
2570 0 : nsIScrollableFrame *scrollable = do_QueryFrame(mOuter);
2571 :
2572 : // If we're the scrollframe for the root, then we want to construct
2573 : // our scrollbar frames no matter what. That way later dynamic
2574 : // changes to propagated overflow styles will show or hide
2575 : // scrollbars on the viewport without requiring frame reconstruction
2576 : // of the viewport (good!).
2577 : bool canHaveHorizontal;
2578 : bool canHaveVertical;
2579 0 : if (!mIsRoot) {
2580 0 : ScrollbarStyles styles = scrollable->GetScrollbarStyles();
2581 0 : canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
2582 0 : canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
2583 0 : if (!canHaveHorizontal && !canHaveVertical && !isResizable) {
2584 : // Nothing to do.
2585 0 : return NS_OK;
2586 : }
2587 : } else {
2588 0 : canHaveHorizontal = true;
2589 0 : canHaveVertical = true;
2590 : }
2591 :
2592 : // The anonymous <div> used by <inputs> never gets scrollbars.
2593 0 : nsITextControlFrame* textFrame = do_QueryFrame(parent);
2594 0 : if (textFrame) {
2595 : // Make sure we are not a text area.
2596 0 : nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement(do_QueryInterface(parent->GetContent()));
2597 0 : if (!textAreaElement) {
2598 0 : mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
2599 0 : return NS_OK;
2600 : }
2601 : }
2602 :
2603 : nsNodeInfoManager *nodeInfoManager =
2604 0 : presContext->Document()->NodeInfoManager();
2605 0 : nsCOMPtr<nsINodeInfo> nodeInfo;
2606 : nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbar, nsnull,
2607 : kNameSpaceID_XUL,
2608 0 : nsIDOMNode::ELEMENT_NODE);
2609 0 : NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
2610 :
2611 0 : if (canHaveHorizontal) {
2612 0 : nsCOMPtr<nsINodeInfo> ni = nodeInfo;
2613 0 : NS_TrustedNewXULElement(getter_AddRefs(mHScrollbarContent), ni.forget());
2614 : mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
2615 0 : NS_LITERAL_STRING("horizontal"), false);
2616 : mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
2617 0 : NS_LITERAL_STRING("always"), false);
2618 0 : if (!aElements.AppendElement(mHScrollbarContent))
2619 0 : return NS_ERROR_OUT_OF_MEMORY;
2620 : }
2621 :
2622 0 : if (canHaveVertical) {
2623 0 : nsCOMPtr<nsINodeInfo> ni = nodeInfo;
2624 0 : NS_TrustedNewXULElement(getter_AddRefs(mVScrollbarContent), ni.forget());
2625 : mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
2626 0 : NS_LITERAL_STRING("vertical"), false);
2627 : mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
2628 0 : NS_LITERAL_STRING("always"), false);
2629 0 : if (!aElements.AppendElement(mVScrollbarContent))
2630 0 : return NS_ERROR_OUT_OF_MEMORY;
2631 : }
2632 :
2633 0 : if (isResizable) {
2634 0 : nsCOMPtr<nsINodeInfo> nodeInfo;
2635 : nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nsnull,
2636 : kNameSpaceID_XUL,
2637 0 : nsIDOMNode::ELEMENT_NODE);
2638 0 : NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
2639 :
2640 0 : NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget());
2641 :
2642 0 : nsAutoString dir;
2643 0 : switch (resizeStyle) {
2644 : case NS_STYLE_RESIZE_HORIZONTAL:
2645 0 : if (IsScrollbarOnRight()) {
2646 0 : dir.AssignLiteral("right");
2647 : }
2648 : else {
2649 0 : dir.AssignLiteral("left");
2650 : }
2651 0 : break;
2652 : case NS_STYLE_RESIZE_VERTICAL:
2653 0 : dir.AssignLiteral("bottom");
2654 0 : break;
2655 : case NS_STYLE_RESIZE_BOTH:
2656 0 : dir.AssignLiteral("bottomend");
2657 0 : break;
2658 : default:
2659 0 : NS_WARNING("only resizable types should have resizers");
2660 : }
2661 0 : mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false);
2662 :
2663 0 : if (mIsRoot) {
2664 0 : nsIContent* browserRoot = GetBrowserRoot(mOuter->GetContent());
2665 : mCollapsedResizer = !(browserRoot &&
2666 0 : browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer));
2667 : }
2668 : else {
2669 : mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element,
2670 0 : NS_LITERAL_STRING("_parent"), false);
2671 : }
2672 :
2673 : mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
2674 0 : NS_LITERAL_STRING("always"), false);
2675 :
2676 0 : if (!aElements.AppendElement(mResizerContent))
2677 0 : return NS_ERROR_OUT_OF_MEMORY;
2678 : }
2679 :
2680 0 : if (canHaveHorizontal && canHaveVertical) {
2681 : nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nsnull,
2682 : kNameSpaceID_XUL,
2683 0 : nsIDOMNode::ELEMENT_NODE);
2684 0 : NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent), nodeInfo.forget());
2685 0 : if (!aElements.AppendElement(mScrollCornerContent))
2686 0 : return NS_ERROR_OUT_OF_MEMORY;
2687 : }
2688 :
2689 0 : return NS_OK;
2690 : }
2691 :
2692 : void
2693 0 : nsGfxScrollFrameInner::AppendAnonymousContentTo(nsBaseContentList& aElements,
2694 : PRUint32 aFilter)
2695 : {
2696 0 : aElements.MaybeAppendElement(mHScrollbarContent);
2697 0 : aElements.MaybeAppendElement(mVScrollbarContent);
2698 0 : aElements.MaybeAppendElement(mScrollCornerContent);
2699 0 : aElements.MaybeAppendElement(mResizerContent);
2700 0 : }
2701 :
2702 : void
2703 0 : nsGfxScrollFrameInner::Destroy()
2704 : {
2705 : // Unbind any content created in CreateAnonymousContent from the tree
2706 0 : nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent);
2707 0 : nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent);
2708 0 : nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent);
2709 0 : nsContentUtils::DestroyAnonymousContent(&mResizerContent);
2710 :
2711 0 : if (mPostedReflowCallback) {
2712 0 : mOuter->PresContext()->PresShell()->CancelReflowCallback(this);
2713 0 : mPostedReflowCallback = false;
2714 : }
2715 0 : }
2716 :
2717 : /**
2718 : * Called when we want to update the scrollbar position, either because scrolling happened
2719 : * or the user moved the scrollbar position and we need to undo that (e.g., when the user
2720 : * clicks to scroll and we're using smooth scrolling, so we need to put the thumb back
2721 : * to its initial position for the start of the smooth sequence).
2722 : */
2723 : void
2724 0 : nsGfxScrollFrameInner::UpdateScrollbarPosition()
2725 : {
2726 0 : mFrameIsUpdatingScrollbar = true;
2727 :
2728 0 : nsPoint pt = GetScrollPosition();
2729 0 : if (mVScrollbarBox) {
2730 : SetCoordAttribute(mVScrollbarBox->GetContent(), nsGkAtoms::curpos,
2731 0 : pt.y - GetScrolledRect().y);
2732 : }
2733 0 : if (mHScrollbarBox) {
2734 : SetCoordAttribute(mHScrollbarBox->GetContent(), nsGkAtoms::curpos,
2735 0 : pt.x - GetScrolledRect().x);
2736 : }
2737 :
2738 0 : mFrameIsUpdatingScrollbar = false;
2739 0 : }
2740 :
2741 0 : void nsGfxScrollFrameInner::CurPosAttributeChanged(nsIContent* aContent)
2742 : {
2743 0 : NS_ASSERTION(aContent, "aContent must not be null");
2744 0 : NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
2745 : (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
2746 : "unexpected child");
2747 :
2748 : // Attribute changes on the scrollbars happen in one of three ways:
2749 : // 1) The scrollbar changed the attribute in response to some user event
2750 : // 2) We changed the attribute in response to a ScrollPositionDidChange
2751 : // callback from the scrolling view
2752 : // 3) We changed the attribute to adjust the scrollbars for the start
2753 : // of a smooth scroll operation
2754 : //
2755 : // In cases 2 and 3 we do not need to scroll because we're just
2756 : // updating our scrollbar.
2757 0 : if (mFrameIsUpdatingScrollbar)
2758 0 : return;
2759 :
2760 0 : nsRect scrolledRect = GetScrolledRect();
2761 :
2762 0 : nsPoint current = GetScrollPosition() - scrolledRect.TopLeft();
2763 0 : nsPoint dest;
2764 0 : dest.x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos, current.x) +
2765 0 : scrolledRect.x;
2766 0 : dest.y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos, current.y) +
2767 0 : scrolledRect.y;
2768 :
2769 : // If we have an async scroll pending don't stomp on that by calling ScrollTo.
2770 0 : if (mAsyncScroll && dest == GetScrollPosition()) {
2771 : return;
2772 : }
2773 :
2774 0 : bool isSmooth = aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth);
2775 0 : if (isSmooth) {
2776 : // Make sure an attribute-setting callback occurs even if the view
2777 : // didn't actually move yet. We need to make sure other listeners
2778 : // see that the scroll position is not (yet) what they thought it
2779 : // was.
2780 0 : UpdateScrollbarPosition();
2781 : }
2782 : ScrollToWithOrigin(dest,
2783 : isSmooth ? nsIScrollableFrame::SMOOTH : nsIScrollableFrame::INSTANT,
2784 0 : nsGkAtoms::scrollbars);
2785 : }
2786 :
2787 : /* ============= Scroll events ========== */
2788 :
2789 : NS_IMETHODIMP
2790 0 : nsGfxScrollFrameInner::ScrollEvent::Run()
2791 : {
2792 0 : if (mInner)
2793 0 : mInner->FireScrollEvent();
2794 0 : return NS_OK;
2795 : }
2796 :
2797 : void
2798 0 : nsGfxScrollFrameInner::FireScrollEvent()
2799 : {
2800 0 : mScrollEvent.Forget();
2801 :
2802 0 : nsScrollbarEvent event(true, NS_SCROLL_EVENT, nsnull);
2803 0 : nsEventStatus status = nsEventStatus_eIgnore;
2804 0 : nsIContent* content = mOuter->GetContent();
2805 0 : nsPresContext* prescontext = mOuter->PresContext();
2806 : // Fire viewport scroll events at the document (where they
2807 : // will bubble to the window)
2808 0 : if (mIsRoot) {
2809 0 : nsIDocument* doc = content->GetCurrentDoc();
2810 0 : if (doc) {
2811 0 : nsEventDispatcher::Dispatch(doc, prescontext, &event, nsnull, &status);
2812 : }
2813 : } else {
2814 : // scroll events fired at elements don't bubble (although scroll events
2815 : // fired at documents do, to the window)
2816 0 : event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
2817 0 : nsEventDispatcher::Dispatch(content, prescontext, &event, nsnull, &status);
2818 : }
2819 0 : }
2820 :
2821 : void
2822 0 : nsGfxScrollFrameInner::PostScrollEvent()
2823 : {
2824 0 : if (mScrollEvent.IsPending())
2825 0 : return;
2826 :
2827 0 : nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext();
2828 0 : if (!rpc)
2829 0 : return;
2830 0 : mScrollEvent = new ScrollEvent(this);
2831 0 : rpc->AddWillPaintObserver(mScrollEvent.get());
2832 : }
2833 :
2834 : NS_IMETHODIMP
2835 0 : nsGfxScrollFrameInner::AsyncScrollPortEvent::Run()
2836 : {
2837 0 : if (mInner) {
2838 0 : mInner->mOuter->PresContext()->GetPresShell()->
2839 0 : FlushPendingNotifications(Flush_InterruptibleLayout);
2840 : }
2841 0 : return mInner ? mInner->FireScrollPortEvent() : NS_OK;
2842 : }
2843 :
2844 : bool
2845 0 : nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom)
2846 : {
2847 0 : if (!mInner.mHScrollbarBox)
2848 0 : return true;
2849 :
2850 0 : return AddRemoveScrollbar(aState, aOnBottom, true, true);
2851 : }
2852 :
2853 : bool
2854 0 : nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight)
2855 : {
2856 0 : if (!mInner.mVScrollbarBox)
2857 0 : return true;
2858 :
2859 0 : return AddRemoveScrollbar(aState, aOnRight, false, true);
2860 : }
2861 :
2862 : void
2863 0 : nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom)
2864 : {
2865 : // removing a scrollbar should always fit
2866 : #ifdef DEBUG
2867 : bool result =
2868 : #endif
2869 0 : AddRemoveScrollbar(aState, aOnBottom, true, false);
2870 0 : NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??");
2871 0 : }
2872 :
2873 : void
2874 0 : nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight)
2875 : {
2876 : // removing a scrollbar should always fit
2877 : #ifdef DEBUG
2878 : bool result =
2879 : #endif
2880 0 : AddRemoveScrollbar(aState, aOnRight, false, false);
2881 0 : NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??");
2882 0 : }
2883 :
2884 : bool
2885 0 : nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState,
2886 : bool aOnRightOrBottom, bool aHorizontal, bool aAdd)
2887 : {
2888 0 : if (aHorizontal) {
2889 0 : if (mInner.mNeverHasHorizontalScrollbar || !mInner.mHScrollbarBox)
2890 0 : return false;
2891 :
2892 0 : nsSize hSize = mInner.mHScrollbarBox->GetPrefSize(aState);
2893 0 : nsBox::AddMargin(mInner.mHScrollbarBox, hSize);
2894 :
2895 0 : mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, aAdd);
2896 :
2897 : bool hasHorizontalScrollbar;
2898 : bool fit = AddRemoveScrollbar(hasHorizontalScrollbar,
2899 : mInner.mScrollPort.y,
2900 : mInner.mScrollPort.height,
2901 0 : hSize.height, aOnRightOrBottom, aAdd);
2902 0 : mInner.mHasHorizontalScrollbar = hasHorizontalScrollbar; // because mHasHorizontalScrollbar is a bool
2903 0 : if (!fit)
2904 0 : mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, !aAdd);
2905 :
2906 0 : return fit;
2907 : } else {
2908 0 : if (mInner.mNeverHasVerticalScrollbar || !mInner.mVScrollbarBox)
2909 0 : return false;
2910 :
2911 0 : nsSize vSize = mInner.mVScrollbarBox->GetPrefSize(aState);
2912 0 : nsBox::AddMargin(mInner.mVScrollbarBox, vSize);
2913 :
2914 0 : mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, aAdd);
2915 :
2916 : bool hasVerticalScrollbar;
2917 : bool fit = AddRemoveScrollbar(hasVerticalScrollbar,
2918 : mInner.mScrollPort.x,
2919 : mInner.mScrollPort.width,
2920 0 : vSize.width, aOnRightOrBottom, aAdd);
2921 0 : mInner.mHasVerticalScrollbar = hasVerticalScrollbar; // because mHasVerticalScrollbar is a bool
2922 0 : if (!fit)
2923 0 : mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, !aAdd);
2924 :
2925 0 : return fit;
2926 : }
2927 : }
2928 :
2929 : bool
2930 0 : nsXULScrollFrame::AddRemoveScrollbar(bool& aHasScrollbar, nscoord& aXY,
2931 : nscoord& aSize, nscoord aSbSize,
2932 : bool aOnRightOrBottom, bool aAdd)
2933 : {
2934 0 : nscoord size = aSize;
2935 0 : nscoord xy = aXY;
2936 :
2937 0 : if (size != NS_INTRINSICSIZE) {
2938 0 : if (aAdd) {
2939 0 : size -= aSbSize;
2940 0 : if (!aOnRightOrBottom && size >= 0)
2941 0 : xy += aSbSize;
2942 : } else {
2943 0 : size += aSbSize;
2944 0 : if (!aOnRightOrBottom)
2945 0 : xy -= aSbSize;
2946 : }
2947 : }
2948 :
2949 : // not enough room? Yes? Return true.
2950 0 : if (size >= 0) {
2951 0 : aHasScrollbar = aAdd;
2952 0 : aSize = size;
2953 0 : aXY = xy;
2954 0 : return true;
2955 : }
2956 :
2957 0 : aHasScrollbar = false;
2958 0 : return false;
2959 : }
2960 :
2961 : void
2962 0 : nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState,
2963 : const nsPoint& aScrollPosition)
2964 : {
2965 0 : PRUint32 oldflags = aState.LayoutFlags();
2966 : nsRect childRect = nsRect(mInner.mScrollPort.TopLeft() - aScrollPosition,
2967 0 : mInner.mScrollPort.Size());
2968 0 : PRInt32 flags = NS_FRAME_NO_MOVE_VIEW;
2969 :
2970 0 : nsRect originalRect = mInner.mScrolledFrame->GetRect();
2971 0 : nsRect originalVisOverflow = mInner.mScrolledFrame->GetVisualOverflowRect();
2972 :
2973 0 : nsSize minSize = mInner.mScrolledFrame->GetMinSize(aState);
2974 :
2975 0 : if (minSize.height > childRect.height)
2976 0 : childRect.height = minSize.height;
2977 :
2978 0 : if (minSize.width > childRect.width)
2979 0 : childRect.width = minSize.width;
2980 :
2981 0 : aState.SetLayoutFlags(flags);
2982 0 : ClampAndSetBounds(aState, childRect, aScrollPosition);
2983 0 : mInner.mScrolledFrame->Layout(aState);
2984 :
2985 0 : childRect = mInner.mScrolledFrame->GetRect();
2986 :
2987 0 : if (childRect.width < mInner.mScrollPort.width ||
2988 : childRect.height < mInner.mScrollPort.height)
2989 : {
2990 0 : childRect.width = NS_MAX(childRect.width, mInner.mScrollPort.width);
2991 0 : childRect.height = NS_MAX(childRect.height, mInner.mScrollPort.height);
2992 :
2993 : // remove overflow areas when we update the bounds,
2994 : // because we've already accounted for it
2995 : // REVIEW: Have we accounted for both?
2996 0 : ClampAndSetBounds(aState, childRect, aScrollPosition, true);
2997 : }
2998 :
2999 0 : nsRect finalRect = mInner.mScrolledFrame->GetRect();
3000 0 : nsRect finalVisOverflow = mInner.mScrolledFrame->GetVisualOverflowRect();
3001 : // The position of the scrolled frame shouldn't change, but if it does or
3002 : // the position of the overflow rect changes just invalidate both the old
3003 : // and new overflow rect.
3004 0 : if (originalRect.TopLeft() != finalRect.TopLeft() ||
3005 0 : originalVisOverflow.TopLeft() != finalVisOverflow.TopLeft())
3006 : {
3007 : // The old overflow rect needs to be adjusted if the frame's position
3008 : // changed.
3009 : mInner.mScrolledFrame->Invalidate(
3010 0 : originalVisOverflow + originalRect.TopLeft() - finalRect.TopLeft());
3011 0 : mInner.mScrolledFrame->Invalidate(finalVisOverflow);
3012 0 : } else if (!originalVisOverflow.IsEqualInterior(finalVisOverflow)) {
3013 : // If the overflow rect changed then invalidate the difference between the
3014 : // old and new overflow rects.
3015 : mInner.mScrolledFrame->CheckInvalidateSizeChange(
3016 0 : originalRect, originalVisOverflow, finalRect.Size());
3017 : mInner.mScrolledFrame->InvalidateRectDifference(
3018 0 : originalVisOverflow, finalVisOverflow);
3019 : }
3020 :
3021 0 : aState.SetLayoutFlags(oldflags);
3022 :
3023 0 : }
3024 :
3025 0 : void nsGfxScrollFrameInner::PostOverflowEvent()
3026 : {
3027 0 : if (mAsyncScrollPortEvent.IsPending())
3028 0 : return;
3029 :
3030 : // Keep this in sync with FireScrollPortEvent().
3031 0 : nsSize scrollportSize = mScrollPort.Size();
3032 0 : nsSize childSize = GetScrolledRect().Size();
3033 :
3034 0 : bool newVerticalOverflow = childSize.height > scrollportSize.height;
3035 0 : bool vertChanged = mVerticalOverflow != newVerticalOverflow;
3036 :
3037 0 : bool newHorizontalOverflow = childSize.width > scrollportSize.width;
3038 0 : bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
3039 :
3040 0 : if (!vertChanged && !horizChanged) {
3041 0 : return;
3042 : }
3043 :
3044 0 : nsRefPtr<AsyncScrollPortEvent> ev = new AsyncScrollPortEvent(this);
3045 0 : if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev)))
3046 0 : mAsyncScrollPortEvent = ev;
3047 : }
3048 :
3049 : bool
3050 0 : nsGfxScrollFrameInner::IsLTR() const
3051 : {
3052 : //TODO make bidi code set these from preferences
3053 :
3054 0 : nsIFrame *frame = mOuter;
3055 : // XXX This is a bit on the slow side.
3056 0 : if (mIsRoot) {
3057 : // If we're the root scrollframe, we need the root element's style data.
3058 0 : nsPresContext *presContext = mOuter->PresContext();
3059 0 : nsIDocument *document = presContext->Document();
3060 0 : Element *root = document->GetRootElement();
3061 :
3062 : // But for HTML and XHTML we want the body element.
3063 0 : nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
3064 0 : if (htmlDoc) {
3065 0 : Element *bodyElement = document->GetBodyElement();
3066 0 : if (bodyElement)
3067 0 : root = bodyElement; // we can trust the document to hold on to it
3068 : }
3069 :
3070 0 : if (root) {
3071 0 : nsIFrame *rootsFrame = root->GetPrimaryFrame();
3072 0 : if (rootsFrame)
3073 0 : frame = rootsFrame;
3074 : }
3075 : }
3076 :
3077 0 : return frame->GetStyleVisibility()->mDirection != NS_STYLE_DIRECTION_RTL;
3078 : }
3079 :
3080 : bool
3081 0 : nsGfxScrollFrameInner::IsScrollbarOnRight() const
3082 : {
3083 0 : nsPresContext *presContext = mOuter->PresContext();
3084 :
3085 : // The position of the scrollbar in top-level windows depends on the pref
3086 : // layout.scrollbar.side. For non-top-level elements, it depends only on the
3087 : // directionaliy of the element (equivalent to a value of "1" for the pref).
3088 0 : if (!mIsRoot)
3089 0 : return IsLTR();
3090 0 : switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) {
3091 : default:
3092 : case 0: // UI directionality
3093 0 : return presContext->GetCachedIntPref(kPresContext_BidiDirection)
3094 0 : == IBMBIDI_TEXTDIRECTION_LTR;
3095 : case 1: // Document / content directionality
3096 0 : return IsLTR();
3097 : case 2: // Always right
3098 0 : return true;
3099 : case 3: // Always left
3100 0 : return false;
3101 : }
3102 : }
3103 :
3104 : /**
3105 : * Reflow the scroll area if it needs it and return its size. Also determine if the reflow will
3106 : * cause any of the scrollbars to need to be reflowed.
3107 : */
3108 : nsresult
3109 0 : nsXULScrollFrame::Layout(nsBoxLayoutState& aState)
3110 : {
3111 0 : bool scrollbarRight = mInner.IsScrollbarOnRight();
3112 0 : bool scrollbarBottom = true;
3113 :
3114 : // get the content rect
3115 0 : nsRect clientRect(0,0,0,0);
3116 0 : GetClientRect(clientRect);
3117 :
3118 0 : nsRect oldScrollAreaBounds = mInner.mScrollPort;
3119 0 : nsPoint oldScrollPosition = mInner.GetLogicalScrollPosition();
3120 :
3121 : // the scroll area size starts off as big as our content area
3122 0 : mInner.mScrollPort = clientRect;
3123 :
3124 : /**************
3125 : Our basic strategy here is to first try laying out the content with
3126 : the scrollbars in their current state. We're hoping that that will
3127 : just "work"; the content will overflow wherever there's a scrollbar
3128 : already visible. If that does work, then there's no need to lay out
3129 : the scrollarea. Otherwise we fix up the scrollbars; first we add a
3130 : vertical one to scroll the content if necessary, or remove it if
3131 : it's not needed. Then we reflow the content if the scrollbar
3132 : changed. Then we add a horizontal scrollbar if necessary (or
3133 : remove if not needed), and if that changed, we reflow the content
3134 : again. At this point, any scrollbars that are needed to scroll the
3135 : content have been added.
3136 :
3137 : In the second phase we check to see if any scrollbars are too small
3138 : to display, and if so, we remove them. We check the horizontal
3139 : scrollbar first; removing it might make room for the vertical
3140 : scrollbar, and if we have room for just one scrollbar we'll save
3141 : the vertical one.
3142 :
3143 : Finally we position and size the scrollbars and scrollcorner (the
3144 : square that is needed in the corner of the window when two
3145 : scrollbars are visible), and reflow any fixed position views
3146 : (if we're the viewport and we added or removed a scrollbar).
3147 : **************/
3148 :
3149 0 : ScrollbarStyles styles = GetScrollbarStyles();
3150 :
3151 : // Look at our style do we always have vertical or horizontal scrollbars?
3152 0 : if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL)
3153 0 : mInner.mHasHorizontalScrollbar = true;
3154 0 : if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL)
3155 0 : mInner.mHasVerticalScrollbar = true;
3156 :
3157 0 : if (mInner.mHasHorizontalScrollbar)
3158 0 : AddHorizontalScrollbar(aState, scrollbarBottom);
3159 :
3160 0 : if (mInner.mHasVerticalScrollbar)
3161 0 : AddVerticalScrollbar(aState, scrollbarRight);
3162 :
3163 : // layout our the scroll area
3164 0 : LayoutScrollArea(aState, oldScrollPosition);
3165 :
3166 : // now look at the content area and see if we need scrollbars or not
3167 0 : bool needsLayout = false;
3168 :
3169 : // if we have 'auto' scrollbars look at the vertical case
3170 0 : if (styles.mVertical != NS_STYLE_OVERFLOW_SCROLL) {
3171 : // These are only good until the call to LayoutScrollArea.
3172 0 : nsRect scrolledRect = mInner.GetScrolledRect();
3173 :
3174 : // There are two cases to consider
3175 0 : if (scrolledRect.height <= mInner.mScrollPort.height
3176 : || styles.mVertical != NS_STYLE_OVERFLOW_AUTO) {
3177 0 : if (mInner.mHasVerticalScrollbar) {
3178 : // We left room for the vertical scrollbar, but it's not needed;
3179 : // remove it.
3180 0 : RemoveVerticalScrollbar(aState, scrollbarRight);
3181 0 : needsLayout = true;
3182 : }
3183 : } else {
3184 0 : if (!mInner.mHasVerticalScrollbar) {
3185 : // We didn't leave room for the vertical scrollbar, but it turns
3186 : // out we needed it
3187 0 : if (AddVerticalScrollbar(aState, scrollbarRight))
3188 0 : needsLayout = true;
3189 : }
3190 : }
3191 :
3192 : // ok layout at the right size
3193 0 : if (needsLayout) {
3194 0 : nsBoxLayoutState resizeState(aState);
3195 0 : LayoutScrollArea(resizeState, oldScrollPosition);
3196 0 : needsLayout = false;
3197 : }
3198 : }
3199 :
3200 :
3201 : // if scrollbars are auto look at the horizontal case
3202 0 : if (styles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL)
3203 : {
3204 : // These are only good until the call to LayoutScrollArea.
3205 0 : nsRect scrolledRect = mInner.GetScrolledRect();
3206 :
3207 : // if the child is wider that the scroll area
3208 : // and we don't have a scrollbar add one.
3209 0 : if ((scrolledRect.width > mInner.mScrollPort.width)
3210 : && styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) {
3211 :
3212 0 : if (!mInner.mHasHorizontalScrollbar) {
3213 : // no scrollbar?
3214 0 : if (AddHorizontalScrollbar(aState, scrollbarBottom))
3215 0 : needsLayout = true;
3216 :
3217 : // if we added a horizontal scrollbar and we did not have a vertical
3218 : // there is a chance that by adding the horizontal scrollbar we will
3219 : // suddenly need a vertical scrollbar. Is a special case but its
3220 : // important.
3221 : //if (!mHasVerticalScrollbar && scrolledRect.height > scrollAreaRect.height - sbSize.height)
3222 : // printf("****Gfx Scrollbar Special case hit!!*****\n");
3223 :
3224 : }
3225 : } else {
3226 : // if the area is smaller or equal to and we have a scrollbar then
3227 : // remove it.
3228 0 : if (mInner.mHasHorizontalScrollbar) {
3229 0 : RemoveHorizontalScrollbar(aState, scrollbarBottom);
3230 0 : needsLayout = true;
3231 : }
3232 : }
3233 : }
3234 :
3235 : // we only need to set the rect. The inner child stays the same size.
3236 0 : if (needsLayout) {
3237 0 : nsBoxLayoutState resizeState(aState);
3238 0 : LayoutScrollArea(resizeState, oldScrollPosition);
3239 0 : needsLayout = false;
3240 : }
3241 :
3242 : // get the preferred size of the scrollbars
3243 0 : nsSize hMinSize(0, 0);
3244 0 : if (mInner.mHScrollbarBox && mInner.mHasHorizontalScrollbar) {
3245 0 : GetScrollbarMetrics(aState, mInner.mHScrollbarBox, &hMinSize, nsnull, false);
3246 : }
3247 0 : nsSize vMinSize(0, 0);
3248 0 : if (mInner.mVScrollbarBox && mInner.mHasVerticalScrollbar) {
3249 0 : GetScrollbarMetrics(aState, mInner.mVScrollbarBox, &vMinSize, nsnull, true);
3250 : }
3251 :
3252 : // Disable scrollbars that are too small
3253 : // Disable horizontal scrollbar first. If we have to disable only one
3254 : // scrollbar, we'd rather keep the vertical scrollbar.
3255 : // Note that we always give horizontal scrollbars their preferred height,
3256 : // never their min-height. So check that there's room for the preferred height.
3257 0 : if (mInner.mHasHorizontalScrollbar &&
3258 : (hMinSize.width > clientRect.width - vMinSize.width
3259 : || hMinSize.height > clientRect.height)) {
3260 0 : RemoveHorizontalScrollbar(aState, scrollbarBottom);
3261 0 : needsLayout = true;
3262 : }
3263 : // Now disable vertical scrollbar if necessary
3264 0 : if (mInner.mHasVerticalScrollbar &&
3265 : (vMinSize.height > clientRect.height - hMinSize.height
3266 : || vMinSize.width > clientRect.width)) {
3267 0 : RemoveVerticalScrollbar(aState, scrollbarRight);
3268 0 : needsLayout = true;
3269 : }
3270 :
3271 : // we only need to set the rect. The inner child stays the same size.
3272 0 : if (needsLayout) {
3273 0 : nsBoxLayoutState resizeState(aState);
3274 0 : LayoutScrollArea(resizeState, oldScrollPosition);
3275 : }
3276 :
3277 0 : if (!mInner.mSupppressScrollbarUpdate) {
3278 0 : mInner.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds);
3279 : }
3280 0 : if (!mInner.mPostedReflowCallback) {
3281 : // Make sure we'll try scrolling to restored position
3282 0 : PresContext()->PresShell()->PostReflowCallback(&mInner);
3283 0 : mInner.mPostedReflowCallback = true;
3284 : }
3285 0 : if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
3286 0 : mInner.mHadNonInitialReflow = true;
3287 : }
3288 :
3289 : // Set up overflow areas for block frames for the benefit of
3290 : // text-overflow.
3291 0 : nsIFrame* f = mInner.mScrolledFrame->GetContentInsertionFrame();
3292 0 : if (nsLayoutUtils::GetAsBlock(f)) {
3293 0 : nsRect origRect = f->GetRect();
3294 0 : nsRect clippedRect = origRect;
3295 0 : clippedRect.MoveBy(mInner.mScrollPort.TopLeft());
3296 0 : clippedRect.IntersectRect(clippedRect, mInner.mScrollPort);
3297 0 : nsOverflowAreas overflow = f->GetOverflowAreas();
3298 0 : f->FinishAndStoreOverflow(overflow, clippedRect.Size());
3299 0 : clippedRect.MoveTo(origRect.TopLeft());
3300 0 : f->SetRect(clippedRect);
3301 : }
3302 :
3303 0 : mInner.PostOverflowEvent();
3304 0 : return NS_OK;
3305 : }
3306 :
3307 : void
3308 0 : nsGfxScrollFrameInner::FinishReflowForScrollbar(nsIContent* aContent,
3309 : nscoord aMinXY, nscoord aMaxXY,
3310 : nscoord aCurPosXY,
3311 : nscoord aPageIncrement,
3312 : nscoord aIncrement)
3313 : {
3314 : // Scrollbars assume zero is the minimum position, so translate for them.
3315 0 : SetCoordAttribute(aContent, nsGkAtoms::curpos, aCurPosXY - aMinXY);
3316 0 : SetScrollbarEnabled(aContent, aMaxXY - aMinXY);
3317 0 : SetCoordAttribute(aContent, nsGkAtoms::maxpos, aMaxXY - aMinXY);
3318 0 : SetCoordAttribute(aContent, nsGkAtoms::pageincrement, aPageIncrement);
3319 0 : SetCoordAttribute(aContent, nsGkAtoms::increment, aIncrement);
3320 0 : }
3321 :
3322 : bool
3323 0 : nsGfxScrollFrameInner::ReflowFinished()
3324 : {
3325 0 : mPostedReflowCallback = false;
3326 :
3327 0 : ScrollToRestoredPosition();
3328 :
3329 : // Clamp scroll position
3330 0 : ScrollToImpl(GetScrollPosition());
3331 :
3332 0 : if (NS_SUBTREE_DIRTY(mOuter) || !mUpdateScrollbarAttributes)
3333 0 : return false;
3334 :
3335 0 : mUpdateScrollbarAttributes = false;
3336 :
3337 : // Update scrollbar attributes.
3338 0 : nsPresContext* presContext = mOuter->PresContext();
3339 :
3340 0 : if (mMayHaveDirtyFixedChildren) {
3341 0 : mMayHaveDirtyFixedChildren = false;
3342 0 : nsIFrame* parentFrame = mOuter->GetParent();
3343 0 : for (nsIFrame* fixedChild =
3344 0 : parentFrame->GetFirstChild(nsIFrame::kFixedList);
3345 : fixedChild; fixedChild = fixedChild->GetNextSibling()) {
3346 : // force a reflow of the fixed child
3347 0 : presContext->PresShell()->
3348 : FrameNeedsReflow(fixedChild, nsIPresShell::eResize,
3349 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
3350 : }
3351 : }
3352 :
3353 0 : nsRect scrolledContentRect = GetScrolledRect();
3354 0 : nscoord minX = scrolledContentRect.x;
3355 0 : nscoord maxX = scrolledContentRect.XMost() - mScrollPort.width;
3356 0 : nscoord minY = scrolledContentRect.y;
3357 0 : nscoord maxY = scrolledContentRect.YMost() - mScrollPort.height;
3358 :
3359 : // Suppress handling of the curpos attribute changes we make here.
3360 0 : NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here");
3361 0 : mFrameIsUpdatingScrollbar = true;
3362 :
3363 : nsCOMPtr<nsIContent> vScroll =
3364 0 : mVScrollbarBox ? mVScrollbarBox->GetContent() : nsnull;
3365 : nsCOMPtr<nsIContent> hScroll =
3366 0 : mHScrollbarBox ? mHScrollbarBox->GetContent() : nsnull;
3367 :
3368 : // Note, in some cases mOuter may get deleted while finishing reflow
3369 : // for scrollbars.
3370 0 : if (vScroll || hScroll) {
3371 0 : nsWeakFrame weakFrame(mOuter);
3372 0 : nsPoint scrollPos = GetScrollPosition();
3373 : // XXX shouldn't we use GetPageScrollAmount/GetLineScrollAmount here?
3374 0 : if (vScroll) {
3375 : const double kScrollMultiplier = Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
3376 0 : NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
3377 0 : nscoord fontHeight = GetLineScrollAmount().height * kScrollMultiplier;
3378 : // We normally use (scrollArea.height - fontHeight) for height
3379 : // of page scrolling. However, it is too small when
3380 : // fontHeight is very large. (If fontHeight is larger than
3381 : // scrollArea.height, direction of scrolling will be opposite).
3382 : // To avoid it, we use (float(scrollArea.height) * 0.8) as
3383 : // lower bound value of height of page scrolling. (bug 383267)
3384 0 : nscoord pageincrement = nscoord(mScrollPort.height - fontHeight);
3385 0 : nscoord pageincrementMin = nscoord(float(mScrollPort.height) * 0.8);
3386 : FinishReflowForScrollbar(vScroll, minY, maxY, scrollPos.y,
3387 0 : NS_MAX(pageincrement, pageincrementMin),
3388 0 : fontHeight);
3389 : }
3390 0 : if (hScroll) {
3391 : FinishReflowForScrollbar(hScroll, minX, maxX, scrollPos.x,
3392 : nscoord(float(mScrollPort.width) * 0.8),
3393 0 : nsPresContext::CSSPixelsToAppUnits(10));
3394 : }
3395 0 : NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
3396 : }
3397 :
3398 0 : mFrameIsUpdatingScrollbar = false;
3399 : // We used to rely on the curpos attribute changes above to scroll the
3400 : // view. However, for scrolling to the left of the viewport, we
3401 : // rescale the curpos attribute, which means that operations like
3402 : // resizing the window while it is scrolled all the way to the left
3403 : // hold the curpos attribute constant at 0 while still requiring
3404 : // scrolling. So we suppress the effect of the changes above with
3405 : // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here.
3406 : // (It actually even works some of the time without this, thanks to
3407 : // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
3408 : // we hide the scrollbar on a large size change, such as
3409 : // maximization.)
3410 0 : if (!mHScrollbarBox && !mVScrollbarBox)
3411 0 : return false;
3412 0 : CurPosAttributeChanged(mVScrollbarBox ? mVScrollbarBox->GetContent()
3413 0 : : mHScrollbarBox->GetContent());
3414 0 : return true;
3415 : }
3416 :
3417 : void
3418 0 : nsGfxScrollFrameInner::ReflowCallbackCanceled()
3419 : {
3420 0 : mPostedReflowCallback = false;
3421 0 : }
3422 :
3423 0 : static void LayoutAndInvalidate(nsBoxLayoutState& aState,
3424 : nsIFrame* aBox, const nsRect& aRect,
3425 : bool aScrollbarIsBeingHidden)
3426 : {
3427 : // When a child box changes shape of position, the parent
3428 : // is responsible for invalidation; the overflow rect must be invalidated
3429 : // to make sure to catch any overflow.
3430 : // We invalidate the parent (i.e. the scrollframe) directly, because
3431 : // invalidates coming from scrollbars are suppressed by nsHTMLScrollFrame when
3432 : // mHasVScrollbar/mHasHScrollbar is false, and this is called after those
3433 : // flags have been set ... if a scrollbar is being hidden, we still need
3434 : // to invalidate the scrollbar area here.
3435 : // But we also need to invalidate the scrollbar itself in case it has
3436 : // its own layer; we need to ensure that layer is updated.
3437 0 : bool rectChanged = !aBox->GetRect().IsEqualInterior(aRect);
3438 0 : if (rectChanged) {
3439 0 : if (aScrollbarIsBeingHidden) {
3440 0 : aBox->GetParent()->Invalidate(aBox->GetVisualOverflowRect() +
3441 0 : aBox->GetPosition());
3442 : } else {
3443 0 : aBox->InvalidateFrameSubtree();
3444 : }
3445 : }
3446 0 : nsBoxFrame::LayoutChildAt(aState, aBox, aRect);
3447 0 : if (rectChanged) {
3448 0 : if (aScrollbarIsBeingHidden) {
3449 0 : aBox->GetParent()->Invalidate(aBox->GetVisualOverflowRect() +
3450 0 : aBox->GetPosition());
3451 : } else {
3452 0 : aBox->InvalidateFrameSubtree();
3453 : }
3454 : }
3455 0 : }
3456 :
3457 : bool
3458 0 : nsGfxScrollFrameInner::UpdateOverflow()
3459 : {
3460 0 : nsIScrollableFrame* sf = do_QueryFrame(mOuter);
3461 0 : ScrollbarStyles ss = sf->GetScrollbarStyles();
3462 :
3463 0 : if (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN ||
3464 : ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN ||
3465 0 : GetScrollPosition() != nsPoint()) {
3466 : // If there are scrollbars, or we're not at the beginning of the pane,
3467 : // the scroll position may change. In this case, mark the frame as
3468 : // needing reflow.
3469 0 : mOuter->PresContext()->PresShell()->FrameNeedsReflow(
3470 0 : mOuter, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
3471 : }
3472 :
3473 : // Scroll frames never have overflow area because they always clip their
3474 : // children, so return false.
3475 0 : return false;
3476 : }
3477 :
3478 : void
3479 0 : nsGfxScrollFrameInner::AdjustScrollbarRectForResizer(
3480 : nsIFrame* aFrame, nsPresContext* aPresContext,
3481 : nsRect& aRect, bool aHasResizer, bool aVertical)
3482 : {
3483 0 : if ((aVertical ? aRect.width : aRect.height) == 0)
3484 0 : return;
3485 :
3486 : // if a content resizer is present, use its size. Otherwise, check if the
3487 : // widget has a resizer.
3488 0 : nsRect resizerRect;
3489 0 : if (aHasResizer) {
3490 0 : resizerRect = mResizerBox->GetRect();
3491 : }
3492 : else {
3493 0 : nsPoint offset;
3494 0 : nsIWidget* widget = aFrame->GetNearestWidget(offset);
3495 0 : nsIntRect widgetRect;
3496 0 : if (!widget || !widget->ShowsResizeIndicator(&widgetRect))
3497 : return;
3498 :
3499 0 : resizerRect = nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
3500 0 : aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
3501 : aPresContext->DevPixelsToAppUnits(widgetRect.width),
3502 0 : aPresContext->DevPixelsToAppUnits(widgetRect.height));
3503 : }
3504 :
3505 0 : if (!resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1)))
3506 : return;
3507 :
3508 0 : if (aVertical)
3509 0 : aRect.height = NS_MAX(0, resizerRect.y - aRect.y);
3510 : else
3511 0 : aRect.width = NS_MAX(0, resizerRect.x - aRect.x);
3512 : }
3513 :
3514 : void
3515 0 : nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
3516 : const nsRect& aContentArea,
3517 : const nsRect& aOldScrollArea)
3518 : {
3519 0 : NS_ASSERTION(!mSupppressScrollbarUpdate,
3520 : "This should have been suppressed");
3521 :
3522 0 : bool hasResizer = HasResizer();
3523 0 : bool scrollbarOnLeft = !IsScrollbarOnRight();
3524 :
3525 : // place the scrollcorner
3526 0 : if (mScrollCornerBox || mResizerBox) {
3527 0 : NS_PRECONDITION(!mScrollCornerBox || mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
3528 :
3529 0 : nsRect r(0, 0, 0, 0);
3530 0 : if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
3531 : // scrollbar (if any) on left
3532 0 : r.x = aContentArea.x;
3533 0 : r.width = mScrollPort.x - aContentArea.x;
3534 0 : NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
3535 : } else {
3536 : // scrollbar (if any) on right
3537 0 : r.width = aContentArea.XMost() - mScrollPort.XMost();
3538 0 : r.x = aContentArea.XMost() - r.width;
3539 0 : NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
3540 : }
3541 0 : if (aContentArea.y != mScrollPort.y) {
3542 0 : NS_ERROR("top scrollbars not supported");
3543 : } else {
3544 : // scrollbar (if any) on bottom
3545 0 : r.height = aContentArea.YMost() - mScrollPort.YMost();
3546 0 : r.y = aContentArea.YMost() - r.height;
3547 0 : NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
3548 : }
3549 :
3550 0 : if (mScrollCornerBox) {
3551 0 : LayoutAndInvalidate(aState, mScrollCornerBox, r, false);
3552 : }
3553 :
3554 0 : if (hasResizer) {
3555 : // if a resizer is present, get its size. Assume a default size of 15 pixels.
3556 0 : nsSize resizerSize;
3557 0 : nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
3558 : resizerSize.width =
3559 0 : mVScrollbarBox ? mVScrollbarBox->GetMinSize(aState).width : defaultSize;
3560 0 : if (resizerSize.width > r.width) {
3561 0 : r.width = resizerSize.width;
3562 0 : if (aContentArea.x == mScrollPort.x && !scrollbarOnLeft)
3563 0 : r.x = aContentArea.XMost() - r.width;
3564 : }
3565 :
3566 : resizerSize.height =
3567 0 : mHScrollbarBox ? mHScrollbarBox->GetMinSize(aState).height : defaultSize;
3568 0 : if (resizerSize.height > r.height) {
3569 0 : r.height = resizerSize.height;
3570 0 : if (aContentArea.y == mScrollPort.y)
3571 0 : r.y = aContentArea.YMost() - r.height;
3572 : }
3573 :
3574 0 : LayoutAndInvalidate(aState, mResizerBox, r, false);
3575 : }
3576 0 : else if (mResizerBox) {
3577 : // otherwise lay out the resizer with an empty rectangle
3578 0 : LayoutAndInvalidate(aState, mResizerBox, nsRect(), false);
3579 : }
3580 : }
3581 :
3582 0 : nsPresContext* presContext = mScrolledFrame->PresContext();
3583 0 : if (mVScrollbarBox) {
3584 0 : NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
3585 0 : nsRect vRect(mScrollPort);
3586 0 : vRect.width = aContentArea.width - mScrollPort.width;
3587 0 : vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost();
3588 0 : nsMargin margin;
3589 0 : mVScrollbarBox->GetMargin(margin);
3590 0 : vRect.Deflate(margin);
3591 0 : AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, true);
3592 0 : LayoutAndInvalidate(aState, mVScrollbarBox, vRect, !mHasVerticalScrollbar);
3593 : }
3594 :
3595 0 : if (mHScrollbarBox) {
3596 0 : NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!");
3597 0 : nsRect hRect(mScrollPort);
3598 0 : hRect.height = aContentArea.height - mScrollPort.height;
3599 0 : hRect.y = true ? mScrollPort.YMost() : aContentArea.y;
3600 0 : nsMargin margin;
3601 0 : mHScrollbarBox->GetMargin(margin);
3602 0 : hRect.Deflate(margin);
3603 0 : AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false);
3604 0 : LayoutAndInvalidate(aState, mHScrollbarBox, hRect, !mHasHorizontalScrollbar);
3605 : }
3606 :
3607 : // may need to update fixed position children of the viewport,
3608 : // if the client area changed size because of an incremental
3609 : // reflow of a descendant. (If the outer frame is dirty, the fixed
3610 : // children will be re-laid out anyway)
3611 0 : if (aOldScrollArea.Size() != mScrollPort.Size() &&
3612 0 : !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) &&
3613 : mIsRoot) {
3614 0 : mMayHaveDirtyFixedChildren = true;
3615 : }
3616 :
3617 : // post reflow callback to modify scrollbar attributes
3618 0 : mUpdateScrollbarAttributes = true;
3619 0 : if (!mPostedReflowCallback) {
3620 0 : aState.PresContext()->PresShell()->PostReflowCallback(this);
3621 0 : mPostedReflowCallback = true;
3622 : }
3623 0 : }
3624 :
3625 : void
3626 0 : nsGfxScrollFrameInner::SetScrollbarEnabled(nsIContent* aContent, nscoord aMaxPos)
3627 : {
3628 0 : if (aMaxPos) {
3629 0 : aContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
3630 : } else {
3631 : aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
3632 0 : NS_LITERAL_STRING("true"), true);
3633 : }
3634 0 : }
3635 :
3636 : void
3637 0 : nsGfxScrollFrameInner::SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom,
3638 : nscoord aSize)
3639 : {
3640 : // convert to pixels
3641 0 : aSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
3642 :
3643 : // only set the attribute if it changed.
3644 :
3645 0 : nsAutoString newValue;
3646 0 : newValue.AppendInt(aSize);
3647 :
3648 0 : if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters))
3649 : return;
3650 :
3651 0 : aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, true);
3652 : }
3653 :
3654 : static void
3655 0 : ReduceRadii(nscoord aXBorder, nscoord aYBorder,
3656 : nscoord& aXRadius, nscoord& aYRadius)
3657 : {
3658 : // In order to ensure that the inside edge of the border has no
3659 : // curvature, we need at least one of its radii to be zero.
3660 0 : if (aXRadius <= aXBorder || aYRadius <= aYBorder)
3661 0 : return;
3662 :
3663 : // For any corner where we reduce the radii, preserve the corner's shape.
3664 : double ratio = NS_MAX(double(aXBorder) / aXRadius,
3665 0 : double(aYBorder) / aYRadius);
3666 0 : aXRadius *= ratio;
3667 0 : aYRadius *= ratio;
3668 : }
3669 :
3670 : /**
3671 : * Implement an override for nsIFrame::GetBorderRadii to ensure that
3672 : * the clipping region for the border radius does not clip the scrollbars.
3673 : *
3674 : * In other words, we require that the border radius be reduced until the
3675 : * inner border radius at the inner edge of the border is 0 wherever we
3676 : * have scrollbars.
3677 : */
3678 : bool
3679 0 : nsGfxScrollFrameInner::GetBorderRadii(nscoord aRadii[8]) const
3680 : {
3681 0 : if (!mOuter->nsContainerFrame::GetBorderRadii(aRadii))
3682 0 : return false;
3683 :
3684 : // Since we can use GetActualScrollbarSizes (rather than
3685 : // GetDesiredScrollbarSizes) since this doesn't affect reflow, we
3686 : // probably should.
3687 0 : nsMargin sb = GetActualScrollbarSizes();
3688 0 : nsMargin border = mOuter->GetUsedBorder();
3689 :
3690 0 : if (sb.left > 0 || sb.top > 0) {
3691 : ReduceRadii(border.left, border.top,
3692 : aRadii[NS_CORNER_TOP_LEFT_X],
3693 0 : aRadii[NS_CORNER_TOP_LEFT_Y]);
3694 : }
3695 :
3696 0 : if (sb.top > 0 || sb.right > 0) {
3697 : ReduceRadii(border.right, border.top,
3698 0 : aRadii[NS_CORNER_TOP_RIGHT_X],
3699 0 : aRadii[NS_CORNER_TOP_RIGHT_Y]);
3700 : }
3701 :
3702 0 : if (sb.right > 0 || sb.bottom > 0) {
3703 : ReduceRadii(border.right, border.bottom,
3704 0 : aRadii[NS_CORNER_BOTTOM_RIGHT_X],
3705 0 : aRadii[NS_CORNER_BOTTOM_RIGHT_Y]);
3706 : }
3707 :
3708 0 : if (sb.bottom > 0 || sb.left > 0) {
3709 : ReduceRadii(border.left, border.bottom,
3710 0 : aRadii[NS_CORNER_BOTTOM_LEFT_X],
3711 0 : aRadii[NS_CORNER_BOTTOM_LEFT_Y]);
3712 : }
3713 :
3714 0 : return true;
3715 : }
3716 :
3717 : nsRect
3718 0 : nsGfxScrollFrameInner::GetScrolledRect() const
3719 : {
3720 : nsRect result =
3721 0 : GetScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(),
3722 0 : mScrollPort.Size());
3723 :
3724 0 : NS_ASSERTION(result.width >= mScrollPort.width,
3725 : "Scrolled rect smaller than scrollport?");
3726 0 : NS_ASSERTION(result.height >= mScrollPort.height,
3727 : "Scrolled rect smaller than scrollport?");
3728 : return result;
3729 : }
3730 :
3731 : nsRect
3732 0 : nsGfxScrollFrameInner::GetScrolledRectInternal(const nsRect& aScrolledFrameOverflowArea,
3733 : const nsSize& aScrollPortSize) const
3734 : {
3735 0 : nscoord x1 = aScrolledFrameOverflowArea.x,
3736 0 : x2 = aScrolledFrameOverflowArea.XMost(),
3737 0 : y1 = aScrolledFrameOverflowArea.y,
3738 0 : y2 = aScrolledFrameOverflowArea.YMost();
3739 0 : if (y1 < 0)
3740 0 : y1 = 0;
3741 0 : if (IsLTR()) {
3742 0 : if (x1 < 0)
3743 0 : x1 = 0;
3744 : } else {
3745 0 : if (x2 > aScrollPortSize.width)
3746 0 : x2 = aScrollPortSize.width;
3747 : // When the scrolled frame chooses a size larger than its available width (because
3748 : // its padding alone is larger than the available width), we need to keep the
3749 : // start-edge of the scroll frame anchored to the start-edge of the scrollport.
3750 : // When the scrolled frame is RTL, this means moving it in our left-based
3751 : // coordinate system, so we need to compensate for its extra width here by
3752 : // effectively repositioning the frame.
3753 0 : nscoord extraWidth = NS_MAX(0, mScrolledFrame->GetSize().width - aScrollPortSize.width);
3754 0 : x2 += extraWidth;
3755 : }
3756 0 : return nsRect(x1, y1, x2 - x1, y2 - y1);
3757 : }
3758 :
3759 : nsMargin
3760 0 : nsGfxScrollFrameInner::GetActualScrollbarSizes() const
3761 : {
3762 0 : nsRect r = mOuter->GetPaddingRect() - mOuter->GetPosition();
3763 :
3764 : return nsMargin(mScrollPort.x - r.x, mScrollPort.y - r.y,
3765 0 : r.XMost() - mScrollPort.XMost(),
3766 0 : r.YMost() - mScrollPort.YMost());
3767 : }
3768 :
3769 : void
3770 0 : nsGfxScrollFrameInner::SetScrollbarVisibility(nsIBox* aScrollbar, bool aVisible)
3771 : {
3772 0 : nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar);
3773 0 : if (scrollbar) {
3774 : // See if we have a mediator.
3775 0 : nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
3776 0 : if (mediator) {
3777 : // Inform the mediator of the visibility change.
3778 0 : mediator->VisibilityChanged(aVisible);
3779 : }
3780 : }
3781 0 : }
3782 :
3783 : PRInt32
3784 0 : nsGfxScrollFrameInner::GetCoordAttribute(nsIBox* aBox, nsIAtom* atom, PRInt32 defaultValue)
3785 : {
3786 0 : if (aBox) {
3787 0 : nsIContent* content = aBox->GetContent();
3788 :
3789 0 : nsAutoString value;
3790 0 : content->GetAttr(kNameSpaceID_None, atom, value);
3791 0 : if (!value.IsEmpty())
3792 : {
3793 : PRInt32 error;
3794 :
3795 : // convert it to an integer
3796 0 : defaultValue = nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
3797 : }
3798 : }
3799 :
3800 0 : return defaultValue;
3801 : }
3802 :
3803 : nsPresState*
3804 0 : nsGfxScrollFrameInner::SaveState(nsIStatefulFrame::SpecialStateID aStateID)
3805 : {
3806 : // Don't save "normal" state for the root scrollframe; that's
3807 : // handled via the eDocumentScrollState state id
3808 0 : if (mIsRoot && aStateID == nsIStatefulFrame::eNoID) {
3809 0 : return nsnull;
3810 : }
3811 :
3812 0 : nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
3813 0 : if (mediator) {
3814 : // child handles its own scroll state, so don't bother saving state here
3815 0 : return nsnull;
3816 : }
3817 :
3818 0 : nsPoint scrollPos = GetLogicalScrollPosition();
3819 : // Don't save scroll position if we are at (0,0)
3820 0 : if (scrollPos == nsPoint(0,0)) {
3821 0 : return nsnull;
3822 : }
3823 :
3824 0 : nsPresState* state = new nsPresState();
3825 :
3826 0 : state->SetScrollState(scrollPos);
3827 :
3828 0 : return state;
3829 : }
3830 :
3831 : void
3832 0 : nsGfxScrollFrameInner::RestoreState(nsPresState* aState)
3833 : {
3834 0 : mRestorePos = aState->GetScrollState();
3835 0 : mLastPos.x = -1;
3836 0 : mLastPos.y = -1;
3837 0 : mDidHistoryRestore = true;
3838 0 : mLastPos = mScrolledFrame ? GetLogicalScrollPosition() : nsPoint(0,0);
3839 0 : }
3840 :
3841 : void
3842 0 : nsGfxScrollFrameInner::PostScrolledAreaEvent()
3843 : {
3844 0 : if (mScrolledAreaEvent.IsPending()) {
3845 0 : return;
3846 : }
3847 0 : mScrolledAreaEvent = new ScrolledAreaEvent(this);
3848 0 : nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get());
3849 : }
3850 :
3851 : ////////////////////////////////////////////////////////////////////////////////
3852 : // ScrolledArea change event dispatch
3853 :
3854 : NS_IMETHODIMP
3855 0 : nsGfxScrollFrameInner::ScrolledAreaEvent::Run()
3856 : {
3857 0 : if (mInner) {
3858 0 : mInner->FireScrolledAreaEvent();
3859 : }
3860 0 : return NS_OK;
3861 : }
3862 :
3863 : void
3864 0 : nsGfxScrollFrameInner::FireScrolledAreaEvent()
3865 : {
3866 0 : mScrolledAreaEvent.Forget();
3867 :
3868 0 : nsScrollAreaEvent event(true, NS_SCROLLEDAREACHANGED, nsnull);
3869 0 : nsPresContext *prescontext = mOuter->PresContext();
3870 0 : nsIContent* content = mOuter->GetContent();
3871 :
3872 0 : event.mArea = mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
3873 :
3874 0 : nsIDocument *doc = content->GetCurrentDoc();
3875 0 : if (doc) {
3876 0 : nsEventDispatcher::Dispatch(doc, prescontext, &event, nsnull);
3877 : }
3878 0 : }
|