Bug 575870 - Enable the firefox button on xp themed, classic, and aero basic. r=dao...
[mozilla-central.git] / layout / generic / nsGfxScrollFrame.cpp
blob7b92c218cda940b9ca803b1cae9ad1e36d51dc67
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 #ifdef NS_DEBUG
1161 NS_IMETHODIMP
1162 nsXULScrollFrame::GetFrameName(nsAString& aResult) const
1164 return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult);
1166 #endif
1168 NS_IMETHODIMP
1169 nsXULScrollFrame::DoLayout(nsBoxLayoutState& aState)
1171 PRUint32 flags = aState.LayoutFlags();
1172 nsresult rv = Layout(aState);
1173 aState.SetLayoutFlags(flags);
1175 nsBox::DoLayout(aState);
1176 return rv;
1179 NS_QUERYFRAME_HEAD(nsXULScrollFrame)
1180 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
1181 NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
1182 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
1183 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
1185 //-------------------- Inner ----------------------
1187 #define SMOOTH_SCROLL_MSECS_PER_FRAME 10
1188 #define SMOOTH_SCROLL_FRAMES 10
1190 #define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll"
1192 class nsGfxScrollFrameInner::AsyncScroll {
1193 public:
1194 AsyncScroll() {}
1195 ~AsyncScroll() {
1196 if (mScrollTimer) mScrollTimer->Cancel();
1199 nsCOMPtr<nsITimer> mScrollTimer;
1200 PRInt32 mVelocities[SMOOTH_SCROLL_FRAMES*2];
1201 PRInt32 mFrameIndex;
1202 PRPackedBool mIsSmoothScroll;
1205 static void ComputeVelocities(PRInt32 aCurVelocity, nscoord aCurPos, nscoord aDstPos,
1206 PRInt32* aVelocities, PRInt32 aP2A)
1208 // scrolling always works in units of whole pixels. So compute velocities
1209 // in pixels and then scale them up. This ensures, for example, that
1210 // a 1-pixel scroll isn't broken into N frames of 1/N pixels each, each
1211 // frame increment being rounded to 0 whole pixels.
1212 aCurPos = NSAppUnitsToIntPixels(aCurPos, aP2A);
1213 aDstPos = NSAppUnitsToIntPixels(aDstPos, aP2A);
1215 PRInt32 i;
1216 PRInt32 direction = (aCurPos < aDstPos ? 1 : -1);
1217 PRInt32 absDelta = (aDstPos - aCurPos)*direction;
1218 PRInt32 baseVelocity = absDelta/SMOOTH_SCROLL_FRAMES;
1220 for (i = 0; i < SMOOTH_SCROLL_FRAMES; i++) {
1221 aVelocities[i*2] = baseVelocity;
1223 nscoord total = baseVelocity*SMOOTH_SCROLL_FRAMES;
1224 for (i = 0; i < SMOOTH_SCROLL_FRAMES; i++) {
1225 if (total < absDelta) {
1226 aVelocities[i*2]++;
1227 total++;
1230 NS_ASSERTION(total == absDelta, "Invalid velocity sum");
1232 PRInt32 scale = NSIntPixelsToAppUnits(direction, aP2A);
1233 for (i = 0; i < SMOOTH_SCROLL_FRAMES; i++) {
1234 aVelocities[i*2] *= scale;
1238 static PRBool
1239 IsSmoothScrollingEnabled()
1241 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
1242 if (prefs) {
1243 PRBool enabled;
1244 nsresult rv = prefs->GetBoolPref(SMOOTH_SCROLL_PREF_NAME, &enabled);
1245 if (NS_SUCCEEDED(rv)) {
1246 return enabled;
1249 return PR_FALSE;
1252 class ScrollFrameActivityTracker : public nsExpirationTracker<nsGfxScrollFrameInner,4> {
1253 public:
1254 // Wait for 75-100ms between scrolls before we switch the appearance back to
1255 // subpixel AA. That's 4 generations of 25ms each.
1256 enum { TIMEOUT_MS = 25 };
1257 ScrollFrameActivityTracker()
1258 : nsExpirationTracker<nsGfxScrollFrameInner,4>(TIMEOUT_MS) {}
1259 ~ScrollFrameActivityTracker() {
1260 AgeAllGenerations();
1263 virtual void NotifyExpired(nsGfxScrollFrameInner *aObject) {
1264 RemoveObject(aObject);
1265 aObject->mScrollingActive = PR_FALSE;
1266 aObject->mOuter->InvalidateOverflowRect();
1270 static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nsnull;
1272 nsGfxScrollFrameInner::nsGfxScrollFrameInner(nsContainerFrame* aOuter,
1273 PRBool aIsRoot,
1274 PRBool aIsXUL)
1275 : mHScrollbarBox(nsnull),
1276 mVScrollbarBox(nsnull),
1277 mScrolledFrame(nsnull),
1278 mScrollCornerBox(nsnull),
1279 mOuter(aOuter),
1280 mAsyncScroll(nsnull),
1281 mDestination(0, 0),
1282 mScrollPosAtLastPaint(0, 0),
1283 mRestorePos(-1, -1),
1284 mLastPos(-1, -1),
1285 mNeverHasVerticalScrollbar(PR_FALSE),
1286 mNeverHasHorizontalScrollbar(PR_FALSE),
1287 mHasVerticalScrollbar(PR_FALSE),
1288 mHasHorizontalScrollbar(PR_FALSE),
1289 mFrameIsUpdatingScrollbar(PR_FALSE),
1290 mDidHistoryRestore(PR_FALSE),
1291 mIsRoot(aIsRoot),
1292 mIsXUL(aIsXUL),
1293 mSupppressScrollbarUpdate(PR_FALSE),
1294 mSkippedScrollbarLayout(PR_FALSE),
1295 mHadNonInitialReflow(PR_FALSE),
1296 mHorizontalOverflow(PR_FALSE),
1297 mVerticalOverflow(PR_FALSE),
1298 mPostedReflowCallback(PR_FALSE),
1299 mMayHaveDirtyFixedChildren(PR_FALSE),
1300 mUpdateScrollbarAttributes(PR_FALSE),
1301 mScrollingActive(PR_FALSE)
1305 nsGfxScrollFrameInner::~nsGfxScrollFrameInner()
1307 if (mActivityExpirationState.IsTracked()) {
1308 gScrollFrameActivityTracker->RemoveObject(this);
1310 if (gScrollFrameActivityTracker &&
1311 gScrollFrameActivityTracker->IsEmpty()) {
1312 delete gScrollFrameActivityTracker;
1313 gScrollFrameActivityTracker = nsnull;
1315 delete mAsyncScroll;
1318 static nscoord
1319 Clamp(nscoord aLower, nscoord aVal, nscoord aUpper)
1321 if (aVal < aLower)
1322 return aLower;
1323 if (aVal > aUpper)
1324 return aUpper;
1325 return aVal;
1328 nsPoint
1329 nsGfxScrollFrameInner::ClampScrollPosition(const nsPoint& aPt) const
1331 nsRect range = GetScrollRange();
1332 return nsPoint(Clamp(range.x, aPt.x, range.XMost()),
1333 Clamp(range.y, aPt.y, range.YMost()));
1337 * Callback function from timer used in nsGfxScrollFrameInner::ScrollTo
1339 void
1340 nsGfxScrollFrameInner::AsyncScrollCallback(nsITimer *aTimer, void* anInstance)
1342 nsGfxScrollFrameInner* self = static_cast<nsGfxScrollFrameInner*>(anInstance);
1343 if (!self || !self->mAsyncScroll)
1344 return;
1346 if (self->mAsyncScroll->mIsSmoothScroll) {
1347 // XXX this is crappy, the scroll position needs to be based on the
1348 // current time
1349 NS_ASSERTION(self->mAsyncScroll->mFrameIndex < SMOOTH_SCROLL_FRAMES,
1350 "Past last frame?");
1351 nscoord* velocities =
1352 &self->mAsyncScroll->mVelocities[self->mAsyncScroll->mFrameIndex*2];
1353 nsPoint destination =
1354 self->GetScrollPosition() + nsPoint(velocities[0], velocities[1]);
1356 self->mAsyncScroll->mFrameIndex++;
1357 if (self->mAsyncScroll->mFrameIndex >= SMOOTH_SCROLL_FRAMES) {
1358 delete self->mAsyncScroll;
1359 self->mAsyncScroll = nsnull;
1362 self->ScrollToImpl(destination);
1363 } else {
1364 delete self->mAsyncScroll;
1365 self->mAsyncScroll = nsnull;
1367 self->ScrollToImpl(self->mDestination);
1372 * this method wraps calls to ScrollToImpl(), either in one shot or incrementally,
1373 * based on the setting of the smooth scroll pref
1375 void
1376 nsGfxScrollFrameInner::ScrollTo(nsPoint aScrollPosition,
1377 nsIScrollableFrame::ScrollMode aMode)
1379 mDestination = ClampScrollPosition(aScrollPosition);
1381 if (aMode == nsIScrollableFrame::INSTANT) {
1382 // Asynchronous scrolling is not allowed, so we'll kill any existing
1383 // async-scrolling process and do an instant scroll
1384 delete mAsyncScroll;
1385 mAsyncScroll = nsnull;
1386 ScrollToImpl(mDestination);
1387 return;
1390 PRInt32 currentVelocityX = 0;
1391 PRInt32 currentVelocityY = 0;
1392 PRBool isSmoothScroll = IsSmoothScrollingEnabled();
1394 if (mAsyncScroll) {
1395 if (mAsyncScroll->mIsSmoothScroll) {
1396 currentVelocityX = mAsyncScroll->mVelocities[mAsyncScroll->mFrameIndex*2];
1397 currentVelocityY = mAsyncScroll->mVelocities[mAsyncScroll->mFrameIndex*2 + 1];
1399 } else {
1400 mAsyncScroll = new AsyncScroll;
1401 if (mAsyncScroll) {
1402 mAsyncScroll->mScrollTimer = do_CreateInstance("@mozilla.org/timer;1");
1403 if (!mAsyncScroll->mScrollTimer) {
1404 delete mAsyncScroll;
1405 mAsyncScroll = nsnull;
1408 if (!mAsyncScroll) {
1409 // some allocation failed. Scroll the normal way.
1410 ScrollToImpl(mDestination);
1411 return;
1413 if (isSmoothScroll) {
1414 mAsyncScroll->mScrollTimer->InitWithFuncCallback(
1415 AsyncScrollCallback, this, SMOOTH_SCROLL_MSECS_PER_FRAME,
1416 nsITimer::TYPE_REPEATING_PRECISE);
1417 } else {
1418 mAsyncScroll->mScrollTimer->InitWithFuncCallback(
1419 AsyncScrollCallback, this, 0, nsITimer::TYPE_ONE_SHOT);
1423 mAsyncScroll->mFrameIndex = 0;
1424 mAsyncScroll->mIsSmoothScroll = isSmoothScroll;
1426 if (isSmoothScroll) {
1427 PRInt32 p2a = mOuter->PresContext()->AppUnitsPerDevPixel();
1429 // compute velocity vectors
1430 nsPoint currentPos = GetScrollPosition();
1431 ComputeVelocities(currentVelocityX, currentPos.x, mDestination.x,
1432 mAsyncScroll->mVelocities, p2a);
1433 ComputeVelocities(currentVelocityY, currentPos.y, mDestination.y,
1434 mAsyncScroll->mVelocities + 1, p2a);
1438 static void InvalidateWidgets(nsIView* aView)
1440 if (aView->HasWidget()) {
1441 nsIWidget* widget = aView->GetWidget();
1442 nsWindowType type;
1443 widget->GetWindowType(type);
1444 if (type != eWindowType_popup) {
1445 // Force the widget and everything in it to repaint. We can't
1446 // just use Invalidate because the widget might have child
1447 // widgets and they wouldn't get updated. We can't call
1448 // UpdateView(aView) because the area to be repainted might be
1449 // outside aView's clipped bounds. This isn't the greatest way
1450 // to achieve this, perhaps, but it works.
1451 widget->Show(PR_FALSE);
1452 widget->Show(PR_TRUE);
1454 return;
1457 for (nsIView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
1458 InvalidateWidgets(v);
1462 // We can't use nsContainerFrame::PositionChildViews here because
1463 // we don't want to invalidate views that have moved.
1464 // aInvalidateWidgets is set to true if we should invalidate the area
1465 // covered by every widget in the subtree.
1466 static void AdjustViewsAndWidgets(nsIFrame* aFrame,
1467 PRBool aInvalidateWidgets)
1469 nsIView* view = aFrame->GetView();
1470 if (view) {
1471 nsPoint pt;
1472 aFrame->GetParent()->GetClosestView(&pt);
1473 pt += aFrame->GetPosition();
1474 view->SetPosition(pt.x, pt.y);
1476 if (aInvalidateWidgets) {
1477 InvalidateWidgets(view);
1479 return;
1482 if (!(aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
1483 return;
1486 nsIAtom* childListName = nsnull;
1487 PRInt32 childListIndex = 0;
1488 do {
1489 // Recursively walk aFrame's child frames
1490 nsIFrame* childFrame = aFrame->GetFirstChild(childListName);
1491 while (childFrame) {
1492 AdjustViewsAndWidgets(childFrame, aInvalidateWidgets);
1494 // Get the next sibling child frame
1495 childFrame = childFrame->GetNextSibling();
1498 // also process the additional child lists, but skip the popup list as the
1499 // views for popups are not scrolled.
1500 do {
1501 childListName = aFrame->GetAdditionalChildListName(childListIndex++);
1502 } while (childListName == nsGkAtoms::popupList);
1503 } while (childListName);
1506 static PRBool
1507 CanScrollWithBlitting(nsIFrame* aFrame, nsIFrame* aDisplayRoot)
1509 for (nsIFrame* f = aFrame; f;
1510 f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
1511 if (f->GetStyleDisplay()->HasTransform()) {
1512 return PR_FALSE;
1514 #ifdef MOZ_SVG
1515 if (nsSVGIntegrationUtils::UsingEffectsForFrame(f) ||
1516 f->IsFrameOfType(nsIFrame::eSVG)) {
1517 return PR_FALSE;
1519 #endif
1520 if (f == aDisplayRoot)
1521 break;
1523 return PR_TRUE;
1526 static void
1527 InvalidateFixedBackgroundFramesFromList(nsDisplayListBuilder* aBuilder,
1528 nsIFrame* aMovingFrame,
1529 const nsDisplayList& aList)
1531 for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) {
1532 nsDisplayList* sublist = item->GetList();
1533 if (sublist) {
1534 InvalidateFixedBackgroundFramesFromList(aBuilder, aMovingFrame, *sublist);
1535 continue;
1537 nsIFrame* f = item->GetUnderlyingFrame();
1538 if (f &&
1539 item->IsVaryingRelativeToMovingFrame(aBuilder, aMovingFrame)) {
1540 if (item->IsFixedAndCoveringViewport(aBuilder)) {
1541 // FrameLayerBuilder takes care of scrolling these
1542 } else {
1543 f->Invalidate(item->GetVisibleRect() - item->ToReferenceFrame());
1549 static void
1550 InvalidateFixedBackgroundFrames(nsIFrame* aRootFrame,
1551 nsIFrame* aMovingFrame,
1552 const nsRect& aUpdateRect)
1554 if (!aMovingFrame->PresContext()->MayHaveFixedBackgroundFrames())
1555 return;
1557 NS_ASSERTION(aRootFrame != aMovingFrame,
1558 "The root frame shouldn't be the one that's moving, that makes no sense");
1560 // Build the 'after' display list over the whole area of interest.
1561 nsDisplayListBuilder builder(aRootFrame, PR_FALSE, PR_TRUE);
1562 builder.EnterPresShell(aRootFrame, aUpdateRect);
1563 nsDisplayList list;
1564 nsresult rv =
1565 aRootFrame->BuildDisplayListForStackingContext(&builder, aUpdateRect, &list);
1566 builder.LeavePresShell(aRootFrame, aUpdateRect);
1567 if (NS_FAILED(rv))
1568 return;
1570 nsRegion visibleRegion(aUpdateRect);
1571 list.ComputeVisibility(&builder, &visibleRegion);
1573 InvalidateFixedBackgroundFramesFromList(&builder, aMovingFrame, list);
1574 list.DeleteAll();
1577 PRBool nsGfxScrollFrameInner::IsAlwaysActive() const
1579 // The root scrollframe for a non-chrome document which is the direct
1580 // child of a chrome document is always treated as "active".
1581 // XXX maybe we should extend this so that IFRAMEs which are fill the
1582 // entire viewport (like GMail!) are always active
1583 return mIsRoot &&
1584 !nsContentUtils::IsChildOfSameType(mOuter->GetContent()->GetCurrentDoc());
1587 PRBool nsGfxScrollFrameInner::IsScrollingActive() const
1589 return mScrollingActive || IsAlwaysActive();
1592 void nsGfxScrollFrameInner::MarkActive()
1594 if (IsAlwaysActive())
1595 return;
1597 mScrollingActive = PR_TRUE;
1598 if (mActivityExpirationState.IsTracked()) {
1599 gScrollFrameActivityTracker->MarkUsed(this);
1600 } else {
1601 if (!gScrollFrameActivityTracker) {
1602 gScrollFrameActivityTracker = new ScrollFrameActivityTracker();
1604 gScrollFrameActivityTracker->AddObject(this);
1608 void nsGfxScrollFrameInner::ScrollVisual(nsIntPoint aPixDelta)
1610 nsRootPresContext* rootPresContext = mOuter->PresContext()->GetRootPresContext();
1611 if (!rootPresContext) {
1612 return;
1615 rootPresContext->RequestUpdatePluginGeometry(mOuter);
1617 AdjustViewsAndWidgets(mScrolledFrame, PR_FALSE);
1618 // We need to call this after fixing up the widget and view positions
1619 // to be consistent with the view and frame hierarchy.
1620 PRUint32 flags = nsIFrame::INVALIDATE_REASON_SCROLL_REPAINT;
1621 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(mOuter);
1622 if (IsScrollingActive() && CanScrollWithBlitting(mOuter, displayRoot)) {
1623 flags |= nsIFrame::INVALIDATE_NO_THEBES_LAYERS;
1625 MarkActive();
1626 mOuter->InvalidateWithFlags(mScrollPort, flags);
1628 if (flags & nsIFrame::INVALIDATE_NO_THEBES_LAYERS) {
1629 nsRect update =
1630 GetScrollPortRect() + mOuter->GetOffsetToCrossDoc(displayRoot);
1631 update = update.ConvertAppUnitsRoundOut(
1632 mOuter->PresContext()->AppUnitsPerDevPixel(),
1633 displayRoot->PresContext()->AppUnitsPerDevPixel());
1634 InvalidateFixedBackgroundFrames(displayRoot, mScrolledFrame, update);
1638 static PRInt32
1639 ClampInt(nscoord aLower, nscoord aVal, nscoord aUpper, nscoord aAppUnitsPerPixel)
1641 PRInt32 low = NSToIntCeil(float(aLower)/aAppUnitsPerPixel);
1642 PRInt32 high = NSToIntFloor(float(aUpper)/aAppUnitsPerPixel);
1643 PRInt32 v = NSToIntRound(float(aVal)/aAppUnitsPerPixel);
1644 NS_ASSERTION(low <= high, "No integers in range; 0 is supposed to be in range");
1645 if (v < low)
1646 return low;
1647 if (v > high)
1648 return high;
1649 return v;
1652 nsPoint
1653 nsGfxScrollFrameInner::ClampAndRestrictToDevPixels(const nsPoint& aPt,
1654 nsIntPoint* aPtDevPx) const
1656 nsPresContext* presContext = mOuter->PresContext();
1657 nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
1658 // Convert to device pixels so we scroll to an integer offset of device
1659 // pixels. But we also need to make sure that our position remains
1660 // inside the allowed region.
1661 nsRect scrollRange = GetScrollRange();
1662 *aPtDevPx = nsIntPoint(ClampInt(scrollRange.x, aPt.x, scrollRange.XMost(), appUnitsPerDevPixel),
1663 ClampInt(scrollRange.y, aPt.y, scrollRange.YMost(), appUnitsPerDevPixel));
1664 return nsPoint(NSIntPixelsToAppUnits(aPtDevPx->x, appUnitsPerDevPixel),
1665 NSIntPixelsToAppUnits(aPtDevPx->y, appUnitsPerDevPixel));
1668 void
1669 nsGfxScrollFrameInner::ScrollToImpl(nsPoint aPt)
1671 nsPresContext* presContext = mOuter->PresContext();
1672 nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
1673 nsIntPoint ptDevPx;
1674 nsPoint pt = ClampAndRestrictToDevPixels(aPt, &ptDevPx);
1676 nsPoint curPos = GetScrollPosition();
1677 if (pt == curPos) {
1678 return;
1680 nsIntPoint curPosDevPx(NSAppUnitsToIntPixels(curPos.x, appUnitsPerDevPixel),
1681 NSAppUnitsToIntPixels(curPos.y, appUnitsPerDevPixel));
1682 // We maintain the invariant that the scroll position is a multiple of device
1683 // pixels.
1684 NS_ASSERTION(curPosDevPx.x*appUnitsPerDevPixel == curPos.x,
1685 "curPos.x not a multiple of device pixels");
1686 NS_ASSERTION(curPosDevPx.y*appUnitsPerDevPixel == curPos.y,
1687 "curPos.y not a multiple of device pixels");
1689 // notify the listeners.
1690 for (PRUint32 i = 0; i < mListeners.Length(); i++) {
1691 mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
1694 // Update frame position for scrolling
1695 mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
1697 // We pass in the amount to move visually
1698 ScrollVisual(curPosDevPx - ptDevPx);
1700 presContext->PresShell()->GetViewManager()->SynthesizeMouseMove(PR_TRUE);
1701 UpdateScrollbarPosition();
1702 PostScrollEvent();
1704 // notify the listeners.
1705 for (PRUint32 i = 0; i < mListeners.Length(); i++) {
1706 mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
1710 static void
1711 AppendToTop(nsDisplayListBuilder* aBuilder, nsDisplayList* aDest,
1712 nsDisplayList* aSource, nsIFrame* aSourceFrame, PRBool aOwnLayer)
1714 if (aOwnLayer) {
1715 aDest->AppendNewToTop(
1716 new (aBuilder) nsDisplayOwnLayer(aBuilder, aSourceFrame, aSource));
1717 } else {
1718 aDest->AppendToTop(aSource);
1722 nsresult
1723 nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1724 const nsRect& aDirtyRect,
1725 const nsDisplayListSet& aLists)
1727 nsresult rv = mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
1728 NS_ENSURE_SUCCESS(rv, rv);
1730 if (aBuilder->IsPaintingToWindow()) {
1731 mScrollPosAtLastPaint = GetScrollPosition();
1734 if (aBuilder->GetIgnoreScrollFrame() == mOuter) {
1735 // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
1736 // The scrolled frame shouldn't have its own background/border, so we
1737 // can just pass aLists directly.
1738 return mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame,
1739 aDirtyRect, aLists);
1742 // Now display the scrollbars and scrollcorner. These parts are drawn
1743 // in the border-background layer, on top of our own background and
1744 // borders and underneath borders and backgrounds of later elements
1745 // in the tree.
1746 PRBool hasResizer = HasResizer();
1747 nsDisplayListCollection scrollParts;
1748 // We put scrollbars in their own layers when this is the root scroll
1749 // frame and we are a toplevel content document. In this situation, the
1750 // scrollbar(s) would normally be assigned their own layer anyway, since
1751 // they're not scrolled with the rest of the document. But when both
1752 // scrollbars are visible, the layer's visible rectangle would be the size
1753 // of the viewport, so most layer implementations would create a layer buffer
1754 // that's much larger than necessary. Creating independent layers for each
1755 // scrollbar works around the problem.
1756 PRBool createLayersForScrollbars = mIsRoot &&
1757 !nsContentUtils::IsChildOfSameType(mOuter->GetContent()->GetCurrentDoc());
1758 for (nsIFrame* kid = mOuter->GetFirstChild(nsnull); kid; kid = kid->GetNextSibling()) {
1759 if (kid != mScrolledFrame) {
1760 if (kid == mScrollCornerBox && hasResizer) {
1761 // skip the resizer as this will be drawn later on top of the scrolled content
1762 continue;
1764 rv = mOuter->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, scrollParts,
1765 nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
1766 NS_ENSURE_SUCCESS(rv, rv);
1767 // DISPLAY_CHILD_FORCE_STACKING_CONTEXT put everything into the
1768 // PositionedDescendants list.
1769 ::AppendToTop(aBuilder, aLists.BorderBackground(),
1770 scrollParts.PositionedDescendants(), kid,
1771 createLayersForScrollbars);
1776 // Overflow clipping can never clip frames outside our subtree, so there
1777 // is no need to worry about whether we are a moving frame that might clip
1778 // non-moving frames.
1779 nsRect dirtyRect;
1780 // Not all our descendants will be clipped by overflow clipping, but all
1781 // the ones that aren't clipped will be out of flow frames that have already
1782 // had dirty rects saved for them by their parent frames calling
1783 // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
1784 // dirty rect here.
1785 dirtyRect.IntersectRect(aDirtyRect, mScrollPort);
1787 nsDisplayListCollection set;
1788 rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
1789 NS_ENSURE_SUCCESS(rv, rv);
1790 nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
1791 // mScrolledFrame may have given us a background, e.g., the scrolled canvas
1792 // frame below the viewport. If so, we want it to be clipped. We also want
1793 // to end up on our BorderBackground list.
1794 // If we are the viewport scrollframe, then clip all our descendants (to ensure
1795 // that fixed-pos elements get clipped by us).
1796 rv = mOuter->OverflowClip(aBuilder, set, aLists, clip, PR_TRUE, mIsRoot);
1797 NS_ENSURE_SUCCESS(rv, rv);
1799 // Place the resizer in the display list in our Content() list above
1800 // scrolled content in the Content() list.
1801 // This ensures that the resizer appears above the content and the mouse can
1802 // still target the resizer even when scrollbars are hidden.
1803 if (hasResizer && mScrollCornerBox) {
1804 rv = mOuter->BuildDisplayListForChild(aBuilder, mScrollCornerBox, aDirtyRect, scrollParts,
1805 nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
1806 NS_ENSURE_SUCCESS(rv, rv);
1807 // DISPLAY_CHILD_FORCE_STACKING_CONTEXT puts everything into the
1808 // PositionedDescendants list.
1809 ::AppendToTop(aBuilder, aLists.Content(),
1810 scrollParts.PositionedDescendants(), mScrollCornerBox,
1811 createLayersForScrollbars);
1814 return NS_OK;
1817 static void HandleScrollPref(nsIScrollable *aScrollable, PRInt32 aOrientation,
1818 PRUint8& aValue)
1820 PRInt32 pref;
1821 aScrollable->GetDefaultScrollbarPreferences(aOrientation, &pref);
1822 switch (pref) {
1823 case nsIScrollable::Scrollbar_Auto:
1824 // leave |aValue| untouched
1825 break;
1826 case nsIScrollable::Scrollbar_Never:
1827 aValue = NS_STYLE_OVERFLOW_HIDDEN;
1828 break;
1829 case nsIScrollable::Scrollbar_Always:
1830 aValue = NS_STYLE_OVERFLOW_SCROLL;
1831 break;
1835 nsGfxScrollFrameInner::ScrollbarStyles
1836 nsGfxScrollFrameInner::GetScrollbarStylesFromFrame() const
1838 ScrollbarStyles result;
1840 nsPresContext* presContext = mOuter->PresContext();
1841 if (!presContext->IsDynamic() &&
1842 !(mIsRoot && presContext->HasPaginatedScrolling())) {
1843 return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN);
1846 if (mIsRoot) {
1847 result = presContext->GetViewportOverflowOverride();
1849 nsCOMPtr<nsISupports> container = presContext->GetContainer();
1850 nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(container);
1851 if (scrollable) {
1852 HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_X,
1853 result.mHorizontal);
1854 HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_Y,
1855 result.mVertical);
1857 } else {
1858 const nsStyleDisplay *disp = mOuter->GetStyleDisplay();
1859 result.mHorizontal = disp->mOverflowX;
1860 result.mVertical = disp->mOverflowY;
1863 NS_ASSERTION(result.mHorizontal != NS_STYLE_OVERFLOW_VISIBLE &&
1864 result.mHorizontal != NS_STYLE_OVERFLOW_CLIP &&
1865 result.mVertical != NS_STYLE_OVERFLOW_VISIBLE &&
1866 result.mVertical != NS_STYLE_OVERFLOW_CLIP,
1867 "scrollbars should not have been created");
1868 return result;
1871 static nscoord
1872 AlignToDevPixelRoundingToZero(nscoord aVal, PRInt32 aAppUnitsPerDevPixel)
1874 return (aVal/aAppUnitsPerDevPixel)*aAppUnitsPerDevPixel;
1877 nsRect
1878 nsGfxScrollFrameInner::GetScrollRange() const
1880 nsRect range = GetScrolledRect();
1881 range.width -= mScrollPort.width;
1882 range.height -= mScrollPort.height;
1884 nsPresContext* presContext = mOuter->PresContext();
1885 PRInt32 appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
1886 range.width =
1887 AlignToDevPixelRoundingToZero(range.XMost(), appUnitsPerDevPixel) - range.x;
1888 range.height =
1889 AlignToDevPixelRoundingToZero(range.YMost(), appUnitsPerDevPixel) - range.y;
1890 range.x = AlignToDevPixelRoundingToZero(range.x, appUnitsPerDevPixel);
1891 range.y = AlignToDevPixelRoundingToZero(range.y, appUnitsPerDevPixel);
1892 return range;
1895 static void
1896 AdjustForWholeDelta(PRInt32 aDelta, nscoord* aCoord)
1898 if (aDelta < 0) {
1899 *aCoord = nscoord_MIN;
1900 } else if (aDelta > 0) {
1901 *aCoord = nscoord_MAX;
1905 void
1906 nsGfxScrollFrameInner::ScrollBy(nsIntPoint aDelta,
1907 nsIScrollableFrame::ScrollUnit aUnit,
1908 nsIScrollableFrame::ScrollMode aMode,
1909 nsIntPoint* aOverflow)
1911 nsSize deltaMultiplier;
1912 switch (aUnit) {
1913 case nsIScrollableFrame::DEVICE_PIXELS: {
1914 nscoord appUnitsPerDevPixel =
1915 mOuter->PresContext()->AppUnitsPerDevPixel();
1916 deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
1917 break;
1919 case nsIScrollableFrame::LINES: {
1920 deltaMultiplier = GetLineScrollAmount();
1921 break;
1923 case nsIScrollableFrame::PAGES: {
1924 deltaMultiplier = GetPageScrollAmount();
1925 break;
1927 case nsIScrollableFrame::WHOLE: {
1928 nsPoint pos = GetScrollPosition();
1929 AdjustForWholeDelta(aDelta.x, &pos.x);
1930 AdjustForWholeDelta(aDelta.y, &pos.y);
1931 ScrollTo(pos, aMode);
1932 if (aOverflow) {
1933 *aOverflow = nsIntPoint(0, 0);
1935 return;
1937 default:
1938 NS_ERROR("Invalid scroll mode");
1939 return;
1942 nsPoint newPos = mDestination +
1943 nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height);
1944 ScrollTo(newPos, aMode);
1946 if (aOverflow) {
1947 nsPoint clampAmount = mDestination - newPos;
1948 float appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
1949 *aOverflow = nsIntPoint(
1950 NSAppUnitsToIntPixels(PR_ABS(clampAmount.x), appUnitsPerDevPixel),
1951 NSAppUnitsToIntPixels(PR_ABS(clampAmount.y), appUnitsPerDevPixel));
1955 nsSize
1956 nsGfxScrollFrameInner::GetLineScrollAmount() const
1958 const nsStyleFont* font = mOuter->GetStyleFont();
1959 const nsFont& f = font->mFont;
1960 nsCOMPtr<nsIFontMetrics> fm = mOuter->PresContext()->GetMetricsFor(f);
1961 NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
1962 nscoord fontHeight = 1;
1963 if (fm) {
1964 fm->GetHeight(fontHeight);
1967 return nsSize(fontHeight, fontHeight);
1970 nsSize
1971 nsGfxScrollFrameInner::GetPageScrollAmount() const
1973 nsSize lineScrollAmount = GetLineScrollAmount();
1974 // The page increment is the size of the page, minus the smaller of
1975 // 10% of the size or 2 lines.
1976 return nsSize(
1977 mScrollPort.width - NS_MIN(mScrollPort.width/10, 2*lineScrollAmount.width),
1978 mScrollPort.height - NS_MIN(mScrollPort.height/10, 2*lineScrollAmount.height));
1982 * this code is resposible for restoring the scroll position back to some
1983 * saved position. if the user has not moved the scroll position manually
1984 * we keep scrolling down until we get to our original position. keep in
1985 * mind that content could incrementally be coming in. we only want to stop
1986 * when we reach our new position.
1988 void
1989 nsGfxScrollFrameInner::ScrollToRestoredPosition()
1991 if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
1992 return;
1994 // make sure our scroll position did not change for where we last put
1995 // it. if it does then the user must have moved it, and we no longer
1996 // need to restore.
1997 nsPoint scrollPos = GetScrollPosition();
1999 // if we didn't move, we still need to restore
2000 if (scrollPos == mLastPos) {
2001 // if our desired position is different to the scroll position, scroll.
2002 // remember that we could be incrementally loading so we may enter
2003 // and scroll many times.
2004 if (mRestorePos != scrollPos) {
2005 ScrollTo(mRestorePos, nsIScrollableFrame::INSTANT);
2006 // Re-get the scroll position, it might not be exactly equal to
2007 // mRestorePos due to rounding and clamping.
2008 mLastPos = GetScrollPosition();
2009 } else {
2010 // if we reached the position then stop
2011 mRestorePos.y = -1;
2012 mLastPos.x = -1;
2013 mLastPos.y = -1;
2015 } else {
2016 // user moved the position, so we won't need to restore
2017 mLastPos.x = -1;
2018 mLastPos.y = -1;
2022 nsresult
2023 nsGfxScrollFrameInner::FireScrollPortEvent()
2025 mAsyncScrollPortEvent.Forget();
2027 // Keep this in sync with PostOverflowEvent().
2028 nsSize scrollportSize = mScrollPort.Size();
2029 nsSize childSize = GetScrolledRect().Size();
2031 PRBool newVerticalOverflow = childSize.height > scrollportSize.height;
2032 PRBool vertChanged = mVerticalOverflow != newVerticalOverflow;
2034 PRBool newHorizontalOverflow = childSize.width > scrollportSize.width;
2035 PRBool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
2037 if (!vertChanged && !horizChanged) {
2038 return NS_OK;
2041 // If both either overflowed or underflowed then we dispatch only one
2042 // DOM event.
2043 PRBool both = vertChanged && horizChanged &&
2044 newVerticalOverflow == newHorizontalOverflow;
2045 nsScrollPortEvent::orientType orient;
2046 if (both) {
2047 orient = nsScrollPortEvent::both;
2048 mHorizontalOverflow = newHorizontalOverflow;
2049 mVerticalOverflow = newVerticalOverflow;
2051 else if (vertChanged) {
2052 orient = nsScrollPortEvent::vertical;
2053 mVerticalOverflow = newVerticalOverflow;
2054 if (horizChanged) {
2055 // We need to dispatch a separate horizontal DOM event. Do that the next
2056 // time around since dispatching the vertical DOM event might destroy
2057 // the frame.
2058 PostOverflowEvent();
2061 else {
2062 orient = nsScrollPortEvent::horizontal;
2063 mHorizontalOverflow = newHorizontalOverflow;
2066 nsScrollPortEvent event(PR_TRUE,
2067 (orient == nsScrollPortEvent::horizontal ?
2068 mHorizontalOverflow : mVerticalOverflow) ?
2069 NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW,
2070 nsnull);
2071 event.orient = orient;
2072 return nsEventDispatcher::Dispatch(mOuter->GetContent(),
2073 mOuter->PresContext(), &event);
2076 void
2077 nsGfxScrollFrameInner::ReloadChildFrames()
2079 mScrolledFrame = nsnull;
2080 mHScrollbarBox = nsnull;
2081 mVScrollbarBox = nsnull;
2082 mScrollCornerBox = nsnull;
2084 nsIFrame* frame = mOuter->GetFirstChild(nsnull);
2085 while (frame) {
2086 nsIContent* content = frame->GetContent();
2087 if (content == mOuter->GetContent()) {
2088 NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
2089 mScrolledFrame = frame;
2090 } else {
2091 nsAutoString value;
2092 content->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, value);
2093 if (!value.IsEmpty()) {
2094 // probably a scrollbar then
2095 if (value.LowerCaseEqualsLiteral("horizontal")) {
2096 NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?");
2097 mHScrollbarBox = frame;
2098 } else {
2099 NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
2100 mVScrollbarBox = frame;
2102 } else {
2103 // probably a scrollcorner
2104 NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
2105 mScrollCornerBox = frame;
2109 frame = frame->GetNextSibling();
2113 nsresult
2114 nsGfxScrollFrameInner::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
2116 nsPresContext* presContext = mOuter->PresContext();
2117 nsIFrame* parent = mOuter->GetParent();
2119 // Don't create scrollbars if we're printing/print previewing
2120 // Get rid of this code when printing moves to its own presentation
2121 if (!presContext->IsDynamic()) {
2122 // allow scrollbars if this is the child of the viewport, because
2123 // we must be the scrollbars for the print preview window
2124 if (!(mIsRoot && presContext->HasPaginatedScrolling())) {
2125 mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = PR_TRUE;
2126 return NS_OK;
2130 // Check if the frame is resizable.
2131 PRInt8 resizeStyle = mOuter->GetStyleDisplay()->mResize;
2132 PRBool isResizable = resizeStyle != NS_STYLE_RESIZE_NONE;
2134 nsIScrollableFrame *scrollable = do_QueryFrame(mOuter);
2136 // If we're the scrollframe for the root, then we want to construct
2137 // our scrollbar frames no matter what. That way later dynamic
2138 // changes to propagated overflow styles will show or hide
2139 // scrollbars on the viewport without requiring frame reconstruction
2140 // of the viewport (good!).
2141 PRBool canHaveHorizontal;
2142 PRBool canHaveVertical;
2143 if (!mIsRoot) {
2144 ScrollbarStyles styles = scrollable->GetScrollbarStyles();
2145 canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
2146 canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
2147 if (!canHaveHorizontal && !canHaveVertical && !isResizable) {
2148 // Nothing to do.
2149 return NS_OK;
2151 } else {
2152 canHaveHorizontal = PR_TRUE;
2153 canHaveVertical = PR_TRUE;
2156 // The anonymous <div> used by <inputs> never gets scrollbars.
2157 nsITextControlFrame* textFrame = do_QueryFrame(parent);
2158 if (textFrame) {
2159 // Make sure we are not a text area.
2160 nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement(do_QueryInterface(parent->GetContent()));
2161 if (!textAreaElement) {
2162 mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = PR_TRUE;
2163 return NS_OK;
2167 nsresult rv;
2169 nsNodeInfoManager *nodeInfoManager =
2170 presContext->Document()->NodeInfoManager();
2171 nsCOMPtr<nsINodeInfo> nodeInfo;
2172 nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbar, nsnull,
2173 kNameSpaceID_XUL);
2174 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
2176 if (canHaveHorizontal) {
2177 nsCOMPtr<nsINodeInfo> ni = nodeInfo;
2178 rv = NS_NewElement(getter_AddRefs(mHScrollbarContent),
2179 kNameSpaceID_XUL, ni.forget(), PR_FALSE);
2180 NS_ENSURE_SUCCESS(rv, rv);
2181 mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
2182 NS_LITERAL_STRING("horizontal"), PR_FALSE);
2183 mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
2184 NS_LITERAL_STRING("always"), PR_FALSE);
2185 if (!aElements.AppendElement(mHScrollbarContent))
2186 return NS_ERROR_OUT_OF_MEMORY;
2189 if (canHaveVertical) {
2190 nsCOMPtr<nsINodeInfo> ni = nodeInfo;
2191 rv = NS_NewElement(getter_AddRefs(mVScrollbarContent),
2192 kNameSpaceID_XUL, ni.forget(), PR_FALSE);
2193 NS_ENSURE_SUCCESS(rv, rv);
2194 mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
2195 NS_LITERAL_STRING("vertical"), PR_FALSE);
2196 mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
2197 NS_LITERAL_STRING("always"), PR_FALSE);
2198 if (!aElements.AppendElement(mVScrollbarContent))
2199 return NS_ERROR_OUT_OF_MEMORY;
2202 if (isResizable) {
2203 nsCOMPtr<nsINodeInfo> nodeInfo;
2204 nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nsnull,
2205 kNameSpaceID_XUL);
2206 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
2208 rv = NS_NewXULElement(getter_AddRefs(mScrollCornerContent),
2209 nodeInfo.forget());
2210 NS_ENSURE_SUCCESS(rv, rv);
2212 nsAutoString dir;
2213 switch (resizeStyle) {
2214 case NS_STYLE_RESIZE_HORIZONTAL:
2215 if (IsScrollbarOnRight()) {
2216 dir.AssignLiteral("right");
2218 else {
2219 dir.AssignLiteral("left");
2221 break;
2222 case NS_STYLE_RESIZE_VERTICAL:
2223 dir.AssignLiteral("bottom");
2224 break;
2225 case NS_STYLE_RESIZE_BOTH:
2226 dir.AssignLiteral("bottomend");
2227 break;
2228 default:
2229 NS_WARNING("only resizable types should have resizers");
2231 mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, PR_FALSE);
2232 mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element,
2233 NS_LITERAL_STRING("_parent"), PR_FALSE);
2234 mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
2235 NS_LITERAL_STRING("always"), PR_FALSE);
2237 if (!aElements.AppendElement(mScrollCornerContent))
2238 return NS_ERROR_OUT_OF_MEMORY;
2240 else if (canHaveHorizontal && canHaveVertical) {
2241 nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nsnull,
2242 kNameSpaceID_XUL);
2243 rv = NS_NewElement(getter_AddRefs(mScrollCornerContent),
2244 kNameSpaceID_XUL, nodeInfo.forget(), PR_FALSE);
2245 NS_ENSURE_SUCCESS(rv, rv);
2246 if (!aElements.AppendElement(mScrollCornerContent))
2247 return NS_ERROR_OUT_OF_MEMORY;
2250 return NS_OK;
2253 void
2254 nsGfxScrollFrameInner::AppendAnonymousContentTo(nsBaseContentList& aElements)
2256 aElements.MaybeAppendElement(mHScrollbarContent);
2257 aElements.MaybeAppendElement(mVScrollbarContent);
2258 aElements.MaybeAppendElement(mScrollCornerContent);
2261 void
2262 nsGfxScrollFrameInner::Destroy()
2264 // Unbind any content created in CreateAnonymousContent from the tree
2265 nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent);
2266 nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent);
2267 nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent);
2269 if (mPostedReflowCallback) {
2270 mOuter->PresContext()->PresShell()->CancelReflowCallback(this);
2271 mPostedReflowCallback = PR_FALSE;
2276 * Called when we want to update the scrollbar position, either because scrolling happened
2277 * or the user moved the scrollbar position and we need to undo that (e.g., when the user
2278 * clicks to scroll and we're using smooth scrolling, so we need to put the thumb back
2279 * to its initial position for the start of the smooth sequence).
2281 void
2282 nsGfxScrollFrameInner::UpdateScrollbarPosition()
2284 mFrameIsUpdatingScrollbar = PR_TRUE;
2286 nsPoint pt = GetScrollPosition();
2287 if (mVScrollbarBox) {
2288 SetCoordAttribute(mVScrollbarBox->GetContent(), nsGkAtoms::curpos,
2289 pt.y - GetScrolledRect().y);
2291 if (mHScrollbarBox) {
2292 SetCoordAttribute(mHScrollbarBox->GetContent(), nsGkAtoms::curpos,
2293 pt.x - GetScrolledRect().x);
2296 mFrameIsUpdatingScrollbar = PR_FALSE;
2299 void nsGfxScrollFrameInner::CurPosAttributeChanged(nsIContent* aContent)
2301 NS_ASSERTION(aContent, "aContent must not be null");
2302 NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
2303 (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
2304 "unexpected child");
2306 // Attribute changes on the scrollbars happen in one of three ways:
2307 // 1) The scrollbar changed the attribute in response to some user event
2308 // 2) We changed the attribute in response to a ScrollPositionDidChange
2309 // callback from the scrolling view
2310 // 3) We changed the attribute to adjust the scrollbars for the start
2311 // of a smooth scroll operation
2313 // In cases 2 and 3 we do not need to scroll because we're just
2314 // updating our scrollbar.
2315 if (mFrameIsUpdatingScrollbar)
2316 return;
2318 nsRect scrolledRect = GetScrolledRect();
2320 nscoord x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos,
2321 -scrolledRect.x) +
2322 scrolledRect.x;
2323 nscoord y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos,
2324 -scrolledRect.y) +
2325 scrolledRect.y;
2327 PRBool isSmooth = aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth);
2328 if (isSmooth) {
2329 // Make sure an attribute-setting callback occurs even if the view
2330 // didn't actually move yet. We need to make sure other listeners
2331 // see that the scroll position is not (yet) what they thought it
2332 // was.
2333 UpdateScrollbarPosition();
2335 ScrollTo(nsPoint(x, y),
2336 isSmooth ? nsIScrollableFrame::SMOOTH : nsIScrollableFrame::INSTANT);
2339 /* ============= Scroll events ========== */
2341 NS_IMETHODIMP
2342 nsGfxScrollFrameInner::ScrollEvent::Run()
2344 if (mInner)
2345 mInner->FireScrollEvent();
2346 return NS_OK;
2349 void
2350 nsGfxScrollFrameInner::FireScrollEvent()
2352 mScrollEvent.Forget();
2354 nsScrollbarEvent event(PR_TRUE, NS_SCROLL_EVENT, nsnull);
2355 nsEventStatus status = nsEventStatus_eIgnore;
2356 nsIContent* content = mOuter->GetContent();
2357 nsPresContext* prescontext = mOuter->PresContext();
2358 // Fire viewport scroll events at the document (where they
2359 // will bubble to the window)
2360 if (mIsRoot) {
2361 nsIDocument* doc = content->GetCurrentDoc();
2362 if (doc) {
2363 nsEventDispatcher::Dispatch(doc, prescontext, &event, nsnull, &status);
2365 } else {
2366 // scroll events fired at elements don't bubble (although scroll events
2367 // fired at documents do, to the window)
2368 event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
2369 nsEventDispatcher::Dispatch(content, prescontext, &event, nsnull, &status);
2373 void
2374 nsGfxScrollFrameInner::PostScrollEvent()
2376 if (mScrollEvent.IsPending())
2377 return;
2379 nsRefPtr<ScrollEvent> ev = new ScrollEvent(this);
2380 if (NS_FAILED(NS_DispatchToCurrentThread(ev))) {
2381 NS_WARNING("failed to dispatch ScrollEvent");
2382 } else {
2383 mScrollEvent = ev;
2387 NS_IMETHODIMP
2388 nsGfxScrollFrameInner::AsyncScrollPortEvent::Run()
2390 if (mInner) {
2391 mInner->mOuter->PresContext()->GetPresShell()->
2392 FlushPendingNotifications(Flush_InterruptibleLayout);
2394 return mInner ? mInner->FireScrollPortEvent() : NS_OK;
2397 PRBool
2398 nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState, PRBool aOnTop)
2400 if (!mInner.mHScrollbarBox)
2401 return PR_TRUE;
2403 return AddRemoveScrollbar(aState, aOnTop, PR_TRUE, PR_TRUE);
2406 PRBool
2407 nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState, PRBool aOnRight)
2409 if (!mInner.mVScrollbarBox)
2410 return PR_TRUE;
2412 return AddRemoveScrollbar(aState, aOnRight, PR_FALSE, PR_TRUE);
2415 void
2416 nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState, PRBool aOnTop)
2418 // removing a scrollbar should always fit
2419 #ifdef DEBUG
2420 PRBool result =
2421 #endif
2422 AddRemoveScrollbar(aState, aOnTop, PR_TRUE, PR_FALSE);
2423 NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??");
2426 void
2427 nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState, PRBool aOnRight)
2429 // removing a scrollbar should always fit
2430 #ifdef DEBUG
2431 PRBool result =
2432 #endif
2433 AddRemoveScrollbar(aState, aOnRight, PR_FALSE, PR_FALSE);
2434 NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??");
2437 PRBool
2438 nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState,
2439 PRBool aOnTop, PRBool aHorizontal, PRBool aAdd)
2441 if (aHorizontal) {
2442 if (mInner.mNeverHasHorizontalScrollbar || !mInner.mHScrollbarBox)
2443 return PR_FALSE;
2445 nsSize hSize = mInner.mHScrollbarBox->GetPrefSize(aState);
2446 nsBox::AddMargin(mInner.mHScrollbarBox, hSize);
2448 mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, aAdd);
2450 PRBool hasHorizontalScrollbar;
2451 PRBool fit = AddRemoveScrollbar(hasHorizontalScrollbar,
2452 mInner.mScrollPort.y,
2453 mInner.mScrollPort.height,
2454 hSize.height, aOnTop, aAdd);
2455 mInner.mHasHorizontalScrollbar = hasHorizontalScrollbar; // because mHasHorizontalScrollbar is a PRPackedBool
2456 if (!fit)
2457 mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, !aAdd);
2459 return fit;
2460 } else {
2461 if (mInner.mNeverHasVerticalScrollbar || !mInner.mVScrollbarBox)
2462 return PR_FALSE;
2464 nsSize vSize = mInner.mVScrollbarBox->GetPrefSize(aState);
2465 nsBox::AddMargin(mInner.mVScrollbarBox, vSize);
2467 mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, aAdd);
2469 PRBool hasVerticalScrollbar;
2470 PRBool fit = AddRemoveScrollbar(hasVerticalScrollbar,
2471 mInner.mScrollPort.x,
2472 mInner.mScrollPort.width,
2473 vSize.width, aOnTop, aAdd);
2474 mInner.mHasVerticalScrollbar = hasVerticalScrollbar; // because mHasVerticalScrollbar is a PRPackedBool
2475 if (!fit)
2476 mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, !aAdd);
2478 return fit;
2482 PRBool
2483 nsXULScrollFrame::AddRemoveScrollbar(PRBool& aHasScrollbar, nscoord& aXY,
2484 nscoord& aSize, nscoord aSbSize,
2485 PRBool aRightOrBottom, PRBool aAdd)
2487 nscoord size = aSize;
2488 nscoord xy = aXY;
2490 if (size != NS_INTRINSICSIZE) {
2491 if (aAdd) {
2492 size -= aSbSize;
2493 if (!aRightOrBottom && size >= 0)
2494 xy += aSbSize;
2495 } else {
2496 size += aSbSize;
2497 if (!aRightOrBottom)
2498 xy -= aSbSize;
2502 // not enough room? Yes? Return true.
2503 if (size >= 0) {
2504 aHasScrollbar = aAdd;
2505 aSize = size;
2506 aXY = xy;
2507 return PR_TRUE;
2510 aHasScrollbar = PR_FALSE;
2511 return PR_FALSE;
2514 void
2515 nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState,
2516 const nsPoint& aScrollPosition)
2518 PRUint32 oldflags = aState.LayoutFlags();
2519 nsRect childRect = nsRect(mInner.mScrollPort.TopLeft() - aScrollPosition,
2520 mInner.mScrollPort.Size());
2521 PRInt32 flags = NS_FRAME_NO_MOVE_VIEW;
2523 nsRect originalRect = mInner.mScrolledFrame->GetRect();
2524 nsRect originalOverflow = mInner.mScrolledFrame->GetOverflowRect();
2526 nsSize minSize = mInner.mScrolledFrame->GetMinSize(aState);
2528 if (minSize.height > childRect.height)
2529 childRect.height = minSize.height;
2531 if (minSize.width > childRect.width)
2532 childRect.width = minSize.width;
2534 aState.SetLayoutFlags(flags);
2535 mInner.mScrolledFrame->SetBounds(aState, childRect);
2536 mInner.mScrolledFrame->Layout(aState);
2538 childRect = mInner.mScrolledFrame->GetRect();
2540 if (childRect.width < mInner.mScrollPort.width ||
2541 childRect.height < mInner.mScrollPort.height)
2543 childRect.width = NS_MAX(childRect.width, mInner.mScrollPort.width);
2544 childRect.height = NS_MAX(childRect.height, mInner.mScrollPort.height);
2546 // remove overflow area when we update the bounds,
2547 // because we've already accounted for it
2548 mInner.mScrolledFrame->SetBounds(aState, childRect);
2549 mInner.mScrolledFrame->ClearOverflowRect();
2552 nsRect finalRect = mInner.mScrolledFrame->GetRect();
2553 nsRect finalOverflow = mInner.mScrolledFrame->GetOverflowRect();
2554 // The position of the scrolled frame shouldn't change, but if it does or
2555 // the position of the overflow rect changes just invalidate both the old
2556 // and new overflow rect.
2557 if (originalRect.TopLeft() != finalRect.TopLeft() ||
2558 originalOverflow.TopLeft() != finalOverflow.TopLeft())
2560 // The old overflow rect needs to be adjusted if the frame's position
2561 // changed.
2562 mInner.mScrolledFrame->Invalidate(
2563 originalOverflow + originalRect.TopLeft() - finalRect.TopLeft());
2564 mInner.mScrolledFrame->Invalidate(finalOverflow);
2565 } else if (!originalOverflow.IsExactEqual(finalOverflow)) {
2566 // If the overflow rect changed then invalidate the difference between the
2567 // old and new overflow rects.
2568 mInner.mScrolledFrame->CheckInvalidateSizeChange(
2569 originalRect, originalOverflow, finalRect.Size());
2570 mInner.mScrolledFrame->InvalidateRectDifference(
2571 originalOverflow, finalOverflow);
2574 aState.SetLayoutFlags(oldflags);
2578 void nsGfxScrollFrameInner::PostOverflowEvent()
2580 if (mAsyncScrollPortEvent.IsPending())
2581 return;
2583 // Keep this in sync with FireScrollPortEvent().
2584 nsSize scrollportSize = mScrollPort.Size();
2585 nsSize childSize = GetScrolledRect().Size();
2587 PRBool newVerticalOverflow = childSize.height > scrollportSize.height;
2588 PRBool vertChanged = mVerticalOverflow != newVerticalOverflow;
2590 PRBool newHorizontalOverflow = childSize.width > scrollportSize.width;
2591 PRBool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
2593 if (!vertChanged && !horizChanged) {
2594 return;
2597 nsRefPtr<AsyncScrollPortEvent> ev = new AsyncScrollPortEvent(this);
2598 if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev)))
2599 mAsyncScrollPortEvent = ev;
2602 PRBool
2603 nsGfxScrollFrameInner::IsLTR() const
2605 //TODO make bidi code set these from preferences
2607 nsIFrame *frame = mOuter;
2608 // XXX This is a bit on the slow side.
2609 if (mIsRoot) {
2610 // If we're the root scrollframe, we need the root element's style data.
2611 nsPresContext *presContext = mOuter->PresContext();
2612 nsIDocument *document = presContext->Document();
2613 Element *root = document->GetRootElement();
2615 // But for HTML and XHTML we want the body element.
2616 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
2617 if (htmlDoc) {
2618 Element *bodyElement = document->GetBodyElement();
2619 if (bodyElement)
2620 root = bodyElement; // we can trust the document to hold on to it
2623 if (root) {
2624 nsIFrame *rootsFrame = root->GetPrimaryFrame();
2625 if (rootsFrame)
2626 frame = rootsFrame;
2630 return frame->GetStyleVisibility()->mDirection != NS_STYLE_DIRECTION_RTL;
2633 PRBool
2634 nsGfxScrollFrameInner::IsScrollbarOnRight() const
2636 nsPresContext *presContext = mOuter->PresContext();
2638 // The position of the scrollbar in top-level windows depends on the pref
2639 // layout.scrollbar.side. For non-top-level elements, it depends only on the
2640 // directionaliy of the element (equivalent to a value of "1" for the pref).
2641 if (!mIsRoot)
2642 return IsLTR();
2643 switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) {
2644 default:
2645 case 0: // UI directionality
2646 return presContext->GetCachedIntPref(kPresContext_BidiDirection)
2647 == IBMBIDI_TEXTDIRECTION_LTR;
2648 case 1: // Document / content directionality
2649 return IsLTR();
2650 case 2: // Always right
2651 return PR_TRUE;
2652 case 3: // Always left
2653 return PR_FALSE;
2658 * Reflow the scroll area if it needs it and return its size. Also determine if the reflow will
2659 * cause any of the scrollbars to need to be reflowed.
2661 nsresult
2662 nsXULScrollFrame::Layout(nsBoxLayoutState& aState)
2664 PRBool scrollbarRight = mInner.IsScrollbarOnRight();
2665 PRBool scrollbarBottom = PR_TRUE;
2667 // get the content rect
2668 nsRect clientRect(0,0,0,0);
2669 GetClientRect(clientRect);
2671 nsRect oldScrollAreaBounds = mInner.mScrollPort;
2672 nsPoint oldScrollPosition = mInner.GetScrollPosition();
2674 // the scroll area size starts off as big as our content area
2675 mInner.mScrollPort = clientRect;
2677 /**************
2678 Our basic strategy here is to first try laying out the content with
2679 the scrollbars in their current state. We're hoping that that will
2680 just "work"; the content will overflow wherever there's a scrollbar
2681 already visible. If that does work, then there's no need to lay out
2682 the scrollarea. Otherwise we fix up the scrollbars; first we add a
2683 vertical one to scroll the content if necessary, or remove it if
2684 it's not needed. Then we reflow the content if the scrollbar
2685 changed. Then we add a horizontal scrollbar if necessary (or
2686 remove if not needed), and if that changed, we reflow the content
2687 again. At this point, any scrollbars that are needed to scroll the
2688 content have been added.
2690 In the second phase we check to see if any scrollbars are too small
2691 to display, and if so, we remove them. We check the horizontal
2692 scrollbar first; removing it might make room for the vertical
2693 scrollbar, and if we have room for just one scrollbar we'll save
2694 the vertical one.
2696 Finally we position and size the scrollbars and scrollcorner (the
2697 square that is needed in the corner of the window when two
2698 scrollbars are visible), and reflow any fixed position views
2699 (if we're the viewport and we added or removed a scrollbar).
2700 **************/
2702 ScrollbarStyles styles = GetScrollbarStyles();
2704 // Look at our style do we always have vertical or horizontal scrollbars?
2705 if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL)
2706 mInner.mHasHorizontalScrollbar = PR_TRUE;
2707 if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL)
2708 mInner.mHasVerticalScrollbar = PR_TRUE;
2710 if (mInner.mHasHorizontalScrollbar)
2711 AddHorizontalScrollbar(aState, scrollbarBottom);
2713 if (mInner.mHasVerticalScrollbar)
2714 AddVerticalScrollbar(aState, scrollbarRight);
2716 // layout our the scroll area
2717 LayoutScrollArea(aState, oldScrollPosition);
2719 // now look at the content area and see if we need scrollbars or not
2720 PRBool needsLayout = PR_FALSE;
2722 // if we have 'auto' scrollbars look at the vertical case
2723 if (styles.mVertical != NS_STYLE_OVERFLOW_SCROLL) {
2724 // These are only good until the call to LayoutScrollArea.
2725 nsRect scrolledRect = mInner.GetScrolledRect();
2726 nsSize scrolledContentSize(scrolledRect.XMost(), scrolledRect.YMost());
2728 // There are two cases to consider
2729 if (scrolledContentSize.height <= mInner.mScrollPort.height
2730 || styles.mVertical != NS_STYLE_OVERFLOW_AUTO) {
2731 if (mInner.mHasVerticalScrollbar) {
2732 // We left room for the vertical scrollbar, but it's not needed;
2733 // remove it.
2734 RemoveVerticalScrollbar(aState, scrollbarRight);
2735 needsLayout = PR_TRUE;
2737 } else {
2738 if (!mInner.mHasVerticalScrollbar) {
2739 // We didn't leave room for the vertical scrollbar, but it turns
2740 // out we needed it
2741 if (AddVerticalScrollbar(aState, scrollbarRight))
2742 needsLayout = PR_TRUE;
2746 // ok layout at the right size
2747 if (needsLayout) {
2748 nsBoxLayoutState resizeState(aState);
2749 LayoutScrollArea(resizeState, oldScrollPosition);
2750 needsLayout = PR_FALSE;
2755 // if scrollbars are auto look at the horizontal case
2756 if (styles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL)
2758 // These are only good until the call to LayoutScrollArea.
2759 nsRect scrolledRect = mInner.GetScrolledRect();
2760 nsSize scrolledContentSize(scrolledRect.XMost(), scrolledRect.YMost());
2762 // if the child is wider that the scroll area
2763 // and we don't have a scrollbar add one.
2764 if (scrolledContentSize.width > mInner.mScrollPort.width
2765 && styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) {
2767 if (!mInner.mHasHorizontalScrollbar) {
2768 // no scrollbar?
2769 if (AddHorizontalScrollbar(aState, scrollbarBottom))
2770 needsLayout = PR_TRUE;
2772 // if we added a horizontal scrollbar and we did not have a vertical
2773 // there is a chance that by adding the horizontal scrollbar we will
2774 // suddenly need a vertical scrollbar. Is a special case but its
2775 // important.
2776 //if (!mHasVerticalScrollbar && scrolledContentSize.height > scrollAreaRect.height - sbSize.height)
2777 // printf("****Gfx Scrollbar Special case hit!!*****\n");
2780 } else {
2781 // if the area is smaller or equal to and we have a scrollbar then
2782 // remove it.
2783 if (mInner.mHasHorizontalScrollbar) {
2784 RemoveHorizontalScrollbar(aState, scrollbarBottom);
2785 needsLayout = PR_TRUE;
2790 // we only need to set the rect. The inner child stays the same size.
2791 if (needsLayout) {
2792 nsBoxLayoutState resizeState(aState);
2793 LayoutScrollArea(resizeState, oldScrollPosition);
2794 needsLayout = PR_FALSE;
2797 // get the preferred size of the scrollbars
2798 nsSize hMinSize(0, 0);
2799 if (mInner.mHScrollbarBox && mInner.mHasHorizontalScrollbar) {
2800 GetScrollbarMetrics(aState, mInner.mHScrollbarBox, &hMinSize, nsnull, PR_FALSE);
2802 nsSize vMinSize(0, 0);
2803 if (mInner.mVScrollbarBox && mInner.mHasVerticalScrollbar) {
2804 GetScrollbarMetrics(aState, mInner.mVScrollbarBox, &vMinSize, nsnull, PR_TRUE);
2807 // Disable scrollbars that are too small
2808 // Disable horizontal scrollbar first. If we have to disable only one
2809 // scrollbar, we'd rather keep the vertical scrollbar.
2810 // Note that we always give horizontal scrollbars their preferred height,
2811 // never their min-height. So check that there's room for the preferred height.
2812 if (mInner.mHasHorizontalScrollbar &&
2813 (hMinSize.width > clientRect.width - vMinSize.width
2814 || hMinSize.height > clientRect.height)) {
2815 RemoveHorizontalScrollbar(aState, scrollbarBottom);
2816 needsLayout = PR_TRUE;
2818 // Now disable vertical scrollbar if necessary
2819 if (mInner.mHasVerticalScrollbar &&
2820 (vMinSize.height > clientRect.height - hMinSize.height
2821 || vMinSize.width > clientRect.width)) {
2822 RemoveVerticalScrollbar(aState, scrollbarRight);
2823 needsLayout = PR_TRUE;
2826 // we only need to set the rect. The inner child stays the same size.
2827 if (needsLayout) {
2828 nsBoxLayoutState resizeState(aState);
2829 LayoutScrollArea(resizeState, oldScrollPosition);
2832 if (!mInner.mSupppressScrollbarUpdate) {
2833 mInner.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds);
2835 if (!mInner.mPostedReflowCallback) {
2836 // Make sure we'll try scrolling to restored position
2837 PresContext()->PresShell()->PostReflowCallback(&mInner);
2838 mInner.mPostedReflowCallback = PR_TRUE;
2840 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
2841 mInner.mHadNonInitialReflow = PR_TRUE;
2844 mInner.PostOverflowEvent();
2845 return NS_OK;
2848 void
2849 nsGfxScrollFrameInner::FinishReflowForScrollbar(nsIContent* aContent,
2850 nscoord aMinXY, nscoord aMaxXY,
2851 nscoord aCurPosXY,
2852 nscoord aPageIncrement,
2853 nscoord aIncrement)
2855 // Scrollbars assume zero is the minimum position, so translate for them.
2856 SetCoordAttribute(aContent, nsGkAtoms::curpos, aCurPosXY - aMinXY);
2857 SetScrollbarEnabled(aContent, aMaxXY - aMinXY);
2858 SetCoordAttribute(aContent, nsGkAtoms::maxpos, aMaxXY - aMinXY);
2859 SetCoordAttribute(aContent, nsGkAtoms::pageincrement, aPageIncrement);
2860 SetCoordAttribute(aContent, nsGkAtoms::increment, aIncrement);
2863 PRBool
2864 nsGfxScrollFrameInner::ReflowFinished()
2866 mPostedReflowCallback = PR_FALSE;
2868 ScrollToRestoredPosition();
2870 // Clamp scroll position
2871 ScrollToImpl(GetScrollPosition());
2873 if (NS_SUBTREE_DIRTY(mOuter) || !mUpdateScrollbarAttributes)
2874 return PR_FALSE;
2876 mUpdateScrollbarAttributes = PR_FALSE;
2878 // Update scrollbar attributes.
2879 nsPresContext* presContext = mOuter->PresContext();
2881 if (mMayHaveDirtyFixedChildren) {
2882 mMayHaveDirtyFixedChildren = PR_FALSE;
2883 nsIFrame* parentFrame = mOuter->GetParent();
2884 for (nsIFrame* fixedChild =
2885 parentFrame->GetFirstChild(nsGkAtoms::fixedList);
2886 fixedChild; fixedChild = fixedChild->GetNextSibling()) {
2887 // force a reflow of the fixed child
2888 presContext->PresShell()->
2889 FrameNeedsReflow(fixedChild, nsIPresShell::eResize,
2890 NS_FRAME_HAS_DIRTY_CHILDREN);
2894 nsRect scrolledContentRect = GetScrolledRect();
2895 nscoord minX = scrolledContentRect.x;
2896 nscoord maxX = scrolledContentRect.XMost() - mScrollPort.width;
2897 nscoord minY = scrolledContentRect.y;
2898 nscoord maxY = scrolledContentRect.YMost() - mScrollPort.height;
2900 // Suppress handling of the curpos attribute changes we make here.
2901 NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here");
2902 mFrameIsUpdatingScrollbar = PR_TRUE;
2904 nsCOMPtr<nsIContent> vScroll =
2905 mVScrollbarBox ? mVScrollbarBox->GetContent() : nsnull;
2906 nsCOMPtr<nsIContent> hScroll =
2907 mHScrollbarBox ? mHScrollbarBox->GetContent() : nsnull;
2909 // Note, in some cases mOuter may get deleted while finishing reflow
2910 // for scrollbars.
2911 if (vScroll || hScroll) {
2912 nsWeakFrame weakFrame(mOuter);
2913 nsPoint scrollPos = GetScrollPosition();
2914 // XXX shouldn't we use GetPageScrollAmount/GetLineScrollAmount here?
2915 if (vScroll) {
2916 nscoord fontHeight = GetLineScrollAmount().height;
2917 // We normally use (scrollArea.height - fontHeight) for height
2918 // of page scrolling. However, it is too small when
2919 // fontHeight is very large. (If fontHeight is larger than
2920 // scrollArea.height, direction of scrolling will be opposite).
2921 // To avoid it, we use (float(scrollArea.height) * 0.8) as
2922 // lower bound value of height of page scrolling. (bug 383267)
2923 nscoord pageincrement = nscoord(mScrollPort.height - fontHeight);
2924 nscoord pageincrementMin = nscoord(float(mScrollPort.height) * 0.8);
2925 FinishReflowForScrollbar(vScroll, minY, maxY, scrollPos.y,
2926 NS_MAX(pageincrement,pageincrementMin),
2927 fontHeight);
2929 if (hScroll) {
2930 FinishReflowForScrollbar(hScroll, minX, maxX, scrollPos.x,
2931 nscoord(float(mScrollPort.width) * 0.8),
2932 nsPresContext::CSSPixelsToAppUnits(10));
2934 NS_ENSURE_TRUE(weakFrame.IsAlive(), PR_FALSE);
2937 mFrameIsUpdatingScrollbar = PR_FALSE;
2938 // We used to rely on the curpos attribute changes above to scroll the
2939 // view. However, for scrolling to the left of the viewport, we
2940 // rescale the curpos attribute, which means that operations like
2941 // resizing the window while it is scrolled all the way to the left
2942 // hold the curpos attribute constant at 0 while still requiring
2943 // scrolling. So we suppress the effect of the changes above with
2944 // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here.
2945 // (It actually even works some of the time without this, thanks to
2946 // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
2947 // we hide the scrollbar on a large size change, such as
2948 // maximization.)
2949 if (!mHScrollbarBox && !mVScrollbarBox)
2950 return PR_FALSE;
2951 CurPosAttributeChanged(mVScrollbarBox ? mVScrollbarBox->GetContent()
2952 : mHScrollbarBox->GetContent());
2953 return PR_TRUE;
2956 void
2957 nsGfxScrollFrameInner::ReflowCallbackCanceled()
2959 mPostedReflowCallback = PR_FALSE;
2962 static void LayoutAndInvalidate(nsBoxLayoutState& aState,
2963 nsIFrame* aBox, const nsRect& aRect,
2964 PRBool aScrollbarIsBeingHidden)
2966 // When a child box changes shape of position, the parent
2967 // is responsible for invalidation; the overflow rect must be invalidated
2968 // to make sure to catch any overflow.
2969 // We invalidate the parent (i.e. the scrollframe) directly, because
2970 // invalidates coming from scrollbars are suppressed by nsHTMLScrollFrame when
2971 // mHasVScrollbar/mHasHScrollbar is false, and this is called after those
2972 // flags have been set ... if a scrollbar is being hidden, we still need
2973 // to invalidate the scrollbar area here.
2974 // But we also need to invalidate the scrollbar itself in case it has
2975 // its own layer; we need to ensure that layer is updated.
2976 PRBool rectChanged = aBox->GetRect() != aRect;
2977 if (rectChanged) {
2978 if (aScrollbarIsBeingHidden) {
2979 aBox->GetParent()->Invalidate(aBox->GetOverflowRect() + aBox->GetPosition());
2980 } else {
2981 aBox->InvalidateOverflowRect();
2984 nsBoxFrame::LayoutChildAt(aState, aBox, aRect);
2985 if (rectChanged) {
2986 if (aScrollbarIsBeingHidden) {
2987 aBox->GetParent()->Invalidate(aBox->GetOverflowRect() + aBox->GetPosition());
2988 } else {
2989 aBox->InvalidateOverflowRect();
2994 void
2995 nsGfxScrollFrameInner::AdjustScrollbarRectForResizer(
2996 nsIFrame* aFrame, nsPresContext* aPresContext,
2997 nsRect& aRect, PRBool aHasResizer, PRBool aVertical)
2999 if ((aVertical ? aRect.width : aRect.height) == 0)
3000 return;
3002 // if a content resizer is present, use its size. Otherwise, check if the
3003 // widget has a resizer.
3004 nsRect resizerRect;
3005 if (aHasResizer && mScrollCornerBox) {
3006 resizerRect = mScrollCornerBox->GetRect();
3008 else {
3009 nsPoint offset;
3010 nsIWidget* widget = aFrame->GetNearestWidget(offset);
3011 nsIntRect widgetRect;
3012 if (!widget || !widget->ShowsResizeIndicator(&widgetRect))
3013 return;
3015 resizerRect = nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
3016 aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
3017 aPresContext->DevPixelsToAppUnits(widgetRect.width),
3018 aPresContext->DevPixelsToAppUnits(widgetRect.height));
3021 if (!resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1)))
3022 return;
3024 if (aVertical)
3025 aRect.height = NS_MAX(0, resizerRect.y - aRect.y);
3026 else
3027 aRect.width = NS_MAX(0, resizerRect.x - aRect.x);
3030 void
3031 nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
3032 const nsRect& aContentArea,
3033 const nsRect& aOldScrollArea)
3035 NS_ASSERTION(!mSupppressScrollbarUpdate,
3036 "This should have been suppressed");
3038 PRBool hasResizer = HasResizer();
3039 PRBool scrollbarOnLeft = !IsScrollbarOnRight();
3041 // place the scrollcorner
3042 if (mScrollCornerBox) {
3043 NS_PRECONDITION(mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
3045 // if a resizer is present, get its size
3046 nsSize resizerSize;
3047 if (HasResizer()) {
3048 // just assume a default size of 15 pixels
3049 nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
3050 resizerSize.width =
3051 mVScrollbarBox ? mVScrollbarBox->GetMinSize(aState).width : defaultSize;
3052 resizerSize.height =
3053 mHScrollbarBox ? mHScrollbarBox->GetMinSize(aState).height : defaultSize;
3055 else {
3056 resizerSize = nsSize(0, 0);
3059 nsRect r(0, 0, 0, 0);
3060 if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
3061 // scrollbar (if any) on left
3062 r.x = aContentArea.x;
3063 r.width = PR_MAX(resizerSize.width, mScrollPort.x - aContentArea.x);
3064 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
3065 } else {
3066 // scrollbar (if any) on right
3067 r.width = PR_MAX(resizerSize.width, aContentArea.XMost() - mScrollPort.XMost());
3068 r.x = aContentArea.XMost() - r.width;
3069 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
3071 if (aContentArea.y != mScrollPort.y) {
3072 NS_ERROR("top scrollbars not supported");
3073 } else {
3074 // scrollbar (if any) on bottom
3075 r.height = PR_MAX(resizerSize.height, aContentArea.YMost() - mScrollPort.YMost());
3076 r.y = aContentArea.YMost() - r.height;
3077 NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
3079 LayoutAndInvalidate(aState, mScrollCornerBox, r, PR_FALSE);
3082 nsPresContext* presContext = mScrolledFrame->PresContext();
3083 if (mVScrollbarBox) {
3084 NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
3085 nsRect vRect(mScrollPort);
3086 vRect.width = aContentArea.width - mScrollPort.width;
3087 vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost();
3088 nsMargin margin;
3089 mVScrollbarBox->GetMargin(margin);
3090 vRect.Deflate(margin);
3091 AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, PR_TRUE);
3092 LayoutAndInvalidate(aState, mVScrollbarBox, vRect, !mHasVerticalScrollbar);
3095 if (mHScrollbarBox) {
3096 NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!");
3097 nsRect hRect(mScrollPort);
3098 hRect.height = aContentArea.height - mScrollPort.height;
3099 hRect.y = PR_TRUE ? mScrollPort.YMost() : aContentArea.y;
3100 nsMargin margin;
3101 mHScrollbarBox->GetMargin(margin);
3102 hRect.Deflate(margin);
3103 AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, PR_FALSE);
3104 LayoutAndInvalidate(aState, mHScrollbarBox, hRect, !mHasHorizontalScrollbar);
3107 // may need to update fixed position children of the viewport,
3108 // if the client area changed size because of an incremental
3109 // reflow of a descendant. (If the outer frame is dirty, the fixed
3110 // children will be re-laid out anyway)
3111 if (aOldScrollArea.Size() != mScrollPort.Size() &&
3112 !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) &&
3113 mIsRoot) {
3114 mMayHaveDirtyFixedChildren = PR_TRUE;
3117 // post reflow callback to modify scrollbar attributes
3118 mUpdateScrollbarAttributes = PR_TRUE;
3119 if (!mPostedReflowCallback) {
3120 aState.PresContext()->PresShell()->PostReflowCallback(this);
3121 mPostedReflowCallback = PR_TRUE;
3125 void
3126 nsGfxScrollFrameInner::SetScrollbarEnabled(nsIContent* aContent, nscoord aMaxPos)
3128 if (aMaxPos) {
3129 aContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, PR_TRUE);
3130 } else {
3131 aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
3132 NS_LITERAL_STRING("true"), PR_TRUE);
3136 void
3137 nsGfxScrollFrameInner::SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom,
3138 nscoord aSize)
3140 // convert to pixels
3141 aSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
3143 // only set the attribute if it changed.
3145 nsAutoString newValue;
3146 newValue.AppendInt(aSize);
3148 if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters))
3149 return;
3151 aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, PR_TRUE);
3154 nsRect
3155 nsGfxScrollFrameInner::GetScrolledRect() const
3157 nsRect result =
3158 GetScrolledRectInternal(mScrolledFrame->GetOverflowRect(),
3159 mScrollPort.Size());
3161 NS_ASSERTION(result.width >= mScrollPort.width,
3162 "Scrolled rect smaller than scrollport?");
3163 NS_ASSERTION(result.height >= mScrollPort.height,
3164 "Scrolled rect smaller than scrollport?");
3165 return result;
3168 nsRect
3169 nsGfxScrollFrameInner::GetScrolledRectInternal(const nsRect& aScrolledFrameOverflowArea,
3170 const nsSize& aScrollPortSize) const
3172 nscoord x1 = aScrolledFrameOverflowArea.x,
3173 x2 = aScrolledFrameOverflowArea.XMost(),
3174 y1 = aScrolledFrameOverflowArea.y,
3175 y2 = aScrolledFrameOverflowArea.YMost();
3176 if (y1 < 0)
3177 y1 = 0;
3178 if (IsLTR() || mIsXUL) {
3179 if (x1 < 0)
3180 x1 = 0;
3181 } else {
3182 if (x2 > aScrollPortSize.width)
3183 x2 = aScrollPortSize.width;
3184 // When the scrolled frame chooses a size larger than its available width (because
3185 // its padding alone is larger than the available width), we need to keep the
3186 // start-edge of the scroll frame anchored to the start-edge of the scrollport.
3187 // When the scrolled frame is RTL, this means moving it in our left-based
3188 // coordinate system, so we need to compensate for its extra width here by
3189 // effectively repositioning the frame.
3190 nscoord extraWidth = NS_MAX(0, mScrolledFrame->GetSize().width - aScrollPortSize.width);
3191 x2 += extraWidth;
3193 return nsRect(x1, y1, x2 - x1, y2 - y1);
3196 nsMargin
3197 nsGfxScrollFrameInner::GetActualScrollbarSizes() const
3199 nsRect r = mOuter->GetPaddingRect() - mOuter->GetPosition();
3201 return nsMargin(mScrollPort.x - r.x, mScrollPort.y - r.y,
3202 r.XMost() - mScrollPort.XMost(),
3203 r.YMost() - mScrollPort.YMost());
3206 void
3207 nsGfxScrollFrameInner::SetScrollbarVisibility(nsIBox* aScrollbar, PRBool aVisible)
3209 if (!aScrollbar)
3210 return;
3212 nsIScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar);
3213 if (scrollbar) {
3214 // See if we have a mediator.
3215 nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
3216 if (mediator) {
3217 // Inform the mediator of the visibility change.
3218 mediator->VisibilityChanged(aVisible);
3223 PRInt32
3224 nsGfxScrollFrameInner::GetCoordAttribute(nsIBox* aBox, nsIAtom* atom, PRInt32 defaultValue)
3226 if (aBox) {
3227 nsIContent* content = aBox->GetContent();
3229 nsAutoString value;
3230 content->GetAttr(kNameSpaceID_None, atom, value);
3231 if (!value.IsEmpty())
3233 PRInt32 error;
3235 // convert it to an integer
3236 defaultValue = nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
3240 return defaultValue;
3243 nsPresState*
3244 nsGfxScrollFrameInner::SaveState(nsIStatefulFrame::SpecialStateID aStateID)
3246 // Don't save "normal" state for the root scrollframe; that's
3247 // handled via the eDocumentScrollState state id
3248 if (mIsRoot && aStateID == nsIStatefulFrame::eNoID) {
3249 return nsnull;
3252 nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
3253 if (mediator) {
3254 // child handles its own scroll state, so don't bother saving state here
3255 return nsnull;
3258 nsPoint scrollPos = GetScrollPosition();
3259 // Don't save scroll position if we are at (0,0)
3260 if (scrollPos == nsPoint(0,0)) {
3261 return nsnull;
3264 nsPresState* state = new nsPresState();
3265 if (!state) {
3266 return nsnull;
3269 state->SetScrollState(scrollPos);
3271 return state;
3274 void
3275 nsGfxScrollFrameInner::RestoreState(nsPresState* aState)
3277 mRestorePos = aState->GetScrollState();
3278 mLastPos.x = -1;
3279 mLastPos.y = -1;
3280 mDidHistoryRestore = PR_TRUE;
3281 mLastPos = mScrolledFrame ? GetScrollPosition() : nsPoint(0,0);
3284 void
3285 nsGfxScrollFrameInner::PostScrolledAreaEvent()
3287 if (mScrolledAreaEvent.IsPending()) {
3288 return;
3290 mScrolledAreaEvent = new ScrolledAreaEvent(this);
3291 NS_DispatchToCurrentThread(mScrolledAreaEvent.get());
3294 ////////////////////////////////////////////////////////////////////////////////
3295 // ScrolledArea change event dispatch
3297 NS_IMETHODIMP
3298 nsGfxScrollFrameInner::ScrolledAreaEvent::Run()
3300 if (mInner) {
3301 mInner->FireScrolledAreaEvent();
3303 return NS_OK;
3306 void
3307 nsGfxScrollFrameInner::FireScrolledAreaEvent()
3309 mScrolledAreaEvent.Forget();
3311 nsScrollAreaEvent event(PR_TRUE, NS_SCROLLEDAREACHANGED, nsnull);
3312 nsPresContext *prescontext = mOuter->PresContext();
3313 nsIContent* content = mOuter->GetContent();
3315 event.mArea = mScrolledFrame->GetOverflowRectRelativeToParent();
3317 nsIDocument *doc = content->GetCurrentDoc();
3318 if (doc) {
3319 nsEventDispatcher::Dispatch(doc, prescontext, &event, nsnull);