Bug 417561, disable tagging of Talkback now we have prebuilt packages, r=rhelmer
[mozilla-1.9.git] / layout / generic / nsGfxScrollFrame.cpp
blob504abedbb9141abe1a31220edbb4bfda776aa092
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 <mats.palmgren@bredband.net>
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 "nsIScrollableView.h"
48 #include "nsIScrollable.h"
49 #include "nsIViewManager.h"
50 #include "nsHTMLContainerFrame.h"
51 #include "nsGfxScrollFrame.h"
52 #include "nsGkAtoms.h"
53 #include "nsINameSpaceManager.h"
54 #include "nsIDocument.h"
55 #include "nsIFontMetrics.h"
56 #include "nsIDocumentObserver.h"
57 #include "nsIDocument.h"
58 #include "nsBoxLayoutState.h"
59 #include "nsINodeInfo.h"
60 #include "nsIScrollbarFrame.h"
61 #include "nsIScrollbarMediator.h"
62 #include "nsITextControlFrame.h"
63 #include "nsIDOMHTMLTextAreaElement.h"
64 #include "nsNodeInfoManager.h"
65 #include "nsIURI.h"
66 #include "nsGUIEvent.h"
67 #include "nsContentCreatorFunctions.h"
68 #include "nsISupportsPrimitives.h"
69 #include "nsAutoPtr.h"
70 #include "nsPresState.h"
71 #include "nsIGlobalHistory3.h"
72 #include "nsDocShellCID.h"
73 #include "nsIHTMLDocument.h"
74 #include "nsEventDispatcher.h"
75 #include "nsContentUtils.h"
76 #include "nsLayoutUtils.h"
77 #ifdef ACCESSIBILITY
78 #include "nsIAccessibilityService.h"
79 #endif
80 #include "nsDisplayList.h"
81 #include "nsBidiUtils.h"
83 //----------------------------------------------------------------------
85 //----------nsHTMLScrollFrame-------------------------------------------
87 nsIFrame*
88 NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRBool aIsRoot)
90 return new (aPresShell) nsHTMLScrollFrame(aPresShell, aContext, aIsRoot);
93 nsHTMLScrollFrame::nsHTMLScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, PRBool aIsRoot)
94 : nsHTMLContainerFrame(aContext),
95 mInner(this, aIsRoot, PR_FALSE)
99 /**
100 * Get the view that we are scrolling within the scrolling view.
101 * @result child view
103 nsIFrame* nsHTMLScrollFrame::GetScrolledFrame() const
105 return mInner.GetScrolledFrame();
108 nsIScrollableView* nsHTMLScrollFrame::GetScrollableView()
110 return mInner.GetScrollableView();
113 nsPoint nsHTMLScrollFrame::GetScrollPosition() const
115 nsIScrollableView* s = mInner.GetScrollableView();
116 nsPoint scrollPosition;
117 s->GetScrollPosition(scrollPosition.x, scrollPosition.y);
118 return scrollPosition;
121 void nsHTMLScrollFrame::ScrollTo(nsPoint aScrollPosition, PRUint32 aFlags)
123 nsIScrollableView* s = mInner.GetScrollableView();
124 s->ScrollTo(aScrollPosition.x, aScrollPosition.y, aFlags);
127 nsGfxScrollFrameInner::ScrollbarStyles
128 nsHTMLScrollFrame::GetScrollbarStyles() const {
129 return mInner.GetScrollbarStylesFromFrame();
132 nsMargin nsHTMLScrollFrame::GetDesiredScrollbarSizes(nsBoxLayoutState* aState) {
133 return mInner.GetDesiredScrollbarSizes(aState);
136 void nsHTMLScrollFrame::SetScrollbarVisibility(PRBool aVerticalVisible, PRBool aHorizontalVisible)
138 mInner.mNeverHasVerticalScrollbar = !aVerticalVisible;
139 mInner.mNeverHasHorizontalScrollbar = !aHorizontalVisible;
142 nsIBox* nsHTMLScrollFrame::GetScrollbarBox(PRBool aVertical)
144 return aVertical ? mInner.mVScrollbarBox : mInner.mHScrollbarBox;
147 nsresult
148 nsHTMLScrollFrame::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
150 return mInner.CreateAnonymousContent(aElements);
153 void
154 nsHTMLScrollFrame::Destroy()
156 mInner.Destroy();
157 nsHTMLContainerFrame::Destroy();
160 NS_IMETHODIMP
161 nsHTMLScrollFrame::
162 SetInitialChildList(nsIAtom* aListName,
163 nsIFrame* aChildList)
165 nsresult rv = nsHTMLContainerFrame::SetInitialChildList(aListName, aChildList);
166 mInner.CreateScrollableView();
167 mInner.ReloadChildFrames();
169 // listen for scroll events.
170 mInner.GetScrollableView()->AddScrollPositionListener(&mInner);
172 return rv;
176 NS_IMETHODIMP
177 nsHTMLScrollFrame::AppendFrames(nsIAtom* aListName,
178 nsIFrame* aFrameList)
180 NS_ASSERTION(!aListName, "Only main list supported");
181 mFrames.AppendFrames(nsnull, aFrameList);
182 mInner.ReloadChildFrames();
183 return NS_OK;
186 NS_IMETHODIMP
187 nsHTMLScrollFrame::InsertFrames(nsIAtom* aListName,
188 nsIFrame* aPrevFrame,
189 nsIFrame* aFrameList)
191 NS_ASSERTION(!aListName, "Only main list supported");
192 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
193 "inserting after sibling frame with different parent");
194 mFrames.InsertFrames(nsnull, aPrevFrame, aFrameList);
195 mInner.ReloadChildFrames();
196 return NS_OK;
199 NS_IMETHODIMP
200 nsHTMLScrollFrame::RemoveFrame(nsIAtom* aListName,
201 nsIFrame* aOldFrame)
203 NS_ASSERTION(!aListName, "Only main list supported");
204 mFrames.DestroyFrame(aOldFrame);
205 mInner.ReloadChildFrames();
206 return NS_OK;
209 nsSplittableType
210 nsHTMLScrollFrame::GetSplittableType() const
212 return NS_FRAME_NOT_SPLITTABLE;
215 PRIntn
216 nsHTMLScrollFrame::GetSkipSides() const
218 return 0;
221 nsIAtom*
222 nsHTMLScrollFrame::GetType() const
224 return nsGkAtoms::scrollFrame;
228 HTML scrolling implementation
230 All other things being equal, we prefer layouts with fewer scrollbars showing.
233 struct ScrollReflowState {
234 const nsHTMLReflowState& mReflowState;
235 nsBoxLayoutState mBoxState;
236 nsGfxScrollFrameInner::ScrollbarStyles mStyles;
237 nsMargin mComputedBorder;
239 // === Filled in by ReflowScrolledFrame ===
240 PRPackedBool mReflowedContentsWithHScrollbar;
241 PRPackedBool mReflowedContentsWithVScrollbar;
243 // === Filled in when TryLayout succeeds ===
244 // The area of the scrollport, in coordinates relative to the scrollframe
245 nsRect mScrollPortRect;
246 // The size of the inside-border area
247 nsSize mInsideBorderSize;
248 // Whether we decided to show the horizontal scrollbar
249 PRPackedBool mShowHScrollbar;
250 // Whether we decided to show the vertical scrollbar
251 PRPackedBool mShowVScrollbar;
253 ScrollReflowState(nsIScrollableFrame* aFrame,
254 const nsHTMLReflowState& aState) :
255 mReflowState(aState),
256 mBoxState(aState.frame->PresContext(), aState.rendContext),
257 mStyles(aFrame->GetScrollbarStyles()) {
261 // XXXldb Can this go away?
262 static nsSize ComputeInsideBorderSize(ScrollReflowState* aState,
263 const nsSize& aDesiredInsideBorderSize)
265 // aDesiredInsideBorderSize is the frame size; i.e., it includes
266 // borders and padding (but the scrolled child doesn't have
267 // borders). The scrolled child has the same padding as us.
268 nscoord contentWidth = aState->mReflowState.ComputedWidth();
269 if (contentWidth == NS_UNCONSTRAINEDSIZE) {
270 contentWidth = aDesiredInsideBorderSize.width -
271 aState->mReflowState.mComputedPadding.LeftRight();
273 nscoord contentHeight = aState->mReflowState.ComputedHeight();
274 if (contentHeight == NS_UNCONSTRAINEDSIZE) {
275 contentHeight = aDesiredInsideBorderSize.height -
276 aState->mReflowState.mComputedPadding.TopBottom();
279 aState->mReflowState.ApplyMinMaxConstraints(&contentWidth, &contentHeight);
280 return nsSize(contentWidth + aState->mReflowState.mComputedPadding.LeftRight(),
281 contentHeight + aState->mReflowState.mComputedPadding.TopBottom());
284 static void
285 GetScrollbarMetrics(nsBoxLayoutState& aState, nsIBox* aBox, nsSize* aMin,
286 nsSize* aPref, PRBool aVertical)
288 NS_ASSERTION(aState.GetRenderingContext(),
289 "Must have rendering context in layout state for size "
290 "computations");
292 if (aMin) {
293 *aMin = aBox->GetMinSize(aState);
294 nsBox::AddMargin(aBox, *aMin);
297 if (aPref) {
298 *aPref = aBox->GetPrefSize(aState);
299 nsBox::AddMargin(aBox, *aPref);
304 * Assuming that we know the metrics for our wrapped frame and
305 * whether the horizontal and/or vertical scrollbars are present,
306 * compute the resulting layout and return PR_TRUE if the layout is
307 * consistent. If the layout is consistent then we fill in the
308 * computed fields of the ScrollReflowState.
310 * The layout is consistent when both scrollbars are showing if and only
311 * if they should be showing. A horizontal scrollbar should be showing if all
312 * following conditions are met:
313 * 1) the style is not HIDDEN
314 * 2) our inside-border height is at least the scrollbar height (i.e., the
315 * scrollbar fits vertically)
316 * 3) our scrollport width (the inside-border width minus the width allocated for a
317 * vertical scrollbar, if showing) is at least the scrollbar's min-width
318 * (i.e., the scrollbar fits horizontally)
319 * 4) the style is SCROLL, or the kid's overflow-area XMost is
320 * greater than the scrollport width
322 * @param aForce if PR_TRUE, then we just assume the layout is consistent.
324 PRBool
325 nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState,
326 nsHTMLReflowMetrics* aKidMetrics,
327 PRBool aAssumeHScroll, PRBool aAssumeVScroll,
328 PRBool aForce, nsresult* aResult)
330 *aResult = NS_OK;
332 if ((aState->mStyles.mVertical == NS_STYLE_OVERFLOW_HIDDEN && aAssumeVScroll) ||
333 (aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && aAssumeHScroll)) {
334 NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
335 return PR_FALSE;
338 if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar ||
339 (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar &&
340 ScrolledContentDependsOnHeight(aState))) {
341 nsresult rv = ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll,
342 aKidMetrics, PR_FALSE);
343 if (NS_FAILED(rv)) {
344 *aResult = rv;
345 return PR_FALSE;
349 nsSize vScrollbarMinSize(0, 0);
350 nsSize vScrollbarPrefSize(0, 0);
351 if (mInner.mVScrollbarBox) {
352 GetScrollbarMetrics(aState->mBoxState, mInner.mVScrollbarBox,
353 &vScrollbarMinSize,
354 aAssumeVScroll ? &vScrollbarPrefSize : nsnull, PR_TRUE);
356 nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0;
357 nscoord vScrollbarMinHeight = aAssumeVScroll ? vScrollbarMinSize.height : 0;
359 nsSize hScrollbarMinSize(0, 0);
360 nsSize hScrollbarPrefSize(0, 0);
361 if (mInner.mHScrollbarBox) {
362 GetScrollbarMetrics(aState->mBoxState, mInner.mHScrollbarBox,
363 &hScrollbarMinSize,
364 aAssumeHScroll ? &hScrollbarPrefSize : nsnull, PR_FALSE);
366 nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0;
367 nscoord hScrollbarMinWidth = aAssumeHScroll ? hScrollbarMinSize.width : 0;
369 // First, compute our inside-border size and scrollport size
370 // XXXldb Can we depend more on ComputeSize here?
371 nsSize desiredInsideBorderSize;
372 desiredInsideBorderSize.width = vScrollbarDesiredWidth +
373 PR_MAX(aKidMetrics->width, hScrollbarMinWidth);
374 desiredInsideBorderSize.height = hScrollbarDesiredHeight +
375 PR_MAX(aKidMetrics->height, vScrollbarMinHeight);
376 aState->mInsideBorderSize =
377 ComputeInsideBorderSize(aState, desiredInsideBorderSize);
378 nsSize scrollPortSize = nsSize(PR_MAX(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth),
379 PR_MAX(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight));
381 if (!aForce) {
382 nsRect scrolledRect = mInner.GetScrolledRect(scrollPortSize);
384 // If the style is HIDDEN then we already know that aAssumeHScroll is PR_FALSE
385 if (aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
386 PRBool wantHScrollbar =
387 aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL ||
388 scrolledRect.XMost() > scrollPortSize.width ||
389 scrolledRect.x < 0;
390 if (aState->mInsideBorderSize.height < hScrollbarMinSize.height ||
391 scrollPortSize.width < hScrollbarMinSize.width)
392 wantHScrollbar = PR_FALSE;
393 if (wantHScrollbar != aAssumeHScroll)
394 return PR_FALSE;
397 // If the style is HIDDEN then we already know that aAssumeVScroll is PR_FALSE
398 if (aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) {
399 PRBool wantVScrollbar =
400 aState->mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL ||
401 scrolledRect.YMost() > scrollPortSize.height ||
402 scrolledRect.y < 0;
403 if (aState->mInsideBorderSize.width < vScrollbarMinSize.width ||
404 scrollPortSize.height < vScrollbarMinSize.height)
405 wantVScrollbar = PR_FALSE;
406 if (wantVScrollbar != aAssumeVScroll)
407 return PR_FALSE;
411 nscoord vScrollbarActualWidth = aState->mInsideBorderSize.width - scrollPortSize.width;
413 aState->mShowHScrollbar = aAssumeHScroll;
414 aState->mShowVScrollbar = aAssumeVScroll;
415 nsPoint scrollPortOrigin(aState->mComputedBorder.left,
416 aState->mComputedBorder.top);
417 if (!mInner.IsScrollbarOnRight()) {
418 scrollPortOrigin.x += vScrollbarActualWidth;
420 aState->mScrollPortRect = nsRect(scrollPortOrigin, scrollPortSize);
421 return PR_TRUE;
424 PRBool
425 nsHTMLScrollFrame::ScrolledContentDependsOnHeight(ScrollReflowState* aState)
427 // Return true if ReflowScrolledFrame is going to do something different
428 // based on the presence of a horizontal scrollbar.
429 return (mInner.mScrolledFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) ||
430 aState->mReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE ||
431 aState->mReflowState.mComputedMinHeight > 0 ||
432 aState->mReflowState.mComputedMaxHeight != NS_UNCONSTRAINEDSIZE;
435 nsresult
436 nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState,
437 PRBool aAssumeHScroll,
438 PRBool aAssumeVScroll,
439 nsHTMLReflowMetrics* aMetrics,
440 PRBool aFirstPass)
442 // these could be NS_UNCONSTRAINEDSIZE ... PR_MIN arithmetic should
443 // be OK
444 nscoord paddingLR = aState->mReflowState.mComputedPadding.LeftRight();
446 nscoord availWidth = aState->mReflowState.ComputedWidth() + paddingLR;
448 nscoord computedHeight = aState->mReflowState.ComputedHeight();
449 nscoord computedMinHeight = aState->mReflowState.mComputedMinHeight;
450 nscoord computedMaxHeight = aState->mReflowState.mComputedMaxHeight;
451 if (!ShouldPropagateComputedHeightToScrolledContent()) {
452 computedHeight = NS_UNCONSTRAINEDSIZE;
453 computedMinHeight = 0;
454 computedMaxHeight = NS_UNCONSTRAINEDSIZE;
456 if (aAssumeHScroll) {
457 nsSize hScrollbarPrefSize =
458 mInner.mHScrollbarBox->GetPrefSize(const_cast<nsBoxLayoutState&>(aState->mBoxState));
459 if (computedHeight != NS_UNCONSTRAINEDSIZE)
460 computedHeight = PR_MAX(0, computedHeight - hScrollbarPrefSize.height);
461 computedMinHeight = PR_MAX(0, computedMinHeight - hScrollbarPrefSize.height);
462 if (computedMaxHeight != NS_UNCONSTRAINEDSIZE)
463 computedMaxHeight = PR_MAX(0, computedMaxHeight - hScrollbarPrefSize.height);
466 if (aAssumeVScroll) {
467 nsSize vScrollbarPrefSize =
468 mInner.mVScrollbarBox->GetPrefSize(const_cast<nsBoxLayoutState&>(aState->mBoxState));
469 availWidth = PR_MAX(0, availWidth - vScrollbarPrefSize.width);
472 // We're forcing the padding on our scrolled frame, so let it know what that
473 // padding is.
474 mInner.mScrolledFrame->
475 SetProperty(nsGkAtoms::usedPaddingProperty,
476 new nsMargin(aState->mReflowState.mComputedPadding),
477 nsCSSOffsetState::DestroyMarginFunc);
479 nsPresContext* presContext = PresContext();
480 // Pass PR_FALSE for aInit so we can pass in the correct padding
481 nsHTMLReflowState kidReflowState(presContext, aState->mReflowState,
482 mInner.mScrolledFrame,
483 nsSize(availWidth, NS_UNCONSTRAINEDSIZE),
484 -1, -1, PR_FALSE);
485 kidReflowState.Init(presContext, -1, -1, nsnull,
486 &aState->mReflowState.mComputedPadding);
487 kidReflowState.mFlags.mAssumingHScrollbar = aAssumeHScroll;
488 kidReflowState.mFlags.mAssumingVScrollbar = aAssumeVScroll;
489 kidReflowState.SetComputedHeight(computedHeight);
490 kidReflowState.mComputedMinHeight = computedMinHeight;
491 kidReflowState.mComputedMaxHeight = computedMaxHeight;
493 nsReflowStatus status;
494 nsresult rv = ReflowChild(mInner.mScrolledFrame, presContext, *aMetrics,
495 kidReflowState, 0, 0,
496 NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_MOVE_VIEW, status);
497 // Don't resize or position the view because we're going to resize
498 // it to the correct size anyway in PlaceScrollArea. Allowing it to
499 // resize here would size it to the natural height of the frame,
500 // which will usually be different from the scrollport height;
501 // invalidating the difference will cause unnecessary repainting.
502 FinishReflowChild(mInner.mScrolledFrame, presContext,
503 &kidReflowState, *aMetrics, 0, 0,
504 NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_SIZE_VIEW);
506 // XXX Some frames (e.g., nsObjectFrame, nsFrameFrame, nsTextFrame) don't bother
507 // setting their mOverflowArea. This is wrong because every frame should
508 // always set mOverflowArea. In fact nsObjectFrame and nsFrameFrame don't
509 // support the 'outline' property because of this. Rather than fix the world
510 // right now, just fix up the overflow area if necessary. Note that we don't
511 // check NS_FRAME_OUTSIDE_CHILDREN because it could be set even though the
512 // overflow area doesn't include the frame bounds.
513 aMetrics->mOverflowArea.UnionRect(aMetrics->mOverflowArea,
514 nsRect(0, 0, aMetrics->width, aMetrics->height));
516 aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
517 aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
519 return rv;
522 PRBool
523 nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowState& aState)
525 if (aState.mStyles.mHorizontal != NS_STYLE_OVERFLOW_AUTO)
526 // no guessing required
527 return aState.mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL;
529 return mInner.mHasHorizontalScrollbar;
532 PRBool
533 nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowState& aState)
535 if (aState.mStyles.mVertical != NS_STYLE_OVERFLOW_AUTO)
536 // no guessing required
537 return aState.mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL;
539 // If we've had at least one non-initial reflow, then just assume
540 // the state of the vertical scrollbar will be what we determined
541 // last time.
542 if (mInner.mHadNonInitialReflow) {
543 return mInner.mHasVerticalScrollbar;
546 // If this is the initial reflow, guess PR_FALSE because usually
547 // we have very little content by then.
548 if (InInitialReflow())
549 return PR_FALSE;
551 if (mInner.mIsRoot) {
552 // For viewports, try getting a hint from global history
553 // as to whether we had a vertical scrollbar last time.
554 PRBool hint;
555 nsresult rv = mInner.GetVScrollbarHintFromGlobalHistory(&hint);
556 if (NS_SUCCEEDED(rv))
557 return hint;
558 // No hint. Assume that there will be a scrollbar; it seems to me
559 // that 'most pages' do have a scrollbar, and anyway, it's cheaper
560 // to do an extra reflow for the pages that *don't* need a
561 // scrollbar (because on average they will have less content).
562 return PR_TRUE;
565 // For non-viewports, just guess that we don't need a scrollbar.
566 // XXX I wonder if statistically this is the right idea; I'm
567 // basically guessing that there are a lot of overflow:auto DIVs
568 // that get their intrinsic size and don't overflow
569 return PR_FALSE;
572 PRBool
573 nsHTMLScrollFrame::InInitialReflow() const
575 // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a
576 // root scrollframe. In that case we want to skip this clause altogether.
577 // The guess here is that there are lots of overflow:auto divs out there that
578 // end up auto-sizing so they don't overflow, and that the root basically
579 // always needs a scrollbar if it did last time we loaded this page (good
580 // assumption, because our initial reflow is no longer synchronous).
581 return !mInner.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW);
584 nsresult
585 nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState,
586 const nsHTMLReflowMetrics& aDesiredSize)
588 nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mFlags);
589 nsresult rv = ReflowScrolledFrame(aState, GuessHScrollbarNeeded(*aState),
590 GuessVScrollbarNeeded(*aState), &kidDesiredSize, PR_TRUE);
591 NS_ENSURE_SUCCESS(rv, rv);
593 // There's an important special case ... if the child appears to fit
594 // in the inside-border rect (but overflows the scrollport), we
595 // should try laying it out without a vertical scrollbar. It will
596 // usually fit because making the available-width wider will not
597 // normally make the child taller. (The only situation I can think
598 // of is when you have a line containing %-width inline replaced
599 // elements whose percentages sum to more than 100%, so increasing
600 // the available width makes the line break where it was fitting
601 // before.) If we don't treat this case specially, then we will
602 // decide that showing scrollbars is OK because the content
603 // overflows when we're showing scrollbars and we won't try to
604 // remove the vertical scrollbar.
606 // Detecting when we enter this special case is important for when
607 // people design layouts that exactly fit the container "most of the
608 // time".
610 // XXX Is this check really sufficient to catch all the incremental cases
611 // where the ideal case doesn't have a scrollbar?
612 if ((aState->mReflowedContentsWithHScrollbar || aState->mReflowedContentsWithVScrollbar) &&
613 aState->mStyles.mVertical != NS_STYLE_OVERFLOW_SCROLL &&
614 aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) {
615 nsSize insideBorderSize =
616 ComputeInsideBorderSize(aState,
617 nsSize(kidDesiredSize.width, kidDesiredSize.height));
618 nsRect scrolledRect = mInner.GetScrolledRect(insideBorderSize);
619 if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
620 // Let's pretend we had no scrollbars coming in here
621 rv = ReflowScrolledFrame(aState, PR_FALSE, PR_FALSE,
622 &kidDesiredSize, PR_FALSE);
623 NS_ENSURE_SUCCESS(rv, rv);
627 // Try vertical scrollbar settings that leave the vertical scrollbar unchanged.
628 // Do this first because changing the vertical scrollbar setting is expensive,
629 // forcing a reflow always.
631 // Try leaving the horizontal scrollbar unchanged first. This will be more
632 // efficient.
633 if (TryLayout(aState, &kidDesiredSize, aState->mReflowedContentsWithHScrollbar,
634 aState->mReflowedContentsWithVScrollbar, PR_FALSE, &rv))
635 return NS_OK;
636 if (TryLayout(aState, &kidDesiredSize, !aState->mReflowedContentsWithHScrollbar,
637 aState->mReflowedContentsWithVScrollbar, PR_FALSE, &rv))
638 return NS_OK;
640 // OK, now try toggling the vertical scrollbar. The performance advantage
641 // of trying the status-quo horizontal scrollbar state
642 // does not exist here (we'll have to reflow due to the vertical scrollbar
643 // change), so always try no horizontal scrollbar first.
644 PRBool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar;
645 if (TryLayout(aState, &kidDesiredSize, PR_FALSE, newVScrollbarState, PR_FALSE, &rv))
646 return NS_OK;
647 if (TryLayout(aState, &kidDesiredSize, PR_TRUE, newVScrollbarState, PR_FALSE, &rv))
648 return NS_OK;
650 // OK, we're out of ideas. Try again enabling whatever scrollbars we can
651 // enable and force the layout to stick even if it's inconsistent.
652 // This just happens sometimes.
653 TryLayout(aState, &kidDesiredSize,
654 aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN,
655 aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN,
656 PR_TRUE, &rv);
657 return rv;
660 void
661 nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowState& aState)
663 nsIView* scrollView = mInner.mScrollableView->View();
664 nsIViewManager* vm = scrollView->GetViewManager();
665 vm->MoveViewTo(scrollView, aState.mScrollPortRect.x, aState.mScrollPortRect.y);
666 vm->ResizeView(scrollView, nsRect(nsPoint(0, 0), aState.mScrollPortRect.Size()),
667 PR_TRUE);
669 nsIFrame *scrolledFrame = mInner.mScrolledFrame;
670 nsIView *scrolledView = scrolledFrame->GetView();
671 // Set the x,y of the scrolled frame to the correct value: the displacement
672 // from its origin to the origin of this frame
673 scrolledFrame->SetPosition(scrolledView->GetOffsetTo(GetView()));
675 nsRect scrolledArea;
676 // Preserve the width or height of empty rects
677 scrolledArea.UnionRectIncludeEmpty(mInner.GetScrolledRect(aState.mScrollPortRect.Size()),
678 nsRect(nsPoint(0,0), aState.mScrollPortRect.Size()));
680 // Store the new overflow area. Note that this changes where an outline
681 // of the scrolled frame would be painted, but scrolled frames can't have
682 // outlines (the outline would go on this scrollframe instead).
683 // Using FinishAndStoreOverflow is needed so NS_FRAME_OUTSIDE_CHILDREN
684 // gets set correctly. It also messes with the overflow rect in the
685 // -moz-hidden-unscrollable case, but scrolled frames can't have
686 // 'overflow' either.
687 // This needs to happen before SyncFrameViewAfterReflow so
688 // NS_FRAME_OUTSIDE_CHILDREN is set.
689 scrolledFrame->FinishAndStoreOverflow(&scrolledArea,
690 scrolledFrame->GetSize());
692 // Note that making the view *exactly* the size of the scrolled area
693 // is critical, since the view scrolling code uses the size of the
694 // scrolled view to clamp scroll requests.
695 nsContainerFrame::SyncFrameViewAfterReflow(scrolledFrame->PresContext(),
696 scrolledFrame,
697 scrolledView,
698 &scrolledArea,
699 NS_FRAME_NO_MOVE_VIEW);
702 nscoord
703 nsHTMLScrollFrame::GetIntrinsicVScrollbarWidth(nsIRenderingContext *aRenderingContext)
705 nsGfxScrollFrameInner::ScrollbarStyles ss = GetScrollbarStyles();
706 if (ss.mVertical != NS_STYLE_OVERFLOW_SCROLL || !mInner.mVScrollbarBox)
707 return 0;
709 nsBoxLayoutState bls(PresContext(), aRenderingContext);
710 nsSize vScrollbarPrefSize(0, 0);
711 GetScrollbarMetrics(bls, mInner.mVScrollbarBox,
712 nsnull, &vScrollbarPrefSize, PR_TRUE);
713 return vScrollbarPrefSize.width;
716 /* virtual */ nscoord
717 nsHTMLScrollFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
719 nscoord result = mInner.mScrolledFrame->GetMinWidth(aRenderingContext);
720 DISPLAY_MIN_WIDTH(this, result);
721 return result + GetIntrinsicVScrollbarWidth(aRenderingContext);
724 /* virtual */ nscoord
725 nsHTMLScrollFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext)
727 nscoord result = mInner.mScrolledFrame->GetPrefWidth(aRenderingContext);
728 DISPLAY_PREF_WIDTH(this, result);
729 return NSCoordSaturatingAdd(result, GetIntrinsicVScrollbarWidth(aRenderingContext));
732 NS_IMETHODIMP
733 nsHTMLScrollFrame::GetPadding(nsMargin& aMargin)
735 // Our padding hangs out on the inside of the scrollframe, but XUL doesn't
736 // reaize that. If we're stuck inside a XUL box, we need to claim no
737 // padding.
738 // @see also nsXULScrollFrame::GetPadding.
739 aMargin.SizeTo(0,0,0,0);
740 return NS_OK;
743 PRBool
744 nsHTMLScrollFrame::IsCollapsed(nsBoxLayoutState& aBoxLayoutState)
746 // We're never collapsed in the box sense.
747 return PR_FALSE;
750 NS_IMETHODIMP
751 nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext,
752 nsHTMLReflowMetrics& aDesiredSize,
753 const nsHTMLReflowState& aReflowState,
754 nsReflowStatus& aStatus)
756 DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
757 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
759 ScrollReflowState state(this, aReflowState);
760 // sanity check: ensure that if we have no scrollbar, we treat it
761 // as hidden.
762 if (!mInner.mVScrollbarBox || mInner.mNeverHasVerticalScrollbar)
763 state.mStyles.mVertical = NS_STYLE_OVERFLOW_HIDDEN;
764 if (!mInner.mHScrollbarBox || mInner.mNeverHasHorizontalScrollbar)
765 state.mStyles.mHorizontal = NS_STYLE_OVERFLOW_HIDDEN;
767 //------------ Handle Incremental Reflow -----------------
768 PRBool reflowContents = PR_TRUE; // XXX Ignored
769 PRBool reflowHScrollbar = PR_TRUE;
770 PRBool reflowVScrollbar = PR_TRUE;
771 PRBool reflowScrollCorner = PR_TRUE;
772 if (!aReflowState.ShouldReflowAllKids()) {
773 #define NEEDS_REFLOW(frame_) \
774 ((frame_) && NS_SUBTREE_DIRTY(frame_))
776 reflowContents = NEEDS_REFLOW(mInner.mScrolledFrame);
777 reflowHScrollbar = NEEDS_REFLOW(mInner.mHScrollbarBox);
778 reflowVScrollbar = NEEDS_REFLOW(mInner.mVScrollbarBox);
779 reflowScrollCorner = NEEDS_REFLOW(mInner.mScrollCornerBox);
781 #undef NEEDS_REFLOW
784 nsRect oldScrollAreaBounds = mInner.mScrollableView->View()->GetBounds();
785 nsRect oldScrolledAreaBounds = mInner.mScrolledFrame->GetView()->GetBounds();
786 state.mComputedBorder = aReflowState.mComputedBorderPadding -
787 aReflowState.mComputedPadding;
789 nsresult rv = ReflowContents(&state, aDesiredSize);
790 if (NS_FAILED(rv))
791 return rv;
793 PlaceScrollArea(state);
794 mInner.ScrollToRestoredPosition();
796 PRBool didHaveHScrollbar = mInner.mHasHorizontalScrollbar;
797 PRBool didHaveVScrollbar = mInner.mHasVerticalScrollbar;
798 mInner.mHasHorizontalScrollbar = state.mShowHScrollbar;
799 mInner.mHasVerticalScrollbar = state.mShowVScrollbar;
800 nsRect newScrollAreaBounds = mInner.mScrollableView->View()->GetBounds();
801 nsRect newScrolledAreaBounds = mInner.mScrolledFrame->GetView()->GetBounds();
802 if (mInner.mSkippedScrollbarLayout ||
803 reflowHScrollbar || reflowVScrollbar || reflowScrollCorner ||
804 (GetStateBits() & NS_FRAME_IS_DIRTY) ||
805 didHaveHScrollbar != state.mShowHScrollbar ||
806 didHaveVScrollbar != state.mShowVScrollbar ||
807 oldScrollAreaBounds != newScrollAreaBounds ||
808 oldScrolledAreaBounds != newScrolledAreaBounds) {
809 if (!mInner.mSupppressScrollbarUpdate) {
810 mInner.mSkippedScrollbarLayout = PR_FALSE;
811 mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, state.mShowHScrollbar);
812 mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, state.mShowVScrollbar);
813 // place and reflow scrollbars
814 nsRect insideBorderArea =
815 nsRect(nsPoint(state.mComputedBorder.left, state.mComputedBorder.top),
816 state.mInsideBorderSize);
817 mInner.LayoutScrollbars(state.mBoxState, insideBorderArea,
818 oldScrollAreaBounds, state.mScrollPortRect);
819 } else {
820 mInner.mSkippedScrollbarLayout = PR_TRUE;
824 aDesiredSize.width = state.mInsideBorderSize.width +
825 state.mComputedBorder.LeftRight();
826 aDesiredSize.height = state.mInsideBorderSize.height +
827 state.mComputedBorder.TopBottom();
829 aDesiredSize.mOverflowArea = nsRect(0, 0, aDesiredSize.width, aDesiredSize.height);
830 FinishAndStoreOverflow(&aDesiredSize);
832 if (!InInitialReflow() && !mInner.mHadNonInitialReflow) {
833 mInner.mHadNonInitialReflow = PR_TRUE;
834 if (mInner.mIsRoot) {
835 // For viewports, record whether we needed a vertical scrollbar
836 // after the first non-initial reflow.
837 mInner.SaveVScrollbarStateToGlobalHistory();
841 aStatus = NS_FRAME_COMPLETE;
842 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
843 mInner.PostOverflowEvent();
844 return rv;
847 NS_IMETHODIMP_(nsrefcnt)
848 nsHTMLScrollFrame::AddRef(void)
850 return NS_OK;
853 NS_IMETHODIMP_(nsrefcnt)
854 nsHTMLScrollFrame::Release(void)
856 return NS_OK;
859 #ifdef NS_DEBUG
860 NS_IMETHODIMP
861 nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const
863 return MakeFrameName(NS_LITERAL_STRING("HTMLScroll"), aResult);
865 #endif
867 #ifdef ACCESSIBILITY
868 NS_IMETHODIMP nsHTMLScrollFrame::GetAccessible(nsIAccessible** aAccessible)
870 *aAccessible = nsnull;
871 if (!IsFocusable()) {
872 return NS_OK;
874 // Focusable via CSS, so needs to be in accessibility hierarchy
875 nsCOMPtr<nsIAccessibilityService> accService = do_GetService("@mozilla.org/accessibilityService;1");
877 if (accService) {
878 return accService->CreateHTMLGenericAccessible(static_cast<nsIFrame*>(this), aAccessible);
881 return NS_ERROR_FAILURE;
883 #endif
885 void
886 nsHTMLScrollFrame::CurPosAttributeChanged(nsIContent* aChild,
887 PRInt32 aModType)
889 mInner.CurPosAttributeChanged(aChild);
892 NS_INTERFACE_MAP_BEGIN(nsHTMLScrollFrame)
893 NS_INTERFACE_MAP_ENTRY(nsIAnonymousContentCreator)
894 #ifdef NS_DEBUG
895 NS_INTERFACE_MAP_ENTRY(nsIFrameDebug)
896 #endif
897 NS_INTERFACE_MAP_ENTRY(nsIScrollableFrame)
898 NS_INTERFACE_MAP_ENTRY(nsIScrollableViewProvider)
899 NS_INTERFACE_MAP_ENTRY(nsIStatefulFrame)
900 NS_INTERFACE_MAP_END_INHERITING(nsHTMLContainerFrame)
902 //----------nsXULScrollFrame-------------------------------------------
904 nsIFrame*
905 NS_NewXULScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRBool aIsRoot)
907 return new (aPresShell) nsXULScrollFrame(aPresShell, aContext, aIsRoot);
910 nsXULScrollFrame::nsXULScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, PRBool aIsRoot)
911 : nsBoxFrame(aShell, aContext, aIsRoot),
912 mInner(this, aIsRoot, PR_TRUE)
914 SetLayoutManager(nsnull);
918 * Get the view that we are scrolling within the scrolling view.
919 * @result child view
921 nsIFrame* nsXULScrollFrame::GetScrolledFrame() const
923 return mInner.GetScrolledFrame();
926 nsIScrollableView* nsXULScrollFrame::GetScrollableView()
928 return mInner.GetScrollableView();
931 nsPoint nsXULScrollFrame::GetScrollPosition() const
933 nsIScrollableView* s = mInner.GetScrollableView();
934 nsPoint scrollPosition;
935 s->GetScrollPosition(scrollPosition.x, scrollPosition.y);
936 return scrollPosition;
939 void nsXULScrollFrame::ScrollTo(nsPoint aScrollPosition, PRUint32 aFlags)
941 nsIScrollableView* s = mInner.GetScrollableView();
942 s->ScrollTo(aScrollPosition.x, aScrollPosition.y, aFlags);
945 nsGfxScrollFrameInner::ScrollbarStyles
946 nsXULScrollFrame::GetScrollbarStyles() const {
947 return mInner.GetScrollbarStylesFromFrame();
950 nsMargin nsXULScrollFrame::GetDesiredScrollbarSizes(nsBoxLayoutState* aState) {
951 return mInner.GetDesiredScrollbarSizes(aState);
954 nsMargin nsGfxScrollFrameInner::GetDesiredScrollbarSizes(nsBoxLayoutState* aState) {
955 NS_ASSERTION(aState && aState->GetRenderingContext(),
956 "Must have rendering context in layout state for size "
957 "computations");
959 nsMargin result(0, 0, 0, 0);
961 if (mVScrollbarBox) {
962 nsSize size = mVScrollbarBox->GetPrefSize(*aState);
963 nsBox::AddMargin(mVScrollbarBox, size);
964 if (IsScrollbarOnRight())
965 result.left = size.width;
966 else
967 result.right = size.width;
970 if (mHScrollbarBox) {
971 nsSize size = mHScrollbarBox->GetPrefSize(*aState);
972 nsBox::AddMargin(mHScrollbarBox, size);
973 // We don't currently support any scripts that would require a scrollbar
974 // at the top. (Are there any?)
975 result.bottom = size.height;
978 return result;
981 void nsXULScrollFrame::SetScrollbarVisibility(PRBool aVerticalVisible, PRBool aHorizontalVisible)
983 mInner.mNeverHasVerticalScrollbar = !aVerticalVisible;
984 mInner.mNeverHasHorizontalScrollbar = !aHorizontalVisible;
987 nsIBox* nsXULScrollFrame::GetScrollbarBox(PRBool aVertical)
989 return aVertical ? mInner.mVScrollbarBox : mInner.mHScrollbarBox;
992 nsresult
993 nsXULScrollFrame::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
995 return mInner.CreateAnonymousContent(aElements);
998 void
999 nsXULScrollFrame::Destroy()
1001 mInner.Destroy();
1002 nsBoxFrame::Destroy();
1005 NS_IMETHODIMP
1006 nsXULScrollFrame::SetInitialChildList(nsIAtom* aListName,
1007 nsIFrame* aChildList)
1009 nsresult rv = nsBoxFrame::SetInitialChildList(aListName, aChildList);
1011 mInner.CreateScrollableView();
1012 mInner.ReloadChildFrames();
1014 // listen for scroll events.
1015 mInner.GetScrollableView()->AddScrollPositionListener(&mInner);
1017 return rv;
1021 NS_IMETHODIMP
1022 nsXULScrollFrame::AppendFrames(nsIAtom* aListName,
1023 nsIFrame* aFrameList)
1025 nsresult rv = nsBoxFrame::AppendFrames(aListName, aFrameList);
1026 mInner.ReloadChildFrames();
1027 return rv;
1030 NS_IMETHODIMP
1031 nsXULScrollFrame::InsertFrames(nsIAtom* aListName,
1032 nsIFrame* aPrevFrame,
1033 nsIFrame* aFrameList)
1035 nsresult rv = nsBoxFrame::InsertFrames(aListName, aPrevFrame, aFrameList);
1036 mInner.ReloadChildFrames();
1037 return rv;
1040 NS_IMETHODIMP
1041 nsXULScrollFrame::RemoveFrame(nsIAtom* aListName,
1042 nsIFrame* aOldFrame)
1044 nsresult rv = nsBoxFrame::RemoveFrame(aListName, aOldFrame);
1045 mInner.ReloadChildFrames();
1046 return rv;
1049 nsSplittableType
1050 nsXULScrollFrame::GetSplittableType() const
1052 return NS_FRAME_NOT_SPLITTABLE;
1055 NS_IMETHODIMP
1056 nsXULScrollFrame::GetPadding(nsMargin& aMargin)
1058 aMargin.SizeTo(0,0,0,0);
1059 return NS_OK;
1062 PRIntn
1063 nsXULScrollFrame::GetSkipSides() const
1065 return 0;
1068 nsIAtom*
1069 nsXULScrollFrame::GetType() const
1071 return nsGkAtoms::scrollFrame;
1074 nscoord
1075 nsXULScrollFrame::GetBoxAscent(nsBoxLayoutState& aState)
1077 if (!mInner.mScrolledFrame)
1078 return 0;
1080 nscoord ascent = mInner.mScrolledFrame->GetBoxAscent(aState);
1081 nsMargin m(0,0,0,0);
1082 GetBorderAndPadding(m);
1083 ascent += m.top;
1084 GetMargin(m);
1085 ascent += m.top;
1087 return ascent;
1090 nsSize
1091 nsXULScrollFrame::GetPrefSize(nsBoxLayoutState& aState)
1093 #ifdef DEBUG_LAYOUT
1094 PropagateDebug(aState);
1095 #endif
1097 nsSize pref = mInner.mScrolledFrame->GetPrefSize(aState);
1099 nsGfxScrollFrameInner::ScrollbarStyles styles = GetScrollbarStyles();
1101 // scrolled frames don't have their own margins
1103 if (mInner.mVScrollbarBox &&
1104 styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1105 nsSize vSize = mInner.mVScrollbarBox->GetPrefSize(aState);
1106 nsBox::AddMargin(mInner.mVScrollbarBox, vSize);
1107 pref.width += vSize.width;
1110 if (mInner.mHScrollbarBox &&
1111 styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1112 nsSize hSize = mInner.mHScrollbarBox->GetPrefSize(aState);
1113 nsBox::AddMargin(mInner.mHScrollbarBox, hSize);
1114 pref.height += hSize.height;
1117 AddBorderAndPadding(pref);
1118 nsIBox::AddCSSPrefSize(aState, this, pref);
1119 return pref;
1122 nsSize
1123 nsXULScrollFrame::GetMinSize(nsBoxLayoutState& aState)
1125 #ifdef DEBUG_LAYOUT
1126 PropagateDebug(aState);
1127 #endif
1129 nsSize min = mInner.mScrolledFrame->GetMinSizeForScrollArea(aState);
1131 nsGfxScrollFrameInner::ScrollbarStyles styles = GetScrollbarStyles();
1133 if (mInner.mVScrollbarBox &&
1134 styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1135 nsSize vSize = mInner.mVScrollbarBox->GetMinSize(aState);
1136 AddMargin(mInner.mVScrollbarBox, vSize);
1137 min.width += vSize.width;
1138 if (min.height < vSize.height)
1139 min.height = vSize.height;
1142 if (mInner.mHScrollbarBox &&
1143 styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1144 nsSize hSize = mInner.mHScrollbarBox->GetMinSize(aState);
1145 AddMargin(mInner.mHScrollbarBox, hSize);
1146 min.height += hSize.height;
1147 if (min.width < hSize.width)
1148 min.width = hSize.width;
1151 AddBorderAndPadding(min);
1152 nsIBox::AddCSSMinSize(aState, this, min);
1153 return min;
1156 nsSize
1157 nsXULScrollFrame::GetMaxSize(nsBoxLayoutState& aState)
1159 #ifdef DEBUG_LAYOUT
1160 PropagateDebug(aState);
1161 #endif
1163 nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
1165 AddBorderAndPadding(maxSize);
1166 nsIBox::AddCSSMaxSize(aState, this, maxSize);
1167 return maxSize;
1170 #if 0 // XXXldb I don't think this is even needed
1171 /* virtual */ nscoord
1172 nsXULScrollFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
1174 nsStyleUnit widthUnit = GetStylePosition()->mWidth.GetUnit();
1175 if (widthUnit == eStyleUnit_Percent || widthUnit == eStyleUnit_Auto) {
1176 nsMargin border = aReflowState.mComputedBorderPadding;
1177 aDesiredSize.mMaxElementWidth = border.right + border.left;
1178 mMaxElementWidth = aDesiredSize.mMaxElementWidth;
1179 } else {
1180 NS_NOTYETIMPLEMENTED("Use the info from the scrolled frame");
1181 #if 0
1182 // if not set then use the cached size. If set then set it.
1183 if (aDesiredSize.mMaxElementWidth == -1)
1184 aDesiredSize.mMaxElementWidth = mMaxElementWidth;
1185 else
1186 mMaxElementWidth = aDesiredSize.mMaxElementWidth;
1187 #endif
1189 return 0;
1191 #endif
1193 NS_IMETHODIMP_(nsrefcnt)
1194 nsXULScrollFrame::AddRef(void)
1196 return NS_OK;
1199 NS_IMETHODIMP_(nsrefcnt)
1200 nsXULScrollFrame::Release(void)
1202 return NS_OK;
1205 #ifdef NS_DEBUG
1206 NS_IMETHODIMP
1207 nsXULScrollFrame::GetFrameName(nsAString& aResult) const
1209 return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult);
1211 #endif
1213 void nsXULScrollFrame::CurPosAttributeChanged(nsIContent* aChild, PRInt32 aModType)
1215 mInner.CurPosAttributeChanged(aChild);
1218 NS_IMETHODIMP
1219 nsXULScrollFrame::DoLayout(nsBoxLayoutState& aState)
1221 PRUint32 flags = aState.LayoutFlags();
1222 nsresult rv = Layout(aState);
1223 aState.SetLayoutFlags(flags);
1225 nsBox::DoLayout(aState);
1226 return rv;
1229 NS_INTERFACE_MAP_BEGIN(nsXULScrollFrame)
1230 NS_INTERFACE_MAP_ENTRY(nsIAnonymousContentCreator)
1231 #ifdef NS_DEBUG
1232 NS_INTERFACE_MAP_ENTRY(nsIFrameDebug)
1233 #endif
1234 NS_INTERFACE_MAP_ENTRY(nsIScrollableFrame)
1235 NS_INTERFACE_MAP_ENTRY(nsIScrollableViewProvider)
1236 NS_INTERFACE_MAP_ENTRY(nsIStatefulFrame)
1237 NS_INTERFACE_MAP_END_INHERITING(nsBoxFrame)
1241 //-------------------- Inner ----------------------
1243 nsGfxScrollFrameInner::nsGfxScrollFrameInner(nsContainerFrame* aOuter,
1244 PRBool aIsRoot,
1245 PRBool aIsXUL)
1246 : mScrollableView(nsnull),
1247 mHScrollbarBox(nsnull),
1248 mVScrollbarBox(nsnull),
1249 mScrolledFrame(nsnull),
1250 mScrollCornerBox(nsnull),
1251 mOuter(aOuter),
1252 mRestoreRect(-1, -1, -1, -1),
1253 mLastPos(-1, -1),
1254 mNeverHasVerticalScrollbar(PR_FALSE),
1255 mNeverHasHorizontalScrollbar(PR_FALSE),
1256 mHasVerticalScrollbar(PR_FALSE),
1257 mHasHorizontalScrollbar(PR_FALSE),
1258 mViewInitiatedScroll(PR_FALSE),
1259 mFrameInitiatedScroll(PR_FALSE),
1260 mDidHistoryRestore(PR_FALSE),
1261 mIsRoot(aIsRoot),
1262 mIsXUL(aIsXUL),
1263 mSupppressScrollbarUpdate(PR_FALSE),
1264 mSkippedScrollbarLayout(PR_FALSE),
1265 mDidLoadHistoryVScrollbarHint(PR_FALSE),
1266 mHistoryVScrollbarHint(PR_FALSE),
1267 mHadNonInitialReflow(PR_FALSE),
1268 mHorizontalOverflow(PR_FALSE),
1269 mVerticalOverflow(PR_FALSE),
1270 mPostedReflowCallback(PR_FALSE),
1271 mMayHaveDirtyFixedChildren(PR_FALSE)
1275 nsGfxScrollFrameInner::~nsGfxScrollFrameInner()
1279 NS_IMETHODIMP_(nsrefcnt) nsGfxScrollFrameInner::AddRef(void)
1281 return 2;
1284 NS_IMETHODIMP_(nsrefcnt) nsGfxScrollFrameInner::Release(void)
1286 return 1;
1289 NS_IMPL_QUERY_INTERFACE1(nsGfxScrollFrameInner, nsIScrollPositionListener)
1291 nsresult
1292 nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1293 const nsRect& aDirtyRect,
1294 const nsDisplayListSet& aLists)
1296 nsresult rv = mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
1297 NS_ENSURE_SUCCESS(rv, rv);
1299 if (aBuilder->GetIgnoreScrollFrame() == mOuter) {
1300 // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
1301 // The scrolled frame shouldn't have its own background/border, so we
1302 // can just pass aLists directly. We do need to replace aDirtyRect with
1303 // the scrolled area though, since callers may have restricted aDirtyRect
1304 // to our bounds.
1305 nsRect newDirty = GetScrolledRect(GetScrollPortSize()) +
1306 aBuilder->ToReferenceFrame(mScrolledFrame);
1307 return mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, newDirty, aLists);
1310 // Overflow clipping can never clip frames outside our subtree, so there
1311 // is no need to worry about whether we are a moving frame that might clip
1312 // non-moving frames.
1313 nsRect frameClip = mScrollableView->View()->GetBounds();
1314 nsRect dirtyRect;
1315 // Not all our descendants will be clipped by overflow clipping, but all
1316 // the ones that aren't clipped will be out of flow frames that have already
1317 // had dirty rects saved for them by their parent frames calling
1318 // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
1319 // dirty rect here.
1320 dirtyRect.IntersectRect(aDirtyRect, frameClip);
1322 nsDisplayListCollection set;
1323 rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
1324 NS_ENSURE_SUCCESS(rv, rv);
1325 nsRect clip = frameClip + aBuilder->ToReferenceFrame(mOuter);
1326 // mScrolledFrame may have given us a background, e.g., the scrolled canvas
1327 // frame below the viewport. If so, we want it to be clipped. We also want
1328 // to end up on our BorderBackground list.
1329 // If we are the viewport scrollframe, then clip all our descendants (to ensure
1330 // that fixed-pos elements get clipped by us).
1331 rv = mOuter->OverflowClip(aBuilder, set, aLists, clip, PR_TRUE, mIsRoot);
1332 NS_ENSURE_SUCCESS(rv, rv);
1334 // Now display the scrollbars and scrollcorner
1335 nsIFrame* kid = mOuter->GetFirstChild(nsnull);
1336 // Put each child's background directly onto the content list
1337 nsDisplayListSet scrollbarSet(aLists, aLists.Content());
1338 while (kid) {
1339 if (kid != mScrolledFrame) {
1340 rv = mOuter->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, scrollbarSet,
1341 nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT);
1342 NS_ENSURE_SUCCESS(rv, rv);
1344 kid = kid->GetNextSibling();
1346 return NS_OK;
1349 void
1350 nsGfxScrollFrameInner::InvalidateInternal(const nsRect& aDamageRect,
1351 nscoord aX, nscoord aY, nsIFrame* aForChild,
1352 PRBool aImmediate)
1354 nsPoint pt = mOuter->GetPosition();
1356 if (aForChild == mScrolledFrame) {
1357 // restrict aDamageRect to the scrollable view's bounds
1358 nsRect r;
1359 if (r.IntersectRect(aDamageRect, mScrollableView->View()->GetBounds() - nsPoint(aX, aY))) {
1360 mOuter->GetParent()->
1361 InvalidateInternal(r, aX + pt.x, aY + pt.y, mOuter, aImmediate);
1363 return;
1366 mOuter->GetParent()->
1367 InvalidateInternal(aDamageRect, aX + pt.x, aY + pt.y, mOuter, aImmediate);
1370 PRBool
1371 nsGfxScrollFrameInner::NeedsClipWidget() const
1373 // Scrollports contained in form controls (e.g., listboxes) don't get
1374 // widgets.
1375 for (nsIFrame* parentFrame = mOuter; parentFrame;
1376 parentFrame = parentFrame->GetParent()) {
1377 nsIFormControlFrame* fcFrame;
1378 if ((NS_SUCCEEDED(parentFrame->QueryInterface(NS_GET_IID(nsIFormControlFrame), (void**)&fcFrame)))) {
1379 return PR_FALSE;
1383 // Scrollports that don't ever show associated scrollbars don't get
1384 // widgets, because they will seldom actually be scrolled.
1385 nsIScrollableFrame *scrollableFrame;
1386 CallQueryInterface(mOuter, &scrollableFrame);
1387 ScrollbarStyles scrollbars = scrollableFrame->GetScrollbarStyles();
1388 if ((scrollbars.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN
1389 || scrollbars.mHorizontal == NS_STYLE_OVERFLOW_VISIBLE)
1390 && (scrollbars.mVertical == NS_STYLE_OVERFLOW_HIDDEN
1391 || scrollbars.mVertical == NS_STYLE_OVERFLOW_VISIBLE)) {
1392 return PR_FALSE;
1395 return PR_TRUE;
1398 void
1399 nsGfxScrollFrameInner::CreateScrollableView()
1401 nsIView* outerView = mOuter->GetView();
1402 NS_ASSERTION(outerView, "scrollframes must have views");
1403 nsIViewManager* viewManager = outerView->GetViewManager();
1404 mScrollableView = viewManager->CreateScrollableView(mOuter->GetRect(), outerView);
1405 if (!mScrollableView)
1406 return;
1408 nsIView* view = mScrollableView->View();
1410 // Insert the view into the view hierarchy
1411 viewManager->InsertChild(outerView, view, nsnull, PR_TRUE);
1413 // Have the scrolling view create its internal widgets
1414 if (NeedsClipWidget()) {
1415 mScrollableView->CreateScrollControls();
1419 static void HandleScrollPref(nsIScrollable *aScrollable, PRInt32 aOrientation,
1420 PRUint8& aValue)
1422 PRInt32 pref;
1423 aScrollable->GetDefaultScrollbarPreferences(aOrientation, &pref);
1424 switch (pref) {
1425 case nsIScrollable::Scrollbar_Auto:
1426 // leave |aValue| untouched
1427 break;
1428 case nsIScrollable::Scrollbar_Never:
1429 aValue = NS_STYLE_OVERFLOW_HIDDEN;
1430 break;
1431 case nsIScrollable::Scrollbar_Always:
1432 aValue = NS_STYLE_OVERFLOW_SCROLL;
1433 break;
1437 nsIView*
1438 nsGfxScrollFrameInner::GetParentViewForChildFrame(nsIFrame* aFrame) const
1440 if (aFrame->GetContent() == mOuter->GetContent()) {
1441 NS_ASSERTION(mScrollableView, "Scrollable view should have been created by now");
1442 // scrolled frame, put it under our anonymous view
1443 return mScrollableView->View();
1445 // scrollbars and stuff; put them under our regular view
1446 return mOuter->GetView();
1449 nsGfxScrollFrameInner::ScrollbarStyles
1450 nsGfxScrollFrameInner::GetScrollbarStylesFromFrame() const
1452 ScrollbarStyles result;
1454 nsPresContext* presContext = mOuter->PresContext();
1455 if (!presContext->IsDynamic() &&
1456 !(mIsRoot && presContext->HasPaginatedScrolling())) {
1457 return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN);
1460 if (mIsRoot) {
1461 result = presContext->GetViewportOverflowOverride();
1463 nsCOMPtr<nsISupports> container = presContext->GetContainer();
1464 nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(container);
1465 if (scrollable) {
1466 HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_X,
1467 result.mHorizontal);
1468 HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_Y,
1469 result.mVertical);
1471 } else {
1472 const nsStyleDisplay *disp = mOuter->GetStyleDisplay();
1473 result.mHorizontal = disp->mOverflowX;
1474 result.mVertical = disp->mOverflowY;
1477 NS_ASSERTION(result.mHorizontal != NS_STYLE_OVERFLOW_VISIBLE &&
1478 result.mHorizontal != NS_STYLE_OVERFLOW_CLIP &&
1479 result.mVertical != NS_STYLE_OVERFLOW_VISIBLE &&
1480 result.mVertical != NS_STYLE_OVERFLOW_CLIP,
1481 "scrollbars should not have been created");
1482 return result;
1486 * this code is resposible for restoring the scroll position back to some
1487 * saved position. if the user has not moved the scroll position manually
1488 * we keep scrolling down until we get to our original position. keep in
1489 * mind that content could incrementally be coming in. we only want to stop
1490 * when we reach our new position.
1492 void
1493 nsGfxScrollFrameInner::ScrollToRestoredPosition()
1495 nsIScrollableView* scrollingView = GetScrollableView();
1496 if (!scrollingView) {
1497 return;
1499 if (mRestoreRect.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
1500 return;
1502 // make sure our scroll position did not change for where we last put
1503 // it. if it does then the user must have moved it, and we no longer
1504 // need to restore.
1505 nscoord x = 0;
1506 nscoord y = 0;
1507 scrollingView->GetScrollPosition(x, y);
1509 // if we didn't move, we still need to restore
1510 if (x == mLastPos.x && y == mLastPos.y) {
1511 nsRect childRect(0, 0, 0, 0);
1512 nsIView* child = nsnull;
1513 nsresult rv = scrollingView->GetScrolledView(child);
1514 if (NS_SUCCEEDED(rv) && child)
1515 childRect = child->GetBounds();
1517 PRInt32 cx, cy, x, y;
1518 scrollingView->GetScrollPosition(cx,cy);
1520 x = (int)mRestoreRect.x;
1521 y = (int)mRestoreRect.y;
1523 // if our position is greater than the scroll position, scroll.
1524 // remember that we could be incrementally loading so we may enter
1525 // and scroll many times.
1526 if (y != cy || x != cx) {
1527 scrollingView->ScrollTo(x, y, 0);
1528 // scrollpostion goes from twips to pixels. this fixes any roundoff
1529 // problems.
1530 scrollingView->GetScrollPosition(mLastPos.x, mLastPos.y);
1531 } else {
1532 // if we reached the position then stop
1533 mRestoreRect.y = -1;
1534 mLastPos.x = -1;
1535 mLastPos.y = -1;
1537 } else {
1538 // user moved the position, so we won't need to restore
1539 mLastPos.x = -1;
1540 mLastPos.y = -1;
1544 nsresult
1545 nsGfxScrollFrameInner::FireScrollPortEvent()
1547 mAsyncScrollPortEvent.Forget();
1549 // Keep this in sync with PostOverflowEvent().
1550 nsSize scrollportSize = GetScrollPortSize();
1551 nsSize childSize = GetScrolledRect(scrollportSize).Size();
1553 PRBool newVerticalOverflow = childSize.height > scrollportSize.height;
1554 PRBool vertChanged = mVerticalOverflow != newVerticalOverflow;
1556 PRBool newHorizontalOverflow = childSize.width > scrollportSize.width;
1557 PRBool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
1559 if (!vertChanged && !horizChanged) {
1560 return NS_OK;
1563 // If both either overflowed or underflowed then we dispatch only one
1564 // DOM event.
1565 PRBool both = vertChanged && horizChanged &&
1566 newVerticalOverflow == newHorizontalOverflow;
1567 nsScrollPortEvent::orientType orient;
1568 if (both) {
1569 orient = nsScrollPortEvent::both;
1570 mHorizontalOverflow = newHorizontalOverflow;
1571 mVerticalOverflow = newVerticalOverflow;
1573 else if (vertChanged) {
1574 orient = nsScrollPortEvent::vertical;
1575 mVerticalOverflow = newVerticalOverflow;
1576 if (horizChanged) {
1577 // We need to dispatch a separate horizontal DOM event. Do that the next
1578 // time around since dispatching the vertical DOM event might destroy
1579 // the frame.
1580 PostOverflowEvent();
1583 else {
1584 orient = nsScrollPortEvent::horizontal;
1585 mHorizontalOverflow = newHorizontalOverflow;
1588 nsScrollPortEvent event(PR_TRUE,
1589 (orient == nsScrollPortEvent::horizontal ?
1590 mHorizontalOverflow : mVerticalOverflow) ?
1591 NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW,
1592 nsnull);
1593 event.orient = orient;
1594 return nsEventDispatcher::Dispatch(mOuter->GetContent(),
1595 mOuter->PresContext(), &event);
1598 void
1599 nsGfxScrollFrameInner::ReloadChildFrames()
1601 mScrolledFrame = nsnull;
1602 mHScrollbarBox = nsnull;
1603 mVScrollbarBox = nsnull;
1604 mScrollCornerBox = nsnull;
1606 nsIFrame* frame = mOuter->GetFirstChild(nsnull);
1607 while (frame) {
1608 nsIContent* content = frame->GetContent();
1609 if (content == mOuter->GetContent()) {
1610 NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
1611 mScrolledFrame = frame;
1612 } else {
1613 nsAutoString value;
1614 content->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, value);
1615 if (!value.IsEmpty()) {
1616 // probably a scrollbar then
1617 if (value.LowerCaseEqualsLiteral("horizontal")) {
1618 NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?");
1619 mHScrollbarBox = frame;
1620 } else {
1621 NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
1622 mVScrollbarBox = frame;
1624 } else {
1625 // probably a scrollcorner
1626 NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
1627 mScrollCornerBox = frame;
1631 frame = frame->GetNextSibling();
1635 nsresult
1636 nsGfxScrollFrameInner::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
1638 nsPresContext* presContext = mOuter->PresContext();
1639 nsIFrame* parent = mOuter->GetParent();
1641 // Don't create scrollbars if we're printing/print previewing
1642 // Get rid of this code when printing moves to its own presentation
1643 if (!presContext->IsDynamic()) {
1644 // allow scrollbars if this is the child of the viewport, because
1645 // we must be the scrollbars for the print preview window
1646 if (!(mIsRoot && presContext->HasPaginatedScrolling())) {
1647 mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = PR_TRUE;
1648 return NS_OK;
1652 nsIScrollableFrame *scrollable;
1653 CallQueryInterface(mOuter, &scrollable);
1655 // At this stage in frame construction, the document element and/or
1656 // BODY overflow styles have not yet been propagated to the
1657 // viewport. So GetScrollbarStylesFromFrame called here will only
1658 // take into account the scrollbar preferences set on the docshell.
1659 // Thus if no scrollbar preferences are set on the docshell, we will
1660 // always create scrollbars, which means later dynamic changes to
1661 // propagated overflow styles will show or hide scrollbars on the
1662 // viewport without requiring frame reconstruction of the viewport
1663 // (good!).
1665 // XXX On the other hand, if scrolling="no" is set on the container
1666 // we won't create scrollbars here so no scrollbars will ever be
1667 // created even if the container's scrolling attribute is later
1668 // changed. However, this has never been supported.
1669 ScrollbarStyles styles = scrollable->GetScrollbarStyles();
1670 PRBool canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
1671 PRBool canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
1672 if (!canHaveHorizontal && !canHaveVertical) {
1673 // Nothing to do.
1674 return NS_OK;
1677 // The anonymous <div> used by <inputs> never gets scrollbars.
1678 nsITextControlFrame* textFrame = nsnull;
1679 CallQueryInterface(parent, &textFrame);
1680 if (textFrame) {
1681 // Make sure we are not a text area.
1682 nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement(do_QueryInterface(parent->GetContent()));
1683 if (!textAreaElement) {
1684 mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = PR_TRUE;
1685 return NS_OK;
1689 nsresult rv;
1691 nsNodeInfoManager *nodeInfoManager =
1692 presContext->Document()->NodeInfoManager();
1693 nsCOMPtr<nsINodeInfo> nodeInfo;
1694 rv = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbar, nsnull,
1695 kNameSpaceID_XUL, getter_AddRefs(nodeInfo));
1696 NS_ENSURE_SUCCESS(rv, rv);
1698 if (canHaveHorizontal) {
1699 rv = NS_NewElement(getter_AddRefs(mHScrollbarContent),
1700 kNameSpaceID_XUL, nodeInfo);
1701 NS_ENSURE_SUCCESS(rv, rv);
1702 mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
1703 NS_LITERAL_STRING("horizontal"), PR_FALSE);
1704 if (!aElements.AppendElement(mHScrollbarContent))
1705 return NS_ERROR_OUT_OF_MEMORY;
1708 if (canHaveVertical) {
1709 rv = NS_NewElement(getter_AddRefs(mVScrollbarContent),
1710 kNameSpaceID_XUL, nodeInfo);
1711 NS_ENSURE_SUCCESS(rv, rv);
1712 mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
1713 NS_LITERAL_STRING("vertical"), PR_FALSE);
1714 if (!aElements.AppendElement(mVScrollbarContent))
1715 return NS_ERROR_OUT_OF_MEMORY;
1718 if (canHaveHorizontal && canHaveVertical) {
1719 nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nsnull,
1720 kNameSpaceID_XUL, getter_AddRefs(nodeInfo));
1721 rv = NS_NewElement(getter_AddRefs(mScrollCornerContent),
1722 kNameSpaceID_XUL, nodeInfo);
1723 NS_ENSURE_SUCCESS(rv, rv);
1724 if (!aElements.AppendElement(mScrollCornerContent))
1725 return NS_ERROR_OUT_OF_MEMORY;
1728 return NS_OK;
1731 void
1732 nsGfxScrollFrameInner::Destroy()
1734 // Unbind any content created in CreateAnonymousContent from the tree
1735 nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent);
1736 nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent);
1737 nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent);
1739 mScrollEvent.Revoke();
1740 mAsyncScrollPortEvent.Revoke();
1741 if (mPostedReflowCallback) {
1742 mOuter->PresContext()->PresShell()->CancelReflowCallback(this);
1743 mPostedReflowCallback = PR_FALSE;
1745 nsIScrollableView *view = GetScrollableView();
1746 NS_ASSERTION(view, "unexpected null pointer");
1747 if (view)
1748 view->RemoveScrollPositionListener(this);
1751 NS_IMETHODIMP
1752 nsGfxScrollFrameInner::ScrollPositionWillChange(nsIScrollableView* aScrollable, nscoord aX, nscoord aY)
1754 // Do nothing.
1755 return NS_OK;
1759 * Called when we want to update the scrollbar position, either because scrolling happened
1760 * or the user moved the scrollbar position and we need to undo that (e.g., when the user
1761 * clicks to scroll and we're using smooth scrolling, so we need to put the thumb back
1762 * to its initial position for the start of the smooth sequence).
1764 void
1765 nsGfxScrollFrameInner::InternalScrollPositionDidChange(nscoord aX, nscoord aY)
1767 if (mVScrollbarBox)
1768 SetCoordAttribute(mVScrollbarBox->GetContent(), nsGkAtoms::curpos,
1769 aY - GetScrolledRect(GetScrollPortSize()).y);
1771 if (mHScrollbarBox)
1772 SetCoordAttribute(mHScrollbarBox->GetContent(), nsGkAtoms::curpos,
1773 aX - GetScrolledRect(GetScrollPortSize()).x);
1777 * Called whenever actual scrolling happens for any reason.
1779 NS_IMETHODIMP
1780 nsGfxScrollFrameInner::ScrollPositionDidChange(nsIScrollableView* aScrollable, nscoord aX, nscoord aY)
1782 NS_ASSERTION(!mViewInitiatedScroll, "Cannot reenter ScrollPositionDidChange");
1784 // Update frame position to match view offsets
1785 nsPoint childOffset = mScrolledFrame->GetView()->GetOffsetTo(mOuter->GetView());
1786 mScrolledFrame->SetPosition(childOffset);
1788 mViewInitiatedScroll = PR_TRUE;
1789 InternalScrollPositionDidChange(aX, aY);
1790 mViewInitiatedScroll = PR_FALSE;
1792 PostScrollEvent();
1794 return NS_OK;
1797 void nsGfxScrollFrameInner::CurPosAttributeChanged(nsIContent* aContent)
1799 NS_ASSERTION(aContent, "aContent must not be null");
1800 NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
1801 (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
1802 "unexpected child");
1804 // Attribute changes on the scrollbars happen in one of three ways:
1805 // 1) The scrollbar changed the attribute in response to some user event
1806 // 2) We changed the attribute in response to a ScrollPositionDidChange
1807 // callback from the scrolling view
1808 // 3) We changed the attribute to adjust the scrollbars for the start
1809 // of a smooth scroll operation
1811 // In case 2), we don't need to scroll the view because the scrolling
1812 // has already happened. In case 3) we don't need to scroll because
1813 // we're just adjusting the scrollbars back to the correct setting
1814 // for the view.
1816 // Cases 1) and 3) do not indicate that actual scrolling has happened. Only
1817 // case 2) indicates actual scrolling. Therefore we do not fire onscroll
1818 // here, but in ScrollPositionDidChange.
1820 // We used to detect this case implicitly because we'd compare the
1821 // scrollbar attributes with the view's current scroll position and
1822 // bail out if they were equal. But that approach is fragile; it can
1823 // fail when, for example, the view scrolls horizontally and
1824 // vertically simultaneously; we'll get here when only the vertical
1825 // attribute has been set, so the attributes and the view scroll
1826 // position don't yet agree, and we'd tell the view to scroll to the
1827 // new vertical position and the old horizontal position! Even worse
1828 // things could happen when smooth scrolling got involved ... crashes
1829 // and other terrors.
1830 if (mViewInitiatedScroll || mFrameInitiatedScroll) return;
1832 nsRect scrolledRect = GetScrolledRect(GetScrollPortSize());
1834 nscoord x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos,
1835 -scrolledRect.x) +
1836 scrolledRect.x;
1837 nscoord y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos,
1838 -scrolledRect.y) +
1839 scrolledRect.y;
1841 // Make sure the scrollbars indeed moved before firing the event.
1842 // I think it is OK to prevent the call to ScrollbarChanged()
1843 // if we didn't actually move. The following check is the first
1844 // thing ScrollbarChanged() does anyway, before deciding to move
1845 // the scrollbars.
1846 nscoord curPosX=0, curPosY=0;
1847 nsIScrollableView* s = GetScrollableView();
1848 if (s) {
1849 s->GetScrollPosition(curPosX, curPosY);
1850 if (x == curPosX && y == curPosY)
1851 return;
1853 PRBool isSmooth = aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth);
1855 if (isSmooth) {
1856 // Make sure an attribute-setting callback occurs even if the view
1857 // didn't actually move yet. We need to make sure other listeners
1858 // see that the scroll position is not (yet) what they thought it
1859 // was.
1861 NS_ASSERTION(!mFrameInitiatedScroll, "Unexpected reentry");
1862 // Make sure we don't do anything in when the view calls us back
1863 // for this scroll operation.
1864 mFrameInitiatedScroll = PR_TRUE;
1865 InternalScrollPositionDidChange(curPosX, curPosY);
1866 mFrameInitiatedScroll = PR_FALSE;
1868 ScrollbarChanged(mOuter->PresContext(), x, y, isSmooth ? NS_VMREFRESH_SMOOTHSCROLL : 0);
1872 /* ============= Scroll events ========== */
1874 NS_IMETHODIMP
1875 nsGfxScrollFrameInner::ScrollEvent::Run()
1877 if (mInner)
1878 mInner->FireScrollEvent();
1879 return NS_OK;
1882 void
1883 nsGfxScrollFrameInner::FireScrollEvent()
1885 mScrollEvent.Forget();
1887 nsScrollbarEvent event(PR_TRUE, NS_SCROLL_EVENT, nsnull);
1888 nsEventStatus status = nsEventStatus_eIgnore;
1889 nsIContent* content = mOuter->GetContent();
1890 nsPresContext* prescontext = mOuter->PresContext();
1891 // Fire viewport scroll events at the document (where they
1892 // will bubble to the window)
1893 if (mIsRoot) {
1894 nsIDocument* doc = content->GetCurrentDoc();
1895 if (doc) {
1896 nsEventDispatcher::Dispatch(doc, prescontext, &event, nsnull, &status);
1898 } else {
1899 // scroll events fired at elements don't bubble (although scroll events
1900 // fired at documents do, to the window)
1901 event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
1902 nsEventDispatcher::Dispatch(content, prescontext, &event, nsnull, &status);
1906 void
1907 nsGfxScrollFrameInner::PostScrollEvent()
1909 if (mScrollEvent.IsPending())
1910 return;
1912 nsRefPtr<ScrollEvent> ev = new ScrollEvent(this);
1913 if (NS_FAILED(NS_DispatchToCurrentThread(ev))) {
1914 NS_WARNING("failed to dispatch ScrollEvent");
1915 } else {
1916 mScrollEvent = ev;
1920 NS_IMETHODIMP
1921 nsGfxScrollFrameInner::AsyncScrollPortEvent::Run()
1923 if (mInner) {
1924 mInner->mOuter->PresContext()->GetPresShell()->
1925 FlushPendingNotifications(Flush_Layout);
1927 return mInner ? mInner->FireScrollPortEvent() : NS_OK;
1930 PRBool
1931 nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState,
1932 nsRect& aScrollAreaSize, PRBool aOnTop)
1934 if (!mInner.mHScrollbarBox)
1935 return PR_TRUE;
1937 return AddRemoveScrollbar(aState, aScrollAreaSize, aOnTop, PR_TRUE, PR_TRUE);
1940 PRBool
1941 nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState,
1942 nsRect& aScrollAreaSize, PRBool aOnRight)
1944 if (!mInner.mVScrollbarBox)
1945 return PR_TRUE;
1947 return AddRemoveScrollbar(aState, aScrollAreaSize, aOnRight, PR_FALSE, PR_TRUE);
1950 void
1951 nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState,
1952 nsRect& aScrollAreaSize, PRBool aOnTop)
1954 // removing a scrollbar should always fit
1955 #ifdef DEBUG
1956 PRBool result =
1957 #endif
1958 AddRemoveScrollbar(aState, aScrollAreaSize, aOnTop, PR_TRUE, PR_FALSE);
1959 NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??");
1962 void
1963 nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState,
1964 nsRect& aScrollAreaSize, PRBool aOnRight)
1966 // removing a scrollbar should always fit
1967 #ifdef DEBUG
1968 PRBool result =
1969 #endif
1970 AddRemoveScrollbar(aState, aScrollAreaSize, aOnRight, PR_FALSE, PR_FALSE);
1971 NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??");
1974 PRBool
1975 nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState, nsRect& aScrollAreaSize,
1976 PRBool aOnTop, PRBool aHorizontal, PRBool aAdd)
1978 if (aHorizontal) {
1979 if (mInner.mNeverHasHorizontalScrollbar || !mInner.mHScrollbarBox)
1980 return PR_FALSE;
1982 nsSize hSize = mInner.mHScrollbarBox->GetPrefSize(aState);
1983 nsBox::AddMargin(mInner.mHScrollbarBox, hSize);
1985 mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, aAdd);
1987 PRBool hasHorizontalScrollbar;
1988 PRBool fit = AddRemoveScrollbar(hasHorizontalScrollbar, aScrollAreaSize.y, aScrollAreaSize.height, hSize.height, aOnTop, aAdd);
1989 mInner.mHasHorizontalScrollbar = hasHorizontalScrollbar; // because mHasHorizontalScrollbar is a PRPackedBool
1990 if (!fit)
1991 mInner.SetScrollbarVisibility(mInner.mHScrollbarBox, !aAdd);
1993 return fit;
1994 } else {
1995 if (mInner.mNeverHasVerticalScrollbar || !mInner.mVScrollbarBox)
1996 return PR_FALSE;
1998 nsSize vSize = mInner.mVScrollbarBox->GetPrefSize(aState);
1999 nsBox::AddMargin(mInner.mVScrollbarBox, vSize);
2001 mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, aAdd);
2003 PRBool hasVerticalScrollbar;
2004 PRBool fit = AddRemoveScrollbar(hasVerticalScrollbar, aScrollAreaSize.x, aScrollAreaSize.width, vSize.width, aOnTop, aAdd);
2005 mInner.mHasVerticalScrollbar = hasVerticalScrollbar; // because mHasVerticalScrollbar is a PRPackedBool
2006 if (!fit)
2007 mInner.SetScrollbarVisibility(mInner.mVScrollbarBox, !aAdd);
2009 return fit;
2013 PRBool
2014 nsXULScrollFrame::AddRemoveScrollbar(PRBool& aHasScrollbar, nscoord& aXY,
2015 nscoord& aSize, nscoord aSbSize,
2016 PRBool aRightOrBottom, PRBool aAdd)
2018 nscoord size = aSize;
2019 nscoord xy = aXY;
2021 if (size != NS_INTRINSICSIZE) {
2022 if (aAdd) {
2023 size -= aSbSize;
2024 if (!aRightOrBottom && size >= 0)
2025 xy += aSbSize;
2026 } else {
2027 size += aSbSize;
2028 if (!aRightOrBottom)
2029 xy -= aSbSize;
2033 // not enough room? Yes? Return true.
2034 if (size >= 0) {
2035 aHasScrollbar = aAdd;
2036 aSize = size;
2037 aXY = xy;
2038 return PR_TRUE;
2041 aHasScrollbar = PR_FALSE;
2042 return PR_FALSE;
2045 void
2046 nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState, const nsRect& aRect)
2048 nsIView* scrollView = mInner.mScrollableView->View();
2049 nsIViewManager* vm = scrollView->GetViewManager();
2050 vm->MoveViewTo(scrollView, aRect.x, aRect.y);
2051 vm->ResizeView(scrollView, nsRect(nsPoint(0, 0), aRect.Size()), PR_TRUE);
2053 PRUint32 oldflags = aState.LayoutFlags();
2054 nsPoint childOffset =
2055 mInner.mScrolledFrame->GetView()->GetOffsetTo(GetView());
2056 nsRect childRect = nsRect(childOffset, aRect.Size());
2058 PRInt32 flags = NS_FRAME_NO_MOVE_VIEW;
2060 nsSize minSize = mInner.mScrolledFrame->GetMinSize(aState);
2062 if (minSize.height > childRect.height)
2063 childRect.height = minSize.height;
2065 if (minSize.width > childRect.width)
2066 childRect.width = minSize.width;
2068 aState.SetLayoutFlags(flags);
2069 mInner.mScrolledFrame->SetBounds(aState, childRect);
2070 mInner.mScrolledFrame->Layout(aState);
2072 childRect = mInner.mScrolledFrame->GetRect();
2074 if (childRect.width < aRect.width || childRect.height < aRect.height)
2076 childRect.width = PR_MAX(childRect.width, aRect.width);
2077 childRect.height = PR_MAX(childRect.height, aRect.height);
2079 // remove overflow area when we update the bounds,
2080 // because we've already accounted for it
2081 mInner.mScrolledFrame->SetBounds(aState, childRect);
2082 PresContext()->PropertyTable()->
2083 DeleteProperty(mInner.mScrolledFrame, nsGkAtoms::overflowAreaProperty);
2084 mInner.mScrolledFrame->RemoveStateBits(NS_FRAME_OUTSIDE_CHILDREN);
2087 aState.SetLayoutFlags(oldflags);
2091 void nsGfxScrollFrameInner::PostOverflowEvent()
2093 if (mAsyncScrollPortEvent.IsPending())
2094 return;
2096 // Keep this in sync with FireScrollPortEvent().
2097 nsSize scrollportSize = GetScrollPortSize();
2098 nsSize childSize = GetScrolledRect(scrollportSize).Size();
2100 PRBool newVerticalOverflow = childSize.height > scrollportSize.height;
2101 PRBool vertChanged = mVerticalOverflow != newVerticalOverflow;
2103 PRBool newHorizontalOverflow = childSize.width > scrollportSize.width;
2104 PRBool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
2106 if (!vertChanged && !horizChanged) {
2107 return;
2110 nsRefPtr<AsyncScrollPortEvent> ev = new AsyncScrollPortEvent(this);
2111 if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev)))
2112 mAsyncScrollPortEvent = ev;
2115 PRBool
2116 nsGfxScrollFrameInner::IsLTR() const
2118 //TODO make bidi code set these from preferences
2120 nsIFrame *frame = mOuter;
2121 // XXX This is a bit on the slow side.
2122 if (mIsRoot) {
2123 // If we're the root scrollframe, we need the root element's style data.
2124 nsPresContext *presContext = mOuter->PresContext();
2125 nsIDocument *document = presContext->Document();
2126 nsIContent *root = document->GetRootContent();
2128 // But for HTML we want the body element.
2129 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
2130 if (htmlDoc) {
2131 nsIContent *bodyContent = htmlDoc->GetBodyContentExternal();
2132 if (bodyContent)
2133 root = bodyContent; // we can trust the document to hold on to it
2136 if (root) {
2137 nsIFrame *rootsFrame =
2138 presContext->PresShell()->GetPrimaryFrameFor(root);
2139 if (rootsFrame)
2140 frame = rootsFrame;
2144 return frame->GetStyleVisibility()->mDirection != NS_STYLE_DIRECTION_RTL;
2147 PRBool
2148 nsGfxScrollFrameInner::IsScrollbarOnRight() const
2150 nsPresContext *presContext = mOuter->PresContext();
2151 switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) {
2152 default:
2153 case 0: // UI directionality
2154 return presContext->GetCachedIntPref(kPresContext_BidiDirection)
2155 == IBMBIDI_TEXTDIRECTION_LTR;
2156 case 1: // Document / content directionality
2157 return IsLTR();
2158 case 2: // Always right
2159 return PR_TRUE;
2160 case 3: // Always left
2161 return PR_FALSE;
2166 * Reflow the scroll area if it needs it and return its size. Also determine if the reflow will
2167 * cause any of the scrollbars to need to be reflowed.
2169 nsresult
2170 nsXULScrollFrame::Layout(nsBoxLayoutState& aState)
2172 PRBool scrollbarRight = mInner.IsScrollbarOnRight();
2173 PRBool scrollbarBottom = PR_TRUE;
2175 // get the content rect
2176 nsRect clientRect(0,0,0,0);
2177 GetClientRect(clientRect);
2179 // the scroll area size starts off as big as our content area
2180 nsRect scrollAreaRect(clientRect);
2182 /**************
2183 Our basic strategy here is to first try laying out the content with
2184 the scrollbars in their current state. We're hoping that that will
2185 just "work"; the content will overflow wherever there's a scrollbar
2186 already visible. If that does work, then there's no need to lay out
2187 the scrollarea. Otherwise we fix up the scrollbars; first we add a
2188 vertical one to scroll the content if necessary, or remove it if
2189 it's not needed. Then we reflow the content if the scrollbar
2190 changed. Then we add a horizontal scrollbar if necessary (or
2191 remove if not needed), and if that changed, we reflow the content
2192 again. At this point, any scrollbars that are needed to scroll the
2193 content have been added.
2195 In the second phase we check to see if any scrollbars are too small
2196 to display, and if so, we remove them. We check the horizontal
2197 scrollbar first; removing it might make room for the vertical
2198 scrollbar, and if we have room for just one scrollbar we'll save
2199 the vertical one.
2201 Finally we position and size the scrollbars and scrollcorner (the
2202 square that is needed in the corner of the window when two
2203 scrollbars are visible), and reflow any fixed position views
2204 (if we're the viewport and we added or removed a scrollbar).
2205 **************/
2207 ScrollbarStyles styles = GetScrollbarStyles();
2209 // Look at our style do we always have vertical or horizontal scrollbars?
2210 if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL)
2211 mInner.mHasHorizontalScrollbar = PR_TRUE;
2212 if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL)
2213 mInner.mHasVerticalScrollbar = PR_TRUE;
2215 if (mInner.mHasHorizontalScrollbar)
2216 AddHorizontalScrollbar(aState, scrollAreaRect, scrollbarBottom);
2218 if (mInner.mHasVerticalScrollbar)
2219 AddVerticalScrollbar(aState, scrollAreaRect, scrollbarRight);
2221 nsRect oldScrollAreaBounds = mInner.mScrollableView->View()->GetBounds();
2223 // layout our the scroll area
2224 LayoutScrollArea(aState, scrollAreaRect);
2226 // now look at the content area and see if we need scrollbars or not
2227 PRBool needsLayout = PR_FALSE;
2229 // if we have 'auto' scrollbars look at the vertical case
2230 if (styles.mVertical != NS_STYLE_OVERFLOW_SCROLL) {
2231 // These are only good until the call to LayoutScrollArea.
2232 nsRect scrolledRect = mInner.GetScrolledRect(scrollAreaRect.Size());
2233 nsSize scrolledContentSize(scrolledRect.XMost(), scrolledRect.YMost());
2235 // There are two cases to consider
2236 if (scrolledContentSize.height <= scrollAreaRect.height
2237 || styles.mVertical != NS_STYLE_OVERFLOW_AUTO) {
2238 if (mInner.mHasVerticalScrollbar) {
2239 // We left room for the vertical scrollbar, but it's not needed;
2240 // remove it.
2241 RemoveVerticalScrollbar(aState, scrollAreaRect, scrollbarRight);
2242 needsLayout = PR_TRUE;
2244 } else {
2245 if (!mInner.mHasVerticalScrollbar) {
2246 // We didn't leave room for the vertical scrollbar, but it turns
2247 // out we needed it
2248 if (AddVerticalScrollbar(aState, scrollAreaRect, scrollbarRight))
2249 needsLayout = PR_TRUE;
2253 // ok layout at the right size
2254 if (needsLayout) {
2255 nsBoxLayoutState resizeState(aState);
2256 LayoutScrollArea(resizeState, scrollAreaRect);
2257 needsLayout = PR_FALSE;
2262 // if scrollbars are auto look at the horizontal case
2263 if (styles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL)
2265 // These are only good until the call to LayoutScrollArea.
2266 nsRect scrolledRect = mInner.GetScrolledRect(scrollAreaRect.Size());
2267 nsSize scrolledContentSize(scrolledRect.XMost(), scrolledRect.YMost());
2269 // if the child is wider that the scroll area
2270 // and we don't have a scrollbar add one.
2271 if (scrolledContentSize.width > scrollAreaRect.width
2272 && styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) {
2274 if (!mInner.mHasHorizontalScrollbar) {
2275 // no scrollbar?
2276 if (AddHorizontalScrollbar(aState, scrollAreaRect, scrollbarBottom))
2277 needsLayout = PR_TRUE;
2279 // if we added a horizonal scrollbar and we did not have a vertical
2280 // there is a chance that by adding the horizonal scrollbar we will
2281 // suddenly need a vertical scrollbar. Is a special case but its
2282 // important.
2283 //if (!mHasVerticalScrollbar && scrolledContentSize.height > scrollAreaRect.height - sbSize.height)
2284 // printf("****Gfx Scrollbar Special case hit!!*****\n");
2287 } else {
2288 // if the area is smaller or equal to and we have a scrollbar then
2289 // remove it.
2290 if (mInner.mHasHorizontalScrollbar) {
2291 RemoveHorizontalScrollbar(aState, scrollAreaRect, scrollbarBottom);
2292 needsLayout = PR_TRUE;
2297 // we only need to set the rect. The inner child stays the same size.
2298 if (needsLayout) {
2299 nsBoxLayoutState resizeState(aState);
2300 LayoutScrollArea(resizeState, scrollAreaRect);
2301 needsLayout = PR_FALSE;
2304 // get the preferred size of the scrollbars
2305 nsSize hMinSize(0, 0);
2306 if (mInner.mHScrollbarBox && mInner.mHasHorizontalScrollbar) {
2307 GetScrollbarMetrics(aState, mInner.mHScrollbarBox, &hMinSize, nsnull, PR_FALSE);
2309 nsSize vMinSize(0, 0);
2310 if (mInner.mVScrollbarBox && mInner.mHasVerticalScrollbar) {
2311 GetScrollbarMetrics(aState, mInner.mVScrollbarBox, &vMinSize, nsnull, PR_TRUE);
2314 // Disable scrollbars that are too small
2315 // Disable horizontal scrollbar first. If we have to disable only one
2316 // scrollbar, we'd rather keep the vertical scrollbar.
2317 // Note that we always give horizontal scrollbars their preferred height,
2318 // never their min-height. So check that there's room for the preferred height.
2319 if (mInner.mHasHorizontalScrollbar &&
2320 (hMinSize.width > clientRect.width - vMinSize.width
2321 || hMinSize.height > clientRect.height)) {
2322 RemoveHorizontalScrollbar(aState, scrollAreaRect, scrollbarBottom);
2323 needsLayout = PR_TRUE;
2325 // Now disable vertical scrollbar if necessary
2326 if (mInner.mHasVerticalScrollbar &&
2327 (vMinSize.height > clientRect.height - hMinSize.height
2328 || vMinSize.width > clientRect.width)) {
2329 RemoveVerticalScrollbar(aState, scrollAreaRect, scrollbarRight);
2330 needsLayout = PR_TRUE;
2333 // we only need to set the rect. The inner child stays the same size.
2334 if (needsLayout) {
2335 nsBoxLayoutState resizeState(aState);
2336 LayoutScrollArea(resizeState, scrollAreaRect);
2339 if (!mInner.mSupppressScrollbarUpdate) {
2340 mInner.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds, scrollAreaRect);
2342 mInner.ScrollToRestoredPosition();
2343 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
2344 mInner.mHadNonInitialReflow = PR_TRUE;
2347 mInner.PostOverflowEvent();
2348 return NS_OK;
2351 void
2352 nsGfxScrollFrameInner::FinishReflowForScrollbar(nsIContent* aContent,
2353 nscoord aMinXY, nscoord aMaxXY,
2354 nscoord aCurPosXY,
2355 nscoord aPageIncrement,
2356 nscoord aIncrement)
2358 // Scrollbars assume zero is the minimum position, so translate for them.
2359 SetCoordAttribute(aContent, nsGkAtoms::curpos, aCurPosXY - aMinXY);
2360 SetScrollbarEnabled(aContent, aMaxXY - aMinXY);
2361 SetCoordAttribute(aContent, nsGkAtoms::maxpos, aMaxXY - aMinXY);
2362 SetCoordAttribute(aContent, nsGkAtoms::pageincrement, aPageIncrement);
2363 SetCoordAttribute(aContent, nsGkAtoms::increment, aIncrement);
2366 PRBool
2367 nsGfxScrollFrameInner::ReflowFinished()
2369 mPostedReflowCallback = PR_FALSE;
2371 if (mMayHaveDirtyFixedChildren) {
2372 mMayHaveDirtyFixedChildren = PR_FALSE;
2373 nsIFrame* parentFrame = mOuter->GetParent();
2374 for (nsIFrame* fixedChild =
2375 parentFrame->GetFirstChild(nsGkAtoms::fixedList);
2376 fixedChild; fixedChild = fixedChild->GetNextSibling()) {
2377 // force a reflow of the fixed child
2378 mOuter->PresContext()->PresShell()->
2379 FrameNeedsReflow(fixedChild, nsIPresShell::eResize,
2380 NS_FRAME_HAS_DIRTY_CHILDREN);
2384 // Update scrollbar attributes.
2385 nsPresContext* presContext = mOuter->PresContext();
2387 nsIScrollableView* scrollable = GetScrollableView();
2388 nsRect scrollArea = scrollable->View()->GetBounds();
2390 const nsStyleFont* font = mOuter->GetStyleFont();
2391 const nsFont& f = font->mFont;
2392 nsCOMPtr<nsIFontMetrics> fm = presContext->GetMetricsFor(f);
2393 nscoord fontHeight = 1;
2394 NS_ASSERTION(fm,"FontMetrics is null assuming fontHeight == 1");
2395 if (fm)
2396 fm->GetHeight(fontHeight);
2397 scrollable->SetLineHeight(fontHeight);
2399 nsRect scrolledContentRect = GetScrolledRect(scrollArea.Size());
2400 nscoord minX = scrolledContentRect.x;
2401 nscoord maxX = scrolledContentRect.XMost() - scrollArea.width;
2402 nscoord minY = scrolledContentRect.y;
2403 nscoord maxY = scrolledContentRect.YMost() - scrollArea.height;
2405 // Suppress handling of the curpos attribute changes we make here.
2406 NS_ASSERTION(!mFrameInitiatedScroll, "We shouldn't be reentering here");
2407 mFrameInitiatedScroll = PR_TRUE;
2409 nsCOMPtr<nsIContent> vScroll =
2410 mVScrollbarBox ? mVScrollbarBox->GetContent() : nsnull;
2411 nsCOMPtr<nsIContent> hScroll =
2412 mHScrollbarBox ? mHScrollbarBox->GetContent() : nsnull;
2414 // Note, in some cases mOuter may get deleted while finishing reflow
2415 // for scrollbars.
2416 if (vScroll || hScroll) {
2417 nsWeakFrame weakFrame(mOuter);
2418 nscoord curPosX, curPosY;
2419 scrollable->GetScrollPosition(curPosX, curPosY);
2420 if (vScroll) {
2421 // We normally use (scrollArea.height - fontHeight) for height
2422 // of page scrolling. However, it is too small when
2423 // fontHeight is very large. (If fontHeight is larger than
2424 // scrollArea.height, direction of scrolling will be opposite).
2425 // To avoid it, we use (float(scrollArea.height) * 0.8) as
2426 // lower bound value of height of page scrolling. (bug 383267)
2427 nscoord pageincrement = nscoord(scrollArea.height - fontHeight);
2428 nscoord pageincrementMin = nscoord(float(scrollArea.height) * 0.8);
2429 FinishReflowForScrollbar(vScroll, minY, maxY, curPosY,
2430 PR_MAX(pageincrement,pageincrementMin),
2431 fontHeight);
2433 if (hScroll) {
2434 FinishReflowForScrollbar(hScroll, minX, maxX, curPosX,
2435 nscoord(float(scrollArea.width) * 0.8),
2436 nsPresContext::CSSPixelsToAppUnits(10));
2438 NS_ENSURE_TRUE(weakFrame.IsAlive(), PR_FALSE);
2441 mFrameInitiatedScroll = PR_FALSE;
2442 // We used to rely on the curpos attribute changes above to scroll the
2443 // view. However, for scrolling to the left of the viewport, we
2444 // rescale the curpos attribute, which means that operations like
2445 // resizing the window while it is scrolled all the way to the left
2446 // hold the curpos attribute constant at 0 while still requiring
2447 // scrolling. So we suppress the effect of the changes above with
2448 // mFrameInitiatedScroll and call CurPosAttributeChanged here.
2449 // (It actually even works some of the time without this, thanks to
2450 // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
2451 // we hide the scrollbar on a large size change, such as
2452 // maximization.)
2453 if (!mHScrollbarBox && !mVScrollbarBox)
2454 return PR_FALSE;
2455 CurPosAttributeChanged(mVScrollbarBox ? mVScrollbarBox->GetContent()
2456 : mHScrollbarBox->GetContent());
2457 return PR_TRUE;
2460 void
2461 nsGfxScrollFrameInner::ReflowCallbackCanceled()
2463 mPostedReflowCallback = PR_FALSE;
2466 static void LayoutAndInvalidate(nsBoxLayoutState& aState,
2467 nsIFrame* aBox, const nsRect& aRect)
2469 // When a child box changes shape of position, the parent
2470 // is responsible for invalidation; the overflow rect must be invalidated
2471 // to make sure to catch any overflow
2472 PRBool rectChanged = aBox->GetRect() != aRect;
2473 if (rectChanged)
2474 aBox->Invalidate(aBox->GetOverflowRect());
2475 nsBoxFrame::LayoutChildAt(aState, aBox, aRect);
2476 if (rectChanged)
2477 aBox->Invalidate(aBox->GetOverflowRect());
2480 void
2481 nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
2482 const nsRect& aContentArea,
2483 const nsRect& aOldScrollArea,
2484 const nsRect& aScrollArea)
2486 NS_ASSERTION(!mSupppressScrollbarUpdate,
2487 "This should have been suppressed");
2489 if (mVScrollbarBox) {
2490 NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
2491 nsRect vRect(aScrollArea);
2492 vRect.width = aContentArea.width - aScrollArea.width;
2493 vRect.x = IsScrollbarOnRight() ? aScrollArea.XMost() : aContentArea.x;
2494 nsMargin margin;
2495 mVScrollbarBox->GetMargin(margin);
2496 vRect.Deflate(margin);
2497 LayoutAndInvalidate(aState, mVScrollbarBox, vRect);
2500 if (mHScrollbarBox) {
2501 NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!");
2502 nsRect hRect(aScrollArea);
2503 hRect.height = aContentArea.height - aScrollArea.height;
2504 hRect.y = PR_TRUE ? aScrollArea.YMost() : aContentArea.y;
2505 nsMargin margin;
2506 mHScrollbarBox->GetMargin(margin);
2507 hRect.Deflate(margin);
2508 LayoutAndInvalidate(aState, mHScrollbarBox, hRect);
2511 // place the scrollcorner
2512 if (mScrollCornerBox) {
2513 NS_PRECONDITION(mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
2514 nsRect r(0, 0, 0, 0);
2515 if (aContentArea.x != aScrollArea.x) {
2516 // scrollbar (if any) on left
2517 r.x = aContentArea.x;
2518 r.width = aScrollArea.x - aContentArea.x;
2519 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
2520 } else {
2521 // scrollbar (if any) on right
2522 r.x = aScrollArea.XMost();
2523 r.width = aContentArea.XMost() - aScrollArea.XMost();
2524 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
2526 if (aContentArea.y != aScrollArea.y) {
2527 // scrollbar (if any) on top
2528 r.y = aContentArea.y;
2529 r.height = aScrollArea.y - aContentArea.y;
2530 NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
2531 } else {
2532 // scrollbar (if any) on bottom
2533 r.y = aScrollArea.YMost();
2534 r.height = aContentArea.YMost() - aScrollArea.YMost();
2535 NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
2537 LayoutAndInvalidate(aState, mScrollCornerBox, r);
2540 // may need to update fixed position children of the viewport,
2541 // if the client area changed size because of an incremental
2542 // reflow of a descendant. (If the outer frame is dirty, the fixed
2543 // children will be re-laid out anyway)
2544 if (aOldScrollArea.Size() != aScrollArea.Size() &&
2545 !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) &&
2546 mIsRoot) {
2547 mMayHaveDirtyFixedChildren = PR_TRUE;
2550 // post reflow callback to modify scrollbar attributes
2551 if (!mPostedReflowCallback) {
2552 aState.PresContext()->PresShell()->PostReflowCallback(this);
2553 mPostedReflowCallback = PR_TRUE;
2557 void
2558 nsGfxScrollFrameInner::ScrollbarChanged(nsPresContext* aPresContext, nscoord aX, nscoord aY, PRUint32 aFlags)
2560 nsIScrollableView* scrollable = GetScrollableView();
2561 scrollable->ScrollTo(aX, aY, aFlags);
2562 // printf("scrolling to: %d, %d\n", aX, aY);
2565 void
2566 nsGfxScrollFrameInner::SetScrollbarEnabled(nsIContent* aContent, nscoord aMaxPos)
2568 if (aMaxPos) {
2569 aContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, PR_TRUE);
2570 } else {
2571 aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
2572 NS_LITERAL_STRING("true"), PR_TRUE);
2576 void
2577 nsGfxScrollFrameInner::SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom,
2578 nscoord aSize)
2580 // convert to pixels
2581 aSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
2583 // only set the attribute if it changed.
2585 nsAutoString newValue;
2586 newValue.AppendInt(aSize);
2588 if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters))
2589 return;
2591 aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, PR_TRUE);
2594 nsRect
2595 nsGfxScrollFrameInner::GetScrolledRect(const nsSize& aScrollPortSize) const
2597 nsRect result = mScrolledFrame->GetOverflowRect();
2598 nscoord x1 = result.x, x2 = result.XMost(),
2599 y1 = result.y, y2 = result.YMost();
2600 if (y1 < 0)
2601 y1 = 0;
2602 if (IsLTR() || mIsXUL) {
2603 if (x1 < 0)
2604 x1 = 0;
2605 } else {
2606 if (x2 > aScrollPortSize.width)
2607 x2 = aScrollPortSize.width;
2610 return nsRect(x1, y1, x2 - x1, y2 - y1);
2613 nsMargin
2614 nsGfxScrollFrameInner::GetActualScrollbarSizes() const {
2615 nsMargin border;
2616 mOuter->GetBorder(border);
2617 nsRect r(nsPoint(0,0), mOuter->GetSize());
2618 r.Deflate(border);
2619 nsRect scrollArea = mScrollableView->View()->GetBounds();
2621 return nsMargin(scrollArea.x - r.x, scrollArea.y - r.y,
2622 r.XMost() - scrollArea.XMost(),
2623 r.YMost() - scrollArea.YMost());
2626 void
2627 nsGfxScrollFrameInner::SetScrollbarVisibility(nsIBox* aScrollbar, PRBool aVisible)
2629 if (!aScrollbar)
2630 return;
2632 nsIScrollbarFrame* scrollbar;
2633 CallQueryInterface(aScrollbar, &scrollbar);
2634 if (scrollbar) {
2635 // See if we have a mediator.
2636 nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
2637 if (mediator) {
2638 // Inform the mediator of the visibility change.
2639 mediator->VisibilityChanged(scrollbar, aVisible);
2644 PRInt32
2645 nsGfxScrollFrameInner::GetCoordAttribute(nsIBox* aBox, nsIAtom* atom, PRInt32 defaultValue)
2647 if (aBox) {
2648 nsIContent* content = aBox->GetContent();
2650 nsAutoString value;
2651 content->GetAttr(kNameSpaceID_None, atom, value);
2652 if (!value.IsEmpty())
2654 PRInt32 error;
2656 // convert it to an integer
2657 defaultValue = nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
2661 return defaultValue;
2664 static nsIURI* GetDocURI(nsIFrame* aFrame)
2666 nsIPresShell* shell = aFrame->PresContext()->GetPresShell();
2667 if (!shell)
2668 return nsnull;
2669 nsIDocument* doc = shell->GetDocument();
2670 if (!doc)
2671 return nsnull;
2672 return doc->GetDocumentURI();
2675 void
2676 nsGfxScrollFrameInner::SaveVScrollbarStateToGlobalHistory()
2678 NS_ASSERTION(mIsRoot, "Only use this on viewports");
2680 // If the hint is the same as the one we loaded, don't bother
2681 // saving it
2682 if (mDidLoadHistoryVScrollbarHint &&
2683 (mHistoryVScrollbarHint == mHasVerticalScrollbar))
2684 return;
2686 nsIURI* uri = GetDocURI(mOuter);
2687 if (!uri)
2688 return;
2690 nsCOMPtr<nsIGlobalHistory3> history(do_GetService(NS_GLOBALHISTORY2_CONTRACTID));
2691 if (!history)
2692 return;
2694 PRUint32 flags = 0;
2695 if (mHasVerticalScrollbar) {
2696 flags |= NS_GECKO_FLAG_NEEDS_VERTICAL_SCROLLBAR;
2698 history->SetURIGeckoFlags(uri, flags);
2699 // if it fails, we don't care
2702 nsresult
2703 nsGfxScrollFrameInner::GetVScrollbarHintFromGlobalHistory(PRBool* aVScrollbarNeeded)
2705 NS_ASSERTION(mIsRoot, "Only use this on viewports");
2706 NS_ASSERTION(!mDidLoadHistoryVScrollbarHint,
2707 "Should only load a hint once, it can be expensive");
2709 nsIURI* uri = GetDocURI(mOuter);
2710 if (!uri)
2711 return NS_ERROR_FAILURE;
2713 nsCOMPtr<nsIGlobalHistory3> history(do_GetService(NS_GLOBALHISTORY2_CONTRACTID));
2714 if (!history)
2715 return NS_ERROR_FAILURE;
2717 PRUint32 flags;
2718 nsresult rv = history->GetURIGeckoFlags(uri, &flags);
2719 if (NS_FAILED(rv))
2720 return rv;
2722 *aVScrollbarNeeded = (flags & NS_GECKO_FLAG_NEEDS_VERTICAL_SCROLLBAR) != 0;
2723 mDidLoadHistoryVScrollbarHint = PR_TRUE;
2724 mHistoryVScrollbarHint = *aVScrollbarNeeded;
2725 return NS_OK;
2728 nsPresState*
2729 nsGfxScrollFrameInner::SaveState(nsIStatefulFrame::SpecialStateID aStateID)
2731 // Don't save "normal" state for the root scrollframe; that's
2732 // handled via the eDocumentScrollState state id
2733 if (mIsRoot && aStateID == nsIStatefulFrame::eNoID) {
2734 return nsnull;
2737 nsIScrollbarMediator* mediator;
2738 CallQueryInterface(GetScrolledFrame(), &mediator);
2739 if (mediator) {
2740 // child handles its own scroll state, so don't bother saving state here
2741 return nsnull;
2744 nsIScrollableView* scrollingView = GetScrollableView();
2745 PRInt32 x,y;
2746 scrollingView->GetScrollPosition(x,y);
2747 // Don't save scroll position if we are at (0,0)
2748 if (!x && !y) {
2749 return nsnull;
2752 nsIView* child = nsnull;
2753 scrollingView->GetScrolledView(child);
2754 if (!child) {
2755 return nsnull;
2758 nsRect childRect = child->GetBounds();
2759 childRect.x = x;
2760 childRect.y = y;
2761 nsAutoPtr<nsPresState> state;
2762 nsresult rv = NS_NewPresState(getter_Transfers(state));
2763 NS_ENSURE_SUCCESS(rv, nsnull);
2765 state->SetScrollState(childRect);
2767 return state.forget();
2770 void
2771 nsGfxScrollFrameInner::RestoreState(nsPresState* aState)
2773 mRestoreRect = aState->GetScrollState();
2774 mLastPos.x = -1;
2775 mLastPos.y = -1;
2776 mDidHistoryRestore = PR_TRUE;
2777 nsIScrollableView* scrollingView = GetScrollableView();
2778 if (scrollingView) {
2779 scrollingView->GetScrollPosition(mLastPos.x, mLastPos.y);
2780 } else {
2781 mLastPos = nsPoint(0, 0);