Bug 574454 - Cleanup nsNativeThemeWin's GetMinimumWidgetSize a bit. r=roc.
[mozilla-central.git] / layout / generic / nsGfxScrollFrame.cpp
blob73dfa901b6a64c49974e3e468d73e1a1ad37dc17
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
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/
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.
15 * The Original Code is mozilla.org code.
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.
22 * Contributor(s):
23 * Pierre Phaneuf <pp@ludusdesign.com>
24 * Mats Palmgren <matspal@gmail.com>
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.
38 * ***** END LICENSE BLOCK ***** */
40 /* rendering object to wrap rendering objects that should be scrollable */
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 "nsHTMLContainerFrame.h"
50 #include "nsGfxScrollFrame.h"
51 #include "nsGkAtoms.h"
52 #include "nsINameSpaceManager.h"
53 #include "nsIDocument.h"
54 #include "nsIFontMetrics.h"
55 #include "nsIDocumentObserver.h"
56 #include "nsIDocument.h"
57 #include "nsBoxLayoutState.h"
58 #include "nsINodeInfo.h"
59 #include "nsIScrollbarFrame.h"
60 #include "nsIScrollbarMediator.h"
61 #include "nsITextControlFrame.h"
62 #include "nsIDOMHTMLTextAreaElement.h"
63 #include "nsNodeInfoManager.h"
64 #include "nsIURI.h"
65 #include "nsGUIEvent.h"
66 #include "nsContentCreatorFunctions.h"
67 #include "nsISupportsPrimitives.h"
68 #include "nsAutoPtr.h"
69 #include "nsPresState.h"
70 #include "nsIGlobalHistory3.h"
71 #include "nsDocShellCID.h"
72 #include "nsIHTMLDocument.h"
73 #include "nsEventDispatcher.h"
74 #include "nsContentUtils.h"
75 #include "nsLayoutUtils.h"
76 #ifdef ACCESSIBILITY
77 #include "nsIAccessibilityService.h"
78 #endif
79 #include "nsDisplayList.h"
80 #include "nsBidiUtils.h"
81 #include "nsFrameManager.h"
82 #include "nsIPrefService.h"
83 #include "mozilla/dom/Element.h"
85 using namespace mozilla::dom;
87 //----------------------------------------------------------------------
89 //----------nsHTMLScrollFrame-------------------------------------------
91 nsIFrame*
92 NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRBool aIsRoot)
94 return new (aPresShell) nsHTMLScrollFrame(aPresShell, aContext, aIsRoot);
97 NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)
99 nsHTMLScrollFrame::nsHTMLScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, PRBool aIsRoot)
100 : nsHTMLContainerFrame(aContext),
101 mInner(this, aIsRoot, PR_FALSE)
105 nsresult
106 nsHTMLScrollFrame::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
108 return mInner.CreateAnonymousContent(aElements);
111 void
112 nsHTMLScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements)
114 mInner.AppendAnonymousContentTo(aElements);
117 void
118 nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot)
120 mInner.Destroy();
121 nsHTMLContainerFrame::DestroyFrom(aDestructRoot);
124 NS_IMETHODIMP
125 nsHTMLScrollFrame::SetInitialChildList(nsIAtom* aListName,
126 nsFrameList& aChildList)
128 nsresult rv = nsHTMLContainerFrame::SetInitialChildList(aListName, aChildList);
129 mInner.ReloadChildFrames();
130 return rv;
134 NS_IMETHODIMP
135 nsHTMLScrollFrame::AppendFrames(nsIAtom* aListName,
136 nsFrameList& aFrameList)
138 NS_ASSERTION(!aListName, "Only main list supported");
139 mFrames.AppendFrames(nsnull, aFrameList);
140 mInner.ReloadChildFrames();
141 return NS_OK;
144 NS_IMETHODIMP
145 nsHTMLScrollFrame::InsertFrames(nsIAtom* aListName,
146 nsIFrame* aPrevFrame,
147 nsFrameList& aFrameList)
149 NS_ASSERTION(!aListName, "Only main list supported");
150 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
151 "inserting after sibling frame with different parent");
152 mFrames.InsertFrames(nsnull, aPrevFrame, aFrameList);
153 mInner.ReloadChildFrames();
154 return NS_OK;
157 NS_IMETHODIMP
158 nsHTMLScrollFrame::RemoveFrame(nsIAtom* aListName,
159 nsIFrame* aOldFrame)
161 NS_ASSERTION(!aListName, "Only main list supported");
162 mFrames.DestroyFrame(aOldFrame);
163 mInner.ReloadChildFrames();
164 return NS_OK;
167 nsSplittableType
168 nsHTMLScrollFrame::GetSplittableType() const
170 return NS_FRAME_NOT_SPLITTABLE;
173 PRIntn
174 nsHTMLScrollFrame::GetSkipSides() const
176 return 0;
179 nsIAtom*
180 nsHTMLScrollFrame::GetType() const
182 return nsGkAtoms::scrollFrame;
185 void
186 nsHTMLScrollFrame::InvalidateInternal(const nsRect& aDamageRect,
187 nscoord aX, nscoord aY, nsIFrame* aForChild,
188 PRUint32 aFlags)
190 if (aForChild) {
191 if (aForChild == mInner.mScrolledFrame) {
192 // restrict aDamageRect to the scrollable view's bounds
193 nsRect damage = aDamageRect + nsPoint(aX, aY);
194 // damage is now in our coordinate system, which means it was
195 // translated using the current scroll position. Adjust it to
196 // reflect the scroll position at last paint, since that's what
197 // the layer system wants us to invalidate.
198 damage += GetScrollPosition() - mInner.mScrollPosAtLastPaint;
199 nsRect r;
200 if (r.IntersectRect(damage, mInner.mScrollPort)) {
201 nsHTMLContainerFrame::InvalidateInternal(r, 0, 0, aForChild, aFlags);
203 if (mInner.mIsRoot && r != damage) {
204 // Make sure we notify our prescontext about invalidations outside
205 // viewport clipping.
206 // This is important for things that are snapshotting the viewport,
207 // possibly outside the scrolled bounds.
208 // We don't need to propagate this any further up, though. Anyone who
209 // cares about scrolled-out-of-view invalidates had better be listening
210 // to our window directly.
211 PresContext()->NotifyInvalidation(damage, aFlags);
213 return;
214 } else if (aForChild == mInner.mHScrollbarBox) {
215 if (!mInner.mHasHorizontalScrollbar) {
216 // Our scrollbars may send up invalidations even when they're collapsed,
217 // because we just size a collapsed scrollbar to empty and some
218 // descendants may be non-empty. Suppress that invalidation here.
219 return;
221 } else if (aForChild == mInner.mVScrollbarBox) {
222 if (!mInner.mHasVerticalScrollbar) {
223 // Our scrollbars may send up invalidations even when they're collapsed,
224 // because we just size a collapsed scrollbar to empty and some
225 // descendants may be non-empty. Suppress that invalidation here.
226 return;
231 nsHTMLContainerFrame::InvalidateInternal(aDamageRect, aX, aY, aForChild, aFlags);
235 HTML scrolling implementation
237 All other things being equal, we prefer layouts with fewer scrollbars showing.
240 struct ScrollReflowState {
241 const nsHTMLReflowState& mReflowState;
242 nsBoxLayoutState mBoxState;
243 nsGfxScrollFrameInner::ScrollbarStyles mStyles;
244 nsMargin mComputedBorder;
246 // === Filled in by ReflowScrolledFrame ===
247 nsRect mContentsOverflowArea;
248 PRPackedBool mReflowedContentsWithHScrollbar;
249 PRPackedBool mReflowedContentsWithVScrollbar;
251 // === Filled in when TryLayout succeeds ===
252 // The size of the inside-border area
253 nsSize mInsideBorderSize;
254 // Whether we decided to show the horizontal scrollbar
255 PRPackedBool mShowHScrollbar;
256 // Whether we decided to show the vertical scrollbar
257 PRPackedBool mShowVScrollbar;
259 ScrollReflowState(nsIScrollableFrame* aFrame,
260 const nsHTMLReflowState& aState) :
261 mReflowState(aState),
262 // mBoxState is just used for scrollbars so we don't need to
263 // worry about the reflow depth here
264 mBoxState(aState.frame->PresContext(), aState.rendContext, 0),
265 mStyles(aFrame->GetScrollbarStyles()) {
269 // XXXldb Can this go away?
270 static nsSize ComputeInsideBorderSize(ScrollReflowState* aState,
271 const nsSize& aDesiredInsideBorderSize)
273 // aDesiredInsideBorderSize is the frame size; i.e., it includes
274 // borders and padding (but the scrolled child doesn't have
275 // borders). The scrolled child has the same padding as us.
276 nscoord contentWidth = aState->mReflowState.ComputedWidth();
277 if (contentWidth == NS_UNCONSTRAINEDSIZE) {
278 contentWidth = aDesiredInsideBorderSize.width -
279 aState->mReflowState.mComputedPadding.LeftRight();
281 nscoord contentHeight = aState->mReflowState.ComputedHeight();
282 if (contentHeight == NS_UNCONSTRAINEDSIZE) {
283 contentHeight = aDesiredInsideBorderSize.height -
284 aState->mReflowState.mComputedPadding.TopBottom();
287 aState->mReflowState.ApplyMinMaxConstraints(&contentWidth, &contentHeight);
288 return nsSize(contentWidth + aState->mReflowState.mComputedPadding.LeftRight(),
289 contentHeight + aState->mReflowState.mComputedPadding.TopBottom());
292 static void
293 GetScrollbarMetrics(nsBoxLayoutState& aState, nsIBox* aBox, nsSize* aMin,
294 nsSize* aPref, PRBool aVertical)
296 NS_ASSERTION(aState.GetRenderingContext(),
297 "Must have rendering context in layout state for size "
298 "computations");
300 if (aMin) {
301 *aMin = aBox->GetMinSize(aState);
302 nsBox::AddMargin(aBox, *aMin);
305 if (aPref) {
306 *aPref = aBox->GetPrefSize(aState);
307 nsBox::AddMargin(aBox, *aPref);
312 * Assuming that we know the metrics for our wrapped frame and
313 * whether the horizontal and/or vertical scrollbars are present,
314 * compute the resulting layout and return PR_TRUE if the layout is
315 * consistent. If the layout is consistent then we fill in the
316 * computed fields of the ScrollReflowState.
318 * The layout is consistent when both scrollbars are showing if and only
319 * if they should be showing. A horizontal scrollbar should be showing if all
320 * following conditions are met:
321 * 1) the style is not HIDDEN
322 * 2) our inside-border height is at least the scrollbar height (i.e., the
323 * scrollbar fits vertically)
324 * 3) our scrollport width (the inside-border width minus the width allocated for a
325 * vertical scrollbar, if showing) is at least the scrollbar's min-width
326 * (i.e., the scrollbar fits horizontally)
327 * 4) the style is SCROLL, or the kid's overflow-area XMost is
328 * greater than the scrollport width
330 * @param aForce if PR_TRUE, then we just assume the layout is consistent.
332 PRBool
333 nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState,
334 nsHTMLReflowMetrics* aKidMetrics,
335 PRBool aAssumeHScroll, PRBool aAssumeVScroll,
336 PRBool aForce, nsresult* aResult)
338 *aResult = NS_OK;
340 if ((aState->mStyles.mVertical == NS_STYLE_OVERFLOW_HIDDEN && aAssumeVScroll) ||
341 (aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && aAssumeHScroll)) {
342 NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
343 return PR_FALSE;
346 if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar ||
347 (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar &&
348 ScrolledContentDependsOnHeight(aState))) {
349 nsresult rv = ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll,
350 aKidMetrics, PR_FALSE);
351 if (NS_FAILED(rv)) {
352 *aResult = rv;
353 return PR_FALSE;
357 nsSize vScrollbarMinSize(0, 0);
358 nsSize vScrollbarPrefSize(0, 0);
359 if (mInner.mVScrollbarBox) {
360 GetScrollbarMetrics(aState->mBoxState, mInner.mVScrollbarBox,
361 &vScrollbarMinSize,
362 aAssumeVScroll ? &vScrollbarPrefSize : nsnull, PR_TRUE);
364 nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0;
365 nscoord vScrollbarMinHeight = aAssumeVScroll ? vScrollbarMinSize.height : 0;
367 nsSize hScrollbarMinSize(0, 0);
368 nsSize hScrollbarPrefSize(0, 0);
369 if (mInner.mHScrollbarBox) {
370 GetScrollbarMetrics(aState->mBoxState, mInner.mHScrollbarBox,
371 &hScrollbarMinSize,
372 aAssumeHScroll ? &hScrollbarPrefSize : nsnull, PR_FALSE);
374 nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0;
375 nscoord hScrollbarMinWidth = aAssumeHScroll ? hScrollbarMinSize.width : 0;
377 // First, compute our inside-border size and scrollport size
378 // XXXldb Can we depend more on ComputeSize here?
379 nsSize desiredInsideBorderSize;
380 desiredInsideBorderSize.width = vScrollbarDesiredWidth +
381 NS_MAX(aKidMetrics->width, hScrollbarMinWidth);
382 desiredInsideBorderSize.height = hScrollbarDesiredHeight +
383 NS_MAX(aKidMetrics->height, vScrollbarMinHeight);
384 aState->mInsideBorderSize =
385 ComputeInsideBorderSize(aState, desiredInsideBorderSize);
386 nsSize scrollPortSize = nsSize(NS_MAX(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth),
387 NS_MAX(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight));
389 if (!aForce) {
390 nsRect scrolledRect =
391 mInner.GetScrolledRectInternal(aState->mContentsOverflowArea, scrollPortSize);
392 nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1);
394 // If the style is HIDDEN then we already know that aAssumeHScroll is PR_FALSE
395 if (aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
396 PRBool wantHScrollbar =
397 aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL ||
398 scrolledRect.XMost() >= scrollPortSize.width + oneDevPixel ||
399 scrolledRect.x <= -oneDevPixel;
400 if (aState->mInsideBorderSize.height < hScrollbarMinSize.height ||
401 scrollPortSize.width < hScrollbarMinSize.width)
402 wantHScrollbar = PR_FALSE;
403 if (wantHScrollbar != aAssumeHScroll)
404 return PR_FALSE;
407 // If the style is HIDDEN then we already know that aAssumeVScroll is PR_FALSE
408 if (aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) {
409 PRBool wantVScrollbar =
410 aState->mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL ||
411 scrolledRect.YMost() >= scrollPortSize.height + oneDevPixel ||
412 scrolledRect.y <= -oneDevPixel;
413 if (aState->mInsideBorderSize.width < vScrollbarMinSize.width ||
414 scrollPortSize.height < vScrollbarMinSize.height)
415 wantVScrollbar = PR_FALSE;
416 if (wantVScrollbar != aAssumeVScroll)
417 return PR_FALSE;
421 nscoord vScrollbarActualWidth = aState->mInsideBorderSize.width - scrollPortSize.width;
423 aState->mShowHScrollbar = aAssumeHScroll;
424 aState->mShowVScrollbar = aAssumeVScroll;
425 nsPoint scrollPortOrigin(aState->mComputedBorder.left,
426 aState->mComputedBorder.top);
427 if (!mInner.IsScrollbarOnRight()) {
428 scrollPortOrigin.x += vScrollbarActualWidth;
430 mInner.mScrollPort = nsRect(scrollPortOrigin, scrollPortSize);
431 return PR_TRUE;
434 PRBool
435 nsHTMLScrollFrame::ScrolledContentDependsOnHeight(ScrollReflowState* aState)
437 // Return true if ReflowScrolledFrame is going to do something different
438 // based on the presence of a horizontal scrollbar.
439 return (mInner.mScrolledFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) ||
440 aState->mReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE ||
441 aState->mReflowState.mComputedMinHeight > 0 ||
442 aState->mReflowState.mComputedMaxHeight != NS_UNCONSTRAINEDSIZE;
445 nsresult
446 nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState,
447 PRBool aAssumeHScroll,
448 PRBool aAssumeVScroll,
449 nsHTMLReflowMetrics* aMetrics,
450 PRBool aFirstPass)
452 // these could be NS_UNCONSTRAINEDSIZE ... NS_MIN arithmetic should
453 // be OK
454 nscoord paddingLR = aState->mReflowState.mComputedPadding.LeftRight();
456 nscoord availWidth = aState->mReflowState.ComputedWidth() + paddingLR;
458 nscoord computedHeight = aState->mReflowState.ComputedHeight();
459 nscoord computedMinHeight = aState->mReflowState.mComputedMinHeight;
460 nscoord computedMaxHeight = aState->mReflowState.mComputedMaxHeight;
461 if (!ShouldPropagateComputedHeightToScrolledContent()) {
462 computedHeight = NS_UNCONSTRAINEDSIZE;
463 computedMinHeight = 0;
464 computedMaxHeight = NS_UNCONSTRAINEDSIZE;
466 if (aAssumeHScroll) {
467 nsSize hScrollbarPrefSize =
468 mInner.mHScrollbarBox->GetPrefSize(const_cast<nsBoxLayoutState&>(aState->mBoxState));
469 if (computedHeight != NS_UNCONSTRAINEDSIZE)
470 computedHeight = NS_MAX(0, computedHeight - hScrollbarPrefSize.height);
471 computedMinHeight = NS_MAX(0, computedMinHeight - hScrollbarPrefSize.height);
472 if (computedMaxHeight != NS_UNCONSTRAINEDSIZE)
473 computedMaxHeight = NS_MAX(0, computedMaxHeight - hScrollbarPrefSize.height);
476 if (aAssumeVScroll) {
477 nsSize vScrollbarPrefSize =
478 mInner.mVScrollbarBox->GetPrefSize(const_cast<nsBoxLayoutState&>(aState->mBoxState));
479 availWidth = NS_MAX(0, availWidth - vScrollbarPrefSize.width);
482 nsPresContext* presContext = PresContext();
484 // We're forcing the padding on our scrolled frame, so let it know what that
485 // padding is.
486 presContext->PropertyTable()->
487 Set(mInner.mScrolledFrame, UsedPaddingProperty(),
488 new nsMargin(aState->mReflowState.mComputedPadding));
490 // Pass PR_FALSE for aInit so we can pass in the correct padding
491 nsHTMLReflowState kidReflowState(presContext, aState->mReflowState,
492 mInner.mScrolledFrame,
493 nsSize(availWidth, NS_UNCONSTRAINEDSIZE),
494 -1, -1, PR_FALSE);
495 kidReflowState.Init(presContext, -1, -1, nsnull,
496 &aState->mReflowState.mComputedPadding);
497 kidReflowState.mFlags.mAssumingHScrollbar = aAssumeHScroll;
498 kidReflowState.mFlags.mAssumingVScrollbar = aAssumeVScroll;
499 kidReflowState.SetComputedHeight(computedHeight);
500 kidReflowState.mComputedMinHeight = computedMinHeight;
501 kidReflowState.mComputedMaxHeight = computedMaxHeight;
503 // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to
504 // reflect our assumptions while we reflow the child.
505 PRBool didHaveHorizontalScrollbar = mInner.mHasHorizontalScrollbar;
506 PRBool didHaveVerticalScrollbar = mInner.mHasVerticalScrollbar;
507 mInner.mHasHorizontalScrollbar = aAssumeHScroll;
508 mInner.mHasVerticalScrollbar = aAssumeVScroll;
510 nsReflowStatus status;
511 nsresult rv = ReflowChild(mInner.mScrolledFrame, presContext, *aMetrics,
512 kidReflowState, 0, 0,
513 NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_MOVE_VIEW, status);
515 mInner.mHasHorizontalScrollbar = didHaveHorizontalScrollbar;
516 mInner.mHasVerticalScrollbar = didHaveVerticalScrollbar;
518 // Don't resize or position the view (if any) because we're going to resize
519 // it to the correct size anyway in PlaceScrollArea. Allowing it to
520 // resize here would size it to the natural height of the frame,
521 // which will usually be different from the scrollport height;
522 // invalidating the difference will cause unnecessary repainting.
523 FinishReflowChild(mInner.mScrolledFrame, presContext,
524 &kidReflowState, *aMetrics, 0, 0,
525 NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_SIZE_VIEW);
527 // XXX Some frames (e.g., nsObjectFrame, nsFrameFrame, nsTextFrame) don't bother
528 // setting their mOverflowArea. This is wrong because every frame should
529 // always set mOverflowArea. In fact nsObjectFrame and nsFrameFrame don't
530 // support the 'outline' property because of this. Rather than fix the world
531 // right now, just fix up the overflow area if necessary. Note that we don't
532 // check HasOverflowRect() because it could be set even though the
533 // overflow area doesn't include the frame bounds.
534 aMetrics->mOverflowArea.UnionRect(aMetrics->mOverflowArea,
535 nsRect(0, 0, aMetrics->width, aMetrics->height));
537 aState->mContentsOverflowArea = aMetrics->mOverflowArea;
538 aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
539 aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
541 return rv;
544 PRBool
545 nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowState& aState)
547 if (aState.mStyles.mHorizontal != NS_STYLE_OVERFLOW_AUTO)
548 // no guessing required
549 return aState.mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL;
551 return mInner.mHasHorizontalScrollbar;
554 PRBool
555 nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowState& aState)
557 if (aState.mStyles.mVertical != NS_STYLE_OVERFLOW_AUTO)
558 // no guessing required
559 return aState.mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL;
561 // If we've had at least one non-initial reflow, then just assume
562 // the state of the vertical scrollbar will be what we determined
563 // last time.
564 if (mInner.mHadNonInitialReflow) {
565 return mInner.mHasVerticalScrollbar;
568 // If this is the initial reflow, guess PR_FALSE because usually
569 // we have very little content by then.
570 if (InInitialReflow())
571 return PR_FALSE;
573 if (mInner.mIsRoot) {
574 // Assume that there will be a scrollbar; it seems to me
575 // that 'most pages' do have a scrollbar, and anyway, it's cheaper
576 // to do an extra reflow for the pages that *don't* need a
577 // scrollbar (because on average they will have less content).
578 return PR_TRUE;
581 // For non-viewports, just guess that we don't need a scrollbar.
582 // XXX I wonder if statistically this is the right idea; I'm
583 // basically guessing that there are a lot of overflow:auto DIVs
584 // that get their intrinsic size and don't overflow
585 return PR_FALSE;
588 PRBool
589 nsHTMLScrollFrame::InInitialReflow() const
591 // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a
592 // root scrollframe. In that case we want to skip this clause altogether.
593 // The guess here is that there are lots of overflow:auto divs out there that
594 // end up auto-sizing so they don't overflow, and that the root basically
595 // always needs a scrollbar if it did last time we loaded this page (good
596 // assumption, because our initial reflow is no longer synchronous).
597 return !mInner.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW);
600 nsresult
601 nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState,
602 const nsHTMLReflowMetrics& aDesiredSize)
604 nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mFlags);
605 nsresult rv = ReflowScrolledFrame(aState, GuessHScrollbarNeeded(*aState),
606 GuessVScrollbarNeeded(*aState), &kidDesiredSize, PR_TRUE);
607 NS_ENSURE_SUCCESS(rv, rv);
609 // There's an important special case ... if the child appears to fit
610 // in the inside-border rect (but overflows the scrollport), we
611 // should try laying it out without a vertical scrollbar. It will
612 // usually fit because making the available-width wider will not
613 // normally make the child taller. (The only situation I can think
614 // of is when you have a line containing %-width inline replaced
615 // elements whose percentages sum to more than 100%, so increasing
616 // the available width makes the line break where it was fitting
617 // before.) If we don't treat this case specially, then we will
618 // decide that showing scrollbars is OK because the content
619 // overflows when we're showing scrollbars and we won't try to
620 // remove the vertical scrollbar.
622 // Detecting when we enter this special case is important for when
623 // people design layouts that exactly fit the container "most of the
624 // time".
626 // XXX Is this check really sufficient to catch all the incremental cases
627 // where the ideal case doesn't have a scrollbar?
628 if ((aState->mReflowedContentsWithHScrollbar || aState->mReflowedContentsWithVScrollbar) &&
629 aState->mStyles.mVertical != NS_STYLE_OVERFLOW_SCROLL &&
630 aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) {
631 nsSize insideBorderSize =
632 ComputeInsideBorderSize(aState,
633 nsSize(kidDesiredSize.width, kidDesiredSize.height));
634 nsRect scrolledRect =
635 mInner.GetScrolledRectInternal(kidDesiredSize.mOverflowArea, insideBorderSize);
636 if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
637 // Let's pretend we had no scrollbars coming in here
638 rv = ReflowScrolledFrame(aState, PR_FALSE, PR_FALSE,
639 &kidDesiredSize, PR_FALSE);
640 NS_ENSURE_SUCCESS(rv, rv);
644 // Try vertical scrollbar settings that leave the vertical scrollbar unchanged.
645 // Do this first because changing the vertical scrollbar setting is expensive,
646 // forcing a reflow always.
648 // Try leaving the horizontal scrollbar unchanged first. This will be more
649 // efficient.
650 if (TryLayout(aState, &kidDesiredSize, aState->mReflowedContentsWithHScrollbar,
651 aState->mReflowedContentsWithVScrollbar, PR_FALSE, &rv))
652 return NS_OK;
653 if (TryLayout(aState, &kidDesiredSize, !aState->mReflowedContentsWithHScrollbar,
654 aState->mReflowedContentsWithVScrollbar, PR_FALSE, &rv))
655 return NS_OK;
657 // OK, now try toggling the vertical scrollbar. The performance advantage
658 // of trying the status-quo horizontal scrollbar state
659 // does not exist here (we'll have to reflow due to the vertical scrollbar
660 // change), so always try no horizontal scrollbar first.
661 PRBool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar;
662 if (TryLayout(aState, &kidDesiredSize, PR_FALSE, newVScrollbarState, PR_FALSE, &rv))
663 return NS_OK;
664 if (TryLayout(aState, &kidDesiredSize, PR_TRUE, newVScrollbarState, PR_FALSE, &rv))
665 return NS_OK;
667 // OK, we're out of ideas. Try again enabling whatever scrollbars we can
668 // enable and force the layout to stick even if it's inconsistent.
669 // This just happens sometimes.
670 TryLayout(aState, &kidDesiredSize,
671 aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN,
672 aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN,
673 PR_TRUE, &rv);
674 return rv;
677 void
678 nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowState& aState,
679 const nsPoint& aScrollPosition)
681 nsIFrame *scrolledFrame = mInner.mScrolledFrame;
682 // Set the x,y of the scrolled frame to the correct value
683 scrolledFrame->SetPosition(mInner.mScrollPort.TopLeft() - aScrollPosition);
685 nsRect scrolledArea;
686 // Preserve the width or height of empty rects
687 nsSize portSize = mInner.mScrollPort.Size();
688 nsRect scrolledRect = mInner.GetScrolledRectInternal(aState.mContentsOverflowArea, portSize);
689 scrolledArea.UnionRectIncludeEmpty(scrolledRect,
690 nsRect(nsPoint(0,0), portSize));
692 // Store the new overflow area. Note that this changes where an outline
693 // of the scrolled frame would be painted, but scrolled frames can't have
694 // outlines (the outline would go on this scrollframe instead).
695 // Using FinishAndStoreOverflow is needed so the overflow rect
696 // gets set correctly. It also messes with the overflow rect in the
697 // -moz-hidden-unscrollable case, but scrolled frames can't have
698 // 'overflow' either.
699 // This needs to happen before SyncFrameViewAfterReflow so
700 // HasOverflowRect() will return the correct value.
701 scrolledFrame->FinishAndStoreOverflow(&scrolledArea,
702 scrolledFrame->GetSize());
704 // Note that making the view *exactly* the size of the scrolled area
705 // is critical, since the view scrolling code uses the size of the
706 // scrolled view to clamp scroll requests.
707 // Normally the scrolledFrame won't have a view but in some cases it
708 // might create its own.
709 nsContainerFrame::SyncFrameViewAfterReflow(scrolledFrame->PresContext(),
710 scrolledFrame,
711 scrolledFrame->GetView(),
712 &scrolledArea,
716 nscoord
717 nsHTMLScrollFrame::GetIntrinsicVScrollbarWidth(nsIRenderingContext *aRenderingContext)
719 nsGfxScrollFrameInner::ScrollbarStyles ss = GetScrollbarStyles();
720 if (ss.mVertical != NS_STYLE_OVERFLOW_SCROLL || !mInner.mVScrollbarBox)
721 return 0;
723 // Don't need to worry about reflow depth here since it's
724 // just for scrollbars
725 nsBoxLayoutState bls(PresContext(), aRenderingContext, 0);
726 nsSize vScrollbarPrefSize(0, 0);
727 GetScrollbarMetrics(bls, mInner.mVScrollbarBox,
728 nsnull, &vScrollbarPrefSize, PR_TRUE);
729 return vScrollbarPrefSize.width;
732 /* virtual */ nscoord
733 nsHTMLScrollFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
735 nscoord result = mInner.mScrolledFrame->GetMinWidth(aRenderingContext);
736 DISPLAY_MIN_WIDTH(this, result);
737 return result + GetIntrinsicVScrollbarWidth(aRenderingContext);
740 /* virtual */ nscoord
741 nsHTMLScrollFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext)
743 nscoord result = mInner.mScrolledFrame->GetPrefWidth(aRenderingContext);
744 DISPLAY_PREF_WIDTH(this, result);
745 return NSCoordSaturatingAdd(result, GetIntrinsicVScrollbarWidth(aRenderingContext));
748 NS_IMETHODIMP
749 nsHTMLScrollFrame::GetPadding(nsMargin& aMargin)
751 // Our padding hangs out on the inside of the scrollframe, but XUL doesn't
752 // reaize that. If we're stuck inside a XUL box, we need to claim no
753 // padding.
754 // @see also nsXULScrollFrame::GetPadding.
755 aMargin.SizeTo(0,0,0,0);
756 return NS_OK;
759 PRBool
760 nsHTMLScrollFrame::IsCollapsed(nsBoxLayoutState& aBoxLayoutState)
762 // We're never collapsed in the box sense.
763 return PR_FALSE;
766 NS_IMETHODIMP
767 nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext,
768 nsHTMLReflowMetrics& aDesiredSize,
769 const nsHTMLReflowState& aReflowState,
770 nsReflowStatus& aStatus)
772 DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
773 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
775 ScrollReflowState state(this, aReflowState);
776 // sanity check: ensure that if we have no scrollbar, we treat it
777 // as hidden.
778 if (!mInner.mVScrollbarBox || mInner.mNeverHasVerticalScrollbar)
779 state.mStyles.mVertical = NS_STYLE_OVERFLOW_HIDDEN;
780 if (!mInner.mHScrollbarBox || mInner.mNeverHasHorizontalScrollbar)
781 state.mStyles.mHorizontal = NS_STYLE_OVERFLOW_HIDDEN;
783 //------------ Handle Incremental Reflow -----------------
784 PRBool reflowContents = PR_TRUE; // XXX Ignored
785 PRBool reflowHScrollbar = PR_TRUE;
786 PRBool reflowVScrollbar = PR_TRUE;
787 PRBool reflowScrollCorner = PR_TRUE;
788 if (!aReflowState.ShouldReflowAllKids()) {
789 #define NEEDS_REFLOW(frame_) \
790 ((frame_) && NS_SUBTREE_DIRTY(frame_))
792 reflowContents = NEEDS_REFLOW(mInner.mScrolledFrame);
793 reflowHScrollbar = NEEDS_REFLOW(mInner.mHScrollbarBox);
794 reflowVScrollbar = NEEDS_REFLOW(mInner.mVScrollbarBox);
795 reflowScrollCorner = NEEDS_REFLOW(mInner.mScrollCornerBox);
797 #undef NEEDS_REFLOW
800 nsRect oldScrollAreaBounds = mInner.mScrollPort;
801 nsRect oldScrolledAreaBounds =
802 mInner.mScrolledFrame->GetOverflowRectRelativeToParent();
803 // Adjust to a multiple of device pixels to restore the invariant that
804 // oldScrollPosition is a multiple of device pixels. This could have been
805 // thrown out by a zoom change.
806 nsIntPoint ptDevPx;
807 nsPoint oldScrollPosition = mInner.GetScrollPosition();
809 state.mComputedBorder = aReflowState.mComputedBorderPadding -
810 aReflowState.mComputedPadding;
812 nsresult rv = ReflowContents(&state, aDesiredSize);
813 if (NS_FAILED(rv))
814 return rv;
816 // Restore the old scroll position, for now, even if that's not valid anymore
817 // because we changed size. We'll fix it up in a post-reflow callback, because
818 // our current size may only be temporary (e.g. we're compute XUL desired sizes).
819 PlaceScrollArea(state, oldScrollPosition);
820 if (!mInner.mPostedReflowCallback) {
821 // Make sure we'll try scrolling to restored position
822 PresContext()->PresShell()->PostReflowCallback(&mInner);
823 mInner.mPostedReflowCallback = PR_TRUE;
826 PRBool didHaveHScrollbar = mInner.mHasHorizontalScrollbar;
827 PRBool didHaveVScrollbar = mInner.mHasVerticalScrollbar;
828 mInner.mHasHorizontalScrollbar = state.mShowHScrollbar;
829 mInner.mHasVerticalScrollbar = state.mShowVScrollbar;
830 nsRect newScrollAreaBounds = mInner.mScrollPort;
831 nsRect newScrolledAreaBounds =
832 mInner.mScrolledFrame->GetOverflowRectRelativeToParent();
833 if (mInner.mSkippedScrollbarLayout ||
834 reflowHScrollbar || reflowVScrollbar || reflowScrollCorner ||
835 (GetStateBits() & NS_FRAME_IS_DIRTY) ||
836 didHaveHScrollbar != state.mShowHScrollbar ||
837 didHaveVScrollbar != state.mShowVScrollbar ||
838 oldScrollAreaBounds != newScrollAreaBounds ||
839 oldScrolledAreaBounds != newScrolledAreaBounds) {
840 if (!mInner.mSupppressScrollbarUpdate) {
841 mInner.mSkippedScrollbarLayout = PR_FALSE;
842 mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, state.mShowHScrollbar);
843 mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, state.mShowVScrollbar);
844 // place and reflow scrollbars
845 nsRect insideBorderArea =
846 nsRect(nsPoint(state.mComputedBorder.left, state.mComputedBorder.top),
847 state.mInsideBorderSize);
848 mInner.LayoutScrollbars(state.mBoxState, insideBorderArea,
849 oldScrollAreaBounds);
850 } else {
851 mInner.mSkippedScrollbarLayout = PR_TRUE;
855 aDesiredSize.width = state.mInsideBorderSize.width +
856 state.mComputedBorder.LeftRight();
857 aDesiredSize.height = state.mInsideBorderSize.height +
858 state.mComputedBorder.TopBottom();
860 aDesiredSize.mOverflowArea = nsRect(0, 0, aDesiredSize.width, aDesiredSize.height);
862 CheckInvalidateSizeChange(aDesiredSize);
864 FinishAndStoreOverflow(&aDesiredSize);
866 if (!InInitialReflow() && !mInner.mHadNonInitialReflow) {
867 mInner.mHadNonInitialReflow = PR_TRUE;
870 if (mInner.mIsRoot && oldScrolledAreaBounds != newScrolledAreaBounds) {
871 mInner.PostScrolledAreaEvent();
874 aStatus = NS_FRAME_COMPLETE;
875 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
876 mInner.PostOverflowEvent();
877 return rv;
881 ////////////////////////////////////////////////////////////////////////////////
883 #ifdef NS_DEBUG
884 NS_IMETHODIMP
885 nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const
887 return MakeFrameName(NS_LITERAL_STRING("HTMLScroll"), aResult);
889 #endif
891 #ifdef ACCESSIBILITY
892 already_AddRefed<nsAccessible>
893 nsHTMLScrollFrame::CreateAccessible()
895 if (!IsFocusable()) {
896 return nsnull;
898 // Focusable via CSS, so needs to be in accessibility hierarchy
899 nsCOMPtr<nsIAccessibilityService> accService = do_GetService("@mozilla.org/accessibilityService;1");
901 if (accService) {
902 return accService->CreateHyperTextAccessible(mContent,
903 PresContext()->PresShell());
906 return nsnull;
908 #endif
910 NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
911 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
912 NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
913 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
914 NS_QUERYFRAME_TAIL_INHERITING(nsHTMLContainerFrame)
916 //----------nsXULScrollFrame-------------------------------------------
918 nsIFrame*
919 NS_NewXULScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRBool aIsRoot)
921 return new (aPresShell) nsXULScrollFrame(aPresShell, aContext, aIsRoot);
924 NS_IMPL_FRAMEARENA_HELPERS(nsXULScrollFrame)
926 nsXULScrollFrame::nsXULScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, PRBool aIsRoot)
927 : nsBoxFrame(aShell, aContext, aIsRoot),
928 mInner(this, aIsRoot, PR_TRUE)
930 SetLayoutManager(nsnull);
933 nsMargin nsGfxScrollFrameInner::GetDesiredScrollbarSizes(nsBoxLayoutState* aState) {
934 NS_ASSERTION(aState && aState->GetRenderingContext(),
935 "Must have rendering context in layout state for size "
936 "computations");
938 nsMargin result(0, 0, 0, 0);
940 if (mVScrollbarBox) {
941 nsSize size = mVScrollbarBox->GetPrefSize(*aState);
942 nsBox::AddMargin(mVScrollbarBox, size);
943 if (IsScrollbarOnRight())
944 result.left = size.width;
945 else
946 result.right = size.width;
949 if (mHScrollbarBox) {
950 nsSize size = mHScrollbarBox->GetPrefSize(*aState);
951 nsBox::AddMargin(mHScrollbarBox, size);
952 // We don't currently support any scripts that would require a scrollbar
953 // at the top. (Are there any?)
954 result.bottom = size.height;
957 return result;
960 nsresult
961 nsXULScrollFrame::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
963 return mInner.CreateAnonymousContent(aElements);
966 void
967 nsXULScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements)
969 mInner.AppendAnonymousContentTo(aElements);
972 void
973 nsXULScrollFrame::DestroyFrom(nsIFrame* aDestructRoot)
975 mInner.Destroy();
976 nsBoxFrame::DestroyFrom(aDestructRoot);
979 NS_IMETHODIMP
980 nsXULScrollFrame::SetInitialChildList(nsIAtom* aListName,
981 nsFrameList& aChildList)
983 nsresult rv = nsBoxFrame::SetInitialChildList(aListName, aChildList);
984 mInner.ReloadChildFrames();
985 return rv;
989 NS_IMETHODIMP
990 nsXULScrollFrame::AppendFrames(nsIAtom* aListName,
991 nsFrameList& aFrameList)
993 nsresult rv = nsBoxFrame::AppendFrames(aListName, aFrameList);
994 mInner.ReloadChildFrames();
995 return rv;
998 NS_IMETHODIMP
999 nsXULScrollFrame::InsertFrames(nsIAtom* aListName,
1000 nsIFrame* aPrevFrame,
1001 nsFrameList& aFrameList)
1003 nsresult rv = nsBoxFrame::InsertFrames(aListName, aPrevFrame, aFrameList);
1004 mInner.ReloadChildFrames();
1005 return rv;
1008 NS_IMETHODIMP
1009 nsXULScrollFrame::RemoveFrame(nsIAtom* aListName,
1010 nsIFrame* aOldFrame)
1012 nsresult rv = nsBoxFrame::RemoveFrame(aListName, aOldFrame);
1013 mInner.ReloadChildFrames();
1014 return rv;
1017 nsSplittableType
1018 nsXULScrollFrame::GetSplittableType() const
1020 return NS_FRAME_NOT_SPLITTABLE;
1023 NS_IMETHODIMP
1024 nsXULScrollFrame::GetPadding(nsMargin& aMargin)
1026 aMargin.SizeTo(0,0,0,0);
1027 return NS_OK;
1030 PRIntn
1031 nsXULScrollFrame::GetSkipSides() const
1033 return 0;
1036 nsIAtom*
1037 nsXULScrollFrame::GetType() const
1039 return nsGkAtoms::scrollFrame;
1042 void
1043 nsXULScrollFrame::InvalidateInternal(const nsRect& aDamageRect,
1044 nscoord aX, nscoord aY, nsIFrame* aForChild,
1045 PRUint32 aFlags)
1047 if (aForChild == mInner.mScrolledFrame) {
1048 // restrict aDamageRect to the scrollable view's bounds
1049 nsRect damage = aDamageRect + nsPoint(aX, aY) +
1050 GetScrollPosition() - mInner.mScrollPosAtLastPaint;
1051 nsRect r;
1052 if (r.IntersectRect(damage, mInner.mScrollPort)) {
1053 nsBoxFrame::InvalidateInternal(r, 0, 0, aForChild, aFlags);
1055 return;
1058 nsBoxFrame::InvalidateInternal(aDamageRect, aX, aY, aForChild, aFlags);
1061 nscoord
1062 nsXULScrollFrame::GetBoxAscent(nsBoxLayoutState& aState)
1064 if (!mInner.mScrolledFrame)
1065 return 0;
1067 nscoord ascent = mInner.mScrolledFrame->GetBoxAscent(aState);
1068 nsMargin m(0,0,0,0);
1069 GetBorderAndPadding(m);
1070 ascent += m.top;
1071 GetMargin(m);
1072 ascent += m.top;
1074 return ascent;
1077 nsSize
1078 nsXULScrollFrame::GetPrefSize(nsBoxLayoutState& aState)
1080 #ifdef DEBUG_LAYOUT
1081 PropagateDebug(aState);
1082 #endif
1084 nsSize pref = mInner.mScrolledFrame->GetPrefSize(aState);
1086 nsGfxScrollFrameInner::ScrollbarStyles styles = GetScrollbarStyles();
1088 // scrolled frames don't have their own margins
1090 if (mInner.mVScrollbarBox &&
1091 styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1092 nsSize vSize = mInner.mVScrollbarBox->GetPrefSize(aState);
1093 nsBox::AddMargin(mInner.mVScrollbarBox, vSize);
1094 pref.width += vSize.width;
1097 if (mInner.mHScrollbarBox &&
1098 styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1099 nsSize hSize = mInner.mHScrollbarBox->GetPrefSize(aState);
1100 nsBox::AddMargin(mInner.mHScrollbarBox, hSize);
1101 pref.height += hSize.height;
1104 AddBorderAndPadding(pref);
1105 PRBool widthSet, heightSet;
1106 nsIBox::AddCSSPrefSize(this, pref, widthSet, heightSet);
1107 return pref;
1110 nsSize
1111 nsXULScrollFrame::GetMinSize(nsBoxLayoutState& aState)
1113 #ifdef DEBUG_LAYOUT
1114 PropagateDebug(aState);
1115 #endif
1117 nsSize min = mInner.mScrolledFrame->GetMinSizeForScrollArea(aState);
1119 nsGfxScrollFrameInner::ScrollbarStyles styles = GetScrollbarStyles();
1121 if (mInner.mVScrollbarBox &&
1122 styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1123 nsSize vSize = mInner.mVScrollbarBox->GetMinSize(aState);
1124 AddMargin(mInner.mVScrollbarBox, vSize);
1125 min.width += vSize.width;
1126 if (min.height < vSize.height)
1127 min.height = vSize.height;
1130 if (mInner.mHScrollbarBox &&
1131 styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1132 nsSize hSize = mInner.mHScrollbarBox->GetMinSize(aState);
1133 AddMargin(mInner.mHScrollbarBox, hSize);
1134 min.height += hSize.height;
1135 if (min.width < hSize.width)
1136 min.width = hSize.width;
1139 AddBorderAndPadding(min);
1140 PRBool widthSet, heightSet;
1141 nsIBox::AddCSSMinSize(aState, this, min, widthSet, heightSet);
1142 return min;
1145 nsSize
1146 nsXULScrollFrame::GetMaxSize(nsBoxLayoutState& aState)
1148 #ifdef DEBUG_LAYOUT
1149 PropagateDebug(aState);
1150 #endif
1152 nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
1154 AddBorderAndPadding(maxSize);
1155 PRBool widthSet, heightSet;
1156 nsIBox::AddCSSMaxSize(this, maxSize, widthSet, heightSet);
1157 return maxSize;
1160 #if 0 // XXXldb I don't think this is even needed
1161 /* virtual */ nscoord
1162 nsXULScrollFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
1164 nsStyleUnit widthUnit = GetStylePosition()->mWidth.GetUnit();
1165 if (widthUnit == eStyleUnit_Percent || widthUnit == eStyleUnit_Auto) {
1166 nsMargin border = aReflowState.mComputedBorderPadding;
1167 aDesiredSize.mMaxElementWidth = border.right + border.left;
1168 mMaxElementWidth = aDesiredSize.mMaxElementWidth;
1169 } else {
1170 NS_NOTYETIMPLEMENTED("Use the info from the scrolled frame");
1171 #if 0
1172 // if not set then use the cached size. If set then set it.
1173 if (aDesiredSize.mMaxElementWidth == -1)
1174 aDesiredSize.mMaxElementWidth = mMaxElementWidth;
1175 else
1176 mMaxElementWidth = aDesiredSize.mMaxElementWidth;
1177 #endif
1179 return 0;
1181 #endif
1183 #ifdef NS_DEBUG
1184 NS_IMETHODIMP
1185 nsXULScrollFrame::GetFrameName(nsAString& aResult) const
1187 return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult);
1189 #endif
1191 NS_IMETHODIMP
1192 nsXULScrollFrame::DoLayout(nsBoxLayoutState& aState)
1194 PRUint32 flags = aState.LayoutFlags();
1195 nsresult rv = Layout(aState);
1196 aState.SetLayoutFlags(flags);
1198 nsBox::DoLayout(aState);
1199 return rv;
1202 NS_QUERYFRAME_HEAD(nsXULScrollFrame)
1203 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
1204 NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
1205 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
1206 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
1208 //-------------------- Inner ----------------------
1210 #define SMOOTH_SCROLL_MSECS_PER_FRAME 10
1211 #define SMOOTH_SCROLL_FRAMES 10
1213 #define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll"
1215 class nsGfxScrollFrameInner::AsyncScroll {
1216 public:
1217 AsyncScroll() {}
1218 ~AsyncScroll() {
1219 if (mScrollTimer) mScrollTimer->Cancel();
1222 nsCOMPtr<nsITimer> mScrollTimer;
1223 PRInt32 mVelocities[SMOOTH_SCROLL_FRAMES*2];
1224 PRInt32 mFrameIndex;
1225 PRPackedBool mIsSmoothScroll;
1228 static void ComputeVelocities(PRInt32 aCurVelocity, nscoord aCurPos, nscoord aDstPos,
1229 PRInt32* aVelocities, PRInt32 aP2A)
1231 // scrolling always works in units of whole pixels. So compute velocities
1232 // in pixels and then scale them up. This ensures, for example, that
1233 // a 1-pixel scroll isn't broken into N frames of 1/N pixels each, each
1234 // frame increment being rounded to 0 whole pixels.
1235 aCurPos = NSAppUnitsToIntPixels(aCurPos, aP2A);
1236 aDstPos = NSAppUnitsToIntPixels(aDstPos, aP2A);
1238 PRInt32 i;
1239 PRInt32 direction = (aCurPos < aDstPos ? 1 : -1);
1240 PRInt32 absDelta = (aDstPos - aCurPos)*direction;
1241 PRInt32 baseVelocity = absDelta/SMOOTH_SCROLL_FRAMES;
1243 for (i = 0; i < SMOOTH_SCROLL_FRAMES; i++) {
1244 aVelocities[i*2] = baseVelocity;
1246 nscoord total = baseVelocity*SMOOTH_SCROLL_FRAMES;
1247 for (i = 0; i < SMOOTH_SCROLL_FRAMES; i++) {
1248 if (total < absDelta) {
1249 aVelocities[i*2]++;
1250 total++;
1253 NS_ASSERTION(total == absDelta, "Invalid velocity sum");
1255 PRInt32 scale = NSIntPixelsToAppUnits(direction, aP2A);
1256 for (i = 0; i < SMOOTH_SCROLL_FRAMES; i++) {
1257 aVelocities[i*2] *= scale;
1261 static PRBool
1262 IsSmoothScrollingEnabled()
1264 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
1265 if (prefs) {
1266 PRBool enabled;
1267 nsresult rv = prefs->GetBoolPref(SMOOTH_SCROLL_PREF_NAME, &enabled);
1268 if (NS_SUCCEEDED(rv)) {
1269 return enabled;
1272 return PR_FALSE;
1275 class ScrollFrameActivityTracker : public nsExpirationTracker<nsGfxScrollFrameInner,4> {
1276 public:
1277 // Wait for 75-100ms between scrolls before we switch the appearance back to
1278 // subpixel AA. That's 4 generations of 25ms each.
1279 enum { TIMEOUT_MS = 25 };
1280 ScrollFrameActivityTracker()
1281 : nsExpirationTracker<nsGfxScrollFrameInner,4>(TIMEOUT_MS) {}
1282 ~ScrollFrameActivityTracker() {
1283 AgeAllGenerations();
1286 virtual void NotifyExpired(nsGfxScrollFrameInner *aObject) {
1287 RemoveObject(aObject);
1288 aObject->mScrollingActive = PR_FALSE;
1289 aObject->mOuter->InvalidateOverflowRect();
1293 static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nsnull;
1295 nsGfxScrollFrameInner::nsGfxScrollFrameInner(nsContainerFrame* aOuter,
1296 PRBool aIsRoot,
1297 PRBool aIsXUL)
1298 : mHScrollbarBox(nsnull),
1299 mVScrollbarBox(nsnull),
1300 mScrolledFrame(nsnull),
1301 mScrollCornerBox(nsnull),
1302 mOuter(aOuter),
1303 mAsyncScroll(nsnull),
1304 mDestination(0, 0),
1305 mScrollPosAtLastPaint(0, 0),
1306 mRestorePos(-1, -1),
1307 mLastPos(-1, -1),
1308 mNeverHasVerticalScrollbar(PR_FALSE),
1309 mNeverHasHorizontalScrollbar(PR_FALSE),
1310 mHasVerticalScrollbar(PR_FALSE),
1311 mHasHorizontalScrollbar(PR_FALSE),
1312 mFrameIsUpdatingScrollbar(PR_FALSE),
1313 mDidHistoryRestore(PR_FALSE),
1314 mIsRoot(aIsRoot),
1315 mIsXUL(aIsXUL),
1316 mSupppressScrollbarUpdate(PR_FALSE),
1317 mSkippedScrollbarLayout(PR_FALSE),
1318 mHadNonInitialReflow(PR_FALSE),
1319 mHorizontalOverflow(PR_FALSE),
1320 mVerticalOverflow(PR_FALSE),
1321 mPostedReflowCallback(PR_FALSE),
1322 mMayHaveDirtyFixedChildren(PR_FALSE),
1323 mUpdateScrollbarAttributes(PR_FALSE),
1324 mScrollingActive(PR_FALSE)
1328 nsGfxScrollFrameInner::~nsGfxScrollFrameInner()
1330 if (mActivityExpirationState.IsTracked()) {
1331 gScrollFrameActivityTracker->RemoveObject(this);
1333 if (gScrollFrameActivityTracker &&
1334 gScrollFrameActivityTracker->IsEmpty()) {
1335 delete gScrollFrameActivityTracker;
1336 gScrollFrameActivityTracker = nsnull;
1338 delete mAsyncScroll;
1341 static nscoord
1342 Clamp(nscoord aLower, nscoord aVal, nscoord aUpper)
1344 if (aVal < aLower)
1345 return aLower;
1346 if (aVal > aUpper)
1347 return aUpper;
1348 return aVal;
1351 nsPoint
1352 nsGfxScrollFrameInner::ClampScrollPosition(const nsPoint& aPt) const
1354 nsRect range = GetScrollRange();
1355 return nsPoint(Clamp(range.x, aPt.x, range.XMost()),
1356 Clamp(range.y, aPt.y, range.YMost()));
1360 * Callback function from timer used in nsGfxScrollFrameInner::ScrollTo
1362 void
1363 nsGfxScrollFrameInner::AsyncScrollCallback(nsITimer *aTimer, void* anInstance)
1365 nsGfxScrollFrameInner* self = static_cast<nsGfxScrollFrameInner*>(anInstance);
1366 if (!self || !self->mAsyncScroll)
1367 return;
1369 if (self->mAsyncScroll->mIsSmoothScroll) {
1370 // XXX this is crappy, the scroll position needs to be based on the
1371 // current time
1372 NS_ASSERTION(self->mAsyncScroll->mFrameIndex < SMOOTH_SCROLL_FRAMES,
1373 "Past last frame?");
1374 nscoord* velocities =
1375 &self->mAsyncScroll->mVelocities[self->mAsyncScroll->mFrameIndex*2];
1376 nsPoint destination =
1377 self->GetScrollPosition() + nsPoint(velocities[0], velocities[1]);
1379 self->mAsyncScroll->mFrameIndex++;
1380 if (self->mAsyncScroll->mFrameIndex >= SMOOTH_SCROLL_FRAMES) {
1381 delete self->mAsyncScroll;
1382 self->mAsyncScroll = nsnull;
1385 self->ScrollToImpl(destination);
1386 } else {
1387 delete self->mAsyncScroll;
1388 self->mAsyncScroll = nsnull;
1390 self->ScrollToImpl(self->mDestination);
1395 * this method wraps calls to ScrollToImpl(), either in one shot or incrementally,
1396 * based on the setting of the smooth scroll pref
1398 void
1399 nsGfxScrollFrameInner::ScrollTo(nsPoint aScrollPosition,
1400 nsIScrollableFrame::ScrollMode aMode)
1402 mDestination = ClampScrollPosition(aScrollPosition);
1404 if (aMode == nsIScrollableFrame::INSTANT) {
1405 // Asynchronous scrolling is not allowed, so we'll kill any existing
1406 // async-scrolling process and do an instant scroll
1407 delete mAsyncScroll;
1408 mAsyncScroll = nsnull;
1409 ScrollToImpl(mDestination);
1410 return;
1413 PRInt32 currentVelocityX = 0;
1414 PRInt32 currentVelocityY = 0;
1415 PRBool isSmoothScroll = IsSmoothScrollingEnabled();
1417 if (mAsyncScroll) {
1418 if (mAsyncScroll->mIsSmoothScroll) {
1419 currentVelocityX = mAsyncScroll->mVelocities[mAsyncScroll->mFrameIndex*2];
1420 currentVelocityY = mAsyncScroll->mVelocities[mAsyncScroll->mFrameIndex*2 + 1];
1422 } else {
1423 mAsyncScroll = new AsyncScroll;
1424 if (mAsyncScroll) {
1425 mAsyncScroll->mScrollTimer = do_CreateInstance("@mozilla.org/timer;1");
1426 if (!mAsyncScroll->mScrollTimer) {
1427 delete mAsyncScroll;
1428 mAsyncScroll = nsnull;
1431 if (!mAsyncScroll) {
1432 // some allocation failed. Scroll the normal way.
1433 ScrollToImpl(mDestination);
1434 return;
1436 if (isSmoothScroll) {
1437 mAsyncScroll->mScrollTimer->InitWithFuncCallback(
1438 AsyncScrollCallback, this, SMOOTH_SCROLL_MSECS_PER_FRAME,
1439 nsITimer::TYPE_REPEATING_PRECISE);
1440 } else {
1441 mAsyncScroll->mScrollTimer->InitWithFuncCallback(
1442 AsyncScrollCallback, this, 0, nsITimer::TYPE_ONE_SHOT);
1446 mAsyncScroll->mFrameIndex = 0;
1447 mAsyncScroll->mIsSmoothScroll = isSmoothScroll;
1449 if (isSmoothScroll) {
1450 PRInt32 p2a = mOuter->PresContext()->AppUnitsPerDevPixel();
1452 // compute velocity vectors
1453 nsPoint currentPos = GetScrollPosition();
1454 ComputeVelocities(currentVelocityX, currentPos.x, mDestination.x,
1455 mAsyncScroll->mVelocities, p2a);
1456 ComputeVelocities(currentVelocityY, currentPos.y, mDestination.y,
1457 mAsyncScroll->mVelocities + 1, p2a);
1461 static void InvalidateWidgets(nsIView* aView)
1463 if (aView->HasWidget()) {
1464 nsIWidget* widget = aView->GetWidget();
1465 nsWindowType type;
1466 widget->GetWindowType(type);
1467 if (type != eWindowType_popup) {
1468 // Force the widget and everything in it to repaint. We can't
1469 // just use Invalidate because the widget might have child
1470 // widgets and they wouldn't get updated. We can't call
1471 // UpdateView(aView) because the area to be repainted might be
1472 // outside aView's clipped bounds. This isn't the greatest way
1473 // to achieve this, perhaps, but it works.
1474 widget->Show(PR_FALSE);
1475 widget->Show(PR_TRUE);
1477 return;
1480 for (nsIView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
1481 InvalidateWidgets(v);
1485 // We can't use nsContainerFrame::PositionChildViews here because
1486 // we don't want to invalidate views that have moved.
1487 // aInvalidateWidgets is set to true if we should invalidate the area
1488 // covered by every widget in the subtree.
1489 static void AdjustViewsAndWidgets(nsIFrame* aFrame,
1490 PRBool aInvalidateWidgets)
1492 nsIView* view = aFrame->GetView();
1493 if (view) {
1494 nsPoint pt;
1495 aFrame->GetParent()->GetClosestView(&pt);
1496 pt += aFrame->GetPosition();
1497 view->SetPosition(pt.x, pt.y);
1499 if (aInvalidateWidgets) {
1500 InvalidateWidgets(view);
1502 return;
1505 if (!(aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
1506 return;
1509 nsIAtom* childListName = nsnull;
1510 PRInt32 childListIndex = 0;
1511 do {
1512 // Recursively walk aFrame's child frames
1513 nsIFrame* childFrame = aFrame->GetFirstChild(childListName);
1514 while (childFrame) {
1515 AdjustViewsAndWidgets(childFrame, aInvalidateWidgets);
1517 // Get the next sibling child frame
1518 childFrame = childFrame->GetNextSibling();
1521 // also process the additional child lists, but skip the popup list as the
1522 // views for popups are not scrolled.
1523 do {
1524 childListName = aFrame->GetAdditionalChildListName(childListIndex++);
1525 } while (childListName == nsGkAtoms::popupList);
1526 } while (childListName);
1529 static PRBool
1530 CanScrollWithBlitting(nsIFrame* aFrame, nsIFrame* aDisplayRoot)
1532 for (nsIFrame* f = aFrame; f;
1533 f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
1534 if (f->GetStyleDisplay()->HasTransform()) {
1535 return PR_FALSE;
1537 #ifdef MOZ_SVG
1538 if (nsSVGIntegrationUtils::UsingEffectsForFrame(f) ||
1539 f->IsFrameOfType(nsIFrame::eSVG)) {
1540 return PR_FALSE;
1542 #endif
1543 if (f == aDisplayRoot)
1544 break;
1546 return PR_TRUE;
1549 static void
1550 InvalidateFixedBackgroundFramesFromList(nsDisplayListBuilder* aBuilder,
1551 const nsDisplayList& aList)
1553 for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) {
1554 nsDisplayList* sublist = item->GetList();
1555 if (sublist) {
1556 InvalidateFixedBackgroundFramesFromList(aBuilder, *sublist);
1557 continue;
1559 nsIFrame* f = item->GetUnderlyingFrame();
1560 if (f && aBuilder->IsMovingFrame(f) &&
1561 item->IsVaryingRelativeToMovingFrame(aBuilder)) {
1562 if (item->IsFixedAndCoveringViewport(aBuilder)) {
1563 // FrameLayerBuilder takes care of scrolling these
1564 } else {
1565 f->Invalidate(item->GetVisibleRect() - aBuilder->ToReferenceFrame(f));
1571 static void
1572 InvalidateFixedBackgroundFrames(nsIFrame* aRootFrame,
1573 nsIFrame* aMovingFrame,
1574 const nsRect& aUpdateRect)
1576 if (!aMovingFrame->PresContext()->MayHaveFixedBackgroundFrames())
1577 return;
1579 NS_ASSERTION(aRootFrame != aMovingFrame,
1580 "The root frame shouldn't be the one that's moving, that makes no sense");
1582 // Build the 'after' display list over the whole area of interest.
1583 nsDisplayListBuilder builder(aRootFrame, PR_FALSE, PR_TRUE);
1584 builder.EnterPresShell(aRootFrame, aUpdateRect);
1585 builder.SetMovingFrame(aMovingFrame);
1586 nsDisplayList list;
1587 nsresult rv =
1588 aRootFrame->BuildDisplayListForStackingContext(&builder, aUpdateRect, &list);
1589 builder.LeavePresShell(aRootFrame, aUpdateRect);
1590 if (NS_FAILED(rv))
1591 return;
1593 nsRegion visibleRegion(aUpdateRect);
1594 list.ComputeVisibility(&builder, &visibleRegion, nsnull);
1596 InvalidateFixedBackgroundFramesFromList(&builder, list);
1597 list.DeleteAll();
1600 PRBool nsGfxScrollFrameInner::IsAlwaysActive() const
1602 // The root scrollframe for a non-chrome document which is the direct
1603 // child of a chrome document is always treated as "active".
1604 // XXX maybe we should extend this so that IFRAMEs which are fill the
1605 // entire viewport (like GMail!) are always active
1606 return mIsRoot &&
1607 !nsContentUtils::IsChildOfSameType(mOuter->GetContent()->GetCurrentDoc());
1610 PRBool nsGfxScrollFrameInner::IsScrollingActive() const
1612 return mScrollingActive || IsAlwaysActive();
1615 void nsGfxScrollFrameInner::MarkActive()
1617 if (IsAlwaysActive())
1618 return;
1620 mScrollingActive = PR_TRUE;
1621 if (mActivityExpirationState.IsTracked()) {
1622 gScrollFrameActivityTracker->MarkUsed(this);
1623 } else {
1624 if (!gScrollFrameActivityTracker) {
1625 gScrollFrameActivityTracker = new ScrollFrameActivityTracker();
1627 gScrollFrameActivityTracker->AddObject(this);
1631 void nsGfxScrollFrameInner::ScrollVisual(nsIntPoint aPixDelta)
1633 nsRootPresContext* rootPresContext = mOuter->PresContext()->GetRootPresContext();
1634 if (!rootPresContext) {
1635 return;
1638 rootPresContext->RequestUpdatePluginGeometry(mOuter);
1640 AdjustViewsAndWidgets(mScrolledFrame, PR_FALSE);
1641 // We need to call this after fixing up the widget and view positions
1642 // to be consistent with the view and frame hierarchy.
1643 PRUint32 flags = nsIFrame::INVALIDATE_REASON_SCROLL_REPAINT;
1644 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(mOuter);
1645 if (IsScrollingActive() && CanScrollWithBlitting(mOuter, displayRoot)) {
1646 flags |= nsIFrame::INVALIDATE_NO_THEBES_LAYERS;
1648 MarkActive();
1649 mOuter->InvalidateWithFlags(mScrollPort, flags);
1651 if (flags & nsIFrame::INVALIDATE_NO_THEBES_LAYERS) {
1652 nsRect update =
1653 GetScrollPortRect() + mOuter->GetOffsetToCrossDoc(displayRoot);
1654 update = update.ConvertAppUnitsRoundOut(
1655 mOuter->PresContext()->AppUnitsPerDevPixel(),
1656 displayRoot->PresContext()->AppUnitsPerDevPixel());
1657 InvalidateFixedBackgroundFrames(displayRoot, mScrolledFrame, update);
1661 static PRInt32
1662 ClampInt(nscoord aLower, nscoord aVal, nscoord aUpper, nscoord aAppUnitsPerPixel)
1664 PRInt32 low = NSToIntCeil(float(aLower)/aAppUnitsPerPixel);
1665 PRInt32 high = NSToIntFloor(float(aUpper)/aAppUnitsPerPixel);
1666 PRInt32 v = NSToIntRound(float(aVal)/aAppUnitsPerPixel);
1667 NS_ASSERTION(low <= high, "No integers in range; 0 is supposed to be in range");
1668 if (v < low)
1669 return low;
1670 if (v > high)
1671 return high;
1672 return v;
1675 nsPoint
1676 nsGfxScrollFrameInner::ClampAndRestrictToDevPixels(const nsPoint& aPt,
1677 nsIntPoint* aPtDevPx) const
1679 nsPresContext* presContext = mOuter->PresContext();
1680 nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
1681 // Convert to device pixels so we scroll to an integer offset of device
1682 // pixels. But we also need to make sure that our position remains
1683 // inside the allowed region.
1684 nsRect scrollRange = GetScrollRange();
1685 *aPtDevPx = nsIntPoint(ClampInt(scrollRange.x, aPt.x, scrollRange.XMost(), appUnitsPerDevPixel),
1686 ClampInt(scrollRange.y, aPt.y, scrollRange.YMost(), appUnitsPerDevPixel));
1687 return nsPoint(NSIntPixelsToAppUnits(aPtDevPx->x, appUnitsPerDevPixel),
1688 NSIntPixelsToAppUnits(aPtDevPx->y, appUnitsPerDevPixel));
1691 void
1692 nsGfxScrollFrameInner::ScrollToImpl(nsPoint aPt)
1694 nsPresContext* presContext = mOuter->PresContext();
1695 nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
1696 nsIntPoint ptDevPx;
1697 nsPoint pt = ClampAndRestrictToDevPixels(aPt, &ptDevPx);
1699 nsPoint curPos = GetScrollPosition();
1700 if (pt == curPos) {
1701 return;
1703 nsIntPoint curPosDevPx(NSAppUnitsToIntPixels(curPos.x, appUnitsPerDevPixel),
1704 NSAppUnitsToIntPixels(curPos.y, appUnitsPerDevPixel));
1705 // We maintain the invariant that the scroll position is a multiple of device
1706 // pixels.
1707 NS_ASSERTION(curPosDevPx.x*appUnitsPerDevPixel == curPos.x,
1708 "curPos.x not a multiple of device pixels");
1709 NS_ASSERTION(curPosDevPx.y*appUnitsPerDevPixel == curPos.y,
1710 "curPos.y not a multiple of device pixels");
1712 // notify the listeners.
1713 for (PRUint32 i = 0; i < mListeners.Length(); i++) {
1714 mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
1717 // Update frame position for scrolling
1718 mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
1720 // We pass in the amount to move visually
1721 ScrollVisual(curPosDevPx - ptDevPx);
1723 presContext->PresShell()->GetViewManager()->SynthesizeMouseMove(PR_TRUE);
1724 UpdateScrollbarPosition();
1725 PostScrollEvent();
1727 // notify the listeners.
1728 for (PRUint32 i = 0; i < mListeners.Length(); i++) {
1729 mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
1733 static void
1734 AppendToTop(nsDisplayListBuilder* aBuilder, nsDisplayList* aDest,
1735 nsDisplayList* aSource, nsIFrame* aSourceFrame, PRBool aOwnLayer)
1737 if (aOwnLayer) {
1738 aDest->AppendNewToTop(new (aBuilder) nsDisplayOwnLayer(aSourceFrame, aSource));
1739 } else {
1740 aDest->AppendToTop(aSource);
1744 nsresult
1745 nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1746 const nsRect& aDirtyRect,
1747 const nsDisplayListSet& aLists)
1749 nsresult rv = mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
1750 NS_ENSURE_SUCCESS(rv, rv);
1752 if (aBuilder->IsPaintingToWindow()) {
1753 mScrollPosAtLastPaint = GetScrollPosition();
1756 if (aBuilder->GetIgnoreScrollFrame() == mOuter) {
1757 // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
1758 // The scrolled frame shouldn't have its own background/border, so we
1759 // can just pass aLists directly.
1760 return mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame,
1761 aDirtyRect, aLists);
1764 // Now display the scrollbars and scrollcorner. These parts are drawn
1765 // in the border-background layer, on top of our own background and
1766 // borders and underneath borders and backgrounds of later elements
1767 // in the tree.
1768 PRBool hasResizer = HasResizer();
1769 nsDisplayListCollection scrollParts;
1770 // We put scrollbars in their own layers when this is the root scroll
1771 // frame and we are a toplevel content document. In this situation, the
1772 // scrollbar(s) would normally be assigned their own layer anyway, since
1773 // they're not scrolled with the rest of the document. But when both
1774 // scrollbars are visible, the layer's visible rectangle would be the size
1775 // of the viewport, so most layer implementations would create a layer buffer
1776 // that's much larger than necessary. Creating independent layers for each
1777 // scrollbar works around the problem.
1778 PRBool createLayersForScrollbars = mIsRoot &&
1779 !nsContentUtils::IsChildOfSameType(mOuter->GetContent()->GetCurrentDoc());
1780 for (nsIFrame* kid = mOuter->GetFirstChild(nsnull); kid; kid = kid->GetNextSibling()) {
1781 if (kid != mScrolledFrame) {
1782 if (kid == mScrollCornerBox && hasResizer) {
1783 // skip the resizer as this will be drawn later on top of the scrolled content
1784 continue;
1786 rv = mOuter->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, scrollParts,
1787 nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
1788 NS_ENSURE_SUCCESS(rv, rv);
1789 // DISPLAY_CHILD_FORCE_STACKING_CONTEXT put everything into the
1790 // PositionedDescendants list.
1791 ::AppendToTop(aBuilder, aLists.BorderBackground(),
1792 scrollParts.PositionedDescendants(), kid,
1793 createLayersForScrollbars);
1798 // Overflow clipping can never clip frames outside our subtree, so there
1799 // is no need to worry about whether we are a moving frame that might clip
1800 // non-moving frames.
1801 nsRect dirtyRect;
1802 // Not all our descendants will be clipped by overflow clipping, but all
1803 // the ones that aren't clipped will be out of flow frames that have already
1804 // had dirty rects saved for them by their parent frames calling
1805 // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
1806 // dirty rect here.
1807 dirtyRect.IntersectRect(aDirtyRect, mScrollPort);
1809 nsDisplayListCollection set;
1810 rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
1811 NS_ENSURE_SUCCESS(rv, rv);
1812 nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
1813 // mScrolledFrame may have given us a background, e.g., the scrolled canvas
1814 // frame below the viewport. If so, we want it to be clipped. We also want
1815 // to end up on our BorderBackground list.
1816 // If we are the viewport scrollframe, then clip all our descendants (to ensure
1817 // that fixed-pos elements get clipped by us).
1818 rv = mOuter->OverflowClip(aBuilder, set, aLists, clip, PR_TRUE, mIsRoot);
1819 NS_ENSURE_SUCCESS(rv, rv);
1821 // Place the resizer in the display list in our Content() list above
1822 // scrolled content in the Content() list.
1823 // This ensures that the resizer appears above the content and the mouse can
1824 // still target the resizer even when scrollbars are hidden.
1825 if (hasResizer && mScrollCornerBox) {
1826 rv = mOuter->BuildDisplayListForChild(aBuilder, mScrollCornerBox, aDirtyRect, scrollParts,
1827 nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
1828 NS_ENSURE_SUCCESS(rv, rv);
1829 // DISPLAY_CHILD_FORCE_STACKING_CONTEXT puts everything into the
1830 // PositionedDescendants list.
1831 ::AppendToTop(aBuilder, aLists.Content(),
1832 scrollParts.PositionedDescendants(), mScrollCornerBox,
1833 createLayersForScrollbars);
1836 return NS_OK;
1839 static void HandleScrollPref(nsIScrollable *aScrollable, PRInt32 aOrientation,
1840 PRUint8& aValue)
1842 PRInt32 pref;
1843 aScrollable->GetDefaultScrollbarPreferences(aOrientation, &pref);
1844 switch (pref) {
1845 case nsIScrollable::Scrollbar_Auto:
1846 // leave |aValue| untouched
1847 break;
1848 case nsIScrollable::Scrollbar_Never:
1849 aValue = NS_STYLE_OVERFLOW_HIDDEN;
1850 break;
1851 case nsIScrollable::Scrollbar_Always:
1852 aValue = NS_STYLE_OVERFLOW_SCROLL;
1853 break;
1857 nsGfxScrollFrameInner::ScrollbarStyles
1858 nsGfxScrollFrameInner::GetScrollbarStylesFromFrame() const
1860 ScrollbarStyles result;
1862 nsPresContext* presContext = mOuter->PresContext();
1863 if (!presContext->IsDynamic() &&
1864 !(mIsRoot && presContext->HasPaginatedScrolling())) {
1865 return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN);
1868 if (mIsRoot) {
1869 result = presContext->GetViewportOverflowOverride();
1871 nsCOMPtr<nsISupports> container = presContext->GetContainer();
1872 nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(container);
1873 if (scrollable) {
1874 HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_X,
1875 result.mHorizontal);
1876 HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_Y,
1877 result.mVertical);
1879 } else {
1880 const nsStyleDisplay *disp = mOuter->GetStyleDisplay();
1881 result.mHorizontal = disp->mOverflowX;
1882 result.mVertical = disp->mOverflowY;
1885 NS_ASSERTION(result.mHorizontal != NS_STYLE_OVERFLOW_VISIBLE &&
1886 result.mHorizontal != NS_STYLE_OVERFLOW_CLIP &&
1887 result.mVertical != NS_STYLE_OVERFLOW_VISIBLE &&
1888 result.mVertical != NS_STYLE_OVERFLOW_CLIP,
1889 "scrollbars should not have been created");
1890 return result;
1893 static nscoord
1894 AlignToDevPixelRoundingToZero(nscoord aVal, PRInt32 aAppUnitsPerDevPixel)
1896 return (aVal/aAppUnitsPerDevPixel)*aAppUnitsPerDevPixel;
1899 nsRect
1900 nsGfxScrollFrameInner::GetScrollRange() const
1902 nsRect range = GetScrolledRect();
1903 range.width -= mScrollPort.width;
1904 range.height -= mScrollPort.height;
1906 nsPresContext* presContext = mOuter->PresContext();
1907 PRInt32 appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
1908 range.width =
1909 AlignToDevPixelRoundingToZero(range.XMost(), appUnitsPerDevPixel) - range.x;
1910 range.height =
1911 AlignToDevPixelRoundingToZero(range.YMost(), appUnitsPerDevPixel) - range.y;
1912 range.x = AlignToDevPixelRoundingToZero(range.x, appUnitsPerDevPixel);
1913 range.y = AlignToDevPixelRoundingToZero(range.y, appUnitsPerDevPixel);
1914 return range;
1917 static void
1918 AdjustForWholeDelta(PRInt32 aDelta, nscoord* aCoord)
1920 if (aDelta < 0) {
1921 *aCoord = nscoord_MIN;
1922 } else if (aDelta > 0) {
1923 *aCoord = nscoord_MAX;
1927 void
1928 nsGfxScrollFrameInner::ScrollBy(nsIntPoint aDelta,
1929 nsIScrollableFrame::ScrollUnit aUnit,
1930 nsIScrollableFrame::ScrollMode aMode,
1931 nsIntPoint* aOverflow)
1933 nsSize deltaMultiplier;
1934 switch (aUnit) {
1935 case nsIScrollableFrame::DEVICE_PIXELS: {
1936 nscoord appUnitsPerDevPixel =
1937 mOuter->PresContext()->AppUnitsPerDevPixel();
1938 deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
1939 break;
1941 case nsIScrollableFrame::LINES: {
1942 deltaMultiplier = GetLineScrollAmount();
1943 break;
1945 case nsIScrollableFrame::PAGES: {
1946 deltaMultiplier = GetPageScrollAmount();
1947 break;
1949 case nsIScrollableFrame::WHOLE: {
1950 nsPoint pos = GetScrollPosition();
1951 AdjustForWholeDelta(aDelta.x, &pos.x);
1952 AdjustForWholeDelta(aDelta.y, &pos.y);
1953 ScrollTo(pos, aMode);
1954 if (aOverflow) {
1955 *aOverflow = nsIntPoint(0, 0);
1957 return;
1959 default:
1960 NS_ERROR("Invalid scroll mode");
1961 return;
1964 nsPoint newPos = mDestination +
1965 nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height);
1966 ScrollTo(newPos, aMode);
1968 if (aOverflow) {
1969 nsPoint clampAmount = mDestination - newPos;
1970 float appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
1971 *aOverflow = nsIntPoint(
1972 NSAppUnitsToIntPixels(PR_ABS(clampAmount.x), appUnitsPerDevPixel),
1973 NSAppUnitsToIntPixels(PR_ABS(clampAmount.y), appUnitsPerDevPixel));
1977 nsSize
1978 nsGfxScrollFrameInner::GetLineScrollAmount() const
1980 const nsStyleFont* font = mOuter->GetStyleFont();
1981 const nsFont& f = font->mFont;
1982 nsCOMPtr<nsIFontMetrics> fm = mOuter->PresContext()->GetMetricsFor(f);
1983 NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
1984 nscoord fontHeight = 1;
1985 if (fm) {
1986 fm->GetHeight(fontHeight);
1989 return nsSize(fontHeight, fontHeight);
1992 nsSize
1993 nsGfxScrollFrameInner::GetPageScrollAmount() const
1995 nsSize lineScrollAmount = GetLineScrollAmount();
1996 // The page increment is the size of the page, minus the smaller of
1997 // 10% of the size or 2 lines.
1998 return nsSize(
1999 mScrollPort.width - NS_MIN(mScrollPort.width/10, 2*lineScrollAmount.width),
2000 mScrollPort.height - NS_MIN(mScrollPort.height/10, 2*lineScrollAmount.height));
2004 * this code is resposible for restoring the scroll position back to some
2005 * saved position. if the user has not moved the scroll position manually
2006 * we keep scrolling down until we get to our original position. keep in
2007 * mind that content could incrementally be coming in. we only want to stop
2008 * when we reach our new position.
2010 void
2011 nsGfxScrollFrameInner::ScrollToRestoredPosition()
2013 if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
2014 return;
2016 // make sure our scroll position did not change for where we last put
2017 // it. if it does then the user must have moved it, and we no longer
2018 // need to restore.
2019 nsPoint scrollPos = GetScrollPosition();
2021 // if we didn't move, we still need to restore
2022 if (scrollPos == mLastPos) {
2023 // if our desired position is different to the scroll position, scroll.
2024 // remember that we could be incrementally loading so we may enter
2025 // and scroll many times.
2026 if (mRestorePos != scrollPos) {
2027 ScrollTo(mRestorePos, nsIScrollableFrame::INSTANT);
2028 // Re-get the scroll position, it might not be exactly equal to
2029 // mRestorePos due to rounding and clamping.
2030 mLastPos = GetScrollPosition();
2031 } else {
2032 // if we reached the position then stop
2033 mRestorePos.y = -1;
2034 mLastPos.x = -1;
2035 mLastPos.y = -1;
2037 } else {
2038 // user moved the position, so we won't need to restore
2039 mLastPos.x = -1;
2040 mLastPos.y = -1;
2044 nsresult
2045 nsGfxScrollFrameInner::FireScrollPortEvent()
2047 mAsyncScrollPortEvent.Forget();
2049 // Keep this in sync with PostOverflowEvent().
2050 nsSize scrollportSize = mScrollPort.Size();
2051 nsSize childSize = GetScrolledRect().Size();
2053 PRBool newVerticalOverflow = childSize.height > scrollportSize.height;
2054 PRBool vertChanged = mVerticalOverflow != newVerticalOverflow;
2056 PRBool newHorizontalOverflow = childSize.width > scrollportSize.width;
2057 PRBool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
2059 if (!vertChanged && !horizChanged) {
2060 return NS_OK;
2063 // If both either overflowed or underflowed then we dispatch only one
2064 // DOM event.
2065 PRBool both = vertChanged && horizChanged &&
2066 newVerticalOverflow == newHorizontalOverflow;
2067 nsScrollPortEvent::orientType orient;
2068 if (both) {
2069 orient = nsScrollPortEvent::both;
2070 mHorizontalOverflow = newHorizontalOverflow;
2071 mVerticalOverflow = newVerticalOverflow;
2073 else if (vertChanged) {
2074 orient = nsScrollPortEvent::vertical;
2075 mVerticalOverflow = newVerticalOverflow;
2076 if (horizChanged) {
2077 // We need to dispatch a separate horizontal DOM event. Do that the next
2078 // time around since dispatching the vertical DOM event might destroy
2079 // the frame.
2080 PostOverflowEvent();
2083 else {
2084 orient = nsScrollPortEvent::horizontal;
2085 mHorizontalOverflow = newHorizontalOverflow;
2088 nsScrollPortEvent event(PR_TRUE,
2089 (orient == nsScrollPortEvent::horizontal ?
2090 mHorizontalOverflow : mVerticalOverflow) ?
2091 NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW,
2092 nsnull);
2093 event.orient = orient;
2094 return nsEventDispatcher::Dispatch(mOuter->GetContent(),
2095 mOuter->PresContext(), &event);
2098 void
2099 nsGfxScrollFrameInner::ReloadChildFrames()
2101 mScrolledFrame = nsnull;
2102 mHScrollbarBox = nsnull;
2103 mVScrollbarBox = nsnull;
2104 mScrollCornerBox = nsnull;
2106 nsIFrame* frame = mOuter->GetFirstChild(nsnull);
2107 while (frame) {
2108 nsIContent* content = frame->GetContent();
2109 if (content == mOuter->GetContent()) {
2110 NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
2111 mScrolledFrame = frame;
2112 } else {
2113 nsAutoString value;
2114 content->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, value);
2115 if (!value.IsEmpty()) {
2116 // probably a scrollbar then
2117 if (value.LowerCaseEqualsLiteral("horizontal")) {
2118 NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?");
2119 mHScrollbarBox = frame;
2120 } else {
2121 NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
2122 mVScrollbarBox = frame;
2124 } else {
2125 // probably a scrollcorner
2126 NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
2127 mScrollCornerBox = frame;
2131 frame = frame->GetNextSibling();
2135 nsresult
2136 nsGfxScrollFrameInner::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
2138 nsPresContext* presContext = mOuter->PresContext();
2139 nsIFrame* parent = mOuter->GetParent();
2141 // Don't create scrollbars if we're printing/print previewing
2142 // Get rid of this code when printing moves to its own presentation
2143 if (!presContext->IsDynamic()) {
2144 // allow scrollbars if this is the child of the viewport, because
2145 // we must be the scrollbars for the print preview window
2146 if (!(mIsRoot && presContext->HasPaginatedScrolling())) {
2147 mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = PR_TRUE;
2148 return NS_OK;
2152 // Check if the frame is resizable.
2153 PRInt8 resizeStyle = mOuter->GetStyleDisplay()->mResize;
2154 PRBool isResizable = resizeStyle != NS_STYLE_RESIZE_NONE;
2156 nsIScrollableFrame *scrollable = do_QueryFrame(mOuter);
2158 // If we're the scrollframe for the root, then we want to construct
2159 // our scrollbar frames no matter what. That way later dynamic
2160 // changes to propagated overflow styles will show or hide
2161 // scrollbars on the viewport without requiring frame reconstruction
2162 // of the viewport (good!).
2163 PRBool canHaveHorizontal;
2164 PRBool canHaveVertical;
2165 if (!mIsRoot) {
2166 ScrollbarStyles styles = scrollable->GetScrollbarStyles();
2167 canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
2168 canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
2169 if (!canHaveHorizontal && !canHaveVertical && !isResizable) {
2170 // Nothing to do.
2171 return NS_OK;
2173 } else {
2174 canHaveHorizontal = PR_TRUE;
2175 canHaveVertical = PR_TRUE;
2178 // The anonymous <div> used by <inputs> never gets scrollbars.
2179 nsITextControlFrame* textFrame = do_QueryFrame(parent);
2180 if (textFrame) {
2181 // Make sure we are not a text area.
2182 nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement(do_QueryInterface(parent->GetContent()));
2183 if (!textAreaElement) {
2184 mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = PR_TRUE;
2185 return NS_OK;
2189 nsresult rv;
2191 nsNodeInfoManager *nodeInfoManager =
2192 presContext->Document()->NodeInfoManager();
2193 nsCOMPtr<nsINodeInfo> nodeInfo;
2194 nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbar, nsnull,
2195 kNameSpaceID_XUL);
2196 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
2198 if (canHaveHorizontal) {
2199 nsCOMPtr<nsINodeInfo> ni = nodeInfo;
2200 rv = NS_NewElement(getter_AddRefs(mHScrollbarContent),
2201 kNameSpaceID_XUL, ni.forget(), PR_FALSE);
2202 NS_ENSURE_SUCCESS(rv, rv);
2203 mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
2204 NS_LITERAL_STRING("horizontal"), PR_FALSE);
2205 mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
2206 NS_LITERAL_STRING("always"), PR_FALSE);
2207 if (!aElements.AppendElement(mHScrollbarContent))
2208 return NS_ERROR_OUT_OF_MEMORY;
2211 if (canHaveVertical) {
2212 nsCOMPtr<nsINodeInfo> ni = nodeInfo;
2213 rv = NS_NewElement(getter_AddRefs(mVScrollbarContent),
2214 kNameSpaceID_XUL, ni.forget(), PR_FALSE);
2215 NS_ENSURE_SUCCESS(rv, rv);
2216 mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
2217 NS_LITERAL_STRING("vertical"), PR_FALSE);
2218 mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
2219 NS_LITERAL_STRING("always"), PR_FALSE);
2220 if (!aElements.AppendElement(mVScrollbarContent))
2221 return NS_ERROR_OUT_OF_MEMORY;
2224 if (isResizable) {
2225 nsCOMPtr<nsINodeInfo> nodeInfo;
2226 nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nsnull,
2227 kNameSpaceID_XUL);
2228 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
2230 rv = NS_NewXULElement(getter_AddRefs(mScrollCornerContent),
2231 nodeInfo.forget());
2232 NS_ENSURE_SUCCESS(rv, rv);
2234 nsAutoString dir;
2235 switch (resizeStyle) {
2236 case NS_STYLE_RESIZE_HORIZONTAL:
2237 if (IsScrollbarOnRight()) {
2238 dir.AssignLiteral("right");
2240 else {
2241 dir.AssignLiteral("left");
2243 break;
2244 case NS_STYLE_RESIZE_VERTICAL:
2245 dir.AssignLiteral("bottom");
2246 break;
2247 case NS_STYLE_RESIZE_BOTH:
2248 dir.AssignLiteral("bottomend");
2249 break;
2250 default:
2251 NS_WARNING("only resizable types should have resizers");
2253 mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, PR_FALSE);
2254 mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element,
2255 NS_LITERAL_STRING("_parent"), PR_FALSE);
2256 mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
2257 NS_LITERAL_STRING("always"), PR_FALSE);
2259 if (!aElements.AppendElement(mScrollCornerContent))
2260 return NS_ERROR_OUT_OF_MEMORY;
2262 else if (canHaveHorizontal && canHaveVertical) {
2263 nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nsnull,
2264 kNameSpaceID_XUL);
2265 rv = NS_NewElement(getter_AddRefs(mScrollCornerContent),
2266 kNameSpaceID_XUL, nodeInfo.forget(), PR_FALSE);
2267 NS_ENSURE_SUCCESS(rv, rv);
2268 if (!aElements.AppendElement(mScrollCornerContent))
2269 return NS_ERROR_OUT_OF_MEMORY;
2272 return NS_OK;
2275 void
2276 nsGfxScrollFrameInner::AppendAnonymousContentTo(nsBaseContentList& aElements)
2278 aElements.MaybeAppendElement(mHScrollbarContent);
2279 aElements.MaybeAppendElement(mVScrollbarContent);
2280 aElements.MaybeAppendElement(mScrollCornerContent);
2283 void
2284 nsGfxScrollFrameInner::Destroy()
2286 // Unbind any content created in CreateAnonymousContent from the tree
2287 nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent);
2288 nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent);
2289 nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent);
2291 if (mPostedReflowCallback) {
2292 mOuter->PresContext()->PresShell()->CancelReflowCallback(this);
2293 mPostedReflowCallback = PR_FALSE;
2298 * Called when we want to update the scrollbar position, either because scrolling happened
2299 * or the user moved the scrollbar position and we need to undo that (e.g., when the user
2300 * clicks to scroll and we're using smooth scrolling, so we need to put the thumb back
2301 * to its initial position for the start of the smooth sequence).
2303 void
2304 nsGfxScrollFrameInner::UpdateScrollbarPosition()
2306 mFrameIsUpdatingScrollbar = PR_TRUE;
2308 nsPoint pt = GetScrollPosition();
2309 if (mVScrollbarBox) {
2310 SetCoordAttribute(mVScrollbarBox->GetContent(), nsGkAtoms::curpos,
2311 pt.y - GetScrolledRect().y);
2313 if (mHScrollbarBox) {
2314 SetCoordAttribute(mHScrollbarBox->GetContent(), nsGkAtoms::curpos,
2315 pt.x - GetScrolledRect().x);
2318 mFrameIsUpdatingScrollbar = PR_FALSE;
2321 void nsGfxScrollFrameInner::CurPosAttributeChanged(nsIContent* aContent)
2323 NS_ASSERTION(aContent, "aContent must not be null");
2324 NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
2325 (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
2326 "unexpected child");
2328 // Attribute changes on the scrollbars happen in one of three ways:
2329 // 1) The scrollbar changed the attribute in response to some user event
2330 // 2) We changed the attribute in response to a ScrollPositionDidChange
2331 // callback from the scrolling view
2332 // 3) We changed the attribute to adjust the scrollbars for the start
2333 // of a smooth scroll operation
2335 // In cases 2 and 3 we do not need to scroll because we're just
2336 // updating our scrollbar.
2337 if (mFrameIsUpdatingScrollbar)
2338 return;
2340 nsRect scrolledRect = GetScrolledRect();
2342 nscoord x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos,
2343 -scrolledRect.x) +
2344 scrolledRect.x;
2345 nscoord y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos,
2346 -scrolledRect.y) +
2347 scrolledRect.y;
2349 PRBool isSmooth = aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth);
2350 if (isSmooth) {
2351 // Make sure an attribute-setting callback occurs even if the view
2352 // didn't actually move yet. We need to make sure other listeners
2353 // see that the scroll position is not (yet) what they thought it
2354 // was.
2355 UpdateScrollbarPosition();
2357 ScrollTo(nsPoint(x, y),
2358 isSmooth ? nsIScrollableFrame::SMOOTH : nsIScrollableFrame::INSTANT);
2361 /* ============= Scroll events ========== */
2363 NS_IMETHODIMP
2364 nsGfxScrollFrameInner::ScrollEvent::Run()
2366 if (mInner)
2367 mInner->FireScrollEvent();
2368 return NS_OK;
2371 void
2372 nsGfxScrollFrameInner::FireScrollEvent()
2374 mScrollEvent.Forget();
2376 nsScrollbarEvent event(PR_TRUE, NS_SCROLL_EVENT, nsnull);
2377 nsEventStatus status = nsEventStatus_eIgnore;
2378 nsIContent* content = mOuter->GetContent();
2379 nsPresContext* prescontext = mOuter->PresContext();
2380 // Fire viewport scroll events at the document (where they
2381 // will bubble to the window)
2382 if (mIsRoot) {
2383 nsIDocument* doc = content->GetCurrentDoc();
2384 if (doc) {
2385 nsEventDispatcher::Dispatch(doc, prescontext, &event, nsnull, &status);
2387 } else {
2388 // scroll events fired at elements don't bubble (although scroll events
2389 // fired at documents do, to the window)
2390 event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
2391 nsEventDispatcher::Dispatch(content, prescontext, &event, nsnull, &status);
2395 void
2396 nsGfxScrollFrameInner::PostScrollEvent()
2398 if (mScrollEvent.IsPending())
2399 return;
2401 nsRefPtr<ScrollEvent> ev = new ScrollEvent(this);
2402 if (NS_FAILED(NS_DispatchToCurrentThread(ev))) {
2403 NS_WARNING("failed to dispatch ScrollEvent");
2404 } else {
2405 mScrollEvent = ev;
2409 NS_IMETHODIMP
2410 nsGfxScrollFrameInner::AsyncScrollPortEvent::Run()
2412 if (mInner) {
2413 mInner->mOuter->PresContext()->GetPresShell()->
2414 FlushPendingNotifications(Flush_InterruptibleLayout);
2416 return mInner ? mInner->FireScrollPortEvent() : NS_OK;
2419 PRBool
2420 nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState, PRBool aOnTop)
2422 if (!mInner.mHScrollbarBox)
2423 return PR_TRUE;
2425 return AddRemoveScrollbar(aState, aOnTop, PR_TRUE, PR_TRUE);
2428 PRBool
2429 nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState, PRBool aOnRight)
2431 if (!mInner.mVScrollbarBox)
2432 return PR_TRUE;
2434 return AddRemoveScrollbar(aState, aOnRight, PR_FALSE, PR_TRUE);
2437 void
2438 nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState, PRBool aOnTop)
2440 // removing a scrollbar should always fit
2441 #ifdef DEBUG
2442 PRBool result =
2443 #endif
2444 AddRemoveScrollbar(aState, aOnTop, PR_TRUE, PR_FALSE);
2445 NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??");
2448 void
2449 nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState, PRBool aOnRight)
2451 // removing a scrollbar should always fit
2452 #ifdef DEBUG
2453 PRBool result =
2454 #endif
2455 AddRemoveScrollbar(aState, aOnRight, PR_FALSE, PR_FALSE);
2456 NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??");
2459 PRBool
2460 nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState,
2461 PRBool aOnTop, PRBool aHorizontal, PRBool aAdd)
2463 if (aHorizontal) {
2464 if (mInner.mNeverHasHorizontalScrollbar || !mInner.mHScrollbarBox)
2465 return PR_FALSE;
2467 nsSize hSize = mInner.mHScrollbarBox->GetPrefSize(aState);
2468 nsBox::AddMargin(mInner.mHScrollbarBox, hSize);
2470 mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, aAdd);
2472 PRBool hasHorizontalScrollbar;
2473 PRBool fit = AddRemoveScrollbar(hasHorizontalScrollbar,
2474 mInner.mScrollPort.y,
2475 mInner.mScrollPort.height,
2476 hSize.height, aOnTop, aAdd);
2477 mInner.mHasHorizontalScrollbar = hasHorizontalScrollbar; // because mHasHorizontalScrollbar is a PRPackedBool
2478 if (!fit)
2479 mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, !aAdd);
2481 return fit;
2482 } else {
2483 if (mInner.mNeverHasVerticalScrollbar || !mInner.mVScrollbarBox)
2484 return PR_FALSE;
2486 nsSize vSize = mInner.mVScrollbarBox->GetPrefSize(aState);
2487 nsBox::AddMargin(mInner.mVScrollbarBox, vSize);
2489 mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, aAdd);
2491 PRBool hasVerticalScrollbar;
2492 PRBool fit = AddRemoveScrollbar(hasVerticalScrollbar,
2493 mInner.mScrollPort.x,
2494 mInner.mScrollPort.width,
2495 vSize.width, aOnTop, aAdd);
2496 mInner.mHasVerticalScrollbar = hasVerticalScrollbar; // because mHasVerticalScrollbar is a PRPackedBool
2497 if (!fit)
2498 mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, !aAdd);
2500 return fit;
2504 PRBool
2505 nsXULScrollFrame::AddRemoveScrollbar(PRBool& aHasScrollbar, nscoord& aXY,
2506 nscoord& aSize, nscoord aSbSize,
2507 PRBool aRightOrBottom, PRBool aAdd)
2509 nscoord size = aSize;
2510 nscoord xy = aXY;
2512 if (size != NS_INTRINSICSIZE) {
2513 if (aAdd) {
2514 size -= aSbSize;
2515 if (!aRightOrBottom && size >= 0)
2516 xy += aSbSize;
2517 } else {
2518 size += aSbSize;
2519 if (!aRightOrBottom)
2520 xy -= aSbSize;
2524 // not enough room? Yes? Return true.
2525 if (size >= 0) {
2526 aHasScrollbar = aAdd;
2527 aSize = size;
2528 aXY = xy;
2529 return PR_TRUE;
2532 aHasScrollbar = PR_FALSE;
2533 return PR_FALSE;
2536 void
2537 nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState,
2538 const nsPoint& aScrollPosition)
2540 PRUint32 oldflags = aState.LayoutFlags();
2541 nsRect childRect = nsRect(mInner.mScrollPort.TopLeft() - aScrollPosition,
2542 mInner.mScrollPort.Size());
2543 PRInt32 flags = NS_FRAME_NO_MOVE_VIEW;
2545 nsRect originalRect = mInner.mScrolledFrame->GetRect();
2546 nsRect originalOverflow = mInner.mScrolledFrame->GetOverflowRect();
2548 nsSize minSize = mInner.mScrolledFrame->GetMinSize(aState);
2550 if (minSize.height > childRect.height)
2551 childRect.height = minSize.height;
2553 if (minSize.width > childRect.width)
2554 childRect.width = minSize.width;
2556 aState.SetLayoutFlags(flags);
2557 mInner.mScrolledFrame->SetBounds(aState, childRect);
2558 mInner.mScrolledFrame->Layout(aState);
2560 childRect = mInner.mScrolledFrame->GetRect();
2562 if (childRect.width < mInner.mScrollPort.width ||
2563 childRect.height < mInner.mScrollPort.height)
2565 childRect.width = NS_MAX(childRect.width, mInner.mScrollPort.width);
2566 childRect.height = NS_MAX(childRect.height, mInner.mScrollPort.height);
2568 // remove overflow area when we update the bounds,
2569 // because we've already accounted for it
2570 mInner.mScrolledFrame->SetBounds(aState, childRect);
2571 mInner.mScrolledFrame->ClearOverflowRect();
2574 nsRect finalRect = mInner.mScrolledFrame->GetRect();
2575 nsRect finalOverflow = mInner.mScrolledFrame->GetOverflowRect();
2576 // The position of the scrolled frame shouldn't change, but if it does or
2577 // the position of the overflow rect changes just invalidate both the old
2578 // and new overflow rect.
2579 if (originalRect.TopLeft() != finalRect.TopLeft() ||
2580 originalOverflow.TopLeft() != finalOverflow.TopLeft())
2582 // The old overflow rect needs to be adjusted if the frame's position
2583 // changed.
2584 mInner.mScrolledFrame->Invalidate(
2585 originalOverflow + originalRect.TopLeft() - finalRect.TopLeft());
2586 mInner.mScrolledFrame->Invalidate(finalOverflow);
2587 } else if (!originalOverflow.IsExactEqual(finalOverflow)) {
2588 // If the overflow rect changed then invalidate the difference between the
2589 // old and new overflow rects.
2590 mInner.mScrolledFrame->CheckInvalidateSizeChange(
2591 originalRect, originalOverflow, finalRect.Size());
2592 mInner.mScrolledFrame->InvalidateRectDifference(
2593 originalOverflow, finalOverflow);
2596 aState.SetLayoutFlags(oldflags);
2600 void nsGfxScrollFrameInner::PostOverflowEvent()
2602 if (mAsyncScrollPortEvent.IsPending())
2603 return;
2605 // Keep this in sync with FireScrollPortEvent().
2606 nsSize scrollportSize = mScrollPort.Size();
2607 nsSize childSize = GetScrolledRect().Size();
2609 PRBool newVerticalOverflow = childSize.height > scrollportSize.height;
2610 PRBool vertChanged = mVerticalOverflow != newVerticalOverflow;
2612 PRBool newHorizontalOverflow = childSize.width > scrollportSize.width;
2613 PRBool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
2615 if (!vertChanged && !horizChanged) {
2616 return;
2619 nsRefPtr<AsyncScrollPortEvent> ev = new AsyncScrollPortEvent(this);
2620 if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev)))
2621 mAsyncScrollPortEvent = ev;
2624 PRBool
2625 nsGfxScrollFrameInner::IsLTR() const
2627 //TODO make bidi code set these from preferences
2629 nsIFrame *frame = mOuter;
2630 // XXX This is a bit on the slow side.
2631 if (mIsRoot) {
2632 // If we're the root scrollframe, we need the root element's style data.
2633 nsPresContext *presContext = mOuter->PresContext();
2634 nsIDocument *document = presContext->Document();
2635 Element *root = document->GetRootElement();
2637 // But for HTML and XHTML we want the body element.
2638 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
2639 if (htmlDoc) {
2640 Element *bodyElement = document->GetBodyElement();
2641 if (bodyElement)
2642 root = bodyElement; // we can trust the document to hold on to it
2645 if (root) {
2646 nsIFrame *rootsFrame = root->GetPrimaryFrame();
2647 if (rootsFrame)
2648 frame = rootsFrame;
2652 return frame->GetStyleVisibility()->mDirection != NS_STYLE_DIRECTION_RTL;
2655 PRBool
2656 nsGfxScrollFrameInner::IsScrollbarOnRight() const
2658 nsPresContext *presContext = mOuter->PresContext();
2660 // The position of the scrollbar in top-level windows depends on the pref
2661 // layout.scrollbar.side. For non-top-level elements, it depends only on the
2662 // directionaliy of the element (equivalent to a value of "1" for the pref).
2663 if (!mIsRoot)
2664 return IsLTR();
2665 switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) {
2666 default:
2667 case 0: // UI directionality
2668 return presContext->GetCachedIntPref(kPresContext_BidiDirection)
2669 == IBMBIDI_TEXTDIRECTION_LTR;
2670 case 1: // Document / content directionality
2671 return IsLTR();
2672 case 2: // Always right
2673 return PR_TRUE;
2674 case 3: // Always left
2675 return PR_FALSE;
2680 * Reflow the scroll area if it needs it and return its size. Also determine if the reflow will
2681 * cause any of the scrollbars to need to be reflowed.
2683 nsresult
2684 nsXULScrollFrame::Layout(nsBoxLayoutState& aState)
2686 PRBool scrollbarRight = mInner.IsScrollbarOnRight();
2687 PRBool scrollbarBottom = PR_TRUE;
2689 // get the content rect
2690 nsRect clientRect(0,0,0,0);
2691 GetClientRect(clientRect);
2693 nsRect oldScrollAreaBounds = mInner.mScrollPort;
2694 nsPoint oldScrollPosition = mInner.GetScrollPosition();
2696 // the scroll area size starts off as big as our content area
2697 mInner.mScrollPort = clientRect;
2699 /**************
2700 Our basic strategy here is to first try laying out the content with
2701 the scrollbars in their current state. We're hoping that that will
2702 just "work"; the content will overflow wherever there's a scrollbar
2703 already visible. If that does work, then there's no need to lay out
2704 the scrollarea. Otherwise we fix up the scrollbars; first we add a
2705 vertical one to scroll the content if necessary, or remove it if
2706 it's not needed. Then we reflow the content if the scrollbar
2707 changed. Then we add a horizontal scrollbar if necessary (or
2708 remove if not needed), and if that changed, we reflow the content
2709 again. At this point, any scrollbars that are needed to scroll the
2710 content have been added.
2712 In the second phase we check to see if any scrollbars are too small
2713 to display, and if so, we remove them. We check the horizontal
2714 scrollbar first; removing it might make room for the vertical
2715 scrollbar, and if we have room for just one scrollbar we'll save
2716 the vertical one.
2718 Finally we position and size the scrollbars and scrollcorner (the
2719 square that is needed in the corner of the window when two
2720 scrollbars are visible), and reflow any fixed position views
2721 (if we're the viewport and we added or removed a scrollbar).
2722 **************/
2724 ScrollbarStyles styles = GetScrollbarStyles();
2726 // Look at our style do we always have vertical or horizontal scrollbars?
2727 if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL)
2728 mInner.mHasHorizontalScrollbar = PR_TRUE;
2729 if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL)
2730 mInner.mHasVerticalScrollbar = PR_TRUE;
2732 if (mInner.mHasHorizontalScrollbar)
2733 AddHorizontalScrollbar(aState, scrollbarBottom);
2735 if (mInner.mHasVerticalScrollbar)
2736 AddVerticalScrollbar(aState, scrollbarRight);
2738 // layout our the scroll area
2739 LayoutScrollArea(aState, oldScrollPosition);
2741 // now look at the content area and see if we need scrollbars or not
2742 PRBool needsLayout = PR_FALSE;
2744 // if we have 'auto' scrollbars look at the vertical case
2745 if (styles.mVertical != NS_STYLE_OVERFLOW_SCROLL) {
2746 // These are only good until the call to LayoutScrollArea.
2747 nsRect scrolledRect = mInner.GetScrolledRect();
2748 nsSize scrolledContentSize(scrolledRect.XMost(), scrolledRect.YMost());
2750 // There are two cases to consider
2751 if (scrolledContentSize.height <= mInner.mScrollPort.height
2752 || styles.mVertical != NS_STYLE_OVERFLOW_AUTO) {
2753 if (mInner.mHasVerticalScrollbar) {
2754 // We left room for the vertical scrollbar, but it's not needed;
2755 // remove it.
2756 RemoveVerticalScrollbar(aState, scrollbarRight);
2757 needsLayout = PR_TRUE;
2759 } else {
2760 if (!mInner.mHasVerticalScrollbar) {
2761 // We didn't leave room for the vertical scrollbar, but it turns
2762 // out we needed it
2763 if (AddVerticalScrollbar(aState, scrollbarRight))
2764 needsLayout = PR_TRUE;
2768 // ok layout at the right size
2769 if (needsLayout) {
2770 nsBoxLayoutState resizeState(aState);
2771 LayoutScrollArea(resizeState, oldScrollPosition);
2772 needsLayout = PR_FALSE;
2777 // if scrollbars are auto look at the horizontal case
2778 if (styles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL)
2780 // These are only good until the call to LayoutScrollArea.
2781 nsRect scrolledRect = mInner.GetScrolledRect();
2782 nsSize scrolledContentSize(scrolledRect.XMost(), scrolledRect.YMost());
2784 // if the child is wider that the scroll area
2785 // and we don't have a scrollbar add one.
2786 if (scrolledContentSize.width > mInner.mScrollPort.width
2787 && styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) {
2789 if (!mInner.mHasHorizontalScrollbar) {
2790 // no scrollbar?
2791 if (AddHorizontalScrollbar(aState, scrollbarBottom))
2792 needsLayout = PR_TRUE;
2794 // if we added a horizontal scrollbar and we did not have a vertical
2795 // there is a chance that by adding the horizontal scrollbar we will
2796 // suddenly need a vertical scrollbar. Is a special case but its
2797 // important.
2798 //if (!mHasVerticalScrollbar && scrolledContentSize.height > scrollAreaRect.height - sbSize.height)
2799 // printf("****Gfx Scrollbar Special case hit!!*****\n");
2802 } else {
2803 // if the area is smaller or equal to and we have a scrollbar then
2804 // remove it.
2805 if (mInner.mHasHorizontalScrollbar) {
2806 RemoveHorizontalScrollbar(aState, scrollbarBottom);
2807 needsLayout = PR_TRUE;
2812 // we only need to set the rect. The inner child stays the same size.
2813 if (needsLayout) {
2814 nsBoxLayoutState resizeState(aState);
2815 LayoutScrollArea(resizeState, oldScrollPosition);
2816 needsLayout = PR_FALSE;
2819 // get the preferred size of the scrollbars
2820 nsSize hMinSize(0, 0);
2821 if (mInner.mHScrollbarBox && mInner.mHasHorizontalScrollbar) {
2822 GetScrollbarMetrics(aState, mInner.mHScrollbarBox, &hMinSize, nsnull, PR_FALSE);
2824 nsSize vMinSize(0, 0);
2825 if (mInner.mVScrollbarBox && mInner.mHasVerticalScrollbar) {
2826 GetScrollbarMetrics(aState, mInner.mVScrollbarBox, &vMinSize, nsnull, PR_TRUE);
2829 // Disable scrollbars that are too small
2830 // Disable horizontal scrollbar first. If we have to disable only one
2831 // scrollbar, we'd rather keep the vertical scrollbar.
2832 // Note that we always give horizontal scrollbars their preferred height,
2833 // never their min-height. So check that there's room for the preferred height.
2834 if (mInner.mHasHorizontalScrollbar &&
2835 (hMinSize.width > clientRect.width - vMinSize.width
2836 || hMinSize.height > clientRect.height)) {
2837 RemoveHorizontalScrollbar(aState, scrollbarBottom);
2838 needsLayout = PR_TRUE;
2840 // Now disable vertical scrollbar if necessary
2841 if (mInner.mHasVerticalScrollbar &&
2842 (vMinSize.height > clientRect.height - hMinSize.height
2843 || vMinSize.width > clientRect.width)) {
2844 RemoveVerticalScrollbar(aState, scrollbarRight);
2845 needsLayout = PR_TRUE;
2848 // we only need to set the rect. The inner child stays the same size.
2849 if (needsLayout) {
2850 nsBoxLayoutState resizeState(aState);
2851 LayoutScrollArea(resizeState, oldScrollPosition);
2854 if (!mInner.mSupppressScrollbarUpdate) {
2855 mInner.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds);
2857 if (!mInner.mPostedReflowCallback) {
2858 // Make sure we'll try scrolling to restored position
2859 PresContext()->PresShell()->PostReflowCallback(&mInner);
2860 mInner.mPostedReflowCallback = PR_TRUE;
2862 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
2863 mInner.mHadNonInitialReflow = PR_TRUE;
2866 mInner.PostOverflowEvent();
2867 return NS_OK;
2870 void
2871 nsGfxScrollFrameInner::FinishReflowForScrollbar(nsIContent* aContent,
2872 nscoord aMinXY, nscoord aMaxXY,
2873 nscoord aCurPosXY,
2874 nscoord aPageIncrement,
2875 nscoord aIncrement)
2877 // Scrollbars assume zero is the minimum position, so translate for them.
2878 SetCoordAttribute(aContent, nsGkAtoms::curpos, aCurPosXY - aMinXY);
2879 SetScrollbarEnabled(aContent, aMaxXY - aMinXY);
2880 SetCoordAttribute(aContent, nsGkAtoms::maxpos, aMaxXY - aMinXY);
2881 SetCoordAttribute(aContent, nsGkAtoms::pageincrement, aPageIncrement);
2882 SetCoordAttribute(aContent, nsGkAtoms::increment, aIncrement);
2885 PRBool
2886 nsGfxScrollFrameInner::ReflowFinished()
2888 mPostedReflowCallback = PR_FALSE;
2890 ScrollToRestoredPosition();
2892 // Clamp scroll position
2893 ScrollToImpl(GetScrollPosition());
2895 if (NS_SUBTREE_DIRTY(mOuter) || !mUpdateScrollbarAttributes)
2896 return PR_FALSE;
2898 mUpdateScrollbarAttributes = PR_FALSE;
2900 // Update scrollbar attributes.
2901 nsPresContext* presContext = mOuter->PresContext();
2903 if (mMayHaveDirtyFixedChildren) {
2904 mMayHaveDirtyFixedChildren = PR_FALSE;
2905 nsIFrame* parentFrame = mOuter->GetParent();
2906 for (nsIFrame* fixedChild =
2907 parentFrame->GetFirstChild(nsGkAtoms::fixedList);
2908 fixedChild; fixedChild = fixedChild->GetNextSibling()) {
2909 // force a reflow of the fixed child
2910 presContext->PresShell()->
2911 FrameNeedsReflow(fixedChild, nsIPresShell::eResize,
2912 NS_FRAME_HAS_DIRTY_CHILDREN);
2916 nsRect scrolledContentRect = GetScrolledRect();
2917 nscoord minX = scrolledContentRect.x;
2918 nscoord maxX = scrolledContentRect.XMost() - mScrollPort.width;
2919 nscoord minY = scrolledContentRect.y;
2920 nscoord maxY = scrolledContentRect.YMost() - mScrollPort.height;
2922 // Suppress handling of the curpos attribute changes we make here.
2923 NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here");
2924 mFrameIsUpdatingScrollbar = PR_TRUE;
2926 nsCOMPtr<nsIContent> vScroll =
2927 mVScrollbarBox ? mVScrollbarBox->GetContent() : nsnull;
2928 nsCOMPtr<nsIContent> hScroll =
2929 mHScrollbarBox ? mHScrollbarBox->GetContent() : nsnull;
2931 // Note, in some cases mOuter may get deleted while finishing reflow
2932 // for scrollbars.
2933 if (vScroll || hScroll) {
2934 nsWeakFrame weakFrame(mOuter);
2935 nsPoint scrollPos = GetScrollPosition();
2936 // XXX shouldn't we use GetPageScrollAmount/GetLineScrollAmount here?
2937 if (vScroll) {
2938 nscoord fontHeight = GetLineScrollAmount().height;
2939 // We normally use (scrollArea.height - fontHeight) for height
2940 // of page scrolling. However, it is too small when
2941 // fontHeight is very large. (If fontHeight is larger than
2942 // scrollArea.height, direction of scrolling will be opposite).
2943 // To avoid it, we use (float(scrollArea.height) * 0.8) as
2944 // lower bound value of height of page scrolling. (bug 383267)
2945 nscoord pageincrement = nscoord(mScrollPort.height - fontHeight);
2946 nscoord pageincrementMin = nscoord(float(mScrollPort.height) * 0.8);
2947 FinishReflowForScrollbar(vScroll, minY, maxY, scrollPos.y,
2948 NS_MAX(pageincrement,pageincrementMin),
2949 fontHeight);
2951 if (hScroll) {
2952 FinishReflowForScrollbar(hScroll, minX, maxX, scrollPos.x,
2953 nscoord(float(mScrollPort.width) * 0.8),
2954 nsPresContext::CSSPixelsToAppUnits(10));
2956 NS_ENSURE_TRUE(weakFrame.IsAlive(), PR_FALSE);
2959 mFrameIsUpdatingScrollbar = PR_FALSE;
2960 // We used to rely on the curpos attribute changes above to scroll the
2961 // view. However, for scrolling to the left of the viewport, we
2962 // rescale the curpos attribute, which means that operations like
2963 // resizing the window while it is scrolled all the way to the left
2964 // hold the curpos attribute constant at 0 while still requiring
2965 // scrolling. So we suppress the effect of the changes above with
2966 // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here.
2967 // (It actually even works some of the time without this, thanks to
2968 // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
2969 // we hide the scrollbar on a large size change, such as
2970 // maximization.)
2971 if (!mHScrollbarBox && !mVScrollbarBox)
2972 return PR_FALSE;
2973 CurPosAttributeChanged(mVScrollbarBox ? mVScrollbarBox->GetContent()
2974 : mHScrollbarBox->GetContent());
2975 return PR_TRUE;
2978 void
2979 nsGfxScrollFrameInner::ReflowCallbackCanceled()
2981 mPostedReflowCallback = PR_FALSE;
2984 static void LayoutAndInvalidate(nsBoxLayoutState& aState,
2985 nsIFrame* aBox, const nsRect& aRect,
2986 PRBool aScrollbarIsBeingHidden)
2988 // When a child box changes shape of position, the parent
2989 // is responsible for invalidation; the overflow rect must be invalidated
2990 // to make sure to catch any overflow.
2991 // We invalidate the parent (i.e. the scrollframe) directly, because
2992 // invalidates coming from scrollbars are suppressed by nsHTMLScrollFrame when
2993 // mHasVScrollbar/mHasHScrollbar is false, and this is called after those
2994 // flags have been set ... if a scrollbar is being hidden, we still need
2995 // to invalidate the scrollbar area here.
2996 // But we also need to invalidate the scrollbar itself in case it has
2997 // its own layer; we need to ensure that layer is updated.
2998 PRBool rectChanged = aBox->GetRect() != aRect;
2999 if (rectChanged) {
3000 if (aScrollbarIsBeingHidden) {
3001 aBox->GetParent()->Invalidate(aBox->GetOverflowRect() + aBox->GetPosition());
3002 } else {
3003 aBox->InvalidateOverflowRect();
3006 nsBoxFrame::LayoutChildAt(aState, aBox, aRect);
3007 if (rectChanged) {
3008 if (aScrollbarIsBeingHidden) {
3009 aBox->GetParent()->Invalidate(aBox->GetOverflowRect() + aBox->GetPosition());
3010 } else {
3011 aBox->InvalidateOverflowRect();
3016 void
3017 nsGfxScrollFrameInner::AdjustScrollbarRectForResizer(
3018 nsIFrame* aFrame, nsPresContext* aPresContext,
3019 nsRect& aRect, PRBool aHasResizer, PRBool aVertical)
3021 if ((aVertical ? aRect.width : aRect.height) == 0)
3022 return;
3024 // if a content resizer is present, use its size. Otherwise, check if the
3025 // widget has a resizer.
3026 nsRect resizerRect;
3027 if (aHasResizer && mScrollCornerBox) {
3028 resizerRect = mScrollCornerBox->GetRect();
3030 else {
3031 nsPoint offset;
3032 nsIWidget* widget = aFrame->GetNearestWidget(offset);
3033 nsIntRect widgetRect;
3034 if (!widget || !widget->ShowsResizeIndicator(&widgetRect))
3035 return;
3037 resizerRect = nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
3038 aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
3039 aPresContext->DevPixelsToAppUnits(widgetRect.width),
3040 aPresContext->DevPixelsToAppUnits(widgetRect.height));
3043 if (!resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1)))
3044 return;
3046 if (aVertical)
3047 aRect.height = NS_MAX(0, resizerRect.y - aRect.y);
3048 else
3049 aRect.width = NS_MAX(0, resizerRect.x - aRect.x);
3052 void
3053 nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
3054 const nsRect& aContentArea,
3055 const nsRect& aOldScrollArea)
3057 NS_ASSERTION(!mSupppressScrollbarUpdate,
3058 "This should have been suppressed");
3060 PRBool hasResizer = HasResizer();
3061 PRBool scrollbarOnLeft = !IsScrollbarOnRight();
3063 // place the scrollcorner
3064 if (mScrollCornerBox) {
3065 NS_PRECONDITION(mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
3067 // if a resizer is present, get its size
3068 nsSize resizerSize;
3069 if (HasResizer()) {
3070 // just assume a default size of 15 pixels
3071 nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
3072 resizerSize.width =
3073 mVScrollbarBox ? mVScrollbarBox->GetMinSize(aState).width : defaultSize;
3074 resizerSize.height =
3075 mHScrollbarBox ? mHScrollbarBox->GetMinSize(aState).height : defaultSize;
3077 else {
3078 resizerSize = nsSize(0, 0);
3081 nsRect r(0, 0, 0, 0);
3082 if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
3083 // scrollbar (if any) on left
3084 r.x = aContentArea.x;
3085 r.width = PR_MAX(resizerSize.width, mScrollPort.x - aContentArea.x);
3086 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
3087 } else {
3088 // scrollbar (if any) on right
3089 r.width = PR_MAX(resizerSize.width, aContentArea.XMost() - mScrollPort.XMost());
3090 r.x = aContentArea.XMost() - r.width;
3091 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
3093 if (aContentArea.y != mScrollPort.y) {
3094 NS_ERROR("top scrollbars not supported");
3095 } else {
3096 // scrollbar (if any) on bottom
3097 r.height = PR_MAX(resizerSize.height, aContentArea.YMost() - mScrollPort.YMost());
3098 r.y = aContentArea.YMost() - r.height;
3099 NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
3101 LayoutAndInvalidate(aState, mScrollCornerBox, r, PR_FALSE);
3104 nsPresContext* presContext = mScrolledFrame->PresContext();
3105 if (mVScrollbarBox) {
3106 NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
3107 nsRect vRect(mScrollPort);
3108 vRect.width = aContentArea.width - mScrollPort.width;
3109 vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost();
3110 nsMargin margin;
3111 mVScrollbarBox->GetMargin(margin);
3112 vRect.Deflate(margin);
3113 AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, PR_TRUE);
3114 LayoutAndInvalidate(aState, mVScrollbarBox, vRect, !mHasVerticalScrollbar);
3117 if (mHScrollbarBox) {
3118 NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!");
3119 nsRect hRect(mScrollPort);
3120 hRect.height = aContentArea.height - mScrollPort.height;
3121 hRect.y = PR_TRUE ? mScrollPort.YMost() : aContentArea.y;
3122 nsMargin margin;
3123 mHScrollbarBox->GetMargin(margin);
3124 hRect.Deflate(margin);
3125 AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, PR_FALSE);
3126 LayoutAndInvalidate(aState, mHScrollbarBox, hRect, !mHasHorizontalScrollbar);
3129 // may need to update fixed position children of the viewport,
3130 // if the client area changed size because of an incremental
3131 // reflow of a descendant. (If the outer frame is dirty, the fixed
3132 // children will be re-laid out anyway)
3133 if (aOldScrollArea.Size() != mScrollPort.Size() &&
3134 !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) &&
3135 mIsRoot) {
3136 mMayHaveDirtyFixedChildren = PR_TRUE;
3139 // post reflow callback to modify scrollbar attributes
3140 mUpdateScrollbarAttributes = PR_TRUE;
3141 if (!mPostedReflowCallback) {
3142 aState.PresContext()->PresShell()->PostReflowCallback(this);
3143 mPostedReflowCallback = PR_TRUE;
3147 void
3148 nsGfxScrollFrameInner::SetScrollbarEnabled(nsIContent* aContent, nscoord aMaxPos)
3150 if (aMaxPos) {
3151 aContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, PR_TRUE);
3152 } else {
3153 aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
3154 NS_LITERAL_STRING("true"), PR_TRUE);
3158 void
3159 nsGfxScrollFrameInner::SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom,
3160 nscoord aSize)
3162 // convert to pixels
3163 aSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
3165 // only set the attribute if it changed.
3167 nsAutoString newValue;
3168 newValue.AppendInt(aSize);
3170 if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters))
3171 return;
3173 aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, PR_TRUE);
3176 nsRect
3177 nsGfxScrollFrameInner::GetScrolledRect() const
3179 nsRect result =
3180 GetScrolledRectInternal(mScrolledFrame->GetOverflowRect(),
3181 mScrollPort.Size());
3183 NS_ASSERTION(result.width >= mScrollPort.width,
3184 "Scrolled rect smaller than scrollport?");
3185 NS_ASSERTION(result.height >= mScrollPort.height,
3186 "Scrolled rect smaller than scrollport?");
3187 return result;
3190 nsRect
3191 nsGfxScrollFrameInner::GetScrolledRectInternal(const nsRect& aScrolledFrameOverflowArea,
3192 const nsSize& aScrollPortSize) const
3194 nscoord x1 = aScrolledFrameOverflowArea.x,
3195 x2 = aScrolledFrameOverflowArea.XMost(),
3196 y1 = aScrolledFrameOverflowArea.y,
3197 y2 = aScrolledFrameOverflowArea.YMost();
3198 if (y1 < 0)
3199 y1 = 0;
3200 if (IsLTR() || mIsXUL) {
3201 if (x1 < 0)
3202 x1 = 0;
3203 } else {
3204 if (x2 > aScrollPortSize.width)
3205 x2 = aScrollPortSize.width;
3206 // When the scrolled frame chooses a size larger than its available width (because
3207 // its padding alone is larger than the available width), we need to keep the
3208 // start-edge of the scroll frame anchored to the start-edge of the scrollport.
3209 // When the scrolled frame is RTL, this means moving it in our left-based
3210 // coordinate system, so we need to compensate for its extra width here by
3211 // effectively repositioning the frame.
3212 nscoord extraWidth = NS_MAX(0, mScrolledFrame->GetSize().width - aScrollPortSize.width);
3213 x2 += extraWidth;
3215 return nsRect(x1, y1, x2 - x1, y2 - y1);
3218 nsMargin
3219 nsGfxScrollFrameInner::GetActualScrollbarSizes() const
3221 nsRect r = mOuter->GetPaddingRect() - mOuter->GetPosition();
3223 return nsMargin(mScrollPort.x - r.x, mScrollPort.y - r.y,
3224 r.XMost() - mScrollPort.XMost(),
3225 r.YMost() - mScrollPort.YMost());
3228 void
3229 nsGfxScrollFrameInner::SetScrollbarVisibility(nsIBox* aScrollbar, PRBool aVisible)
3231 if (!aScrollbar)
3232 return;
3234 nsIScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar);
3235 if (scrollbar) {
3236 // See if we have a mediator.
3237 nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
3238 if (mediator) {
3239 // Inform the mediator of the visibility change.
3240 mediator->VisibilityChanged(aVisible);
3245 PRInt32
3246 nsGfxScrollFrameInner::GetCoordAttribute(nsIBox* aBox, nsIAtom* atom, PRInt32 defaultValue)
3248 if (aBox) {
3249 nsIContent* content = aBox->GetContent();
3251 nsAutoString value;
3252 content->GetAttr(kNameSpaceID_None, atom, value);
3253 if (!value.IsEmpty())
3255 PRInt32 error;
3257 // convert it to an integer
3258 defaultValue = nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
3262 return defaultValue;
3265 nsPresState*
3266 nsGfxScrollFrameInner::SaveState(nsIStatefulFrame::SpecialStateID aStateID)
3268 // Don't save "normal" state for the root scrollframe; that's
3269 // handled via the eDocumentScrollState state id
3270 if (mIsRoot && aStateID == nsIStatefulFrame::eNoID) {
3271 return nsnull;
3274 nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
3275 if (mediator) {
3276 // child handles its own scroll state, so don't bother saving state here
3277 return nsnull;
3280 nsPoint scrollPos = GetScrollPosition();
3281 // Don't save scroll position if we are at (0,0)
3282 if (scrollPos == nsPoint(0,0)) {
3283 return nsnull;
3286 nsPresState* state = new nsPresState();
3287 if (!state) {
3288 return nsnull;
3291 state->SetScrollState(scrollPos);
3293 return state;
3296 void
3297 nsGfxScrollFrameInner::RestoreState(nsPresState* aState)
3299 mRestorePos = aState->GetScrollState();
3300 mLastPos.x = -1;
3301 mLastPos.y = -1;
3302 mDidHistoryRestore = PR_TRUE;
3303 mLastPos = mScrolledFrame ? GetScrollPosition() : nsPoint(0,0);
3306 void
3307 nsGfxScrollFrameInner::PostScrolledAreaEvent()
3309 if (mScrolledAreaEvent.IsPending()) {
3310 return;
3312 mScrolledAreaEvent = new ScrolledAreaEvent(this);
3313 NS_DispatchToCurrentThread(mScrolledAreaEvent.get());
3316 ////////////////////////////////////////////////////////////////////////////////
3317 // ScrolledArea change event dispatch
3319 NS_IMETHODIMP
3320 nsGfxScrollFrameInner::ScrolledAreaEvent::Run()
3322 if (mInner) {
3323 mInner->FireScrolledAreaEvent();
3325 return NS_OK;
3328 void
3329 nsGfxScrollFrameInner::FireScrolledAreaEvent()
3331 mScrolledAreaEvent.Forget();
3333 nsScrollAreaEvent event(PR_TRUE, NS_SCROLLEDAREACHANGED, nsnull);
3334 nsPresContext *prescontext = mOuter->PresContext();
3335 nsIContent* content = mOuter->GetContent();
3337 event.mArea = mScrolledFrame->GetOverflowRectRelativeToParent();
3339 nsIDocument *doc = content->GetCurrentDoc();
3340 if (doc) {
3341 nsEventDispatcher::Dispatch(doc, prescontext, &event, nsnull);