michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* rendering object to wrap rendering objects that should be scrollable */ michael@0: michael@0: #include "nsGfxScrollFrame.h" michael@0: michael@0: #include "base/compiler_specific.h" michael@0: #include "DisplayItemClip.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsView.h" michael@0: #include "nsIScrollable.h" michael@0: #include "nsContainerFrame.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsContentList.h" michael@0: #include "nsIDocumentInlines.h" michael@0: #include "nsFontMetrics.h" michael@0: #include "nsBoxLayoutState.h" michael@0: #include "nsINodeInfo.h" michael@0: #include "nsScrollbarFrame.h" michael@0: #include "nsIScrollbarMediator.h" michael@0: #include "nsITextControlFrame.h" michael@0: #include "nsIDOMHTMLTextAreaElement.h" michael@0: #include "nsNodeInfoManager.h" michael@0: #include "nsContentCreatorFunctions.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsPresState.h" michael@0: #include "nsIHTMLDocument.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsBidiUtils.h" michael@0: #include "mozilla/ContentEvents.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "FrameLayerBuilder.h" michael@0: #include "nsSMILKeySpline.h" michael@0: #include "nsSubDocumentFrame.h" michael@0: #include "nsSVGOuterSVGFrame.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "ScrollbarActivity.h" michael@0: #include "nsRefreshDriver.h" michael@0: #include "nsThemeConstants.h" michael@0: #include "nsSVGIntegrationUtils.h" michael@0: #include "nsIScrollPositionListener.h" michael@0: #include "StickyScrollContainer.h" michael@0: #include "nsIFrameInlines.h" michael@0: #include "gfxPrefs.h" michael@0: #include michael@0: #include // for std::abs(int/long) michael@0: #include // for std::abs(float/double) michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::layout; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: //----------nsHTMLScrollFrame------------------------------------------- michael@0: michael@0: nsIFrame* michael@0: NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot) michael@0: { michael@0: return new (aPresShell) nsHTMLScrollFrame(aPresShell, aContext, aIsRoot); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame) michael@0: michael@0: nsHTMLScrollFrame::nsHTMLScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, bool aIsRoot) michael@0: : nsContainerFrame(aContext), michael@0: mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsHTMLScrollFrame::ScrollbarActivityStarted() const michael@0: { michael@0: if (mHelper.mScrollbarActivity) { michael@0: mHelper.mScrollbarActivity->ActivityStarted(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHTMLScrollFrame::ScrollbarActivityStopped() const michael@0: { michael@0: if (mHelper.mScrollbarActivity) { michael@0: mHelper.mScrollbarActivity->ActivityStopped(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLScrollFrame::CreateAnonymousContent(nsTArray& aElements) michael@0: { michael@0: return mHelper.CreateAnonymousContent(aElements); michael@0: } michael@0: michael@0: void michael@0: nsHTMLScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, michael@0: uint32_t aFilter) michael@0: { michael@0: mHelper.AppendAnonymousContentTo(aElements, aFilter); michael@0: } michael@0: michael@0: void michael@0: nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: DestroyAbsoluteFrames(aDestructRoot); michael@0: mHelper.Destroy(); michael@0: nsContainerFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLScrollFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: nsresult rv = nsContainerFrame::SetInitialChildList(aListID, aChildList); michael@0: mHelper.ReloadChildFrames(); michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLScrollFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "Only main list supported"); michael@0: mFrames.AppendFrames(nullptr, aFrameList); michael@0: mHelper.ReloadChildFrames(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLScrollFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "Only main list supported"); michael@0: NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, michael@0: "inserting after sibling frame with different parent"); michael@0: mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); michael@0: mHelper.ReloadChildFrames(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLScrollFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "Only main list supported"); michael@0: mFrames.DestroyFrame(aOldFrame); michael@0: mHelper.ReloadChildFrames(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsSplittableType michael@0: nsHTMLScrollFrame::GetSplittableType() const michael@0: { michael@0: return NS_FRAME_NOT_SPLITTABLE; michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsHTMLScrollFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::scrollFrame; michael@0: } michael@0: michael@0: /** michael@0: HTML scrolling implementation michael@0: michael@0: All other things being equal, we prefer layouts with fewer scrollbars showing. michael@0: */ michael@0: michael@0: struct MOZ_STACK_CLASS ScrollReflowState { michael@0: const nsHTMLReflowState& mReflowState; michael@0: nsBoxLayoutState mBoxState; michael@0: ScrollbarStyles mStyles; michael@0: nsMargin mComputedBorder; michael@0: michael@0: // === Filled in by ReflowScrolledFrame === michael@0: nsOverflowAreas mContentsOverflowAreas; michael@0: bool mReflowedContentsWithHScrollbar; michael@0: bool mReflowedContentsWithVScrollbar; michael@0: michael@0: // === Filled in when TryLayout succeeds === michael@0: // The size of the inside-border area michael@0: nsSize mInsideBorderSize; michael@0: // Whether we decided to show the horizontal scrollbar michael@0: bool mShowHScrollbar; michael@0: // Whether we decided to show the vertical scrollbar michael@0: bool mShowVScrollbar; michael@0: michael@0: ScrollReflowState(nsIScrollableFrame* aFrame, michael@0: const nsHTMLReflowState& aState) : michael@0: mReflowState(aState), michael@0: // mBoxState is just used for scrollbars so we don't need to michael@0: // worry about the reflow depth here michael@0: mBoxState(aState.frame->PresContext(), aState.rendContext, 0), michael@0: mStyles(aFrame->GetScrollbarStyles()) { michael@0: } michael@0: }; michael@0: michael@0: // XXXldb Can this go away? michael@0: static nsSize ComputeInsideBorderSize(ScrollReflowState* aState, michael@0: const nsSize& aDesiredInsideBorderSize) michael@0: { michael@0: // aDesiredInsideBorderSize is the frame size; i.e., it includes michael@0: // borders and padding (but the scrolled child doesn't have michael@0: // borders). The scrolled child has the same padding as us. michael@0: nscoord contentWidth = aState->mReflowState.ComputedWidth(); michael@0: if (contentWidth == NS_UNCONSTRAINEDSIZE) { michael@0: contentWidth = aDesiredInsideBorderSize.width - michael@0: aState->mReflowState.ComputedPhysicalPadding().LeftRight(); michael@0: } michael@0: nscoord contentHeight = aState->mReflowState.ComputedHeight(); michael@0: if (contentHeight == NS_UNCONSTRAINEDSIZE) { michael@0: contentHeight = aDesiredInsideBorderSize.height - michael@0: aState->mReflowState.ComputedPhysicalPadding().TopBottom(); michael@0: } michael@0: michael@0: contentWidth = aState->mReflowState.ApplyMinMaxWidth(contentWidth); michael@0: contentHeight = aState->mReflowState.ApplyMinMaxHeight(contentHeight); michael@0: return nsSize(contentWidth + aState->mReflowState.ComputedPhysicalPadding().LeftRight(), michael@0: contentHeight + aState->mReflowState.ComputedPhysicalPadding().TopBottom()); michael@0: } michael@0: michael@0: static void michael@0: GetScrollbarMetrics(nsBoxLayoutState& aState, nsIFrame* aBox, nsSize* aMin, michael@0: nsSize* aPref, bool aVertical) michael@0: { michael@0: NS_ASSERTION(aState.GetRenderingContext(), michael@0: "Must have rendering context in layout state for size " michael@0: "computations"); michael@0: michael@0: if (aMin) { michael@0: *aMin = aBox->GetMinSize(aState); michael@0: nsBox::AddMargin(aBox, *aMin); michael@0: if (aMin->width < 0) { michael@0: aMin->width = 0; michael@0: } michael@0: if (aMin->height < 0) { michael@0: aMin->height = 0; michael@0: } michael@0: } michael@0: michael@0: if (aPref) { michael@0: *aPref = aBox->GetPrefSize(aState); michael@0: nsBox::AddMargin(aBox, *aPref); michael@0: if (aPref->width < 0) { michael@0: aPref->width = 0; michael@0: } michael@0: if (aPref->height < 0) { michael@0: aPref->height = 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Assuming that we know the metrics for our wrapped frame and michael@0: * whether the horizontal and/or vertical scrollbars are present, michael@0: * compute the resulting layout and return true if the layout is michael@0: * consistent. If the layout is consistent then we fill in the michael@0: * computed fields of the ScrollReflowState. michael@0: * michael@0: * The layout is consistent when both scrollbars are showing if and only michael@0: * if they should be showing. A horizontal scrollbar should be showing if all michael@0: * following conditions are met: michael@0: * 1) the style is not HIDDEN michael@0: * 2) our inside-border height is at least the scrollbar height (i.e., the michael@0: * scrollbar fits vertically) michael@0: * 3) our scrollport width (the inside-border width minus the width allocated for a michael@0: * vertical scrollbar, if showing) is at least the scrollbar's min-width michael@0: * (i.e., the scrollbar fits horizontally) michael@0: * 4) the style is SCROLL, or the kid's overflow-area XMost is michael@0: * greater than the scrollport width michael@0: * michael@0: * @param aForce if true, then we just assume the layout is consistent. michael@0: */ michael@0: bool michael@0: nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState, michael@0: nsHTMLReflowMetrics* aKidMetrics, michael@0: bool aAssumeHScroll, bool aAssumeVScroll, michael@0: bool aForce, nsresult* aResult) michael@0: { michael@0: *aResult = NS_OK; michael@0: michael@0: if ((aState->mStyles.mVertical == NS_STYLE_OVERFLOW_HIDDEN && aAssumeVScroll) || michael@0: (aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && aAssumeHScroll)) { michael@0: NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!"); michael@0: return false; michael@0: } michael@0: michael@0: if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar || michael@0: (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar && michael@0: ScrolledContentDependsOnHeight(aState))) { michael@0: nsresult rv = ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll, michael@0: aKidMetrics, false); michael@0: if (NS_FAILED(rv)) { michael@0: *aResult = rv; michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: nsSize vScrollbarMinSize(0, 0); michael@0: nsSize vScrollbarPrefSize(0, 0); michael@0: if (mHelper.mVScrollbarBox) { michael@0: GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox, michael@0: &vScrollbarMinSize, michael@0: aAssumeVScroll ? &vScrollbarPrefSize : nullptr, true); michael@0: } michael@0: nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0; michael@0: nscoord vScrollbarMinHeight = aAssumeVScroll ? vScrollbarMinSize.height : 0; michael@0: michael@0: nsSize hScrollbarMinSize(0, 0); michael@0: nsSize hScrollbarPrefSize(0, 0); michael@0: if (mHelper.mHScrollbarBox) { michael@0: GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, michael@0: &hScrollbarMinSize, michael@0: aAssumeHScroll ? &hScrollbarPrefSize : nullptr, false); michael@0: } michael@0: nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0; michael@0: nscoord hScrollbarMinWidth = aAssumeHScroll ? hScrollbarMinSize.width : 0; michael@0: michael@0: // First, compute our inside-border size and scrollport size michael@0: // XXXldb Can we depend more on ComputeSize here? michael@0: nsSize desiredInsideBorderSize; michael@0: desiredInsideBorderSize.width = vScrollbarDesiredWidth + michael@0: std::max(aKidMetrics->Width(), hScrollbarMinWidth); michael@0: desiredInsideBorderSize.height = hScrollbarDesiredHeight + michael@0: std::max(aKidMetrics->Height(), vScrollbarMinHeight); michael@0: aState->mInsideBorderSize = michael@0: ComputeInsideBorderSize(aState, desiredInsideBorderSize); michael@0: nsSize scrollPortSize = nsSize(std::max(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth), michael@0: std::max(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight)); michael@0: michael@0: if (!aForce) { michael@0: nsRect scrolledRect = michael@0: mHelper.GetScrolledRectInternal(aState->mContentsOverflowAreas.ScrollableOverflow(), michael@0: scrollPortSize); michael@0: nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1); michael@0: michael@0: // If the style is HIDDEN then we already know that aAssumeHScroll is false michael@0: if (aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) { michael@0: bool wantHScrollbar = michael@0: aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL || michael@0: scrolledRect.XMost() >= scrollPortSize.width + oneDevPixel || michael@0: scrolledRect.x <= -oneDevPixel; michael@0: if (scrollPortSize.width < hScrollbarMinSize.width) michael@0: wantHScrollbar = false; michael@0: if (wantHScrollbar != aAssumeHScroll) michael@0: return false; michael@0: } michael@0: michael@0: // If the style is HIDDEN then we already know that aAssumeVScroll is false michael@0: if (aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) { michael@0: bool wantVScrollbar = michael@0: aState->mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL || michael@0: scrolledRect.YMost() >= scrollPortSize.height + oneDevPixel || michael@0: scrolledRect.y <= -oneDevPixel; michael@0: if (scrollPortSize.height < vScrollbarMinSize.height) michael@0: wantVScrollbar = false; michael@0: if (wantVScrollbar != aAssumeVScroll) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: nscoord vScrollbarActualWidth = aState->mInsideBorderSize.width - scrollPortSize.width; michael@0: michael@0: aState->mShowHScrollbar = aAssumeHScroll; michael@0: aState->mShowVScrollbar = aAssumeVScroll; michael@0: nsPoint scrollPortOrigin(aState->mComputedBorder.left, michael@0: aState->mComputedBorder.top); michael@0: if (!mHelper.IsScrollbarOnRight()) { michael@0: scrollPortOrigin.x += vScrollbarActualWidth; michael@0: } michael@0: mHelper.mScrollPort = nsRect(scrollPortOrigin, scrollPortSize); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLScrollFrame::ScrolledContentDependsOnHeight(ScrollReflowState* aState) michael@0: { michael@0: // Return true if ReflowScrolledFrame is going to do something different michael@0: // based on the presence of a horizontal scrollbar. michael@0: return (mHelper.mScrolledFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) || michael@0: aState->mReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE || michael@0: aState->mReflowState.ComputedMinHeight() > 0 || michael@0: aState->mReflowState.ComputedMaxHeight() != NS_UNCONSTRAINEDSIZE; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState, michael@0: bool aAssumeHScroll, michael@0: bool aAssumeVScroll, michael@0: nsHTMLReflowMetrics* aMetrics, michael@0: bool aFirstPass) michael@0: { michael@0: // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should michael@0: // be OK michael@0: const nsMargin& padding = aState->mReflowState.ComputedPhysicalPadding(); michael@0: nscoord availWidth = aState->mReflowState.ComputedWidth() + padding.LeftRight(); michael@0: michael@0: nscoord computedHeight = aState->mReflowState.ComputedHeight(); michael@0: nscoord computedMinHeight = aState->mReflowState.ComputedMinHeight(); michael@0: nscoord computedMaxHeight = aState->mReflowState.ComputedMaxHeight(); michael@0: if (!ShouldPropagateComputedHeightToScrolledContent()) { michael@0: computedHeight = NS_UNCONSTRAINEDSIZE; michael@0: computedMinHeight = 0; michael@0: computedMaxHeight = NS_UNCONSTRAINEDSIZE; michael@0: } michael@0: if (aAssumeHScroll) { michael@0: nsSize hScrollbarPrefSize; michael@0: GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, michael@0: nullptr, &hScrollbarPrefSize, false); michael@0: if (computedHeight != NS_UNCONSTRAINEDSIZE) { michael@0: computedHeight = std::max(0, computedHeight - hScrollbarPrefSize.height); michael@0: } michael@0: computedMinHeight = std::max(0, computedMinHeight - hScrollbarPrefSize.height); michael@0: if (computedMaxHeight != NS_UNCONSTRAINEDSIZE) { michael@0: computedMaxHeight = std::max(0, computedMaxHeight - hScrollbarPrefSize.height); michael@0: } michael@0: } michael@0: michael@0: if (aAssumeVScroll) { michael@0: nsSize vScrollbarPrefSize; michael@0: GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox, michael@0: nullptr, &vScrollbarPrefSize, true); michael@0: availWidth = std::max(0, availWidth - vScrollbarPrefSize.width); michael@0: } michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: // Pass false for aInit so we can pass in the correct padding. michael@0: nsHTMLReflowState kidReflowState(presContext, aState->mReflowState, michael@0: mHelper.mScrolledFrame, michael@0: nsSize(availWidth, NS_UNCONSTRAINEDSIZE), michael@0: -1, -1, nsHTMLReflowState::CALLER_WILL_INIT); michael@0: kidReflowState.Init(presContext, -1, -1, nullptr, michael@0: &padding); michael@0: kidReflowState.mFlags.mAssumingHScrollbar = aAssumeHScroll; michael@0: kidReflowState.mFlags.mAssumingVScrollbar = aAssumeVScroll; michael@0: kidReflowState.SetComputedHeight(computedHeight); michael@0: kidReflowState.ComputedMinHeight() = computedMinHeight; michael@0: kidReflowState.ComputedMaxHeight() = computedMaxHeight; michael@0: michael@0: // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to michael@0: // reflect our assumptions while we reflow the child. michael@0: bool didHaveHorizontalScrollbar = mHelper.mHasHorizontalScrollbar; michael@0: bool didHaveVerticalScrollbar = mHelper.mHasVerticalScrollbar; michael@0: mHelper.mHasHorizontalScrollbar = aAssumeHScroll; michael@0: mHelper.mHasVerticalScrollbar = aAssumeVScroll; michael@0: michael@0: nsReflowStatus status; michael@0: nsresult rv = ReflowChild(mHelper.mScrolledFrame, presContext, *aMetrics, michael@0: kidReflowState, 0, 0, michael@0: NS_FRAME_NO_MOVE_FRAME, status); michael@0: michael@0: mHelper.mHasHorizontalScrollbar = didHaveHorizontalScrollbar; michael@0: mHelper.mHasVerticalScrollbar = didHaveVerticalScrollbar; michael@0: michael@0: // Don't resize or position the view (if any) because we're going to resize michael@0: // it to the correct size anyway in PlaceScrollArea. Allowing it to michael@0: // resize here would size it to the natural height of the frame, michael@0: // which will usually be different from the scrollport height; michael@0: // invalidating the difference will cause unnecessary repainting. michael@0: FinishReflowChild(mHelper.mScrolledFrame, presContext, michael@0: *aMetrics, &kidReflowState, 0, 0, michael@0: NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW); michael@0: michael@0: // XXX Some frames (e.g., nsObjectFrame, nsFrameFrame, nsTextFrame) don't bother michael@0: // setting their mOverflowArea. This is wrong because every frame should michael@0: // always set mOverflowArea. In fact nsObjectFrame and nsFrameFrame don't michael@0: // support the 'outline' property because of this. Rather than fix the world michael@0: // right now, just fix up the overflow area if necessary. Note that we don't michael@0: // check HasOverflowRect() because it could be set even though the michael@0: // overflow area doesn't include the frame bounds. michael@0: aMetrics->UnionOverflowAreasWithDesiredBounds(); michael@0: michael@0: if (MOZ_UNLIKELY(StyleDisplay()->mOverflowClipBox == michael@0: NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) { michael@0: nsOverflowAreas childOverflow; michael@0: nsLayoutUtils::UnionChildOverflow(mHelper.mScrolledFrame, childOverflow); michael@0: nsRect childScrollableOverflow = childOverflow.ScrollableOverflow(); michael@0: childScrollableOverflow.Inflate(padding); michael@0: nsRect contentArea = nsRect(0, 0, availWidth, computedHeight); michael@0: if (!contentArea.Contains(childScrollableOverflow)) { michael@0: aMetrics->mOverflowAreas.ScrollableOverflow() = childScrollableOverflow; michael@0: } michael@0: } michael@0: michael@0: aState->mContentsOverflowAreas = aMetrics->mOverflowAreas; michael@0: aState->mReflowedContentsWithHScrollbar = aAssumeHScroll; michael@0: aState->mReflowedContentsWithVScrollbar = aAssumeVScroll; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowState& aState) michael@0: { michael@0: if (aState.mStyles.mHorizontal != NS_STYLE_OVERFLOW_AUTO) michael@0: // no guessing required michael@0: return aState.mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL; michael@0: michael@0: return mHelper.mHasHorizontalScrollbar; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowState& aState) michael@0: { michael@0: if (aState.mStyles.mVertical != NS_STYLE_OVERFLOW_AUTO) michael@0: // no guessing required michael@0: return aState.mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL; michael@0: michael@0: // If we've had at least one non-initial reflow, then just assume michael@0: // the state of the vertical scrollbar will be what we determined michael@0: // last time. michael@0: if (mHelper.mHadNonInitialReflow) { michael@0: return mHelper.mHasVerticalScrollbar; michael@0: } michael@0: michael@0: // If this is the initial reflow, guess false because usually michael@0: // we have very little content by then. michael@0: if (InInitialReflow()) michael@0: return false; michael@0: michael@0: if (mHelper.mIsRoot) { michael@0: nsIFrame *f = mHelper.mScrolledFrame->GetFirstPrincipalChild(); michael@0: if (f && f->GetType() == nsGkAtoms::svgOuterSVGFrame && michael@0: static_cast(f)->VerticalScrollbarNotNeeded()) { michael@0: // Common SVG case - avoid a bad guess. michael@0: return false; michael@0: } michael@0: // Assume that there will be a scrollbar; it seems to me michael@0: // that 'most pages' do have a scrollbar, and anyway, it's cheaper michael@0: // to do an extra reflow for the pages that *don't* need a michael@0: // scrollbar (because on average they will have less content). michael@0: return true; michael@0: } michael@0: michael@0: // For non-viewports, just guess that we don't need a scrollbar. michael@0: // XXX I wonder if statistically this is the right idea; I'm michael@0: // basically guessing that there are a lot of overflow:auto DIVs michael@0: // that get their intrinsic size and don't overflow michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLScrollFrame::InInitialReflow() const michael@0: { michael@0: // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a michael@0: // root scrollframe. In that case we want to skip this clause altogether. michael@0: // The guess here is that there are lots of overflow:auto divs out there that michael@0: // end up auto-sizing so they don't overflow, and that the root basically michael@0: // always needs a scrollbar if it did last time we loaded this page (good michael@0: // assumption, because our initial reflow is no longer synchronous). michael@0: return !mHelper.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState, michael@0: const nsHTMLReflowMetrics& aDesiredSize) michael@0: { michael@0: nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.GetWritingMode(), aDesiredSize.mFlags); michael@0: nsresult rv = ReflowScrolledFrame(aState, GuessHScrollbarNeeded(*aState), michael@0: GuessVScrollbarNeeded(*aState), &kidDesiredSize, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // There's an important special case ... if the child appears to fit michael@0: // in the inside-border rect (but overflows the scrollport), we michael@0: // should try laying it out without a vertical scrollbar. It will michael@0: // usually fit because making the available-width wider will not michael@0: // normally make the child taller. (The only situation I can think michael@0: // of is when you have a line containing %-width inline replaced michael@0: // elements whose percentages sum to more than 100%, so increasing michael@0: // the available width makes the line break where it was fitting michael@0: // before.) If we don't treat this case specially, then we will michael@0: // decide that showing scrollbars is OK because the content michael@0: // overflows when we're showing scrollbars and we won't try to michael@0: // remove the vertical scrollbar. michael@0: michael@0: // Detecting when we enter this special case is important for when michael@0: // people design layouts that exactly fit the container "most of the michael@0: // time". michael@0: michael@0: // XXX Is this check really sufficient to catch all the incremental cases michael@0: // where the ideal case doesn't have a scrollbar? michael@0: if ((aState->mReflowedContentsWithHScrollbar || aState->mReflowedContentsWithVScrollbar) && michael@0: aState->mStyles.mVertical != NS_STYLE_OVERFLOW_SCROLL && michael@0: aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) { michael@0: nsSize insideBorderSize = michael@0: ComputeInsideBorderSize(aState, michael@0: nsSize(kidDesiredSize.Width(), kidDesiredSize.Height())); michael@0: nsRect scrolledRect = michael@0: mHelper.GetScrolledRectInternal(kidDesiredSize.ScrollableOverflow(), michael@0: insideBorderSize); michael@0: if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) { michael@0: // Let's pretend we had no scrollbars coming in here michael@0: rv = ReflowScrolledFrame(aState, false, false, michael@0: &kidDesiredSize, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: // Try vertical scrollbar settings that leave the vertical scrollbar unchanged. michael@0: // Do this first because changing the vertical scrollbar setting is expensive, michael@0: // forcing a reflow always. michael@0: michael@0: // Try leaving the horizontal scrollbar unchanged first. This will be more michael@0: // efficient. michael@0: if (TryLayout(aState, &kidDesiredSize, aState->mReflowedContentsWithHScrollbar, michael@0: aState->mReflowedContentsWithVScrollbar, false, &rv)) michael@0: return NS_OK; michael@0: if (TryLayout(aState, &kidDesiredSize, !aState->mReflowedContentsWithHScrollbar, michael@0: aState->mReflowedContentsWithVScrollbar, false, &rv)) michael@0: return NS_OK; michael@0: michael@0: // OK, now try toggling the vertical scrollbar. The performance advantage michael@0: // of trying the status-quo horizontal scrollbar state michael@0: // does not exist here (we'll have to reflow due to the vertical scrollbar michael@0: // change), so always try no horizontal scrollbar first. michael@0: bool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar; michael@0: if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false, &rv)) michael@0: return NS_OK; michael@0: if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false, &rv)) michael@0: return NS_OK; michael@0: michael@0: // OK, we're out of ideas. Try again enabling whatever scrollbars we can michael@0: // enable and force the layout to stick even if it's inconsistent. michael@0: // This just happens sometimes. michael@0: TryLayout(aState, &kidDesiredSize, michael@0: aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN, michael@0: aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN, michael@0: true, &rv); michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowState& aState, michael@0: const nsPoint& aScrollPosition) michael@0: { michael@0: nsIFrame *scrolledFrame = mHelper.mScrolledFrame; michael@0: // Set the x,y of the scrolled frame to the correct value michael@0: scrolledFrame->SetPosition(mHelper.mScrollPort.TopLeft() - aScrollPosition); michael@0: michael@0: nsRect scrolledArea; michael@0: // Preserve the width or height of empty rects michael@0: nsSize portSize = mHelper.mScrollPort.Size(); michael@0: nsRect scrolledRect = michael@0: mHelper.GetScrolledRectInternal(aState.mContentsOverflowAreas.ScrollableOverflow(), michael@0: portSize); michael@0: scrolledArea.UnionRectEdges(scrolledRect, michael@0: nsRect(nsPoint(0,0), portSize)); michael@0: michael@0: // Store the new overflow area. Note that this changes where an outline michael@0: // of the scrolled frame would be painted, but scrolled frames can't have michael@0: // outlines (the outline would go on this scrollframe instead). michael@0: // Using FinishAndStoreOverflow is needed so the overflow rect michael@0: // gets set correctly. It also messes with the overflow rect in the michael@0: // -moz-hidden-unscrollable case, but scrolled frames can't have michael@0: // 'overflow' either. michael@0: // This needs to happen before SyncFrameViewAfterReflow so michael@0: // HasOverflowRect() will return the correct value. michael@0: nsOverflowAreas overflow(scrolledArea, scrolledArea); michael@0: scrolledFrame->FinishAndStoreOverflow(overflow, michael@0: scrolledFrame->GetSize()); michael@0: michael@0: // Note that making the view *exactly* the size of the scrolled area michael@0: // is critical, since the view scrolling code uses the size of the michael@0: // scrolled view to clamp scroll requests. michael@0: // Normally the scrolledFrame won't have a view but in some cases it michael@0: // might create its own. michael@0: nsContainerFrame::SyncFrameViewAfterReflow(scrolledFrame->PresContext(), michael@0: scrolledFrame, michael@0: scrolledFrame->GetView(), michael@0: scrolledArea, michael@0: 0); michael@0: } michael@0: michael@0: nscoord michael@0: nsHTMLScrollFrame::GetIntrinsicVScrollbarWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: ScrollbarStyles ss = GetScrollbarStyles(); michael@0: if (ss.mVertical != NS_STYLE_OVERFLOW_SCROLL || !mHelper.mVScrollbarBox) michael@0: return 0; michael@0: michael@0: // Don't need to worry about reflow depth here since it's michael@0: // just for scrollbars michael@0: nsBoxLayoutState bls(PresContext(), aRenderingContext, 0); michael@0: nsSize vScrollbarPrefSize(0, 0); michael@0: GetScrollbarMetrics(bls, mHelper.mVScrollbarBox, michael@0: nullptr, &vScrollbarPrefSize, true); michael@0: return vScrollbarPrefSize.width; michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsHTMLScrollFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nscoord result = mHelper.mScrolledFrame->GetMinWidth(aRenderingContext); michael@0: DISPLAY_MIN_WIDTH(this, result); michael@0: return result + GetIntrinsicVScrollbarWidth(aRenderingContext); michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsHTMLScrollFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nscoord result = mHelper.mScrolledFrame->GetPrefWidth(aRenderingContext); michael@0: DISPLAY_PREF_WIDTH(this, result); michael@0: return NSCoordSaturatingAdd(result, GetIntrinsicVScrollbarWidth(aRenderingContext)); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLScrollFrame::GetPadding(nsMargin& aMargin) michael@0: { michael@0: // Our padding hangs out on the inside of the scrollframe, but XUL doesn't michael@0: // reaize that. If we're stuck inside a XUL box, we need to claim no michael@0: // padding. michael@0: // @see also nsXULScrollFrame::GetPadding. michael@0: aMargin.SizeTo(0,0,0,0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLScrollFrame::IsCollapsed() michael@0: { michael@0: // We're never collapsed in the box sense. michael@0: return false; michael@0: } michael@0: michael@0: // Return the if the scrollframe is for the root frame directly michael@0: // inside a . michael@0: static nsIContent* michael@0: GetBrowserRoot(nsIContent* aContent) michael@0: { michael@0: if (aContent) { michael@0: nsIDocument* doc = aContent->GetCurrentDoc(); michael@0: nsPIDOMWindow* win = doc->GetWindow(); michael@0: if (win) { michael@0: nsCOMPtr frameContent = michael@0: do_QueryInterface(win->GetFrameElementInternal()); michael@0: if (frameContent && michael@0: frameContent->NodeInfo()->Equals(nsGkAtoms::browser, kNameSpaceID_XUL)) michael@0: return frameContent; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); michael@0: michael@0: mHelper.HandleScrollbarStyleSwitching(); michael@0: michael@0: ScrollReflowState state(this, aReflowState); michael@0: // sanity check: ensure that if we have no scrollbar, we treat it michael@0: // as hidden. michael@0: if (!mHelper.mVScrollbarBox || mHelper.mNeverHasVerticalScrollbar) michael@0: state.mStyles.mVertical = NS_STYLE_OVERFLOW_HIDDEN; michael@0: if (!mHelper.mHScrollbarBox || mHelper.mNeverHasHorizontalScrollbar) michael@0: state.mStyles.mHorizontal = NS_STYLE_OVERFLOW_HIDDEN; michael@0: michael@0: //------------ Handle Incremental Reflow ----------------- michael@0: bool reflowHScrollbar = true; michael@0: bool reflowVScrollbar = true; michael@0: bool reflowScrollCorner = true; michael@0: if (!aReflowState.ShouldReflowAllKids()) { michael@0: #define NEEDS_REFLOW(frame_) \ michael@0: ((frame_) && NS_SUBTREE_DIRTY(frame_)) michael@0: michael@0: reflowHScrollbar = NEEDS_REFLOW(mHelper.mHScrollbarBox); michael@0: reflowVScrollbar = NEEDS_REFLOW(mHelper.mVScrollbarBox); michael@0: reflowScrollCorner = NEEDS_REFLOW(mHelper.mScrollCornerBox) || michael@0: NEEDS_REFLOW(mHelper.mResizerBox); michael@0: michael@0: #undef NEEDS_REFLOW michael@0: } michael@0: michael@0: if (mHelper.mIsRoot) { michael@0: mHelper.mCollapsedResizer = true; michael@0: michael@0: nsIContent* browserRoot = GetBrowserRoot(mContent); michael@0: if (browserRoot) { michael@0: bool showResizer = browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer); michael@0: reflowScrollCorner = showResizer == mHelper.mCollapsedResizer; michael@0: mHelper.mCollapsedResizer = !showResizer; michael@0: } michael@0: } michael@0: michael@0: nsRect oldScrollAreaBounds = mHelper.mScrollPort; michael@0: nsRect oldScrolledAreaBounds = michael@0: mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent(); michael@0: nsPoint oldScrollPosition = mHelper.GetScrollPosition(); michael@0: michael@0: state.mComputedBorder = aReflowState.ComputedPhysicalBorderPadding() - michael@0: aReflowState.ComputedPhysicalPadding(); michael@0: michael@0: nsresult rv = ReflowContents(&state, aDesiredSize); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Restore the old scroll position, for now, even if that's not valid anymore michael@0: // because we changed size. We'll fix it up in a post-reflow callback, because michael@0: // our current size may only be temporary (e.g. we're compute XUL desired sizes). michael@0: PlaceScrollArea(state, oldScrollPosition); michael@0: if (!mHelper.mPostedReflowCallback) { michael@0: // Make sure we'll try scrolling to restored position michael@0: PresContext()->PresShell()->PostReflowCallback(&mHelper); michael@0: mHelper.mPostedReflowCallback = true; michael@0: } michael@0: michael@0: bool didHaveHScrollbar = mHelper.mHasHorizontalScrollbar; michael@0: bool didHaveVScrollbar = mHelper.mHasVerticalScrollbar; michael@0: mHelper.mHasHorizontalScrollbar = state.mShowHScrollbar; michael@0: mHelper.mHasVerticalScrollbar = state.mShowVScrollbar; michael@0: nsRect newScrollAreaBounds = mHelper.mScrollPort; michael@0: nsRect newScrolledAreaBounds = michael@0: mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent(); michael@0: if (mHelper.mSkippedScrollbarLayout || michael@0: reflowHScrollbar || reflowVScrollbar || reflowScrollCorner || michael@0: (GetStateBits() & NS_FRAME_IS_DIRTY) || michael@0: didHaveHScrollbar != state.mShowHScrollbar || michael@0: didHaveVScrollbar != state.mShowVScrollbar || michael@0: !oldScrollAreaBounds.IsEqualEdges(newScrollAreaBounds) || michael@0: !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) { michael@0: if (!mHelper.mSupppressScrollbarUpdate) { michael@0: mHelper.mSkippedScrollbarLayout = false; michael@0: mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, state.mShowHScrollbar); michael@0: mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, state.mShowVScrollbar); michael@0: // place and reflow scrollbars michael@0: nsRect insideBorderArea = michael@0: nsRect(nsPoint(state.mComputedBorder.left, state.mComputedBorder.top), michael@0: state.mInsideBorderSize); michael@0: mHelper.LayoutScrollbars(state.mBoxState, insideBorderArea, michael@0: oldScrollAreaBounds); michael@0: } else { michael@0: mHelper.mSkippedScrollbarLayout = true; michael@0: } michael@0: } michael@0: michael@0: aDesiredSize.Width() = state.mInsideBorderSize.width + michael@0: state.mComputedBorder.LeftRight(); michael@0: aDesiredSize.Height() = state.mInsideBorderSize.height + michael@0: state.mComputedBorder.TopBottom(); michael@0: michael@0: aDesiredSize.SetOverflowAreasToDesiredBounds(); michael@0: if (mHelper.IsIgnoringViewportClipping()) { michael@0: aDesiredSize.mOverflowAreas.UnionWith( michael@0: state.mContentsOverflowAreas + mHelper.mScrolledFrame->GetPosition()); michael@0: } michael@0: michael@0: mHelper.UpdateSticky(); michael@0: FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus); michael@0: michael@0: if (!InInitialReflow() && !mHelper.mHadNonInitialReflow) { michael@0: mHelper.mHadNonInitialReflow = true; michael@0: } michael@0: michael@0: if (mHelper.mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) { michael@0: mHelper.PostScrolledAreaEvent(); michael@0: } michael@0: michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: mHelper.PostOverflowEvent(); michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("HTMLScroll"), aResult); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsHTMLScrollFrame::AccessibleType() michael@0: { michael@0: // Create an accessible regardless of focusable state because the state can be michael@0: // changed during frame life cycle without any notifications to accessibility. michael@0: if (mContent->IsRootOfNativeAnonymousSubtree() || michael@0: GetScrollbarStyles() == michael@0: ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN) ) { michael@0: return a11y::eNoType; michael@0: } michael@0: michael@0: return a11y::eHyperTextType; michael@0: } michael@0: #endif michael@0: michael@0: NS_QUERYFRAME_HEAD(nsHTMLScrollFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) michael@0: NS_QUERYFRAME_ENTRY(nsIScrollbarOwner) michael@0: NS_QUERYFRAME_ENTRY(nsIScrollableFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIStatefulFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) michael@0: michael@0: //----------nsXULScrollFrame------------------------------------------- michael@0: michael@0: nsIFrame* michael@0: NS_NewXULScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, michael@0: bool aIsRoot, bool aClipAllDescendants) michael@0: { michael@0: return new (aPresShell) nsXULScrollFrame(aPresShell, aContext, aIsRoot, michael@0: aClipAllDescendants); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsXULScrollFrame) michael@0: michael@0: nsXULScrollFrame::nsXULScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, michael@0: bool aIsRoot, bool aClipAllDescendants) michael@0: : nsBoxFrame(aShell, aContext, aIsRoot), michael@0: mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot) michael@0: { michael@0: SetLayoutManager(nullptr); michael@0: mHelper.mClipAllDescendants = aClipAllDescendants; michael@0: } michael@0: michael@0: void michael@0: nsXULScrollFrame::ScrollbarActivityStarted() const michael@0: { michael@0: if (mHelper.mScrollbarActivity) { michael@0: mHelper.mScrollbarActivity->ActivityStarted(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXULScrollFrame::ScrollbarActivityStopped() const michael@0: { michael@0: if (mHelper.mScrollbarActivity) { michael@0: mHelper.mScrollbarActivity->ActivityStopped(); michael@0: } michael@0: } michael@0: michael@0: nsMargin michael@0: ScrollFrameHelper::GetDesiredScrollbarSizes(nsBoxLayoutState* aState) michael@0: { michael@0: NS_ASSERTION(aState && aState->GetRenderingContext(), michael@0: "Must have rendering context in layout state for size " michael@0: "computations"); michael@0: michael@0: nsMargin result(0, 0, 0, 0); michael@0: michael@0: if (mVScrollbarBox) { michael@0: nsSize size = mVScrollbarBox->GetPrefSize(*aState); michael@0: nsBox::AddMargin(mVScrollbarBox, size); michael@0: if (IsScrollbarOnRight()) michael@0: result.left = size.width; michael@0: else michael@0: result.right = size.width; michael@0: } michael@0: michael@0: if (mHScrollbarBox) { michael@0: nsSize size = mHScrollbarBox->GetPrefSize(*aState); michael@0: nsBox::AddMargin(mHScrollbarBox, size); michael@0: // We don't currently support any scripts that would require a scrollbar michael@0: // at the top. (Are there any?) michael@0: result.bottom = size.height; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nscoord michael@0: ScrollFrameHelper::GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState) michael@0: { michael@0: NS_ASSERTION(aState && aState->GetRenderingContext(), michael@0: "Must have rendering context in layout state for size " michael@0: "computations"); michael@0: michael@0: if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { michael@0: // We're using overlay scrollbars, so we need to get the width that michael@0: // non-disappearing scrollbars would have. michael@0: nsITheme* theme = aState->PresContext()->GetTheme(); michael@0: if (theme && michael@0: theme->ThemeSupportsWidget(aState->PresContext(), michael@0: mVScrollbarBox, michael@0: NS_THEME_SCROLLBAR_NON_DISAPPEARING)) { michael@0: nsIntSize size; michael@0: nsRenderingContext* rendContext = aState->GetRenderingContext(); michael@0: if (rendContext) { michael@0: bool canOverride = true; michael@0: theme->GetMinimumWidgetSize(rendContext, michael@0: mVScrollbarBox, michael@0: NS_THEME_SCROLLBAR_NON_DISAPPEARING, michael@0: &size, michael@0: &canOverride); michael@0: if (size.width) { michael@0: return aState->PresContext()->DevPixelsToAppUnits(size.width); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return GetDesiredScrollbarSizes(aState).LeftRight(); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::HandleScrollbarStyleSwitching() michael@0: { michael@0: // Check if we switched between scrollbar styles. michael@0: if (mScrollbarActivity && michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) == 0) { michael@0: mScrollbarActivity->Destroy(); michael@0: mScrollbarActivity = nullptr; michael@0: mOuter->PresContext()->ThemeChanged(); michael@0: } michael@0: else if (!mScrollbarActivity && michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { michael@0: mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(mOuter)); michael@0: mOuter->PresContext()->ThemeChanged(); michael@0: } michael@0: } michael@0: michael@0: static bool IsFocused(nsIContent* aContent) michael@0: { michael@0: // Some content elements, like the GetContent() of a scroll frame michael@0: // for a text input field, are inside anonymous subtrees, but the focus michael@0: // manager always reports a non-anonymous element as the focused one, so michael@0: // walk up the tree until we reach a non-anonymous element. michael@0: while (aContent && aContent->IsInAnonymousSubtree()) { michael@0: aContent = aContent->GetParent(); michael@0: } michael@0: michael@0: return aContent ? nsContentUtils::IsFocusedContent(aContent) : false; michael@0: } michael@0: michael@0: bool michael@0: ScrollFrameHelper::WantAsyncScroll() const michael@0: { michael@0: nsRect scrollRange = GetScrollRange(); michael@0: ScrollbarStyles styles = GetScrollbarStylesFromFrame(); michael@0: bool isFocused = IsFocused(mOuter->GetContent()); michael@0: bool isVScrollable = (scrollRange.height > 0) michael@0: && (styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN); michael@0: bool isHScrollable = (scrollRange.width > 0) michael@0: && (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN); michael@0: // The check for scroll bars was added in bug 825692 to prevent layerization michael@0: // of text inputs for performance reasons. However, if a text input is michael@0: // focused we want to layerize it so we can async scroll it (bug 946408). michael@0: bool isVAsyncScrollable = isVScrollable && (mVScrollbarBox || isFocused); michael@0: bool isHAsyncScrollable = isHScrollable && (mHScrollbarBox || isFocused); michael@0: return isVAsyncScrollable || isHAsyncScrollable; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULScrollFrame::CreateAnonymousContent(nsTArray& aElements) michael@0: { michael@0: return mHelper.CreateAnonymousContent(aElements); michael@0: } michael@0: michael@0: void michael@0: nsXULScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, michael@0: uint32_t aFilter) michael@0: { michael@0: mHelper.AppendAnonymousContentTo(aElements, aFilter); michael@0: } michael@0: michael@0: void michael@0: nsXULScrollFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: mHelper.Destroy(); michael@0: nsBoxFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: nsresult michael@0: nsXULScrollFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: nsresult rv = nsBoxFrame::SetInitialChildList(aListID, aChildList); michael@0: mHelper.ReloadChildFrames(); michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsXULScrollFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: nsresult rv = nsBoxFrame::AppendFrames(aListID, aFrameList); michael@0: mHelper.ReloadChildFrames(); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULScrollFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: nsresult rv = nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); michael@0: mHelper.ReloadChildFrames(); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULScrollFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: nsresult rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame); michael@0: mHelper.ReloadChildFrames(); michael@0: return rv; michael@0: } michael@0: michael@0: nsSplittableType michael@0: nsXULScrollFrame::GetSplittableType() const michael@0: { michael@0: return NS_FRAME_NOT_SPLITTABLE; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULScrollFrame::GetPadding(nsMargin& aMargin) michael@0: { michael@0: aMargin.SizeTo(0,0,0,0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsXULScrollFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::scrollFrame; michael@0: } michael@0: michael@0: nscoord michael@0: nsXULScrollFrame::GetBoxAscent(nsBoxLayoutState& aState) michael@0: { michael@0: if (!mHelper.mScrolledFrame) michael@0: return 0; michael@0: michael@0: nscoord ascent = mHelper.mScrolledFrame->GetBoxAscent(aState); michael@0: nsMargin m(0,0,0,0); michael@0: GetBorderAndPadding(m); michael@0: ascent += m.top; michael@0: GetMargin(m); michael@0: ascent += m.top; michael@0: michael@0: return ascent; michael@0: } michael@0: michael@0: nsSize michael@0: nsXULScrollFrame::GetPrefSize(nsBoxLayoutState& aState) michael@0: { michael@0: #ifdef DEBUG_LAYOUT michael@0: PropagateDebug(aState); michael@0: #endif michael@0: michael@0: nsSize pref = mHelper.mScrolledFrame->GetPrefSize(aState); michael@0: michael@0: ScrollbarStyles styles = GetScrollbarStyles(); michael@0: michael@0: // scrolled frames don't have their own margins michael@0: michael@0: if (mHelper.mVScrollbarBox && michael@0: styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) { michael@0: nsSize vSize = mHelper.mVScrollbarBox->GetPrefSize(aState); michael@0: nsBox::AddMargin(mHelper.mVScrollbarBox, vSize); michael@0: pref.width += vSize.width; michael@0: } michael@0: michael@0: if (mHelper.mHScrollbarBox && michael@0: styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) { michael@0: nsSize hSize = mHelper.mHScrollbarBox->GetPrefSize(aState); michael@0: nsBox::AddMargin(mHelper.mHScrollbarBox, hSize); michael@0: pref.height += hSize.height; michael@0: } michael@0: michael@0: AddBorderAndPadding(pref); michael@0: bool widthSet, heightSet; michael@0: nsIFrame::AddCSSPrefSize(this, pref, widthSet, heightSet); michael@0: return pref; michael@0: } michael@0: michael@0: nsSize michael@0: nsXULScrollFrame::GetMinSize(nsBoxLayoutState& aState) michael@0: { michael@0: #ifdef DEBUG_LAYOUT michael@0: PropagateDebug(aState); michael@0: #endif michael@0: michael@0: nsSize min = mHelper.mScrolledFrame->GetMinSizeForScrollArea(aState); michael@0: michael@0: ScrollbarStyles styles = GetScrollbarStyles(); michael@0: michael@0: if (mHelper.mVScrollbarBox && michael@0: styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) { michael@0: nsSize vSize = mHelper.mVScrollbarBox->GetMinSize(aState); michael@0: AddMargin(mHelper.mVScrollbarBox, vSize); michael@0: min.width += vSize.width; michael@0: if (min.height < vSize.height) michael@0: min.height = vSize.height; michael@0: } michael@0: michael@0: if (mHelper.mHScrollbarBox && michael@0: styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) { michael@0: nsSize hSize = mHelper.mHScrollbarBox->GetMinSize(aState); michael@0: AddMargin(mHelper.mHScrollbarBox, hSize); michael@0: min.height += hSize.height; michael@0: if (min.width < hSize.width) michael@0: min.width = hSize.width; michael@0: } michael@0: michael@0: AddBorderAndPadding(min); michael@0: bool widthSet, heightSet; michael@0: nsIFrame::AddCSSMinSize(aState, this, min, widthSet, heightSet); michael@0: return min; michael@0: } michael@0: michael@0: nsSize michael@0: nsXULScrollFrame::GetMaxSize(nsBoxLayoutState& aState) michael@0: { michael@0: #ifdef DEBUG_LAYOUT michael@0: PropagateDebug(aState); michael@0: #endif michael@0: michael@0: nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE); michael@0: michael@0: AddBorderAndPadding(maxSize); michael@0: bool widthSet, heightSet; michael@0: nsIFrame::AddCSSMaxSize(this, maxSize, widthSet, heightSet); michael@0: return maxSize; michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsXULScrollFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult); michael@0: } michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: nsXULScrollFrame::DoLayout(nsBoxLayoutState& aState) michael@0: { michael@0: uint32_t flags = aState.LayoutFlags(); michael@0: nsresult rv = Layout(aState); michael@0: aState.SetLayoutFlags(flags); michael@0: michael@0: nsBox::DoLayout(aState); michael@0: return rv; michael@0: } michael@0: michael@0: NS_QUERYFRAME_HEAD(nsXULScrollFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) michael@0: NS_QUERYFRAME_ENTRY(nsIScrollbarOwner) michael@0: NS_QUERYFRAME_ENTRY(nsIScrollableFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIStatefulFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) michael@0: michael@0: //-------------------- Helper ---------------------- michael@0: michael@0: #define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll" michael@0: michael@0: const double kCurrentVelocityWeighting = 0.25; michael@0: const double kStopDecelerationWeighting = 0.4; michael@0: michael@0: // AsyncScroll has ref counting. michael@0: class ScrollFrameHelper::AsyncScroll MOZ_FINAL : public nsARefreshObserver { michael@0: public: michael@0: typedef mozilla::TimeStamp TimeStamp; michael@0: typedef mozilla::TimeDuration TimeDuration; michael@0: michael@0: AsyncScroll(nsPoint aStartPos) michael@0: : mIsFirstIteration(true) michael@0: , mStartPos(aStartPos) michael@0: , mCallee(nullptr) michael@0: {} michael@0: michael@0: private: michael@0: // Private destructor, to discourage deletion outside of Release(): michael@0: ~AsyncScroll() { michael@0: RemoveObserver(); michael@0: } michael@0: michael@0: public: michael@0: nsPoint PositionAt(TimeStamp aTime); michael@0: nsSize VelocityAt(TimeStamp aTime); // In nscoords per second michael@0: michael@0: void InitSmoothScroll(TimeStamp aTime, nsPoint aDestination, michael@0: nsIAtom *aOrigin, const nsRect& aRange); michael@0: void Init(const nsRect& aRange) { michael@0: mRange = aRange; michael@0: } michael@0: michael@0: bool IsFinished(TimeStamp aTime) { michael@0: return aTime > mStartTime + mDuration; // XXX or if we've hit the wall michael@0: } michael@0: michael@0: TimeStamp mStartTime; michael@0: michael@0: // mPrevEventTime holds previous 3 timestamps for intervals averaging (to michael@0: // reduce duration fluctuations). When AsyncScroll is constructed and no michael@0: // previous timestamps are available (indicated with mIsFirstIteration), michael@0: // initialize mPrevEventTime using imaginary previous timestamps with maximum michael@0: // relevant intervals between them. michael@0: TimeStamp mPrevEventTime[3]; michael@0: bool mIsFirstIteration; michael@0: michael@0: // Cached Preferences values to avoid re-reading them when extending an existing michael@0: // animation for the same event origin (can be as frequent as every 10(!)ms for michael@0: // a quick roll of the mouse wheel). michael@0: // These values are minimum and maximum animation duration per event origin, michael@0: // and a global ratio which defines how longer is the animation's duration michael@0: // compared to the average recent events intervals (such that for a relatively michael@0: // consistent events rate, the next event arrives before current animation ends) michael@0: nsCOMPtr mOrigin; michael@0: int32_t mOriginMinMS; michael@0: int32_t mOriginMaxMS; michael@0: double mIntervalRatio; michael@0: michael@0: TimeDuration mDuration; michael@0: nsPoint mStartPos; michael@0: nsPoint mDestination; michael@0: // Allowed destination positions around mDestination michael@0: nsRect mRange; michael@0: nsSMILKeySpline mTimingFunctionX; michael@0: nsSMILKeySpline mTimingFunctionY; michael@0: bool mIsSmoothScroll; michael@0: michael@0: protected: michael@0: double ProgressAt(TimeStamp aTime) { michael@0: return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0); michael@0: } michael@0: michael@0: nscoord VelocityComponent(double aTimeProgress, michael@0: nsSMILKeySpline& aTimingFunction, michael@0: nscoord aStart, nscoord aDestination); michael@0: michael@0: // Initializes the timing function in such a way that the current velocity is michael@0: // preserved. michael@0: void InitTimingFunction(nsSMILKeySpline& aTimingFunction, michael@0: nscoord aCurrentPos, nscoord aCurrentVelocity, michael@0: nscoord aDestination); michael@0: michael@0: TimeDuration CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin); michael@0: michael@0: // The next section is observer/callback management michael@0: // Bodies of WillRefresh and RefreshDriver contain ScrollFrameHelper specific code. michael@0: public: michael@0: NS_INLINE_DECL_REFCOUNTING(AsyncScroll) michael@0: michael@0: /* michael@0: * Set a refresh observer for smooth scroll iterations (and start observing). michael@0: * Should be used at most once during the lifetime of this object. michael@0: * Return value: true on success, false otherwise. michael@0: */ michael@0: bool SetRefreshObserver(ScrollFrameHelper *aCallee) { michael@0: NS_ASSERTION(aCallee && !mCallee, "AsyncScroll::SetRefreshObserver - Invalid usage."); michael@0: michael@0: if (!RefreshDriver(aCallee)->AddRefreshObserver(this, Flush_Style)) { michael@0: return false; michael@0: } michael@0: michael@0: mCallee = aCallee; michael@0: return true; michael@0: } michael@0: michael@0: virtual void WillRefresh(mozilla::TimeStamp aTime) MOZ_OVERRIDE { michael@0: // The callback may release "this". michael@0: // We don't access members after returning, so no need for KungFuDeathGrip. michael@0: ScrollFrameHelper::AsyncScrollCallback(mCallee, aTime); michael@0: } michael@0: michael@0: private: michael@0: ScrollFrameHelper *mCallee; michael@0: michael@0: nsRefreshDriver* RefreshDriver(ScrollFrameHelper* aCallee) { michael@0: return aCallee->mOuter->PresContext()->RefreshDriver(); michael@0: } michael@0: michael@0: /* michael@0: * The refresh driver doesn't hold a reference to its observers, michael@0: * so releasing this object can (and is) used to remove the observer on DTOR. michael@0: * Currently, this object is released once the scrolling ends. michael@0: */ michael@0: void RemoveObserver() { michael@0: if (mCallee) { michael@0: RefreshDriver(mCallee)->RemoveRefreshObserver(this, Flush_Style); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: nsPoint michael@0: ScrollFrameHelper::AsyncScroll::PositionAt(TimeStamp aTime) { michael@0: double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime)); michael@0: double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime)); michael@0: return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x + progressX * mDestination.x), michael@0: NSToCoordRound((1 - progressY) * mStartPos.y + progressY * mDestination.y)); michael@0: } michael@0: michael@0: nsSize michael@0: ScrollFrameHelper::AsyncScroll::VelocityAt(TimeStamp aTime) { michael@0: double timeProgress = ProgressAt(aTime); michael@0: return nsSize(VelocityComponent(timeProgress, mTimingFunctionX, michael@0: mStartPos.x, mDestination.x), michael@0: VelocityComponent(timeProgress, mTimingFunctionY, michael@0: mStartPos.y, mDestination.y)); michael@0: } michael@0: michael@0: /* michael@0: * Calculate duration, possibly dynamically according to events rate and event origin. michael@0: * (also maintain previous timestamps - which are only used here). michael@0: */ michael@0: TimeDuration michael@0: ScrollFrameHelper:: michael@0: AsyncScroll::CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin) { michael@0: if (!aOrigin){ michael@0: aOrigin = nsGkAtoms::other; michael@0: } michael@0: michael@0: // Read preferences only on first iteration or for a different event origin. michael@0: if (mIsFirstIteration || aOrigin != mOrigin) { michael@0: mOrigin = aOrigin; michael@0: mOriginMinMS = mOriginMaxMS = 0; michael@0: bool isOriginSmoothnessEnabled = false; michael@0: mIntervalRatio = 1; michael@0: michael@0: // Default values for all preferences are defined in all.js michael@0: static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150; michael@0: static const bool kDefaultIsSmoothEnabled = true; michael@0: michael@0: nsAutoCString originName; michael@0: aOrigin->ToUTF8String(originName); michael@0: nsAutoCString prefBase = NS_LITERAL_CSTRING("general.smoothScroll.") + originName; michael@0: michael@0: isOriginSmoothnessEnabled = Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled); michael@0: if (isOriginSmoothnessEnabled) { michael@0: nsAutoCString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS"); michael@0: nsAutoCString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS"); michael@0: mOriginMinMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS); michael@0: mOriginMaxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS); michael@0: michael@0: static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000; michael@0: mOriginMaxMS = clamped(mOriginMaxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS); michael@0: mOriginMinMS = clamped(mOriginMinMS, 0, mOriginMaxMS); michael@0: } michael@0: michael@0: // Keep the animation duration longer than the average event intervals michael@0: // (to "connect" consecutive scroll animations before the scroll comes to a stop). michael@0: static const double kDefaultDurationToIntervalRatio = 2; // Duplicated at all.js michael@0: mIntervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio", michael@0: kDefaultDurationToIntervalRatio * 100) / 100.0; michael@0: michael@0: // Duration should be at least as long as the intervals -> ratio is at least 1 michael@0: mIntervalRatio = std::max(1.0, mIntervalRatio); michael@0: michael@0: if (mIsFirstIteration) { michael@0: // Starting a new scroll (i.e. not when extending an existing scroll animation), michael@0: // create imaginary prev timestamps with maximum relevant intervals between them. michael@0: michael@0: // Longest relevant interval (which results in maximum duration) michael@0: TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio); michael@0: mPrevEventTime[0] = aTime - maxDelta; michael@0: mPrevEventTime[1] = mPrevEventTime[0] - maxDelta; michael@0: mPrevEventTime[2] = mPrevEventTime[1] - maxDelta; michael@0: } michael@0: } michael@0: michael@0: // Average last 3 delta durations (rounding errors up to 2ms are negligible for us) michael@0: int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3; michael@0: mPrevEventTime[2] = mPrevEventTime[1]; michael@0: mPrevEventTime[1] = mPrevEventTime[0]; michael@0: mPrevEventTime[0] = aTime; michael@0: michael@0: // Modulate duration according to events rate (quicker events -> shorter durations). michael@0: // The desired effect is to use longer duration when scrolling slowly, such that michael@0: // it's easier to follow, but reduce the duration to make it feel more snappy when michael@0: // scrolling quickly. To reduce fluctuations of the duration, we average event michael@0: // intervals using the recent 4 timestamps (now + three prev -> 3 intervals). michael@0: int32_t durationMS = clamped(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS); michael@0: michael@0: return TimeDuration::FromMilliseconds(durationMS); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::AsyncScroll::InitSmoothScroll(TimeStamp aTime, michael@0: nsPoint aDestination, michael@0: nsIAtom *aOrigin, michael@0: const nsRect& aRange) { michael@0: mRange = aRange; michael@0: TimeDuration duration = CalcDurationForEventTime(aTime, aOrigin); michael@0: nsSize currentVelocity(0, 0); michael@0: if (!mIsFirstIteration) { michael@0: // If an additional event has not changed the destination, then do not let michael@0: // another minimum duration reset slow things down. If it would then michael@0: // instead continue with the existing timing function. michael@0: if (aDestination == mDestination && michael@0: aTime + duration > mStartTime + mDuration) michael@0: return; michael@0: michael@0: currentVelocity = VelocityAt(aTime); michael@0: mStartPos = PositionAt(aTime); michael@0: } michael@0: mStartTime = aTime; michael@0: mDuration = duration; michael@0: mDestination = aDestination; michael@0: InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width, michael@0: aDestination.x); michael@0: InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height, michael@0: aDestination.y); michael@0: mIsFirstIteration = false; michael@0: } michael@0: michael@0: michael@0: nscoord michael@0: ScrollFrameHelper::AsyncScroll::VelocityComponent(double aTimeProgress, michael@0: nsSMILKeySpline& aTimingFunction, michael@0: nscoord aStart, michael@0: nscoord aDestination) michael@0: { michael@0: double dt, dxy; michael@0: aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy); michael@0: if (dt == 0) michael@0: return dxy >= 0 ? nscoord_MAX : nscoord_MIN; michael@0: michael@0: const TimeDuration oneSecond = TimeDuration::FromSeconds(1); michael@0: double slope = dxy / dt; michael@0: return NSToCoordRound(slope * (aDestination - aStart) / (mDuration / oneSecond)); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::AsyncScroll::InitTimingFunction(nsSMILKeySpline& aTimingFunction, michael@0: nscoord aCurrentPos, michael@0: nscoord aCurrentVelocity, michael@0: nscoord aDestination) michael@0: { michael@0: if (aDestination == aCurrentPos || kCurrentVelocityWeighting == 0) { michael@0: aTimingFunction.Init(0, 0, 1 - kStopDecelerationWeighting, 1); michael@0: return; michael@0: } michael@0: michael@0: const TimeDuration oneSecond = TimeDuration::FromSeconds(1); michael@0: double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos); michael@0: double normalization = sqrt(1.0 + slope * slope); michael@0: double dt = 1.0 / normalization * kCurrentVelocityWeighting; michael@0: double dxy = slope / normalization * kCurrentVelocityWeighting; michael@0: aTimingFunction.Init(dt, dxy, 1 - kStopDecelerationWeighting, 1); michael@0: } michael@0: michael@0: static bool michael@0: IsSmoothScrollingEnabled() michael@0: { michael@0: return Preferences::GetBool(SMOOTH_SCROLL_PREF_NAME, false); michael@0: } michael@0: michael@0: class ScrollFrameActivityTracker MOZ_FINAL : public nsExpirationTracker { michael@0: public: michael@0: // Wait for 3-4s between scrolls before we remove our layers. michael@0: // That's 4 generations of 1s each. michael@0: enum { TIMEOUT_MS = 1000 }; michael@0: ScrollFrameActivityTracker() michael@0: : nsExpirationTracker(TIMEOUT_MS) {} michael@0: ~ScrollFrameActivityTracker() { michael@0: AgeAllGenerations(); michael@0: } michael@0: michael@0: virtual void NotifyExpired(ScrollFrameHelper *aObject) { michael@0: RemoveObject(aObject); michael@0: aObject->MarkInactive(); michael@0: } michael@0: }; michael@0: michael@0: static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nullptr; michael@0: michael@0: // There are situations when a scroll frame is destroyed and then re-created michael@0: // for the same content element. In this case we want to increment the scroll michael@0: // generation between the old and new scrollframes. If the new one knew about michael@0: // the old one then it could steal the old generation counter and increment it michael@0: // but it doesn't have that reference so instead we use a static global to michael@0: // ensure the new one gets a fresh value. michael@0: static uint32_t sScrollGenerationCounter = 0; michael@0: michael@0: ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter, michael@0: bool aIsRoot) michael@0: : mHScrollbarBox(nullptr) michael@0: , mVScrollbarBox(nullptr) michael@0: , mScrolledFrame(nullptr) michael@0: , mScrollCornerBox(nullptr) michael@0: , mResizerBox(nullptr) michael@0: , mOuter(aOuter) michael@0: , mAsyncScroll(nullptr) michael@0: , mOriginOfLastScroll(nsGkAtoms::other) michael@0: , mScrollGeneration(++sScrollGenerationCounter) michael@0: , mDestination(0, 0) michael@0: , mScrollPosAtLastPaint(0, 0) michael@0: , mRestorePos(-1, -1) michael@0: , mLastPos(-1, -1) michael@0: , mResolution(1.0, 1.0) michael@0: , mScrollPosForLayerPixelAlignment(-1, -1) michael@0: , mLastUpdateImagesPos(-1, -1) michael@0: , mNeverHasVerticalScrollbar(false) michael@0: , mNeverHasHorizontalScrollbar(false) michael@0: , mHasVerticalScrollbar(false) michael@0: , mHasHorizontalScrollbar(false) michael@0: , mFrameIsUpdatingScrollbar(false) michael@0: , mDidHistoryRestore(false) michael@0: , mIsRoot(aIsRoot) michael@0: , mClipAllDescendants(aIsRoot) michael@0: , mSupppressScrollbarUpdate(false) michael@0: , mSkippedScrollbarLayout(false) michael@0: , mHadNonInitialReflow(false) michael@0: , mHorizontalOverflow(false) michael@0: , mVerticalOverflow(false) michael@0: , mPostedReflowCallback(false) michael@0: , mMayHaveDirtyFixedChildren(false) michael@0: , mUpdateScrollbarAttributes(false) michael@0: , mCollapsedResizer(false) michael@0: , mShouldBuildScrollableLayer(false) michael@0: , mHasBeenScrolled(false) michael@0: , mIsResolutionSet(false) michael@0: { michael@0: mScrollingActive = IsAlwaysActive(); michael@0: michael@0: if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { michael@0: mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter)); michael@0: } michael@0: michael@0: EnsureImageVisPrefsCached(); michael@0: } michael@0: michael@0: ScrollFrameHelper::~ScrollFrameHelper() michael@0: { michael@0: if (mActivityExpirationState.IsTracked()) { michael@0: gScrollFrameActivityTracker->RemoveObject(this); michael@0: } michael@0: if (gScrollFrameActivityTracker && michael@0: gScrollFrameActivityTracker->IsEmpty()) { michael@0: delete gScrollFrameActivityTracker; michael@0: gScrollFrameActivityTracker = nullptr; michael@0: } michael@0: michael@0: if (mScrollActivityTimer) { michael@0: mScrollActivityTimer->Cancel(); michael@0: mScrollActivityTimer = nullptr; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Callback function from AsyncScroll, used in ScrollFrameHelper::ScrollTo michael@0: */ michael@0: void michael@0: ScrollFrameHelper::AsyncScrollCallback(void* anInstance, mozilla::TimeStamp aTime) michael@0: { michael@0: ScrollFrameHelper* self = static_cast(anInstance); michael@0: if (!self || !self->mAsyncScroll) michael@0: return; michael@0: michael@0: nsRect range = self->mAsyncScroll->mRange; michael@0: if (self->mAsyncScroll->mIsSmoothScroll) { michael@0: if (!self->mAsyncScroll->IsFinished(aTime)) { michael@0: nsPoint destination = self->mAsyncScroll->PositionAt(aTime); michael@0: // Allow this scroll operation to land on any pixel boundary between the michael@0: // current position and the final allowed range. (We don't want michael@0: // intermediate steps to be more constrained than the final step!) michael@0: nsRect intermediateRange = michael@0: nsRect(self->GetScrollPosition(), nsSize()).UnionEdges(range); michael@0: self->ScrollToImpl(destination, intermediateRange); michael@0: // 'self' might be destroyed here michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Apply desired destination range since this is the last step of scrolling. michael@0: self->mAsyncScroll = nullptr; michael@0: nsWeakFrame weakFrame(self->mOuter); michael@0: self->ScrollToImpl(self->mDestination, range); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: // We are done scrolling, set our destination to wherever we actually ended michael@0: // up scrolling to. michael@0: self->mDestination = self->GetScrollPosition(); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::ScrollToCSSPixels(const CSSIntPoint& aScrollPosition) michael@0: { michael@0: nsPoint current = GetScrollPosition(); michael@0: CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels(); michael@0: nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition); michael@0: nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f); michael@0: nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2*halfPixel - 1, 2*halfPixel - 1); michael@0: // XXX I don't think the following blocks are needed anymore, now that michael@0: // ScrollToImpl simply tries to scroll an integer number of layer michael@0: // pixels from the current position michael@0: if (currentCSSPixels.x == aScrollPosition.x) { michael@0: pt.x = current.x; michael@0: range.x = pt.x; michael@0: range.width = 0; michael@0: } michael@0: if (currentCSSPixels.y == aScrollPosition.y) { michael@0: pt.y = current.y; michael@0: range.y = pt.y; michael@0: range.height = 0; michael@0: } michael@0: ScrollTo(pt, nsIScrollableFrame::INSTANT, &range); michael@0: // 'this' might be destroyed here michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::ScrollToCSSPixelsApproximate(const CSSPoint& aScrollPosition, michael@0: nsIAtom *aOrigin) michael@0: { michael@0: nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition); michael@0: nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000); michael@0: nsRect range(pt.x - halfRange, pt.y - halfRange, 2*halfRange - 1, 2*halfRange - 1); michael@0: ScrollToWithOrigin(pt, nsIScrollableFrame::INSTANT, aOrigin, &range); michael@0: // 'this' might be destroyed here michael@0: } michael@0: michael@0: CSSIntPoint michael@0: ScrollFrameHelper::GetScrollPositionCSSPixels() michael@0: { michael@0: return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition()); michael@0: } michael@0: michael@0: /* michael@0: * this method wraps calls to ScrollToImpl(), either in one shot or incrementally, michael@0: * based on the setting of the smoothness scroll pref michael@0: */ michael@0: void michael@0: ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition, michael@0: nsIScrollableFrame::ScrollMode aMode, michael@0: nsIAtom *aOrigin, michael@0: const nsRect* aRange) michael@0: { michael@0: nsRect scrollRange = GetScrollRangeForClamping(); michael@0: mDestination = scrollRange.ClampPoint(aScrollPosition); michael@0: michael@0: nsRect range = aRange ? *aRange : nsRect(aScrollPosition, nsSize(0, 0)); michael@0: michael@0: if (aMode == nsIScrollableFrame::INSTANT) { michael@0: // Asynchronous scrolling is not allowed, so we'll kill any existing michael@0: // async-scrolling process and do an instant scroll. michael@0: mAsyncScroll = nullptr; michael@0: nsWeakFrame weakFrame(mOuter); michael@0: ScrollToImpl(mDestination, range, aOrigin); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: // We are done scrolling, set our destination to wherever we actually ended michael@0: // up scrolling to. michael@0: mDestination = GetScrollPosition(); michael@0: return; michael@0: } michael@0: michael@0: TimeStamp now = TimeStamp::Now(); michael@0: bool isSmoothScroll = (aMode == nsIScrollableFrame::SMOOTH) && michael@0: IsSmoothScrollingEnabled(); michael@0: michael@0: if (!mAsyncScroll) { michael@0: mAsyncScroll = new AsyncScroll(GetScrollPosition()); michael@0: if (!mAsyncScroll->SetRefreshObserver(this)) { michael@0: mAsyncScroll = nullptr; michael@0: // Observer setup failed. Scroll the normal way. michael@0: nsWeakFrame weakFrame(mOuter); michael@0: ScrollToImpl(mDestination, range, aOrigin); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: // We are done scrolling, set our destination to wherever we actually michael@0: // ended up scrolling to. michael@0: mDestination = GetScrollPosition(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: mAsyncScroll->mIsSmoothScroll = isSmoothScroll; michael@0: michael@0: if (isSmoothScroll) { michael@0: mAsyncScroll->InitSmoothScroll(now, mDestination, aOrigin, range); michael@0: } else { michael@0: mAsyncScroll->Init(range); michael@0: } michael@0: } michael@0: michael@0: // We can't use nsContainerFrame::PositionChildViews here because michael@0: // we don't want to invalidate views that have moved. michael@0: static void AdjustViews(nsIFrame* aFrame) michael@0: { michael@0: nsView* view = aFrame->GetView(); michael@0: if (view) { michael@0: nsPoint pt; michael@0: aFrame->GetParent()->GetClosestView(&pt); michael@0: pt += aFrame->GetPosition(); michael@0: view->SetPosition(pt.x, pt.y); michael@0: michael@0: return; michael@0: } michael@0: michael@0: if (!(aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) { michael@0: return; michael@0: } michael@0: michael@0: // Call AdjustViews recursively for all child frames except the popup list as michael@0: // the views for popups are not scrolled. michael@0: nsIFrame::ChildListIterator lists(aFrame); michael@0: for (; !lists.IsDone(); lists.Next()) { michael@0: if (lists.CurrentID() == nsIFrame::kPopupList) { michael@0: continue; michael@0: } michael@0: nsFrameList::Enumerator childFrames(lists.CurrentList()); michael@0: for (; !childFrames.AtEnd(); childFrames.Next()) { michael@0: AdjustViews(childFrames.get()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: CanScrollWithBlitting(nsIFrame* aFrame) michael@0: { michael@0: if (aFrame->GetStateBits() & NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL) michael@0: return false; michael@0: michael@0: for (nsIFrame* f = aFrame; f; michael@0: f = nsLayoutUtils::GetCrossDocParentFrame(f)) { michael@0: if (nsSVGIntegrationUtils::UsingEffectsForFrame(f) || michael@0: f->IsFrameOfType(nsIFrame::eSVG) || michael@0: f->GetStateBits() & NS_FRAME_NO_COMPONENT_ALPHA) { michael@0: return false; michael@0: } michael@0: if (nsLayoutUtils::IsPopup(f)) michael@0: break; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool ScrollFrameHelper::IsIgnoringViewportClipping() const michael@0: { michael@0: if (!mIsRoot) michael@0: return false; michael@0: nsSubDocumentFrame* subdocFrame = static_cast michael@0: (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame())); michael@0: return subdocFrame && !subdocFrame->ShouldClipSubdocument(); michael@0: } michael@0: michael@0: bool ScrollFrameHelper::ShouldClampScrollPosition() const michael@0: { michael@0: if (!mIsRoot) michael@0: return true; michael@0: nsSubDocumentFrame* subdocFrame = static_cast michael@0: (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame())); michael@0: return !subdocFrame || subdocFrame->ShouldClampScrollPosition(); michael@0: } michael@0: michael@0: bool ScrollFrameHelper::IsAlwaysActive() const michael@0: { michael@0: if (nsDisplayItem::ForceActiveLayers()) { michael@0: return true; michael@0: } michael@0: michael@0: const nsStyleDisplay* disp = mOuter->StyleDisplay(); michael@0: if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL)) { michael@0: return true; michael@0: } michael@0: michael@0: // Unless this is the root scrollframe for a non-chrome document michael@0: // which is the direct child of a chrome document, we default to not michael@0: // being "active". michael@0: if (!(mIsRoot && mOuter->PresContext()->IsRootContentDocument())) { michael@0: return false; michael@0: } michael@0: michael@0: // If we have scrolled before, then we should stay active. michael@0: if (mHasBeenScrolled) { michael@0: return true; michael@0: } michael@0: michael@0: // If we're overflow:hidden, then start as inactive until michael@0: // we get scrolled manually. michael@0: ScrollbarStyles styles = GetScrollbarStylesFromFrame(); michael@0: return (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN && michael@0: styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN); michael@0: } michael@0: michael@0: void ScrollFrameHelper::MarkInactive() michael@0: { michael@0: if (IsAlwaysActive() || !mScrollingActive) michael@0: return; michael@0: michael@0: mScrollingActive = false; michael@0: mOuter->InvalidateFrameSubtree(); michael@0: } michael@0: michael@0: void ScrollFrameHelper::MarkActive() michael@0: { michael@0: mScrollingActive = true; michael@0: if (IsAlwaysActive()) michael@0: return; michael@0: michael@0: if (mActivityExpirationState.IsTracked()) { michael@0: gScrollFrameActivityTracker->MarkUsed(this); michael@0: } else { michael@0: if (!gScrollFrameActivityTracker) { michael@0: gScrollFrameActivityTracker = new ScrollFrameActivityTracker(); michael@0: } michael@0: gScrollFrameActivityTracker->AddObject(this); michael@0: } michael@0: } michael@0: michael@0: void ScrollFrameHelper::ScrollVisual(nsPoint aOldScrolledFramePos) michael@0: { michael@0: // Mark this frame as having been scrolled. If this is the root michael@0: // scroll frame of a content document, then IsAlwaysActive() michael@0: // will return true from now on and MarkInactive() won't michael@0: // have any effect. michael@0: mHasBeenScrolled = true; michael@0: michael@0: AdjustViews(mScrolledFrame); michael@0: // We need to call this after fixing up the view positions michael@0: // to be consistent with the frame hierarchy. michael@0: bool canScrollWithBlitting = CanScrollWithBlitting(mOuter); michael@0: mOuter->RemoveStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL); michael@0: if (IsScrollingActive()) { michael@0: if (!canScrollWithBlitting) { michael@0: MarkInactive(); michael@0: } michael@0: } michael@0: if (canScrollWithBlitting) { michael@0: MarkActive(); michael@0: } michael@0: michael@0: mOuter->SchedulePaint(); michael@0: } michael@0: michael@0: /** michael@0: * Clamp desired scroll position aDesired and range [aDestLower, aDestUpper] michael@0: * to [aBoundLower, aBoundUpper] and then select the appunit value from among michael@0: * aBoundLower, aBoundUpper and those such that (aDesired - aCurrent) * michael@0: * aRes/aAppUnitsPerPixel is an integer (or as close as we can get michael@0: * modulo rounding to appunits) that is in [aDestLower, aDestUpper] and michael@0: * closest to aDesired. If no such value exists, return the nearest in michael@0: * [aDestLower, aDestUpper]. michael@0: */ michael@0: static nscoord michael@0: ClampAndAlignWithPixels(nscoord aDesired, michael@0: nscoord aBoundLower, nscoord aBoundUpper, michael@0: nscoord aDestLower, nscoord aDestUpper, michael@0: nscoord aAppUnitsPerPixel, double aRes, michael@0: nscoord aCurrent) michael@0: { michael@0: // Intersect scroll range with allowed range, by clamping the ends michael@0: // of aRange to be within bounds michael@0: nscoord destLower = clamped(aDestLower, aBoundLower, aBoundUpper); michael@0: nscoord destUpper = clamped(aDestUpper, aBoundLower, aBoundUpper); michael@0: michael@0: nscoord desired = clamped(aDesired, destLower, destUpper); michael@0: michael@0: double currentLayerVal = (aRes*aCurrent)/aAppUnitsPerPixel; michael@0: double desiredLayerVal = (aRes*desired)/aAppUnitsPerPixel; michael@0: double delta = desiredLayerVal - currentLayerVal; michael@0: double nearestLayerVal = NS_round(delta) + currentLayerVal; michael@0: michael@0: // Convert back from ThebesLayer space to appunits relative to the top-left michael@0: // of the scrolled frame. michael@0: nscoord aligned = michael@0: NSToCoordRoundWithClamp(nearestLayerVal*aAppUnitsPerPixel/aRes); michael@0: michael@0: // Use a bound if it is within the allowed range and closer to desired than michael@0: // the nearest pixel-aligned value. michael@0: if (aBoundUpper == destUpper && michael@0: static_cast(aBoundUpper - desired) < michael@0: Abs(desired - aligned)) michael@0: return aBoundUpper; michael@0: michael@0: if (aBoundLower == destLower && michael@0: static_cast(desired - aBoundLower) < michael@0: Abs(aligned - desired)) michael@0: return aBoundLower; michael@0: michael@0: // Accept the nearest pixel-aligned value if it is within the allowed range. michael@0: if (aligned >= destLower && aligned <= destUpper) michael@0: return aligned; michael@0: michael@0: // Check if opposite pixel boundary fits into allowed range. michael@0: double oppositeLayerVal = michael@0: nearestLayerVal + ((nearestLayerVal < desiredLayerVal) ? 1.0 : -1.0); michael@0: nscoord opposite = michael@0: NSToCoordRoundWithClamp(oppositeLayerVal*aAppUnitsPerPixel/aRes); michael@0: if (opposite >= destLower && opposite <= destUpper) { michael@0: return opposite; michael@0: } michael@0: michael@0: // No alignment available. michael@0: return desired; michael@0: } michael@0: michael@0: /** michael@0: * Clamp desired scroll position aPt to aBounds and then snap michael@0: * it to the same layer pixel edges as aCurrent, keeping it within aRange michael@0: * during snapping. aCurrent is the current scroll position. michael@0: */ michael@0: static nsPoint michael@0: ClampAndAlignWithLayerPixels(const nsPoint& aPt, michael@0: const nsRect& aBounds, michael@0: const nsRect& aRange, michael@0: const nsPoint& aCurrent, michael@0: nscoord aAppUnitsPerPixel, michael@0: const gfxSize& aScale) michael@0: { michael@0: return nsPoint(ClampAndAlignWithPixels(aPt.x, aBounds.x, aBounds.XMost(), michael@0: aRange.x, aRange.XMost(), michael@0: aAppUnitsPerPixel, aScale.width, michael@0: aCurrent.x), michael@0: ClampAndAlignWithPixels(aPt.y, aBounds.y, aBounds.YMost(), michael@0: aRange.y, aRange.YMost(), michael@0: aAppUnitsPerPixel, aScale.height, michael@0: aCurrent.y)); michael@0: } michael@0: michael@0: /* static */ void michael@0: ScrollFrameHelper::ScrollActivityCallback(nsITimer *aTimer, void* anInstance) michael@0: { michael@0: ScrollFrameHelper* self = static_cast(anInstance); michael@0: michael@0: // Fire the synth mouse move. michael@0: self->mScrollActivityTimer->Cancel(); michael@0: self->mScrollActivityTimer = nullptr; michael@0: self->mOuter->PresContext()->PresShell()->SynthesizeMouseMove(true); michael@0: } michael@0: michael@0: michael@0: void michael@0: ScrollFrameHelper::ScheduleSyntheticMouseMove() michael@0: { michael@0: if (!mScrollActivityTimer) { michael@0: mScrollActivityTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: if (!mScrollActivityTimer) michael@0: return; michael@0: } michael@0: michael@0: mScrollActivityTimer->InitWithFuncCallback( michael@0: ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOrigin) michael@0: { michael@0: if (aOrigin == nullptr) { michael@0: // If no origin was specified, we still want to set it to something that's michael@0: // non-null, so that we can use nullness to distinguish if the frame was scrolled michael@0: // at all. Default it to some generic placeholder. michael@0: aOrigin = nsGkAtoms::other; michael@0: } michael@0: michael@0: nsPresContext* presContext = mOuter->PresContext(); michael@0: nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); michael@0: // 'scale' is our estimate of the scale factor that will be applied michael@0: // when rendering the scrolled content to its own ThebesLayer. michael@0: gfxSize scale = FrameLayerBuilder::GetThebesLayerScaleForFrame(mScrolledFrame); michael@0: nsPoint curPos = GetScrollPosition(); michael@0: nsPoint alignWithPos = mScrollPosForLayerPixelAlignment == nsPoint(-1,-1) michael@0: ? curPos : mScrollPosForLayerPixelAlignment; michael@0: // Try to align aPt with curPos so they have an integer number of layer michael@0: // pixels between them. This gives us the best chance of scrolling without michael@0: // having to invalidate due to changes in subpixel rendering. michael@0: // Note that when we actually draw into a ThebesLayer, the coordinates michael@0: // that get mapped onto the layer buffer pixels are from the display list, michael@0: // which are relative to the display root frame's top-left increasing down, michael@0: // whereas here our coordinates are scroll positions which increase upward michael@0: // and are relative to the scrollport top-left. This difference doesn't actually michael@0: // matter since all we are about is that there be an integer number of michael@0: // layer pixels between pt and curPos. michael@0: nsPoint pt = michael@0: ClampAndAlignWithLayerPixels(aPt, michael@0: GetScrollRangeForClamping(), michael@0: aRange, michael@0: alignWithPos, michael@0: appUnitsPerDevPixel, michael@0: scale); michael@0: if (pt == curPos) { michael@0: return; michael@0: } michael@0: michael@0: bool needImageVisibilityUpdate = (mLastUpdateImagesPos == nsPoint(-1,-1)); michael@0: michael@0: nsPoint dist(std::abs(pt.x - mLastUpdateImagesPos.x), michael@0: std::abs(pt.y - mLastUpdateImagesPos.y)); michael@0: nsSize scrollPortSize = GetScrollPositionClampingScrollPortSize(); michael@0: nscoord horzAllowance = std::max(scrollPortSize.width / std::max(sHorzScrollFraction, 1), michael@0: nsPresContext::AppUnitsPerCSSPixel()); michael@0: nscoord vertAllowance = std::max(scrollPortSize.height / std::max(sVertScrollFraction, 1), michael@0: nsPresContext::AppUnitsPerCSSPixel()); michael@0: if (dist.x >= horzAllowance || dist.y >= vertAllowance) { michael@0: needImageVisibilityUpdate = true; michael@0: } michael@0: michael@0: if (needImageVisibilityUpdate) { michael@0: presContext->PresShell()->ScheduleImageVisibilityUpdate(); michael@0: } michael@0: michael@0: // notify the listeners. michael@0: for (uint32_t i = 0; i < mListeners.Length(); i++) { michael@0: mListeners[i]->ScrollPositionWillChange(pt.x, pt.y); michael@0: } michael@0: michael@0: nsPoint oldScrollFramePos = mScrolledFrame->GetPosition(); michael@0: // Update frame position for scrolling michael@0: mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt); michael@0: mOriginOfLastScroll = aOrigin; michael@0: mScrollGeneration = ++sScrollGenerationCounter; michael@0: michael@0: // We pass in the amount to move visually michael@0: ScrollVisual(oldScrollFramePos); michael@0: michael@0: ScheduleSyntheticMouseMove(); michael@0: nsWeakFrame weakFrame(mOuter); michael@0: UpdateScrollbarPosition(); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: PostScrollEvent(); michael@0: michael@0: // notify the listeners. michael@0: for (uint32_t i = 0; i < mListeners.Length(); i++) { michael@0: mListeners[i]->ScrollPositionDidChange(pt.x, pt.y); michael@0: } michael@0: michael@0: nsCOMPtr docShell = presContext->GetDocShell(); michael@0: if (docShell) { michael@0: docShell->NotifyScrollObservers(); michael@0: } michael@0: } michael@0: michael@0: static int32_t michael@0: MaxZIndexInList(nsDisplayList* aList, nsDisplayListBuilder* aBuilder) michael@0: { michael@0: int32_t maxZIndex = 0; michael@0: for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) { michael@0: maxZIndex = std::max(maxZIndex, item->ZIndex()); michael@0: } michael@0: return maxZIndex; michael@0: } michael@0: michael@0: static void michael@0: AppendToTop(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists, michael@0: nsDisplayList* aSource, nsIFrame* aSourceFrame, bool aOwnLayer, michael@0: uint32_t aFlags, mozilla::layers::FrameMetrics::ViewID aScrollTargetId, michael@0: bool aPositioned) michael@0: { michael@0: if (aSource->IsEmpty()) michael@0: return; michael@0: michael@0: nsDisplayWrapList* newItem = aOwnLayer? michael@0: new (aBuilder) nsDisplayOwnLayer(aBuilder, aSourceFrame, aSource, michael@0: aFlags, aScrollTargetId) : michael@0: new (aBuilder) nsDisplayWrapList(aBuilder, aSourceFrame, aSource); michael@0: michael@0: if (aPositioned) { michael@0: // We want overlay scrollbars to always be on top of the scrolled content, michael@0: // but we don't want them to unnecessarily cover overlapping elements from michael@0: // outside our scroll frame. michael@0: nsDisplayList* positionedDescendants = aLists.PositionedDescendants(); michael@0: if (!positionedDescendants->IsEmpty()) { michael@0: newItem->SetOverrideZIndex(MaxZIndexInList(positionedDescendants, aBuilder)); michael@0: positionedDescendants->AppendNewToTop(newItem); michael@0: } else { michael@0: aLists.Outlines()->AppendNewToTop(newItem); michael@0: } michael@0: } else { michael@0: aLists.BorderBackground()->AppendNewToTop(newItem); michael@0: } michael@0: } michael@0: michael@0: struct HoveredStateComparator michael@0: { michael@0: bool Equals(nsIFrame* A, nsIFrame* B) const { michael@0: bool aHovered = A->GetContent()->HasAttr(kNameSpaceID_None, michael@0: nsGkAtoms::hover); michael@0: bool bHovered = B->GetContent()->HasAttr(kNameSpaceID_None, michael@0: nsGkAtoms::hover); michael@0: return aHovered == bHovered; michael@0: } michael@0: bool LessThan(nsIFrame* A, nsIFrame* B) const { michael@0: bool aHovered = A->GetContent()->HasAttr(kNameSpaceID_None, michael@0: nsGkAtoms::hover); michael@0: bool bHovered = B->GetContent()->HasAttr(kNameSpaceID_None, michael@0: nsGkAtoms::hover); michael@0: return !aHovered && bHovered; michael@0: } michael@0: }; michael@0: michael@0: void michael@0: ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists, michael@0: bool& aCreateLayer, michael@0: bool aPositioned) michael@0: { michael@0: nsITheme* theme = mOuter->PresContext()->GetTheme(); michael@0: if (theme && michael@0: theme->ShouldHideScrollbars()) { michael@0: return; michael@0: } michael@0: michael@0: bool overlayScrollbars = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0; michael@0: michael@0: nsAutoTArray scrollParts; michael@0: for (nsIFrame* kid = mOuter->GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) { michael@0: if (kid == mScrolledFrame || michael@0: (kid->IsPositioned() || overlayScrollbars) != aPositioned) michael@0: continue; michael@0: michael@0: scrollParts.AppendElement(kid); michael@0: } michael@0: michael@0: mozilla::layers::FrameMetrics::ViewID scrollTargetId = aCreateLayer michael@0: ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent()) michael@0: : mozilla::layers::FrameMetrics::NULL_SCROLL_ID; michael@0: michael@0: scrollParts.Sort(HoveredStateComparator()); michael@0: michael@0: for (uint32_t i = 0; i < scrollParts.Length(); ++i) { michael@0: nsDisplayListCollection partList; michael@0: mOuter->BuildDisplayListForChild( michael@0: aBuilder, scrollParts[i], aDirtyRect, partList, michael@0: nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT); michael@0: michael@0: uint32_t flags = 0; michael@0: if (scrollParts[i] == mVScrollbarBox) { michael@0: flags |= nsDisplayOwnLayer::VERTICAL_SCROLLBAR; michael@0: } michael@0: if (scrollParts[i] == mHScrollbarBox) { michael@0: flags |= nsDisplayOwnLayer::HORIZONTAL_SCROLLBAR; michael@0: } michael@0: michael@0: // DISPLAY_CHILD_FORCE_STACKING_CONTEXT put everything into michael@0: // partList.PositionedDescendants(). michael@0: ::AppendToTop(aBuilder, aLists, michael@0: partList.PositionedDescendants(), scrollParts[i], michael@0: aCreateLayer, flags, scrollTargetId, aPositioned); michael@0: } michael@0: } michael@0: michael@0: class ScrollLayerWrapper : public nsDisplayWrapper michael@0: { michael@0: public: michael@0: ScrollLayerWrapper(nsIFrame* aScrollFrame, nsIFrame* aScrolledFrame) michael@0: : mCount(0) michael@0: , mProps(aScrolledFrame->Properties()) michael@0: , mScrollFrame(aScrollFrame) michael@0: , mScrolledFrame(aScrolledFrame) michael@0: { michael@0: SetCount(0); michael@0: } michael@0: michael@0: virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame, michael@0: nsDisplayList* aList) MOZ_OVERRIDE { michael@0: SetCount(++mCount); michael@0: return new (aBuilder) nsDisplayScrollLayer(aBuilder, aList, mScrolledFrame, mScrolledFrame, mScrollFrame); michael@0: } michael@0: michael@0: virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder, michael@0: nsDisplayItem* aItem) MOZ_OVERRIDE { michael@0: michael@0: SetCount(++mCount); michael@0: return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->Frame(), mScrolledFrame, mScrollFrame); michael@0: } michael@0: michael@0: protected: michael@0: void SetCount(intptr_t aCount) { michael@0: mProps.Set(nsIFrame::ScrollLayerCount(), reinterpret_cast(aCount)); michael@0: } michael@0: michael@0: intptr_t mCount; michael@0: FrameProperties mProps; michael@0: nsIFrame* mScrollFrame; michael@0: nsIFrame* mScrolledFrame; michael@0: }; michael@0: michael@0: /* static */ bool ScrollFrameHelper::sImageVisPrefsCached = false; michael@0: /* static */ uint32_t ScrollFrameHelper::sHorzExpandScrollPort = 0; michael@0: /* static */ uint32_t ScrollFrameHelper::sVertExpandScrollPort = 1; michael@0: /* static */ int32_t ScrollFrameHelper::sHorzScrollFraction = 2; michael@0: /* static */ int32_t ScrollFrameHelper::sVertScrollFraction = 2; michael@0: michael@0: /* static */ void michael@0: ScrollFrameHelper::EnsureImageVisPrefsCached() michael@0: { michael@0: if (!sImageVisPrefsCached) { michael@0: Preferences::AddUintVarCache(&sHorzExpandScrollPort, michael@0: "layout.imagevisibility.numscrollportwidths", (uint32_t)0); michael@0: Preferences::AddUintVarCache(&sVertExpandScrollPort, michael@0: "layout.imagevisibility.numscrollportheights", 1); michael@0: michael@0: Preferences::AddIntVarCache(&sHorzScrollFraction, michael@0: "layout.imagevisibility.amountscrollbeforeupdatehorizontal", 2); michael@0: Preferences::AddIntVarCache(&sVertScrollFraction, michael@0: "layout.imagevisibility.amountscrollbeforeupdatevertical", 2); michael@0: michael@0: sImageVisPrefsCached = true; michael@0: } michael@0: } michael@0: michael@0: nsRect michael@0: ScrollFrameHelper::ExpandRect(const nsRect& aRect) const michael@0: { michael@0: // We don't want to expand a rect in a direction that we can't scroll, so we michael@0: // check the scroll range. michael@0: nsRect scrollRange = GetScrollRangeForClamping(); michael@0: nsPoint scrollPos = GetScrollPosition(); michael@0: nsMargin expand(0, 0, 0, 0); michael@0: michael@0: nscoord vertShift = sVertExpandScrollPort * aRect.height; michael@0: if (scrollRange.y < scrollPos.y) { michael@0: expand.top = vertShift; michael@0: } michael@0: if (scrollPos.y < scrollRange.YMost()) { michael@0: expand.bottom = vertShift; michael@0: } michael@0: michael@0: nscoord horzShift = sHorzExpandScrollPort * aRect.width; michael@0: if (scrollRange.x < scrollPos.x) { michael@0: expand.left = horzShift; michael@0: } michael@0: if (scrollPos.x < scrollRange.XMost()) { michael@0: expand.right = horzShift; michael@0: } michael@0: michael@0: nsRect rect = aRect; michael@0: rect.Inflate(expand); michael@0: return rect; michael@0: } michael@0: michael@0: static bool michael@0: ShouldBeClippedByFrame(nsIFrame* aClipFrame, nsIFrame* aClippedFrame) michael@0: { michael@0: return nsLayoutUtils::IsProperAncestorFrame(aClipFrame, aClippedFrame); michael@0: } michael@0: michael@0: static void michael@0: ClipItemsExceptCaret(nsDisplayList* aList, nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aClipFrame, const DisplayItemClip& aClip) michael@0: { michael@0: nsDisplayItem* i = aList->GetBottom(); michael@0: for (; i; i = i->GetAbove()) { michael@0: if (!::ShouldBeClippedByFrame(aClipFrame, i->Frame())) { michael@0: continue; michael@0: } michael@0: michael@0: bool unused; michael@0: nsRect bounds = i->GetBounds(aBuilder, &unused); michael@0: bool isAffectedByClip = aClip.IsRectAffectedByClip(bounds); michael@0: if (isAffectedByClip && nsDisplayItem::TYPE_CARET == i->GetType()) { michael@0: // Don't clip the caret if it overflows vertically only, and by half michael@0: // its height at most. This is to avoid clipping it when the line-height michael@0: // is small. michael@0: auto half = bounds.height / 2; michael@0: bounds.y += half; michael@0: bounds.height -= half; michael@0: isAffectedByClip = aClip.IsRectAffectedByClip(bounds); michael@0: if (isAffectedByClip) { michael@0: // Don't clip the caret if it's just outside on the right side. michael@0: nsRect rightSide(bounds.x - 1, bounds.y, 1, bounds.height); michael@0: isAffectedByClip = aClip.IsRectAffectedByClip(rightSide); michael@0: // Also, avoid clipping it in a zero-height line box (heuristic only). michael@0: if (isAffectedByClip) { michael@0: isAffectedByClip = i->Frame()->GetRect().height != 0; michael@0: } michael@0: } michael@0: } michael@0: if (isAffectedByClip) { michael@0: DisplayItemClip newClip; michael@0: newClip.IntersectWith(i->GetClip()); michael@0: newClip.IntersectWith(aClip); michael@0: i->SetClip(aBuilder, newClip); michael@0: } michael@0: nsDisplayList* children = i->GetSameCoordinateSystemChildren(); michael@0: if (children) { michael@0: ClipItemsExceptCaret(children, aBuilder, aClipFrame, aClip); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void michael@0: ClipListsExceptCaret(nsDisplayListCollection* aLists, michael@0: nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aClipFrame, michael@0: const DisplayItemClip& aClip) michael@0: { michael@0: ::ClipItemsExceptCaret(aLists->BorderBackground(), aBuilder, aClipFrame, aClip); michael@0: ::ClipItemsExceptCaret(aLists->BlockBorderBackgrounds(), aBuilder, aClipFrame, aClip); michael@0: ::ClipItemsExceptCaret(aLists->Floats(), aBuilder, aClipFrame, aClip); michael@0: ::ClipItemsExceptCaret(aLists->PositionedDescendants(), aBuilder, aClipFrame, aClip); michael@0: ::ClipItemsExceptCaret(aLists->Outlines(), aBuilder, aClipFrame, aClip); michael@0: ::ClipItemsExceptCaret(aLists->Content(), aBuilder, aClipFrame, aClip); michael@0: } michael@0: michael@0: static bool michael@0: DisplayportExceedsMaxTextureSize(nsPresContext* aPresContext, const nsRect& aDisplayPort) michael@0: { michael@0: #ifdef MOZ_WIDGET_GONK michael@0: // On B2G we actively run into max texture size limits because the displayport-sizing code michael@0: // in the AsyncPanZoomController code is slightly busted (bug 957668 will fix it properly). michael@0: // We sometimes end up requesting displayports for which the corresponding layer will be michael@0: // larger than the max GL texture size (which we assume to be 4096 here). michael@0: // If we run into this case, we should just not use the displayport at all and fall back to michael@0: // just making a ScrollInfoLayer so that we use the APZC's synchronous scrolling fallback michael@0: // mechanism. michael@0: // Note also that if we don't do this here, it is quite likely that the parent B2G process michael@0: // will kill this child process to prevent OOMs (see the patch that landed as part of bug michael@0: // 965945 for details). michael@0: gfxSize resolution = aPresContext->PresShell()->GetCumulativeResolution(); michael@0: return (aPresContext->AppUnitsToDevPixels(aDisplayPort.width) * resolution.width > 4096) || michael@0: (aPresContext->AppUnitsToDevPixels(aDisplayPort.height) * resolution.height > 4096); michael@0: #else michael@0: return false; michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (aBuilder->IsForImageVisibility()) { michael@0: mLastUpdateImagesPos = GetScrollPosition(); michael@0: } michael@0: michael@0: mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists); michael@0: michael@0: if (aBuilder->IsPaintingToWindow()) { michael@0: mScrollPosAtLastPaint = GetScrollPosition(); michael@0: if (IsScrollingActive() && !CanScrollWithBlitting(mOuter)) { michael@0: MarkInactive(); michael@0: } michael@0: if (IsScrollingActive()) { michael@0: if (mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)) { michael@0: mScrollPosForLayerPixelAlignment = mScrollPosAtLastPaint; michael@0: } michael@0: } else { michael@0: mScrollPosForLayerPixelAlignment = nsPoint(-1,-1); michael@0: } michael@0: } michael@0: michael@0: // We put scrollbars in their own layers when this is the root scroll michael@0: // frame and we are a toplevel content document. In this situation, the michael@0: // scrollbar(s) would normally be assigned their own layer anyway, since michael@0: // they're not scrolled with the rest of the document. But when both michael@0: // scrollbars are visible, the layer's visible rectangle would be the size michael@0: // of the viewport, so most layer implementations would create a layer buffer michael@0: // that's much larger than necessary. Creating independent layers for each michael@0: // scrollbar works around the problem. michael@0: bool createLayersForScrollbars = mIsRoot && michael@0: mOuter->PresContext()->IsRootContentDocument(); michael@0: michael@0: if (aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping()) { michael@0: michael@0: // If we are a root scroll frame that has a display port we want to add michael@0: // scrollbars, they will be children of the scrollable layer, but they get michael@0: // adjusted by the APZC automatically. michael@0: bool addScrollBars = mIsRoot && michael@0: nsLayoutUtils::GetDisplayPort(mOuter->GetContent()) && michael@0: !aBuilder->IsForEventDelivery(); michael@0: // For now, don't add them for display root documents, cause we've never michael@0: // had them there. michael@0: if (aBuilder->RootReferenceFrame()->PresContext() == mOuter->PresContext()) { michael@0: addScrollBars = false; michael@0: } michael@0: michael@0: if (addScrollBars) { michael@0: // Add classic scrollbars. michael@0: AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars, michael@0: false); michael@0: } michael@0: michael@0: // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner. michael@0: // The scrolled frame shouldn't have its own background/border, so we michael@0: // can just pass aLists directly. michael@0: mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, michael@0: aDirtyRect, aLists); michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: // TODO: only layerize the overlay scrollbars if this scrollframe can be michael@0: // panned asynchronously. For now just always layerize on B2G because. michael@0: // that's where we want the layerized scrollbars michael@0: createLayersForScrollbars = true; michael@0: #endif michael@0: if (addScrollBars) { michael@0: // Add overlay scrollbars. michael@0: AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars, michael@0: true); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: // Now display the scrollbars and scrollcorner. These parts are drawn michael@0: // in the border-background layer, on top of our own background and michael@0: // borders and underneath borders and backgrounds of later elements michael@0: // in the tree. michael@0: // Note that this does not apply for overlay scrollbars; those are drawn michael@0: // in the positioned-elements layer on top of everything else by the call michael@0: // to AppendScrollPartsTo(..., true) further down. michael@0: AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars, michael@0: false); michael@0: michael@0: // Overflow clipping can never clip frames outside our subtree, so there michael@0: // is no need to worry about whether we are a moving frame that might clip michael@0: // non-moving frames. michael@0: // Not all our descendants will be clipped by overflow clipping, but all michael@0: // the ones that aren't clipped will be out of flow frames that have already michael@0: // had dirty rects saved for them by their parent frames calling michael@0: // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our michael@0: // dirty rect here. michael@0: nsRect dirtyRect = aDirtyRect.Intersect(mScrollPort); michael@0: michael@0: nsRect displayPort; michael@0: bool usingDisplayport = false; michael@0: if (!aBuilder->IsForEventDelivery()) { michael@0: if (!mIsRoot) { michael@0: // For a non-root scroll frame, override the value of the display port michael@0: // base rect, and possibly create a display port if there isn't one michael@0: // already. For root scroll frame, nsLayoutUtils::PaintFrame or michael@0: // nsSubDocumentFrame::BuildDisplayList takes care of this. michael@0: nsRect displayportBase = dirtyRect; michael@0: usingDisplayport = nsLayoutUtils::GetOrMaybeCreateDisplayPort( michael@0: *aBuilder, mOuter, displayportBase, &displayPort); michael@0: } else { michael@0: // For a root frmae, just get the value of the existing of the display michael@0: // port, if any. michael@0: usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort); michael@0: } michael@0: michael@0: if (usingDisplayport && DisplayportExceedsMaxTextureSize(mOuter->PresContext(), displayPort)) { michael@0: usingDisplayport = false; michael@0: } michael@0: michael@0: // Override the dirty rectangle if the displayport has been set. michael@0: if (usingDisplayport) { michael@0: dirtyRect = displayPort; michael@0: } michael@0: } michael@0: michael@0: if (aBuilder->IsForImageVisibility()) { michael@0: // We expand the dirty rect to catch images just outside of the scroll port. michael@0: // We use the dirty rect instead of the whole scroll port to prevent michael@0: // too much expansion in the presence of very large (bigger than the michael@0: // viewport) scroll ports. michael@0: dirtyRect = ExpandRect(dirtyRect); michael@0: } michael@0: michael@0: // Since making new layers is expensive, only use nsDisplayScrollLayer michael@0: // if the area is scrollable and we're the content process (unless we're on michael@0: // B2G, where we support async scrolling for scrollable elements in the michael@0: // parent process as well). michael@0: // When a displayport is being used, force building of a layer so that michael@0: // CompositorParent can always find the scrollable layer for the root content michael@0: // document. michael@0: // If the element is marked 'scrollgrab', also force building of a layer michael@0: // so that APZ can implement scroll grabbing. michael@0: mShouldBuildScrollableLayer = usingDisplayport || nsContentUtils::HasScrollgrab(mOuter->GetContent()); michael@0: bool shouldBuildLayer = false; michael@0: if (mShouldBuildScrollableLayer) { michael@0: shouldBuildLayer = true; michael@0: } else { michael@0: shouldBuildLayer = michael@0: nsLayoutUtils::WantSubAPZC() && michael@0: WantAsyncScroll() && michael@0: // If we are the root scroll frame for the display root then we don't need a scroll michael@0: // info layer to make a RecordFrameMetrics call for us as michael@0: // nsDisplayList::PaintForFrame already calls RecordFrameMetrics for us. michael@0: (!mIsRoot || aBuilder->RootReferenceFrame()->PresContext() != mOuter->PresContext()); michael@0: } michael@0: michael@0: nsDisplayListCollection scrolledContent; michael@0: { michael@0: // Note that setting the current scroll parent id here means that positioned children michael@0: // of this scroll info layer will pick up the scroll info layer as their scroll handoff michael@0: // parent. This is intentional because that is what happens for positioned children michael@0: // of scroll layers, and we want to maintain consistent behaviour between scroll layers michael@0: // and scroll info layers. michael@0: nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter( michael@0: aBuilder, michael@0: shouldBuildLayer && mScrolledFrame->GetContent() michael@0: ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent()) michael@0: : aBuilder->GetCurrentScrollParentId()); michael@0: DisplayListClipState::AutoSaveRestore clipState(aBuilder); michael@0: michael@0: if (usingDisplayport) { michael@0: nsRect clip = displayPort + aBuilder->ToReferenceFrame(mOuter); michael@0: michael@0: // If we are using a display port, then ignore any pre-existing clip michael@0: // passed down from our parents, and use only the clip computed here michael@0: // based on the display port. The pre-existing clip would just defeat michael@0: // the purpose of a display port which is to paint regions that are not michael@0: // currently visible so that they can be brought into view asynchronously. michael@0: // Notes: michael@0: // - The pre-existing clip state will be restored when the michael@0: // AutoSaveRestore goes out of scope, so there is no permanent change michael@0: // to this clip state. michael@0: // - We still set a clip to the scroll port further below where we michael@0: // build the scroll wrapper. This doesn't prevent us from painting michael@0: // the entire displayport, but it lets the compositor know to michael@0: // clip to the scroll port after compositing. michael@0: clipState.Clear(); michael@0: michael@0: if (mClipAllDescendants) { michael@0: clipState.ClipContentDescendants(clip); michael@0: } else { michael@0: clipState.ClipContainingBlockDescendants(clip, nullptr); michael@0: } michael@0: } else { michael@0: nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter); michael@0: nscoord radii[8]; michael@0: bool haveRadii = mOuter->GetPaddingBoxBorderRadii(radii); michael@0: // Our override of GetBorderRadii ensures we never have a radius at michael@0: // the corners where we have a scrollbar. michael@0: if (mClipAllDescendants) { michael@0: clipState.ClipContentDescendants(clip, haveRadii ? radii : nullptr); michael@0: } else { michael@0: clipState.ClipContainingBlockDescendants(clip, haveRadii ? radii : nullptr); michael@0: } michael@0: } michael@0: michael@0: mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, scrolledContent); michael@0: } michael@0: michael@0: if (MOZ_UNLIKELY(mOuter->StyleDisplay()->mOverflowClipBox == michael@0: NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) { michael@0: // We only clip if there is *scrollable* overflow, to avoid clipping michael@0: // *visual* overflow unnecessarily. michael@0: nsRect clipRect = mScrollPort + aBuilder->ToReferenceFrame(mOuter); michael@0: nsRect so = mScrolledFrame->GetScrollableOverflowRect(); michael@0: if (clipRect.width != so.width || clipRect.height != so.height || michael@0: so.x < 0 || so.y < 0) { michael@0: // The 'scrolledContent' items are clipped to the padding-box at this point. michael@0: // Now clip them again to the content-box, except the nsDisplayCaret item michael@0: // which we allow to overflow the content-box in various situations -- michael@0: // see ::ClipItemsExceptCaret. michael@0: clipRect.Deflate(mOuter->GetUsedPadding()); michael@0: DisplayItemClip clip; michael@0: clip.SetTo(clipRect); michael@0: ::ClipListsExceptCaret(&scrolledContent, aBuilder, mScrolledFrame, clip); michael@0: } michael@0: } michael@0: michael@0: if (shouldBuildLayer) { michael@0: // ScrollLayerWrapper must always be created because it initializes the michael@0: // scroll layer count. The display lists depend on this. michael@0: ScrollLayerWrapper wrapper(mOuter, mScrolledFrame); michael@0: michael@0: if (mShouldBuildScrollableLayer) { michael@0: DisplayListClipState::AutoSaveRestore clipState(aBuilder); michael@0: michael@0: // For root scrollframes in documents where the CSS viewport has been michael@0: // modified, the CSS viewport no longer corresponds to what is visible, michael@0: // so we don't want to clip the content to it. For root scrollframes michael@0: // in documents where the CSS viewport is NOT modified, the mScrollPort michael@0: // is the same as the CSS viewport, modulo scrollbars. michael@0: if (!(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden())) { michael@0: nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter); michael@0: if (mClipAllDescendants) { michael@0: clipState.ClipContentDescendants(clip); michael@0: } else { michael@0: clipState.ClipContainingBlockDescendants(clip); michael@0: } michael@0: } michael@0: michael@0: // Once a displayport is set, assume that scrolling needs to be fast michael@0: // so create a layer with all the content inside. The compositor michael@0: // process will be able to scroll the content asynchronously. michael@0: wrapper.WrapListsInPlace(aBuilder, mOuter, scrolledContent); michael@0: } michael@0: michael@0: // In case we are not using displayport or the nsDisplayScrollLayers are michael@0: // flattened during visibility computation, we still need to export the michael@0: // metadata about this scroll box to the compositor process. michael@0: nsDisplayScrollInfoLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer( michael@0: aBuilder, mScrolledFrame, mOuter); michael@0: scrolledContent.BorderBackground()->AppendNewToBottom(layerItem); michael@0: } michael@0: // Now display overlay scrollbars and the resizer, if we have one. michael@0: #ifdef MOZ_WIDGET_GONK michael@0: // TODO: only layerize the overlay scrollbars if this scrollframe can be michael@0: // panned asynchronously. For now just always layerize on B2G because. michael@0: // that's where we want the layerized scrollbars michael@0: createLayersForScrollbars = true; michael@0: #endif michael@0: AppendScrollPartsTo(aBuilder, aDirtyRect, scrolledContent, michael@0: createLayersForScrollbars, true); michael@0: scrolledContent.MoveTo(aLists); michael@0: } michael@0: michael@0: bool michael@0: ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const michael@0: { michael@0: // Use the right rect depending on if a display port is set. michael@0: nsRect displayPort; michael@0: bool usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort); michael@0: return aRect.Intersects(ExpandRect(usingDisplayport ? displayPort : mScrollPort)); michael@0: } michael@0: michael@0: static void HandleScrollPref(nsIScrollable *aScrollable, int32_t aOrientation, michael@0: uint8_t& aValue) michael@0: { michael@0: int32_t pref; michael@0: aScrollable->GetDefaultScrollbarPreferences(aOrientation, &pref); michael@0: switch (pref) { michael@0: case nsIScrollable::Scrollbar_Auto: michael@0: // leave |aValue| untouched michael@0: break; michael@0: case nsIScrollable::Scrollbar_Never: michael@0: aValue = NS_STYLE_OVERFLOW_HIDDEN; michael@0: break; michael@0: case nsIScrollable::Scrollbar_Always: michael@0: aValue = NS_STYLE_OVERFLOW_SCROLL; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: ScrollbarStyles michael@0: ScrollFrameHelper::GetScrollbarStylesFromFrame() const michael@0: { michael@0: nsPresContext* presContext = mOuter->PresContext(); michael@0: if (!presContext->IsDynamic() && michael@0: !(mIsRoot && presContext->HasPaginatedScrolling())) { michael@0: return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN); michael@0: } michael@0: michael@0: if (!mIsRoot) { michael@0: const nsStyleDisplay* disp = mOuter->StyleDisplay(); michael@0: return ScrollbarStyles(disp->mOverflowX, disp->mOverflowY); michael@0: } michael@0: michael@0: ScrollbarStyles result = presContext->GetViewportOverflowOverride(); michael@0: nsCOMPtr container = presContext->GetContainerWeak(); michael@0: nsCOMPtr scrollable = do_QueryInterface(container); michael@0: if (scrollable) { michael@0: HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_X, michael@0: result.mHorizontal); michael@0: HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_Y, michael@0: result.mVertical); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: nsRect michael@0: ScrollFrameHelper::GetScrollRange() const michael@0: { michael@0: return GetScrollRange(mScrollPort.width, mScrollPort.height); michael@0: } michael@0: michael@0: nsRect michael@0: ScrollFrameHelper::GetScrollRange(nscoord aWidth, nscoord aHeight) const michael@0: { michael@0: nsRect range = GetScrolledRect(); michael@0: range.width = std::max(range.width - aWidth, 0); michael@0: range.height = std::max(range.height - aHeight, 0); michael@0: return range; michael@0: } michael@0: michael@0: nsRect michael@0: ScrollFrameHelper::GetScrollRangeForClamping() const michael@0: { michael@0: if (!ShouldClampScrollPosition()) { michael@0: return nsRect(nscoord_MIN/2, nscoord_MIN/2, michael@0: nscoord_MAX - nscoord_MIN/2, nscoord_MAX - nscoord_MIN/2); michael@0: } michael@0: nsSize scrollPortSize = GetScrollPositionClampingScrollPortSize(); michael@0: return GetScrollRange(scrollPortSize.width, scrollPortSize.height); michael@0: } michael@0: michael@0: nsSize michael@0: ScrollFrameHelper::GetScrollPositionClampingScrollPortSize() const michael@0: { michael@0: nsIPresShell* presShell = mOuter->PresContext()->PresShell(); michael@0: if (mIsRoot && presShell->IsScrollPositionClampingScrollPortSizeSet()) { michael@0: return presShell->GetScrollPositionClampingScrollPortSize(); michael@0: } michael@0: return mScrollPort.Size(); michael@0: } michael@0: michael@0: gfxSize michael@0: ScrollFrameHelper::GetResolution() const michael@0: { michael@0: return mResolution; michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::SetResolution(const gfxSize& aResolution) michael@0: { michael@0: mResolution = aResolution; michael@0: mIsResolutionSet = true; michael@0: } michael@0: michael@0: static void michael@0: AdjustForWholeDelta(int32_t aDelta, nscoord* aCoord) michael@0: { michael@0: if (aDelta < 0) { michael@0: *aCoord = nscoord_MIN; michael@0: } else if (aDelta > 0) { michael@0: *aCoord = nscoord_MAX; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Calculate lower/upper scrollBy range in given direction. michael@0: * @param aDelta specifies scrollBy direction, if 0 then range will be 0 size michael@0: * @param aPos desired destination in AppUnits michael@0: * @param aNeg/PosTolerance defines relative range distance michael@0: * below and above of aPos point michael@0: * @param aMultiplier used for conversion of tolerance into appUnis michael@0: */ michael@0: static void michael@0: CalcRangeForScrollBy(int32_t aDelta, nscoord aPos, michael@0: float aNegTolerance, michael@0: float aPosTolerance, michael@0: nscoord aMultiplier, michael@0: nscoord* aLower, nscoord* aUpper) michael@0: { michael@0: if (!aDelta) { michael@0: *aLower = *aUpper = aPos; michael@0: return; michael@0: } michael@0: *aLower = aPos - NSToCoordRound(aMultiplier * (aDelta > 0 ? aNegTolerance : aPosTolerance)); michael@0: *aUpper = aPos + NSToCoordRound(aMultiplier * (aDelta > 0 ? aPosTolerance : aNegTolerance)); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::ScrollBy(nsIntPoint aDelta, michael@0: nsIScrollableFrame::ScrollUnit aUnit, michael@0: nsIScrollableFrame::ScrollMode aMode, michael@0: nsIntPoint* aOverflow, michael@0: nsIAtom *aOrigin) michael@0: { michael@0: nsSize deltaMultiplier; michael@0: float negativeTolerance; michael@0: float positiveTolerance; michael@0: if (!aOrigin){ michael@0: aOrigin = nsGkAtoms::other; michael@0: } michael@0: bool isGenericOrigin = (aOrigin == nsGkAtoms::other); michael@0: switch (aUnit) { michael@0: case nsIScrollableFrame::DEVICE_PIXELS: { michael@0: nscoord appUnitsPerDevPixel = michael@0: mOuter->PresContext()->AppUnitsPerDevPixel(); michael@0: deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel); michael@0: if (isGenericOrigin){ michael@0: aOrigin = nsGkAtoms::pixels; michael@0: } michael@0: negativeTolerance = positiveTolerance = 0.5f; michael@0: break; michael@0: } michael@0: case nsIScrollableFrame::LINES: { michael@0: deltaMultiplier = GetLineScrollAmount(); michael@0: if (isGenericOrigin){ michael@0: aOrigin = nsGkAtoms::lines; michael@0: } michael@0: negativeTolerance = positiveTolerance = 0.1f; michael@0: break; michael@0: } michael@0: case nsIScrollableFrame::PAGES: { michael@0: deltaMultiplier = GetPageScrollAmount(); michael@0: if (isGenericOrigin){ michael@0: aOrigin = nsGkAtoms::pages; michael@0: } michael@0: negativeTolerance = 0.05f; michael@0: positiveTolerance = 0; michael@0: break; michael@0: } michael@0: case nsIScrollableFrame::WHOLE: { michael@0: nsPoint pos = GetScrollPosition(); michael@0: AdjustForWholeDelta(aDelta.x, &pos.x); michael@0: AdjustForWholeDelta(aDelta.y, &pos.y); michael@0: ScrollTo(pos, aMode); michael@0: // 'this' might be destroyed here michael@0: if (aOverflow) { michael@0: *aOverflow = nsIntPoint(0, 0); michael@0: } michael@0: return; michael@0: } michael@0: default: michael@0: NS_ERROR("Invalid scroll mode"); michael@0: return; michael@0: } michael@0: michael@0: nsPoint newPos = mDestination + michael@0: nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height); michael@0: // Calculate desired range values. michael@0: nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY; michael@0: CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance, michael@0: deltaMultiplier.width, &rangeLowerX, &rangeUpperX); michael@0: CalcRangeForScrollBy(aDelta.y, newPos.y, negativeTolerance, positiveTolerance, michael@0: deltaMultiplier.height, &rangeLowerY, &rangeUpperY); michael@0: nsRect range(rangeLowerX, michael@0: rangeLowerY, michael@0: rangeUpperX - rangeLowerX, michael@0: rangeUpperY - rangeLowerY); michael@0: nsWeakFrame weakFrame(mOuter); michael@0: ScrollToWithOrigin(newPos, aMode, aOrigin, &range); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: michael@0: if (aOverflow) { michael@0: nsPoint clampAmount = newPos - mDestination; michael@0: float appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel(); michael@0: *aOverflow = nsIntPoint( michael@0: NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel), michael@0: NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel)); michael@0: } michael@0: } michael@0: michael@0: nsSize michael@0: ScrollFrameHelper::GetLineScrollAmount() const michael@0: { michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForFrame(mOuter, getter_AddRefs(fm), michael@0: nsLayoutUtils::FontSizeInflationFor(mOuter)); michael@0: NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit"); michael@0: static nscoord sMinLineScrollAmountInPixels = -1; michael@0: if (sMinLineScrollAmountInPixels < 0) { michael@0: Preferences::AddIntVarCache(&sMinLineScrollAmountInPixels, michael@0: "mousewheel.min_line_scroll_amount", 1); michael@0: } michael@0: int32_t appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel(); michael@0: nscoord minScrollAmountInAppUnits = michael@0: std::max(1, sMinLineScrollAmountInPixels) * appUnitsPerDevPixel; michael@0: nscoord horizontalAmount = fm ? fm->AveCharWidth() : 0; michael@0: nscoord verticalAmount = fm ? fm->MaxHeight() : 0; michael@0: return nsSize(std::max(horizontalAmount, minScrollAmountInAppUnits), michael@0: std::max(verticalAmount, minScrollAmountInAppUnits)); michael@0: } michael@0: michael@0: /** michael@0: * Compute the scrollport size excluding any fixed-pos headers and michael@0: * footers. A header or footer is an box that spans that entire width michael@0: * of the viewport and touches the top (or bottom, respectively) of the michael@0: * viewport. We also want to consider fixed elements that stack or overlap michael@0: * to effectively create a larger header or footer. Headers and footers that michael@0: * cover more than a third of the the viewport are ignored since they michael@0: * probably aren't true headers and footers and we don't want to restrict michael@0: * scrolling too much in such cases. This is a bit conservative --- some michael@0: * pages use elements as headers or footers that don't span the entire width michael@0: * of the viewport --- but it should be a good start. michael@0: */ michael@0: struct TopAndBottom michael@0: { michael@0: TopAndBottom(nscoord aTop, nscoord aBottom) : top(aTop), bottom(aBottom) {} michael@0: michael@0: nscoord top, bottom; michael@0: }; michael@0: struct TopComparator michael@0: { michael@0: bool Equals(const TopAndBottom& A, const TopAndBottom& B) const { michael@0: return A.top == B.top; michael@0: } michael@0: bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const { michael@0: return A.top < B.top; michael@0: } michael@0: }; michael@0: struct ReverseBottomComparator michael@0: { michael@0: bool Equals(const TopAndBottom& A, const TopAndBottom& B) const { michael@0: return A.bottom == B.bottom; michael@0: } michael@0: bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const { michael@0: return A.bottom > B.bottom; michael@0: } michael@0: }; michael@0: static nsSize michael@0: GetScrollPortSizeExcludingHeadersAndFooters(nsIFrame* aViewportFrame, michael@0: const nsRect& aScrollPort) michael@0: { michael@0: nsTArray list; michael@0: nsFrameList fixedFrames = aViewportFrame->GetChildList(nsIFrame::kFixedList); michael@0: for (nsFrameList::Enumerator iterator(fixedFrames); !iterator.AtEnd(); michael@0: iterator.Next()) { michael@0: nsIFrame* f = iterator.get(); michael@0: nsRect r = f->GetRect().Intersect(aScrollPort); michael@0: if (r.x == 0 && r.width == aScrollPort.width && michael@0: r.height <= aScrollPort.height/3) { michael@0: list.AppendElement(TopAndBottom(r.y, r.YMost())); michael@0: } michael@0: } michael@0: michael@0: list.Sort(TopComparator()); michael@0: nscoord headerBottom = 0; michael@0: for (uint32_t i = 0; i < list.Length(); ++i) { michael@0: if (list[i].top <= headerBottom) { michael@0: headerBottom = std::max(headerBottom, list[i].bottom); michael@0: } michael@0: } michael@0: michael@0: list.Sort(ReverseBottomComparator()); michael@0: nscoord footerTop = aScrollPort.height; michael@0: for (uint32_t i = 0; i < list.Length(); ++i) { michael@0: if (list[i].bottom >= footerTop) { michael@0: footerTop = std::min(footerTop, list[i].top); michael@0: } michael@0: } michael@0: michael@0: headerBottom = std::min(aScrollPort.height/3, headerBottom); michael@0: footerTop = std::max(aScrollPort.height - aScrollPort.height/3, footerTop); michael@0: michael@0: return nsSize(aScrollPort.width, footerTop - headerBottom); michael@0: } michael@0: michael@0: nsSize michael@0: ScrollFrameHelper::GetPageScrollAmount() const michael@0: { michael@0: nsSize lineScrollAmount = GetLineScrollAmount(); michael@0: nsSize effectiveScrollPortSize; michael@0: if (mIsRoot) { michael@0: // Reduce effective scrollport height by the height of any fixed-pos michael@0: // headers or footers michael@0: nsIFrame* root = mOuter->PresContext()->PresShell()->GetRootFrame(); michael@0: effectiveScrollPortSize = michael@0: GetScrollPortSizeExcludingHeadersAndFooters(root, mScrollPort); michael@0: } else { michael@0: effectiveScrollPortSize = mScrollPort.Size(); michael@0: } michael@0: // The page increment is the size of the page, minus the smaller of michael@0: // 10% of the size or 2 lines. michael@0: return nsSize( michael@0: effectiveScrollPortSize.width - michael@0: std::min(effectiveScrollPortSize.width/10, 2*lineScrollAmount.width), michael@0: effectiveScrollPortSize.height - michael@0: std::min(effectiveScrollPortSize.height/10, 2*lineScrollAmount.height)); michael@0: } michael@0: michael@0: /** michael@0: * this code is resposible for restoring the scroll position back to some michael@0: * saved position. if the user has not moved the scroll position manually michael@0: * we keep scrolling down until we get to our original position. keep in michael@0: * mind that content could incrementally be coming in. we only want to stop michael@0: * when we reach our new position. michael@0: */ michael@0: void michael@0: ScrollFrameHelper::ScrollToRestoredPosition() michael@0: { michael@0: if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) { michael@0: return; michael@0: } michael@0: // make sure our scroll position did not change for where we last put michael@0: // it. if it does then the user must have moved it, and we no longer michael@0: // need to restore. michael@0: // michael@0: // In the RTL case, we check whether the scroll position changed using the michael@0: // logical scroll position, but we scroll to the physical scroll position in michael@0: // all cases michael@0: michael@0: // if we didn't move, we still need to restore michael@0: if (GetLogicalScrollPosition() == mLastPos) { michael@0: // if our desired position is different to the scroll position, scroll. michael@0: // remember that we could be incrementally loading so we may enter michael@0: // and scroll many times. michael@0: if (mRestorePos != mLastPos /* GetLogicalScrollPosition() */) { michael@0: nsPoint scrollToPos = mRestorePos; michael@0: if (!IsLTR()) michael@0: // convert from logical to physical scroll position michael@0: scrollToPos.x = mScrollPort.x - michael@0: (mScrollPort.XMost() - scrollToPos.x - mScrolledFrame->GetRect().width); michael@0: nsWeakFrame weakFrame(mOuter); michael@0: ScrollTo(scrollToPos, nsIScrollableFrame::INSTANT); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: // Re-get the scroll position, it might not be exactly equal to michael@0: // mRestorePos due to rounding and clamping. michael@0: mLastPos = GetLogicalScrollPosition(); michael@0: } else { michael@0: // if we reached the position then stop michael@0: mRestorePos.y = -1; michael@0: mLastPos.x = -1; michael@0: mLastPos.y = -1; michael@0: } michael@0: } else { michael@0: // user moved the position, so we won't need to restore michael@0: mLastPos.x = -1; michael@0: mLastPos.y = -1; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: ScrollFrameHelper::FireScrollPortEvent() michael@0: { michael@0: mAsyncScrollPortEvent.Forget(); michael@0: michael@0: // Keep this in sync with PostOverflowEvent(). michael@0: nsSize scrollportSize = mScrollPort.Size(); michael@0: nsSize childSize = GetScrolledRect().Size(); michael@0: michael@0: bool newVerticalOverflow = childSize.height > scrollportSize.height; michael@0: bool vertChanged = mVerticalOverflow != newVerticalOverflow; michael@0: michael@0: bool newHorizontalOverflow = childSize.width > scrollportSize.width; michael@0: bool horizChanged = mHorizontalOverflow != newHorizontalOverflow; michael@0: michael@0: if (!vertChanged && !horizChanged) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If both either overflowed or underflowed then we dispatch only one michael@0: // DOM event. michael@0: bool both = vertChanged && horizChanged && michael@0: newVerticalOverflow == newHorizontalOverflow; michael@0: InternalScrollPortEvent::orientType orient; michael@0: if (both) { michael@0: orient = InternalScrollPortEvent::both; michael@0: mHorizontalOverflow = newHorizontalOverflow; michael@0: mVerticalOverflow = newVerticalOverflow; michael@0: } michael@0: else if (vertChanged) { michael@0: orient = InternalScrollPortEvent::vertical; michael@0: mVerticalOverflow = newVerticalOverflow; michael@0: if (horizChanged) { michael@0: // We need to dispatch a separate horizontal DOM event. Do that the next michael@0: // time around since dispatching the vertical DOM event might destroy michael@0: // the frame. michael@0: PostOverflowEvent(); michael@0: } michael@0: } michael@0: else { michael@0: orient = InternalScrollPortEvent::horizontal; michael@0: mHorizontalOverflow = newHorizontalOverflow; michael@0: } michael@0: michael@0: InternalScrollPortEvent event(true, michael@0: (orient == InternalScrollPortEvent::horizontal ? mHorizontalOverflow : michael@0: mVerticalOverflow) ? michael@0: NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW, nullptr); michael@0: event.orient = orient; michael@0: return EventDispatcher::Dispatch(mOuter->GetContent(), michael@0: mOuter->PresContext(), &event); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::ReloadChildFrames() michael@0: { michael@0: mScrolledFrame = nullptr; michael@0: mHScrollbarBox = nullptr; michael@0: mVScrollbarBox = nullptr; michael@0: mScrollCornerBox = nullptr; michael@0: mResizerBox = nullptr; michael@0: michael@0: nsIFrame* frame = mOuter->GetFirstPrincipalChild(); michael@0: while (frame) { michael@0: nsIContent* content = frame->GetContent(); michael@0: if (content == mOuter->GetContent()) { michael@0: NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame"); michael@0: mScrolledFrame = frame; michael@0: } else { michael@0: nsAutoString value; michael@0: content->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, value); michael@0: if (!value.IsEmpty()) { michael@0: // probably a scrollbar then michael@0: if (value.LowerCaseEqualsLiteral("horizontal")) { michael@0: NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?"); michael@0: mHScrollbarBox = frame; michael@0: } else { michael@0: NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?"); michael@0: mVScrollbarBox = frame; michael@0: } michael@0: } else if (content->Tag() == nsGkAtoms::resizer) { michael@0: NS_ASSERTION(!mResizerBox, "Found multiple resizers"); michael@0: mResizerBox = frame; michael@0: } else if (content->Tag() == nsGkAtoms::scrollcorner) { michael@0: // probably a scrollcorner michael@0: NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners"); michael@0: mScrollCornerBox = frame; michael@0: } michael@0: } michael@0: michael@0: frame = frame->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: ScrollFrameHelper::CreateAnonymousContent( michael@0: nsTArray& aElements) michael@0: { michael@0: nsPresContext* presContext = mOuter->PresContext(); michael@0: nsIFrame* parent = mOuter->GetParent(); michael@0: michael@0: // Don't create scrollbars if we're an SVG document being used as an image, michael@0: // or if we're printing/print previewing. michael@0: // (In the printing case, we allow scrollbars if this is the child of the michael@0: // viewport & paginated scrolling is enabled, because then we must be the michael@0: // scroll frame for the print preview window, & that does need scrollbars.) michael@0: if (presContext->Document()->IsBeingUsedAsImage() || michael@0: (!presContext->IsDynamic() && michael@0: !(mIsRoot && presContext->HasPaginatedScrolling()))) { michael@0: mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Check if the frame is resizable. michael@0: int8_t resizeStyle = mOuter->StyleDisplay()->mResize; michael@0: bool isResizable = resizeStyle != NS_STYLE_RESIZE_NONE; michael@0: michael@0: nsIScrollableFrame *scrollable = do_QueryFrame(mOuter); michael@0: michael@0: // If we're the scrollframe for the root, then we want to construct michael@0: // our scrollbar frames no matter what. That way later dynamic michael@0: // changes to propagated overflow styles will show or hide michael@0: // scrollbars on the viewport without requiring frame reconstruction michael@0: // of the viewport (good!). michael@0: bool canHaveHorizontal; michael@0: bool canHaveVertical; michael@0: if (!mIsRoot) { michael@0: ScrollbarStyles styles = scrollable->GetScrollbarStyles(); michael@0: canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN; michael@0: canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN; michael@0: if (!canHaveHorizontal && !canHaveVertical && !isResizable) { michael@0: // Nothing to do. michael@0: return NS_OK; michael@0: } michael@0: } else { michael@0: canHaveHorizontal = true; michael@0: canHaveVertical = true; michael@0: } michael@0: michael@0: // The anonymous
used by never gets scrollbars. michael@0: nsITextControlFrame* textFrame = do_QueryFrame(parent); michael@0: if (textFrame) { michael@0: // Make sure we are not a text area. michael@0: nsCOMPtr textAreaElement(do_QueryInterface(parent->GetContent())); michael@0: if (!textAreaElement) { michael@0: mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: nsNodeInfoManager *nodeInfoManager = michael@0: presContext->Document()->NodeInfoManager(); michael@0: nsCOMPtr nodeInfo; michael@0: nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbar, nullptr, michael@0: kNameSpaceID_XUL, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: if (canHaveHorizontal) { michael@0: nsCOMPtr ni = nodeInfo; michael@0: NS_TrustedNewXULElement(getter_AddRefs(mHScrollbarContent), ni.forget()); michael@0: mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, michael@0: NS_LITERAL_STRING("horizontal"), false); michael@0: mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough, michael@0: NS_LITERAL_STRING("always"), false); michael@0: if (mIsRoot) { michael@0: mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_, michael@0: NS_LITERAL_STRING("true"), false); michael@0: } michael@0: if (!aElements.AppendElement(mHScrollbarContent)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (canHaveVertical) { michael@0: nsCOMPtr ni = nodeInfo; michael@0: NS_TrustedNewXULElement(getter_AddRefs(mVScrollbarContent), ni.forget()); michael@0: mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, michael@0: NS_LITERAL_STRING("vertical"), false); michael@0: mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough, michael@0: NS_LITERAL_STRING("always"), false); michael@0: if (mIsRoot) { michael@0: mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_, michael@0: NS_LITERAL_STRING("true"), false); michael@0: } michael@0: if (!aElements.AppendElement(mVScrollbarContent)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (isResizable) { michael@0: nsCOMPtr nodeInfo; michael@0: nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nullptr, michael@0: kNameSpaceID_XUL, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget()); michael@0: michael@0: nsAutoString dir; michael@0: switch (resizeStyle) { michael@0: case NS_STYLE_RESIZE_HORIZONTAL: michael@0: if (IsScrollbarOnRight()) { michael@0: dir.AssignLiteral("right"); michael@0: } michael@0: else { michael@0: dir.AssignLiteral("left"); michael@0: } michael@0: break; michael@0: case NS_STYLE_RESIZE_VERTICAL: michael@0: dir.AssignLiteral("bottom"); michael@0: break; michael@0: case NS_STYLE_RESIZE_BOTH: michael@0: dir.AssignLiteral("bottomend"); michael@0: break; michael@0: default: michael@0: NS_WARNING("only resizable types should have resizers"); michael@0: } michael@0: mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false); michael@0: michael@0: if (mIsRoot) { michael@0: nsIContent* browserRoot = GetBrowserRoot(mOuter->GetContent()); michael@0: mCollapsedResizer = !(browserRoot && michael@0: browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer)); michael@0: } michael@0: else { michael@0: mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element, michael@0: NS_LITERAL_STRING("_parent"), false); michael@0: } michael@0: michael@0: mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough, michael@0: NS_LITERAL_STRING("always"), false); michael@0: michael@0: if (!aElements.AppendElement(mResizerContent)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (canHaveHorizontal && canHaveVertical) { michael@0: nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nullptr, michael@0: kNameSpaceID_XUL, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent), nodeInfo.forget()); michael@0: if (!aElements.AppendElement(mScrollCornerContent)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::AppendAnonymousContentTo(nsBaseContentList& aElements, michael@0: uint32_t aFilter) michael@0: { michael@0: aElements.MaybeAppendElement(mHScrollbarContent); michael@0: aElements.MaybeAppendElement(mVScrollbarContent); michael@0: aElements.MaybeAppendElement(mScrollCornerContent); michael@0: aElements.MaybeAppendElement(mResizerContent); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::Destroy() michael@0: { michael@0: if (mScrollbarActivity) { michael@0: mScrollbarActivity->Destroy(); michael@0: mScrollbarActivity = nullptr; michael@0: } michael@0: michael@0: // Unbind any content created in CreateAnonymousContent from the tree michael@0: nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent); michael@0: nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent); michael@0: nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent); michael@0: nsContentUtils::DestroyAnonymousContent(&mResizerContent); michael@0: michael@0: if (mPostedReflowCallback) { michael@0: mOuter->PresContext()->PresShell()->CancelReflowCallback(this); michael@0: mPostedReflowCallback = false; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Called when we want to update the scrollbar position, either because scrolling happened michael@0: * or the user moved the scrollbar position and we need to undo that (e.g., when the user michael@0: * clicks to scroll and we're using smooth scrolling, so we need to put the thumb back michael@0: * to its initial position for the start of the smooth sequence). michael@0: */ michael@0: void michael@0: ScrollFrameHelper::UpdateScrollbarPosition() michael@0: { michael@0: nsWeakFrame weakFrame(mOuter); michael@0: mFrameIsUpdatingScrollbar = true; michael@0: michael@0: nsPoint pt = GetScrollPosition(); michael@0: if (mVScrollbarBox) { michael@0: SetCoordAttribute(mVScrollbarBox->GetContent(), nsGkAtoms::curpos, michael@0: pt.y - GetScrolledRect().y); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: } michael@0: if (mHScrollbarBox) { michael@0: SetCoordAttribute(mHScrollbarBox->GetContent(), nsGkAtoms::curpos, michael@0: pt.x - GetScrolledRect().x); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: mFrameIsUpdatingScrollbar = false; michael@0: } michael@0: michael@0: void ScrollFrameHelper::CurPosAttributeChanged(nsIContent* aContent) michael@0: { michael@0: NS_ASSERTION(aContent, "aContent must not be null"); michael@0: NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) || michael@0: (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent), michael@0: "unexpected child"); michael@0: michael@0: // Attribute changes on the scrollbars happen in one of three ways: michael@0: // 1) The scrollbar changed the attribute in response to some user event michael@0: // 2) We changed the attribute in response to a ScrollPositionDidChange michael@0: // callback from the scrolling view michael@0: // 3) We changed the attribute to adjust the scrollbars for the start michael@0: // of a smooth scroll operation michael@0: // michael@0: // In cases 2 and 3 we do not need to scroll because we're just michael@0: // updating our scrollbar. michael@0: if (mFrameIsUpdatingScrollbar) michael@0: return; michael@0: michael@0: nsRect scrolledRect = GetScrolledRect(); michael@0: michael@0: nsPoint current = GetScrollPosition() - scrolledRect.TopLeft(); michael@0: nsPoint dest; michael@0: nsRect allowedRange; michael@0: dest.x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos, current.x, michael@0: &allowedRange.x, &allowedRange.width); michael@0: dest.y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos, current.y, michael@0: &allowedRange.y, &allowedRange.height); michael@0: current += scrolledRect.TopLeft(); michael@0: dest += scrolledRect.TopLeft(); michael@0: allowedRange += scrolledRect.TopLeft(); michael@0: michael@0: // Don't try to scroll if we're already at an acceptable place. michael@0: // Don't call Contains here since Contains returns false when the point is michael@0: // on the bottom or right edge of the rectangle. michael@0: if (allowedRange.ClampPoint(current) == current) { michael@0: return; michael@0: } michael@0: michael@0: if (mScrollbarActivity) { michael@0: nsRefPtr scrollbarActivity(mScrollbarActivity); michael@0: scrollbarActivity->ActivityOccurred(); michael@0: } michael@0: michael@0: bool isSmooth = aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth); michael@0: if (isSmooth) { michael@0: // Make sure an attribute-setting callback occurs even if the view michael@0: // didn't actually move yet. We need to make sure other listeners michael@0: // see that the scroll position is not (yet) what they thought it michael@0: // was. michael@0: nsWeakFrame weakFrame(mOuter); michael@0: UpdateScrollbarPosition(); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: } michael@0: ScrollToWithOrigin(dest, michael@0: isSmooth ? nsIScrollableFrame::SMOOTH : nsIScrollableFrame::INSTANT, michael@0: nsGkAtoms::scrollbars, &allowedRange); michael@0: // 'this' might be destroyed here michael@0: } michael@0: michael@0: /* ============= Scroll events ========== */ michael@0: michael@0: NS_IMETHODIMP michael@0: ScrollFrameHelper::ScrollEvent::Run() michael@0: { michael@0: if (mHelper) michael@0: mHelper->FireScrollEvent(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::FireScrollEvent() michael@0: { michael@0: mScrollEvent.Forget(); michael@0: michael@0: WidgetGUIEvent event(true, NS_SCROLL_EVENT, nullptr); michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: nsIContent* content = mOuter->GetContent(); michael@0: nsPresContext* prescontext = mOuter->PresContext(); michael@0: // Fire viewport scroll events at the document (where they michael@0: // will bubble to the window) michael@0: if (mIsRoot) { michael@0: nsIDocument* doc = content->GetCurrentDoc(); michael@0: if (doc) { michael@0: EventDispatcher::Dispatch(doc, prescontext, &event, nullptr, &status); michael@0: } michael@0: } else { michael@0: // scroll events fired at elements don't bubble (although scroll events michael@0: // fired at documents do, to the window) michael@0: event.mFlags.mBubbles = false; michael@0: EventDispatcher::Dispatch(content, prescontext, &event, nullptr, &status); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::PostScrollEvent() michael@0: { michael@0: if (mScrollEvent.IsPending()) michael@0: return; michael@0: michael@0: nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext(); michael@0: if (!rpc) michael@0: return; michael@0: mScrollEvent = new ScrollEvent(this); michael@0: rpc->AddWillPaintObserver(mScrollEvent.get()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ScrollFrameHelper::AsyncScrollPortEvent::Run() michael@0: { michael@0: if (mHelper) { michael@0: mHelper->mOuter->PresContext()->GetPresShell()-> michael@0: FlushPendingNotifications(Flush_InterruptibleLayout); michael@0: } michael@0: return mHelper ? mHelper->FireScrollPortEvent() : NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom) michael@0: { michael@0: if (!mHelper.mHScrollbarBox) michael@0: return true; michael@0: michael@0: return AddRemoveScrollbar(aState, aOnBottom, true, true); michael@0: } michael@0: michael@0: bool michael@0: nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight) michael@0: { michael@0: if (!mHelper.mVScrollbarBox) michael@0: return true; michael@0: michael@0: return AddRemoveScrollbar(aState, aOnRight, false, true); michael@0: } michael@0: michael@0: void michael@0: nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom) michael@0: { michael@0: // removing a scrollbar should always fit michael@0: DebugOnly result = AddRemoveScrollbar(aState, aOnBottom, true, false); michael@0: NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??"); michael@0: } michael@0: michael@0: void michael@0: nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight) michael@0: { michael@0: // removing a scrollbar should always fit michael@0: DebugOnly result = AddRemoveScrollbar(aState, aOnRight, false, false); michael@0: NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??"); michael@0: } michael@0: michael@0: bool michael@0: nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState, michael@0: bool aOnRightOrBottom, bool aHorizontal, bool aAdd) michael@0: { michael@0: if (aHorizontal) { michael@0: if (mHelper.mNeverHasHorizontalScrollbar || !mHelper.mHScrollbarBox) michael@0: return false; michael@0: michael@0: nsSize hSize = mHelper.mHScrollbarBox->GetPrefSize(aState); michael@0: nsBox::AddMargin(mHelper.mHScrollbarBox, hSize); michael@0: michael@0: mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, aAdd); michael@0: michael@0: bool hasHorizontalScrollbar; michael@0: bool fit = AddRemoveScrollbar(hasHorizontalScrollbar, michael@0: mHelper.mScrollPort.y, michael@0: mHelper.mScrollPort.height, michael@0: hSize.height, aOnRightOrBottom, aAdd); michael@0: mHelper.mHasHorizontalScrollbar = hasHorizontalScrollbar; // because mHasHorizontalScrollbar is a bool michael@0: if (!fit) michael@0: mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, !aAdd); michael@0: michael@0: return fit; michael@0: } else { michael@0: if (mHelper.mNeverHasVerticalScrollbar || !mHelper.mVScrollbarBox) michael@0: return false; michael@0: michael@0: nsSize vSize = mHelper.mVScrollbarBox->GetPrefSize(aState); michael@0: nsBox::AddMargin(mHelper.mVScrollbarBox, vSize); michael@0: michael@0: mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, aAdd); michael@0: michael@0: bool hasVerticalScrollbar; michael@0: bool fit = AddRemoveScrollbar(hasVerticalScrollbar, michael@0: mHelper.mScrollPort.x, michael@0: mHelper.mScrollPort.width, michael@0: vSize.width, aOnRightOrBottom, aAdd); michael@0: mHelper.mHasVerticalScrollbar = hasVerticalScrollbar; // because mHasVerticalScrollbar is a bool michael@0: if (!fit) michael@0: mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, !aAdd); michael@0: michael@0: return fit; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsXULScrollFrame::AddRemoveScrollbar(bool& aHasScrollbar, nscoord& aXY, michael@0: nscoord& aSize, nscoord aSbSize, michael@0: bool aOnRightOrBottom, bool aAdd) michael@0: { michael@0: nscoord size = aSize; michael@0: nscoord xy = aXY; michael@0: michael@0: if (size != NS_INTRINSICSIZE) { michael@0: if (aAdd) { michael@0: size -= aSbSize; michael@0: if (!aOnRightOrBottom && size >= 0) michael@0: xy += aSbSize; michael@0: } else { michael@0: size += aSbSize; michael@0: if (!aOnRightOrBottom) michael@0: xy -= aSbSize; michael@0: } michael@0: } michael@0: michael@0: // not enough room? Yes? Return true. michael@0: if (size >= 0) { michael@0: aHasScrollbar = aAdd; michael@0: aSize = size; michael@0: aXY = xy; michael@0: return true; michael@0: } michael@0: michael@0: aHasScrollbar = false; michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState, michael@0: const nsPoint& aScrollPosition) michael@0: { michael@0: uint32_t oldflags = aState.LayoutFlags(); michael@0: nsRect childRect = nsRect(mHelper.mScrollPort.TopLeft() - aScrollPosition, michael@0: mHelper.mScrollPort.Size()); michael@0: int32_t flags = NS_FRAME_NO_MOVE_VIEW; michael@0: michael@0: nsSize minSize = mHelper.mScrolledFrame->GetMinSize(aState); michael@0: michael@0: if (minSize.height > childRect.height) michael@0: childRect.height = minSize.height; michael@0: michael@0: if (minSize.width > childRect.width) michael@0: childRect.width = minSize.width; michael@0: michael@0: aState.SetLayoutFlags(flags); michael@0: ClampAndSetBounds(aState, childRect, aScrollPosition); michael@0: mHelper.mScrolledFrame->Layout(aState); michael@0: michael@0: childRect = mHelper.mScrolledFrame->GetRect(); michael@0: michael@0: if (childRect.width < mHelper.mScrollPort.width || michael@0: childRect.height < mHelper.mScrollPort.height) michael@0: { michael@0: childRect.width = std::max(childRect.width, mHelper.mScrollPort.width); michael@0: childRect.height = std::max(childRect.height, mHelper.mScrollPort.height); michael@0: michael@0: // remove overflow areas when we update the bounds, michael@0: // because we've already accounted for it michael@0: // REVIEW: Have we accounted for both? michael@0: ClampAndSetBounds(aState, childRect, aScrollPosition, true); michael@0: } michael@0: michael@0: aState.SetLayoutFlags(oldflags); michael@0: michael@0: } michael@0: michael@0: void ScrollFrameHelper::PostOverflowEvent() michael@0: { michael@0: if (mAsyncScrollPortEvent.IsPending()) michael@0: return; michael@0: michael@0: // Keep this in sync with FireScrollPortEvent(). michael@0: nsSize scrollportSize = mScrollPort.Size(); michael@0: nsSize childSize = GetScrolledRect().Size(); michael@0: michael@0: bool newVerticalOverflow = childSize.height > scrollportSize.height; michael@0: bool vertChanged = mVerticalOverflow != newVerticalOverflow; michael@0: michael@0: bool newHorizontalOverflow = childSize.width > scrollportSize.width; michael@0: bool horizChanged = mHorizontalOverflow != newHorizontalOverflow; michael@0: michael@0: if (!vertChanged && !horizChanged) { michael@0: return; michael@0: } michael@0: michael@0: nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext(); michael@0: if (!rpc) michael@0: return; michael@0: michael@0: mAsyncScrollPortEvent = new AsyncScrollPortEvent(this); michael@0: rpc->AddWillPaintObserver(mAsyncScrollPortEvent.get()); michael@0: } michael@0: michael@0: bool michael@0: ScrollFrameHelper::IsLTR() const michael@0: { michael@0: //TODO make bidi code set these from preferences michael@0: michael@0: nsIFrame *frame = mOuter; michael@0: // XXX This is a bit on the slow side. michael@0: if (mIsRoot) { michael@0: // If we're the root scrollframe, we need the root element's style data. michael@0: nsPresContext *presContext = mOuter->PresContext(); michael@0: nsIDocument *document = presContext->Document(); michael@0: Element *root = document->GetRootElement(); michael@0: michael@0: // But for HTML and XHTML we want the body element. michael@0: nsCOMPtr htmlDoc = do_QueryInterface(document); michael@0: if (htmlDoc) { michael@0: Element *bodyElement = document->GetBodyElement(); michael@0: if (bodyElement) michael@0: root = bodyElement; // we can trust the document to hold on to it michael@0: } michael@0: michael@0: if (root) { michael@0: nsIFrame *rootsFrame = root->GetPrimaryFrame(); michael@0: if (rootsFrame) michael@0: frame = rootsFrame; michael@0: } michael@0: } michael@0: michael@0: return frame->StyleVisibility()->mDirection != NS_STYLE_DIRECTION_RTL; michael@0: } michael@0: michael@0: bool michael@0: ScrollFrameHelper::IsScrollbarOnRight() const michael@0: { michael@0: nsPresContext *presContext = mOuter->PresContext(); michael@0: michael@0: // The position of the scrollbar in top-level windows depends on the pref michael@0: // layout.scrollbar.side. For non-top-level elements, it depends only on the michael@0: // directionaliy of the element (equivalent to a value of "1" for the pref). michael@0: if (!mIsRoot) michael@0: return IsLTR(); michael@0: switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) { michael@0: default: michael@0: case 0: // UI directionality michael@0: return presContext->GetCachedIntPref(kPresContext_BidiDirection) michael@0: == IBMBIDI_TEXTDIRECTION_LTR; michael@0: case 1: // Document / content directionality michael@0: return IsLTR(); michael@0: case 2: // Always right michael@0: return true; michael@0: case 3: // Always left michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Reflow the scroll area if it needs it and return its size. Also determine if the reflow will michael@0: * cause any of the scrollbars to need to be reflowed. michael@0: */ michael@0: nsresult michael@0: nsXULScrollFrame::Layout(nsBoxLayoutState& aState) michael@0: { michael@0: bool scrollbarRight = mHelper.IsScrollbarOnRight(); michael@0: bool scrollbarBottom = true; michael@0: michael@0: // get the content rect michael@0: nsRect clientRect(0,0,0,0); michael@0: GetClientRect(clientRect); michael@0: michael@0: nsRect oldScrollAreaBounds = mHelper.mScrollPort; michael@0: nsPoint oldScrollPosition = mHelper.GetLogicalScrollPosition(); michael@0: michael@0: // the scroll area size starts off as big as our content area michael@0: mHelper.mScrollPort = clientRect; michael@0: michael@0: /************** michael@0: Our basic strategy here is to first try laying out the content with michael@0: the scrollbars in their current state. We're hoping that that will michael@0: just "work"; the content will overflow wherever there's a scrollbar michael@0: already visible. If that does work, then there's no need to lay out michael@0: the scrollarea. Otherwise we fix up the scrollbars; first we add a michael@0: vertical one to scroll the content if necessary, or remove it if michael@0: it's not needed. Then we reflow the content if the scrollbar michael@0: changed. Then we add a horizontal scrollbar if necessary (or michael@0: remove if not needed), and if that changed, we reflow the content michael@0: again. At this point, any scrollbars that are needed to scroll the michael@0: content have been added. michael@0: michael@0: In the second phase we check to see if any scrollbars are too small michael@0: to display, and if so, we remove them. We check the horizontal michael@0: scrollbar first; removing it might make room for the vertical michael@0: scrollbar, and if we have room for just one scrollbar we'll save michael@0: the vertical one. michael@0: michael@0: Finally we position and size the scrollbars and scrollcorner (the michael@0: square that is needed in the corner of the window when two michael@0: scrollbars are visible), and reflow any fixed position views michael@0: (if we're the viewport and we added or removed a scrollbar). michael@0: **************/ michael@0: michael@0: ScrollbarStyles styles = GetScrollbarStyles(); michael@0: michael@0: // Look at our style do we always have vertical or horizontal scrollbars? michael@0: if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) michael@0: mHelper.mHasHorizontalScrollbar = true; michael@0: if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) michael@0: mHelper.mHasVerticalScrollbar = true; michael@0: michael@0: if (mHelper.mHasHorizontalScrollbar) michael@0: AddHorizontalScrollbar(aState, scrollbarBottom); michael@0: michael@0: if (mHelper.mHasVerticalScrollbar) michael@0: AddVerticalScrollbar(aState, scrollbarRight); michael@0: michael@0: // layout our the scroll area michael@0: LayoutScrollArea(aState, oldScrollPosition); michael@0: michael@0: // now look at the content area and see if we need scrollbars or not michael@0: bool needsLayout = false; michael@0: michael@0: // if we have 'auto' scrollbars look at the vertical case michael@0: if (styles.mVertical != NS_STYLE_OVERFLOW_SCROLL) { michael@0: // These are only good until the call to LayoutScrollArea. michael@0: nsRect scrolledRect = mHelper.GetScrolledRect(); michael@0: michael@0: // There are two cases to consider michael@0: if (scrolledRect.height <= mHelper.mScrollPort.height michael@0: || styles.mVertical != NS_STYLE_OVERFLOW_AUTO) { michael@0: if (mHelper.mHasVerticalScrollbar) { michael@0: // We left room for the vertical scrollbar, but it's not needed; michael@0: // remove it. michael@0: RemoveVerticalScrollbar(aState, scrollbarRight); michael@0: needsLayout = true; michael@0: } michael@0: } else { michael@0: if (!mHelper.mHasVerticalScrollbar) { michael@0: // We didn't leave room for the vertical scrollbar, but it turns michael@0: // out we needed it michael@0: if (AddVerticalScrollbar(aState, scrollbarRight)) michael@0: needsLayout = true; michael@0: } michael@0: } michael@0: michael@0: // ok layout at the right size michael@0: if (needsLayout) { michael@0: nsBoxLayoutState resizeState(aState); michael@0: LayoutScrollArea(resizeState, oldScrollPosition); michael@0: needsLayout = false; michael@0: } michael@0: } michael@0: michael@0: michael@0: // if scrollbars are auto look at the horizontal case michael@0: if (styles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) michael@0: { michael@0: // These are only good until the call to LayoutScrollArea. michael@0: nsRect scrolledRect = mHelper.GetScrolledRect(); michael@0: michael@0: // if the child is wider that the scroll area michael@0: // and we don't have a scrollbar add one. michael@0: if ((scrolledRect.width > mHelper.mScrollPort.width) michael@0: && styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) { michael@0: michael@0: if (!mHelper.mHasHorizontalScrollbar) { michael@0: // no scrollbar? michael@0: if (AddHorizontalScrollbar(aState, scrollbarBottom)) michael@0: needsLayout = true; michael@0: michael@0: // if we added a horizontal scrollbar and we did not have a vertical michael@0: // there is a chance that by adding the horizontal scrollbar we will michael@0: // suddenly need a vertical scrollbar. Is a special case but its michael@0: // important. michael@0: //if (!mHasVerticalScrollbar && scrolledRect.height > scrollAreaRect.height - sbSize.height) michael@0: // printf("****Gfx Scrollbar Special case hit!!*****\n"); michael@0: michael@0: } michael@0: } else { michael@0: // if the area is smaller or equal to and we have a scrollbar then michael@0: // remove it. michael@0: if (mHelper.mHasHorizontalScrollbar) { michael@0: RemoveHorizontalScrollbar(aState, scrollbarBottom); michael@0: needsLayout = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // we only need to set the rect. The inner child stays the same size. michael@0: if (needsLayout) { michael@0: nsBoxLayoutState resizeState(aState); michael@0: LayoutScrollArea(resizeState, oldScrollPosition); michael@0: needsLayout = false; michael@0: } michael@0: michael@0: // get the preferred size of the scrollbars michael@0: nsSize hMinSize(0, 0); michael@0: if (mHelper.mHScrollbarBox && mHelper.mHasHorizontalScrollbar) { michael@0: GetScrollbarMetrics(aState, mHelper.mHScrollbarBox, &hMinSize, nullptr, false); michael@0: } michael@0: nsSize vMinSize(0, 0); michael@0: if (mHelper.mVScrollbarBox && mHelper.mHasVerticalScrollbar) { michael@0: GetScrollbarMetrics(aState, mHelper.mVScrollbarBox, &vMinSize, nullptr, true); michael@0: } michael@0: michael@0: // Disable scrollbars that are too small michael@0: // Disable horizontal scrollbar first. If we have to disable only one michael@0: // scrollbar, we'd rather keep the vertical scrollbar. michael@0: // Note that we always give horizontal scrollbars their preferred height, michael@0: // never their min-height. So check that there's room for the preferred height. michael@0: if (mHelper.mHasHorizontalScrollbar && michael@0: (hMinSize.width > clientRect.width - vMinSize.width michael@0: || hMinSize.height > clientRect.height)) { michael@0: RemoveHorizontalScrollbar(aState, scrollbarBottom); michael@0: needsLayout = true; michael@0: } michael@0: // Now disable vertical scrollbar if necessary michael@0: if (mHelper.mHasVerticalScrollbar && michael@0: (vMinSize.height > clientRect.height - hMinSize.height michael@0: || vMinSize.width > clientRect.width)) { michael@0: RemoveVerticalScrollbar(aState, scrollbarRight); michael@0: needsLayout = true; michael@0: } michael@0: michael@0: // we only need to set the rect. The inner child stays the same size. michael@0: if (needsLayout) { michael@0: nsBoxLayoutState resizeState(aState); michael@0: LayoutScrollArea(resizeState, oldScrollPosition); michael@0: } michael@0: michael@0: if (!mHelper.mSupppressScrollbarUpdate) { michael@0: mHelper.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds); michael@0: } michael@0: if (!mHelper.mPostedReflowCallback) { michael@0: // Make sure we'll try scrolling to restored position michael@0: PresContext()->PresShell()->PostReflowCallback(&mHelper); michael@0: mHelper.mPostedReflowCallback = true; michael@0: } michael@0: if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) { michael@0: mHelper.mHadNonInitialReflow = true; michael@0: } michael@0: michael@0: mHelper.UpdateSticky(); michael@0: michael@0: // Set up overflow areas for block frames for the benefit of michael@0: // text-overflow. michael@0: nsIFrame* f = mHelper.mScrolledFrame->GetContentInsertionFrame(); michael@0: if (nsLayoutUtils::GetAsBlock(f)) { michael@0: nsRect origRect = f->GetRect(); michael@0: nsRect clippedRect = origRect; michael@0: clippedRect.MoveBy(mHelper.mScrollPort.TopLeft()); michael@0: clippedRect.IntersectRect(clippedRect, mHelper.mScrollPort); michael@0: nsOverflowAreas overflow = f->GetOverflowAreas(); michael@0: f->FinishAndStoreOverflow(overflow, clippedRect.Size()); michael@0: clippedRect.MoveTo(origRect.TopLeft()); michael@0: f->SetRect(clippedRect); michael@0: } michael@0: michael@0: mHelper.PostOverflowEvent(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::FinishReflowForScrollbar(nsIContent* aContent, michael@0: nscoord aMinXY, nscoord aMaxXY, michael@0: nscoord aCurPosXY, michael@0: nscoord aPageIncrement, michael@0: nscoord aIncrement) michael@0: { michael@0: // Scrollbars assume zero is the minimum position, so translate for them. michael@0: SetCoordAttribute(aContent, nsGkAtoms::curpos, aCurPosXY - aMinXY); michael@0: SetScrollbarEnabled(aContent, aMaxXY - aMinXY); michael@0: SetCoordAttribute(aContent, nsGkAtoms::maxpos, aMaxXY - aMinXY); michael@0: SetCoordAttribute(aContent, nsGkAtoms::pageincrement, aPageIncrement); michael@0: SetCoordAttribute(aContent, nsGkAtoms::increment, aIncrement); michael@0: } michael@0: michael@0: bool michael@0: ScrollFrameHelper::ReflowFinished() michael@0: { michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: mPostedReflowCallback = false; michael@0: michael@0: ScrollToRestoredPosition(); michael@0: michael@0: // Clamp current scroll position to new bounds. Normally this won't michael@0: // do anything. michael@0: nsPoint currentScrollPos = GetScrollPosition(); michael@0: ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0))); michael@0: if (!mAsyncScroll) { michael@0: // We need to have mDestination track the current scroll position, michael@0: // in case it falls outside the new reflow area. mDestination is used michael@0: // by ScrollBy as its starting position. michael@0: mDestination = GetScrollPosition(); michael@0: } michael@0: michael@0: if (NS_SUBTREE_DIRTY(mOuter) || !mUpdateScrollbarAttributes) michael@0: return false; michael@0: michael@0: mUpdateScrollbarAttributes = false; michael@0: michael@0: // Update scrollbar attributes. michael@0: nsPresContext* presContext = mOuter->PresContext(); michael@0: michael@0: if (mMayHaveDirtyFixedChildren) { michael@0: mMayHaveDirtyFixedChildren = false; michael@0: nsIFrame* parentFrame = mOuter->GetParent(); michael@0: for (nsIFrame* fixedChild = michael@0: parentFrame->GetFirstChild(nsIFrame::kFixedList); michael@0: fixedChild; fixedChild = fixedChild->GetNextSibling()) { michael@0: // force a reflow of the fixed child michael@0: presContext->PresShell()-> michael@0: FrameNeedsReflow(fixedChild, nsIPresShell::eResize, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: } michael@0: michael@0: nsRect scrolledContentRect = GetScrolledRect(); michael@0: nscoord minX = scrolledContentRect.x; michael@0: nscoord maxX = scrolledContentRect.XMost() - mScrollPort.width; michael@0: nscoord minY = scrolledContentRect.y; michael@0: nscoord maxY = scrolledContentRect.YMost() - mScrollPort.height; michael@0: michael@0: // Suppress handling of the curpos attribute changes we make here. michael@0: NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here"); michael@0: mFrameIsUpdatingScrollbar = true; michael@0: michael@0: nsCOMPtr vScroll = michael@0: mVScrollbarBox ? mVScrollbarBox->GetContent() : nullptr; michael@0: nsCOMPtr hScroll = michael@0: mHScrollbarBox ? mHScrollbarBox->GetContent() : nullptr; michael@0: michael@0: // Note, in some cases mOuter may get deleted while finishing reflow michael@0: // for scrollbars. XXXmats is this still true now that we have a script michael@0: // blocker in this scope? (if not, remove the weak frame checks below). michael@0: if (vScroll || hScroll) { michael@0: nsWeakFrame weakFrame(mOuter); michael@0: nsPoint scrollPos = GetScrollPosition(); michael@0: nsSize lineScrollAmount = GetLineScrollAmount(); michael@0: if (vScroll) { michael@0: const double kScrollMultiplier = michael@0: Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance", michael@0: NS_DEFAULT_VERTICAL_SCROLL_DISTANCE); michael@0: nscoord increment = lineScrollAmount.height * kScrollMultiplier; michael@0: // We normally use (scrollArea.height - increment) for height michael@0: // of page scrolling. However, it is too small when michael@0: // increment is very large. (If increment is larger than michael@0: // scrollArea.height, direction of scrolling will be opposite). michael@0: // To avoid it, we use (float(scrollArea.height) * 0.8) as michael@0: // lower bound value of height of page scrolling. (bug 383267) michael@0: // XXX shouldn't we use GetPageScrollAmount here? michael@0: nscoord pageincrement = nscoord(mScrollPort.height - increment); michael@0: nscoord pageincrementMin = nscoord(float(mScrollPort.height) * 0.8); michael@0: FinishReflowForScrollbar(vScroll, minY, maxY, scrollPos.y, michael@0: std::max(pageincrement, pageincrementMin), michael@0: increment); michael@0: } michael@0: if (hScroll) { michael@0: const double kScrollMultiplier = michael@0: Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance", michael@0: NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE); michael@0: nscoord increment = lineScrollAmount.width * kScrollMultiplier; michael@0: FinishReflowForScrollbar(hScroll, minX, maxX, scrollPos.x, michael@0: nscoord(float(mScrollPort.width) * 0.8), michael@0: increment); michael@0: } michael@0: NS_ENSURE_TRUE(weakFrame.IsAlive(), false); michael@0: } michael@0: michael@0: mFrameIsUpdatingScrollbar = false; michael@0: // We used to rely on the curpos attribute changes above to scroll the michael@0: // view. However, for scrolling to the left of the viewport, we michael@0: // rescale the curpos attribute, which means that operations like michael@0: // resizing the window while it is scrolled all the way to the left michael@0: // hold the curpos attribute constant at 0 while still requiring michael@0: // scrolling. So we suppress the effect of the changes above with michael@0: // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here. michael@0: // (It actually even works some of the time without this, thanks to michael@0: // nsSliderFrame::AttributeChanged's handling of maxpos, but not when michael@0: // we hide the scrollbar on a large size change, such as michael@0: // maximization.) michael@0: if (!mHScrollbarBox && !mVScrollbarBox) michael@0: return false; michael@0: CurPosAttributeChanged(mVScrollbarBox ? mVScrollbarBox->GetContent() michael@0: : mHScrollbarBox->GetContent()); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::ReflowCallbackCanceled() michael@0: { michael@0: mPostedReflowCallback = false; michael@0: } michael@0: michael@0: bool michael@0: ScrollFrameHelper::UpdateOverflow() michael@0: { michael@0: nsIScrollableFrame* sf = do_QueryFrame(mOuter); michael@0: ScrollbarStyles ss = sf->GetScrollbarStyles(); michael@0: michael@0: if (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN || michael@0: ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN || michael@0: GetScrollPosition() != nsPoint()) { michael@0: // If there are scrollbars, or we're not at the beginning of the pane, michael@0: // the scroll position may change. In this case, mark the frame as michael@0: // needing reflow. Don't use NS_FRAME_IS_DIRTY as dirty as that means michael@0: // we have to reflow the frame and all its descendants, and we don't michael@0: // have to do that here. Only this frame needs to be reflowed. michael@0: mOuter->PresContext()->PresShell()->FrameNeedsReflow( michael@0: mOuter, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: // Ensure that next time nsHTMLScrollFrame::Reflow runs, we don't skip michael@0: // updating the scrollbars. (Because the overflow area of the scrolled michael@0: // frame has probably just been updated, Reflow won't see it change.) michael@0: mSkippedScrollbarLayout = true; michael@0: return false; // reflowing will update overflow michael@0: } michael@0: PostOverflowEvent(); michael@0: return mOuter->nsContainerFrame::UpdateOverflow(); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::UpdateSticky() michael@0: { michael@0: StickyScrollContainer* ssc = StickyScrollContainer:: michael@0: GetStickyScrollContainerForScrollFrame(mOuter); michael@0: if (ssc) { michael@0: nsIScrollableFrame* scrollFrame = do_QueryFrame(mOuter); michael@0: ssc->UpdatePositions(scrollFrame->GetScrollPosition(), mOuter); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::AdjustScrollbarRectForResizer( michael@0: nsIFrame* aFrame, nsPresContext* aPresContext, michael@0: nsRect& aRect, bool aHasResizer, bool aVertical) michael@0: { michael@0: if ((aVertical ? aRect.width : aRect.height) == 0) michael@0: return; michael@0: michael@0: // if a content resizer is present, use its size. Otherwise, check if the michael@0: // widget has a resizer. michael@0: nsRect resizerRect; michael@0: if (aHasResizer) { michael@0: resizerRect = mResizerBox->GetRect(); michael@0: } michael@0: else { michael@0: nsPoint offset; michael@0: nsIWidget* widget = aFrame->GetNearestWidget(offset); michael@0: nsIntRect widgetRect; michael@0: if (!widget || !widget->ShowsResizeIndicator(&widgetRect)) michael@0: return; michael@0: michael@0: resizerRect = nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x, michael@0: aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y, michael@0: aPresContext->DevPixelsToAppUnits(widgetRect.width), michael@0: aPresContext->DevPixelsToAppUnits(widgetRect.height)); michael@0: } michael@0: michael@0: if (!resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1))) michael@0: return; michael@0: michael@0: if (aVertical) michael@0: aRect.height = std::max(0, resizerRect.y - aRect.y); michael@0: else michael@0: aRect.width = std::max(0, resizerRect.x - aRect.x); michael@0: } michael@0: michael@0: static void michael@0: AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect) michael@0: { michael@0: if (aVRect.IsEmpty() || aHRect.IsEmpty()) michael@0: return; michael@0: michael@0: const nsRect oldVRect = aVRect; michael@0: const nsRect oldHRect = aHRect; michael@0: if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) { michael@0: aHRect.width = std::max(0, oldVRect.x - oldHRect.x); michael@0: } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) { michael@0: nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x); michael@0: aHRect.x += overlap; michael@0: aHRect.width -= overlap; michael@0: } michael@0: if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) { michael@0: aVRect.height = std::max(0, oldHRect.y - oldVRect.y); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::LayoutScrollbars(nsBoxLayoutState& aState, michael@0: const nsRect& aContentArea, michael@0: const nsRect& aOldScrollArea) michael@0: { michael@0: NS_ASSERTION(!mSupppressScrollbarUpdate, michael@0: "This should have been suppressed"); michael@0: michael@0: bool hasResizer = HasResizer(); michael@0: bool scrollbarOnLeft = !IsScrollbarOnRight(); michael@0: michael@0: // place the scrollcorner michael@0: if (mScrollCornerBox || mResizerBox) { michael@0: NS_PRECONDITION(!mScrollCornerBox || mScrollCornerBox->IsBoxFrame(), "Must be a box frame!"); michael@0: michael@0: nsRect r(0, 0, 0, 0); michael@0: if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) { michael@0: // scrollbar (if any) on left michael@0: r.x = aContentArea.x; michael@0: r.width = mScrollPort.x - aContentArea.x; michael@0: NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect"); michael@0: } else { michael@0: // scrollbar (if any) on right michael@0: r.width = aContentArea.XMost() - mScrollPort.XMost(); michael@0: r.x = aContentArea.XMost() - r.width; michael@0: NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect"); michael@0: } michael@0: if (aContentArea.y != mScrollPort.y) { michael@0: NS_ERROR("top scrollbars not supported"); michael@0: } else { michael@0: // scrollbar (if any) on bottom michael@0: r.height = aContentArea.YMost() - mScrollPort.YMost(); michael@0: r.y = aContentArea.YMost() - r.height; michael@0: NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect"); michael@0: } michael@0: michael@0: if (mScrollCornerBox) { michael@0: nsBoxFrame::LayoutChildAt(aState, mScrollCornerBox, r); michael@0: } michael@0: michael@0: if (hasResizer) { michael@0: // if a resizer is present, get its size. Assume a default size of 15 pixels. michael@0: nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15); michael@0: nsSize resizerMinSize = mResizerBox->GetMinSize(aState); michael@0: michael@0: nscoord vScrollbarWidth = mVScrollbarBox ? michael@0: mVScrollbarBox->GetPrefSize(aState).width : defaultSize; michael@0: r.width = std::max(std::max(r.width, vScrollbarWidth), resizerMinSize.width); michael@0: if (aContentArea.x == mScrollPort.x && !scrollbarOnLeft) { michael@0: r.x = aContentArea.XMost() - r.width; michael@0: } michael@0: michael@0: nscoord hScrollbarHeight = mHScrollbarBox ? michael@0: mHScrollbarBox->GetPrefSize(aState).height : defaultSize; michael@0: r.height = std::max(std::max(r.height, hScrollbarHeight), resizerMinSize.height); michael@0: if (aContentArea.y == mScrollPort.y) { michael@0: r.y = aContentArea.YMost() - r.height; michael@0: } michael@0: michael@0: nsBoxFrame::LayoutChildAt(aState, mResizerBox, r); michael@0: } michael@0: else if (mResizerBox) { michael@0: // otherwise lay out the resizer with an empty rectangle michael@0: nsBoxFrame::LayoutChildAt(aState, mResizerBox, nsRect()); michael@0: } michael@0: } michael@0: michael@0: nsPresContext* presContext = mScrolledFrame->PresContext(); michael@0: nsRect vRect; michael@0: if (mVScrollbarBox) { michael@0: NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!"); michael@0: vRect = mScrollPort; michael@0: vRect.width = aContentArea.width - mScrollPort.width; michael@0: vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost(); michael@0: if (mHasVerticalScrollbar) { michael@0: nsMargin margin; michael@0: mVScrollbarBox->GetMargin(margin); michael@0: vRect.Deflate(margin); michael@0: } michael@0: AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, true); michael@0: } michael@0: michael@0: nsRect hRect; michael@0: if (mHScrollbarBox) { michael@0: NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!"); michael@0: hRect = mScrollPort; michael@0: hRect.height = aContentArea.height - mScrollPort.height; michael@0: hRect.y = true ? mScrollPort.YMost() : aContentArea.y; michael@0: if (mHasHorizontalScrollbar) { michael@0: nsMargin margin; michael@0: mHScrollbarBox->GetMargin(margin); michael@0: hRect.Deflate(margin); michael@0: } michael@0: AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false); michael@0: } michael@0: michael@0: if (!LookAndFeel::GetInt(LookAndFeel::eIntID_AllowOverlayScrollbarsOverlap)) { michael@0: AdjustOverlappingScrollbars(vRect, hRect); michael@0: } michael@0: if (mVScrollbarBox) { michael@0: nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect); michael@0: } michael@0: if (mHScrollbarBox) { michael@0: nsBoxFrame::LayoutChildAt(aState, mHScrollbarBox, hRect); michael@0: } michael@0: michael@0: // may need to update fixed position children of the viewport, michael@0: // if the client area changed size because of an incremental michael@0: // reflow of a descendant. (If the outer frame is dirty, the fixed michael@0: // children will be re-laid out anyway) michael@0: if (aOldScrollArea.Size() != mScrollPort.Size() && michael@0: !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) && michael@0: mIsRoot) { michael@0: mMayHaveDirtyFixedChildren = true; michael@0: } michael@0: michael@0: // post reflow callback to modify scrollbar attributes michael@0: mUpdateScrollbarAttributes = true; michael@0: if (!mPostedReflowCallback) { michael@0: aState.PresContext()->PresShell()->PostReflowCallback(this); michael@0: mPostedReflowCallback = true; michael@0: } michael@0: } michael@0: michael@0: #if DEBUG michael@0: static bool ShellIsAlive(nsWeakPtr& aWeakPtr) michael@0: { michael@0: nsCOMPtr shell(do_QueryReferent(aWeakPtr)); michael@0: return !!shell; michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: ScrollFrameHelper::SetScrollbarEnabled(nsIContent* aContent, nscoord aMaxPos) michael@0: { michael@0: DebugOnly weakShell( michael@0: do_GetWeakReference(mOuter->PresContext()->PresShell())); michael@0: if (aMaxPos) { michael@0: aContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); michael@0: } else { michael@0: aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, michael@0: NS_LITERAL_STRING("true"), true); michael@0: } michael@0: MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling"); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom, michael@0: nscoord aSize) michael@0: { michael@0: DebugOnly weakShell( michael@0: do_GetWeakReference(mOuter->PresContext()->PresShell())); michael@0: // convert to pixels michael@0: aSize = nsPresContext::AppUnitsToIntCSSPixels(aSize); michael@0: michael@0: // only set the attribute if it changed. michael@0: michael@0: nsAutoString newValue; michael@0: newValue.AppendInt(aSize); michael@0: michael@0: if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters)) michael@0: return; michael@0: michael@0: nsWeakFrame weakFrame(mOuter); michael@0: nsCOMPtr kungFuDeathGrip = aContent; michael@0: aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, true); michael@0: MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling"); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: michael@0: if (mScrollbarActivity) { michael@0: nsRefPtr scrollbarActivity(mScrollbarActivity); michael@0: scrollbarActivity->ActivityOccurred(); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: ReduceRadii(nscoord aXBorder, nscoord aYBorder, michael@0: nscoord& aXRadius, nscoord& aYRadius) michael@0: { michael@0: // In order to ensure that the inside edge of the border has no michael@0: // curvature, we need at least one of its radii to be zero. michael@0: if (aXRadius <= aXBorder || aYRadius <= aYBorder) michael@0: return; michael@0: michael@0: // For any corner where we reduce the radii, preserve the corner's shape. michael@0: double ratio = std::max(double(aXBorder) / aXRadius, michael@0: double(aYBorder) / aYRadius); michael@0: aXRadius *= ratio; michael@0: aYRadius *= ratio; michael@0: } michael@0: michael@0: /** michael@0: * Implement an override for nsIFrame::GetBorderRadii to ensure that michael@0: * the clipping region for the border radius does not clip the scrollbars. michael@0: * michael@0: * In other words, we require that the border radius be reduced until the michael@0: * inner border radius at the inner edge of the border is 0 wherever we michael@0: * have scrollbars. michael@0: */ michael@0: bool michael@0: ScrollFrameHelper::GetBorderRadii(nscoord aRadii[8]) const michael@0: { michael@0: if (!mOuter->nsContainerFrame::GetBorderRadii(aRadii)) michael@0: return false; michael@0: michael@0: // Since we can use GetActualScrollbarSizes (rather than michael@0: // GetDesiredScrollbarSizes) since this doesn't affect reflow, we michael@0: // probably should. michael@0: nsMargin sb = GetActualScrollbarSizes(); michael@0: nsMargin border = mOuter->GetUsedBorder(); michael@0: michael@0: if (sb.left > 0 || sb.top > 0) { michael@0: ReduceRadii(border.left, border.top, michael@0: aRadii[NS_CORNER_TOP_LEFT_X], michael@0: aRadii[NS_CORNER_TOP_LEFT_Y]); michael@0: } michael@0: michael@0: if (sb.top > 0 || sb.right > 0) { michael@0: ReduceRadii(border.right, border.top, michael@0: aRadii[NS_CORNER_TOP_RIGHT_X], michael@0: aRadii[NS_CORNER_TOP_RIGHT_Y]); michael@0: } michael@0: michael@0: if (sb.right > 0 || sb.bottom > 0) { michael@0: ReduceRadii(border.right, border.bottom, michael@0: aRadii[NS_CORNER_BOTTOM_RIGHT_X], michael@0: aRadii[NS_CORNER_BOTTOM_RIGHT_Y]); michael@0: } michael@0: michael@0: if (sb.bottom > 0 || sb.left > 0) { michael@0: ReduceRadii(border.left, border.bottom, michael@0: aRadii[NS_CORNER_BOTTOM_LEFT_X], michael@0: aRadii[NS_CORNER_BOTTOM_LEFT_Y]); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsRect michael@0: ScrollFrameHelper::GetScrolledRect() const michael@0: { michael@0: nsRect result = michael@0: GetScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(), michael@0: mScrollPort.Size()); michael@0: michael@0: if (result.width < mScrollPort.width) { michael@0: NS_WARNING("Scrolled rect smaller than scrollport?"); michael@0: } michael@0: if (result.height < mScrollPort.height) { michael@0: NS_WARNING("Scrolled rect smaller than scrollport?"); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: nsRect michael@0: ScrollFrameHelper::GetScrolledRectInternal(const nsRect& aScrolledFrameOverflowArea, michael@0: const nsSize& aScrollPortSize) const michael@0: { michael@0: return nsLayoutUtils::GetScrolledRect(mScrolledFrame, michael@0: aScrolledFrameOverflowArea, aScrollPortSize, michael@0: IsLTR() ? NS_STYLE_DIRECTION_LTR : NS_STYLE_DIRECTION_RTL); michael@0: } michael@0: michael@0: nsMargin michael@0: ScrollFrameHelper::GetActualScrollbarSizes() const michael@0: { michael@0: nsRect r = mOuter->GetPaddingRect() - mOuter->GetPosition(); michael@0: michael@0: return nsMargin(mScrollPort.y - r.y, michael@0: r.XMost() - mScrollPort.XMost(), michael@0: r.YMost() - mScrollPort.YMost(), michael@0: mScrollPort.x - r.x); michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::SetScrollbarVisibility(nsIFrame* aScrollbar, bool aVisible) michael@0: { michael@0: nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar); michael@0: if (scrollbar) { michael@0: // See if we have a mediator. michael@0: nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator(); michael@0: if (mediator) { michael@0: // Inform the mediator of the visibility change. michael@0: mediator->VisibilityChanged(aVisible); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nscoord michael@0: ScrollFrameHelper::GetCoordAttribute(nsIFrame* aBox, nsIAtom* aAtom, michael@0: nscoord aDefaultValue, michael@0: nscoord* aRangeStart, michael@0: nscoord* aRangeLength) michael@0: { michael@0: if (aBox) { michael@0: nsIContent* content = aBox->GetContent(); michael@0: michael@0: nsAutoString value; michael@0: content->GetAttr(kNameSpaceID_None, aAtom, value); michael@0: if (!value.IsEmpty()) michael@0: { michael@0: nsresult error; michael@0: // convert it to appunits michael@0: nscoord result = nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); michael@0: nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f); michael@0: // Any nscoord value that would round to the attribute value when converted michael@0: // to CSS pixels is allowed. michael@0: *aRangeStart = result - halfPixel; michael@0: *aRangeLength = halfPixel*2 - 1; michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: // Only this exact default value is allowed. michael@0: *aRangeStart = aDefaultValue; michael@0: *aRangeLength = 0; michael@0: return aDefaultValue; michael@0: } michael@0: michael@0: nsPresState* michael@0: ScrollFrameHelper::SaveState() const michael@0: { michael@0: nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame()); michael@0: if (mediator) { michael@0: // child handles its own scroll state, so don't bother saving state here michael@0: return nullptr; michael@0: } michael@0: michael@0: // Don't store a scroll state if we never have been scrolled or restored michael@0: // a previous scroll state. michael@0: if (!mHasBeenScrolled && !mDidHistoryRestore) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsPresState* state = new nsPresState(); michael@0: // Save mRestorePos instead of our actual current scroll position, if it's michael@0: // valid and we haven't moved since the last update of mLastPos (same check michael@0: // that ScrollToRestoredPosition uses). This ensures if a reframe occurs michael@0: // while we're in the process of loading content to scroll to a restored michael@0: // position, we'll keep trying after the reframe. michael@0: nsPoint pt = GetLogicalScrollPosition(); michael@0: if (mRestorePos.y != -1 && pt == mLastPos) { michael@0: pt = mRestorePos; michael@0: } michael@0: state->SetScrollState(pt); michael@0: state->SetResolution(mResolution); michael@0: return state; michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::RestoreState(nsPresState* aState) michael@0: { michael@0: mRestorePos = aState->GetScrollState(); michael@0: mDidHistoryRestore = true; michael@0: mLastPos = mScrolledFrame ? GetLogicalScrollPosition() : nsPoint(0,0); michael@0: mResolution = aState->GetResolution(); michael@0: mIsResolutionSet = true; michael@0: michael@0: if (mIsRoot) { michael@0: mOuter->PresContext()->PresShell()->SetResolution(mResolution.width, mResolution.height); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::PostScrolledAreaEvent() michael@0: { michael@0: if (mScrolledAreaEvent.IsPending()) { michael@0: return; michael@0: } michael@0: mScrolledAreaEvent = new ScrolledAreaEvent(this); michael@0: nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get()); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // ScrolledArea change event dispatch michael@0: michael@0: NS_IMETHODIMP michael@0: ScrollFrameHelper::ScrolledAreaEvent::Run() michael@0: { michael@0: if (mHelper) { michael@0: mHelper->FireScrolledAreaEvent(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ScrollFrameHelper::FireScrolledAreaEvent() michael@0: { michael@0: mScrolledAreaEvent.Forget(); michael@0: michael@0: InternalScrollAreaEvent event(true, NS_SCROLLEDAREACHANGED, nullptr); michael@0: nsPresContext *prescontext = mOuter->PresContext(); michael@0: nsIContent* content = mOuter->GetContent(); michael@0: michael@0: event.mArea = mScrolledFrame->GetScrollableOverflowRectRelativeToParent(); michael@0: michael@0: nsIDocument *doc = content->GetCurrentDoc(); michael@0: if (doc) { michael@0: EventDispatcher::Dispatch(doc, prescontext, &event, nullptr); michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: nsIScrollableFrame::GetPerceivedScrollingDirections() const michael@0: { michael@0: nscoord oneDevPixel = GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel(); michael@0: uint32_t directions = GetScrollbarVisibility(); michael@0: nsRect scrollRange = GetScrollRange(); michael@0: if (scrollRange.width >= oneDevPixel) { michael@0: directions |= HORIZONTAL; michael@0: } michael@0: if (scrollRange.height >= oneDevPixel) { michael@0: directions |= VERTICAL; michael@0: } michael@0: return directions; michael@0: }