/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* rendering object to wrap rendering objects that should be scrollable */ #include "nsGfxScrollFrame.h" #include "nsIXULRuntime.h" #include "ActiveLayerTracker.h" #include "base/compiler_specific.h" #include "DisplayItemClip.h" #include "Layers.h" #include "nsCOMPtr.h" #include "nsIContentViewer.h" #include "nsPresContext.h" #include "nsView.h" #include "nsViewportInfo.h" #include "nsContainerFrame.h" #include "nsGkAtoms.h" #include "nsNameSpaceManager.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/gfx/gfxVars.h" #include "nsFontMetrics.h" #include "nsBoxLayoutState.h" #include "mozilla/dom/NodeInfo.h" #include "nsScrollbarFrame.h" #include "nsINode.h" #include "nsIScrollbarMediator.h" #include "nsITextControlFrame.h" #include "nsILayoutHistoryState.h" #include "nsNodeInfoManager.h" #include "nsContentCreatorFunctions.h" #include "nsStyleTransformMatrix.h" #include "mozilla/PresState.h" #include "nsContentUtils.h" #include "nsHTMLDocument.h" #include "nsLayoutUtils.h" #include "nsBidiPresUtils.h" #include "nsBidiUtils.h" #include "nsDocShell.h" #include "mozilla/ContentEvents.h" #include "mozilla/DisplayPortUtils.h" #include "mozilla/EventDispatcher.h" #include "mozilla/Preferences.h" #include "mozilla/PresShell.h" #include "mozilla/ScopeExit.h" #include "mozilla/ScrollbarPreferences.h" #include "mozilla/SVGOuterSVGFrame.h" #include "mozilla/ViewportUtils.h" #include "mozilla/LookAndFeel.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/HTMLMarqueeElement.h" #include #include "mozilla/MathAlgorithms.h" #include "mozilla/Telemetry.h" #include "FrameLayerBuilder.h" #include "nsSubDocumentFrame.h" #include "mozilla/Attributes.h" #include "ScrollbarActivity.h" #include "nsRefreshDriver.h" #include "nsStyleConsts.h" #include "nsIScrollPositionListener.h" #include "StickyScrollContainer.h" #include "nsIFrameInlines.h" #include "gfxPlatform.h" #include "mozilla/StaticPrefs_apz.h" #include "mozilla/StaticPrefs_general.h" #include "mozilla/StaticPrefs_layers.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/StaticPrefs_mousewheel.h" #include "mozilla/ToString.h" #include "ScrollAnimationPhysics.h" #include "ScrollAnimationBezierPhysics.h" #include "ScrollAnimationMSDPhysics.h" #include "ScrollSnap.h" #include "UnitTransforms.h" #include "nsSliderFrame.h" #include "ViewportFrame.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/layers/APZCCallbackHelper.h" #include "mozilla/layers/APZPublicUtils.h" #include "mozilla/layers/AxisPhysicsModel.h" #include "mozilla/layers/AxisPhysicsMSDModel.h" #include "mozilla/layers/LayerTransactionChild.h" #include "mozilla/layers/ScrollLinkedEffectDetector.h" #include "mozilla/Unused.h" #include "MobileViewportManager.h" #include "VisualViewport.h" #include #include // for std::abs(int/long) #include // for std::abs(float/double) #include // for std::tie static mozilla::LazyLogModule sApzPaintSkipLog("apz.paintskip"); #define PAINT_SKIP_LOG(...) \ MOZ_LOG(sApzPaintSkipLog, LogLevel::Debug, (__VA_ARGS__)) static mozilla::LazyLogModule sScrollRestoreLog("scrollrestore"); #define SCROLLRESTORE_LOG(...) \ MOZ_LOG(sScrollRestoreLog, LogLevel::Debug, (__VA_ARGS__)) static mozilla::LazyLogModule sRootScrollbarsLog("rootscrollbars"); #define ROOT_SCROLLBAR_LOG(...) \ if (mHelper.mIsRoot) { \ MOZ_LOG(sRootScrollbarsLog, LogLevel::Debug, (__VA_ARGS__)); \ } static mozilla::LazyLogModule sDisplayportLog("apz.displayport"); using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::layers; using namespace mozilla::layout; using nsStyleTransformMatrix::TransformReferenceBox; static ScrollDirections GetOverflowChange(const nsRect& aCurScrolledRect, const nsRect& aPrevScrolledRect) { ScrollDirections result; if (aPrevScrolledRect.x != aCurScrolledRect.x || aPrevScrolledRect.width != aCurScrolledRect.width) { result += ScrollDirection::eHorizontal; } if (aPrevScrolledRect.y != aCurScrolledRect.y || aPrevScrolledRect.height != aCurScrolledRect.height) { result += ScrollDirection::eVertical; } return result; } /** * This class handles the dispatching of scroll events to content. * * Scroll events are posted to the refresh driver via * nsRefreshDriver::PostScrollEvent(), and they are fired during a refresh * driver tick, after running requestAnimationFrame callbacks but before * the style flush. This allows rAF callbacks to perform scrolling and have * that scrolling be reflected on the same refresh driver tick, while at * the same time allowing scroll event listeners to make style changes and * have those style changes be reflected on the same refresh driver tick. * * ScrollEvents cannot be refresh observers, because none of the existing * categories of refresh observers (FlushType::Style, FlushType::Layout, * and FlushType::Display) are run at the desired time in a refresh driver * tick. They behave similarly to refresh observers in that their presence * causes the refresh driver to tick. * * ScrollEvents are one-shot runnables; the refresh driver drops them after * running them. */ class ScrollFrameHelper::ScrollEvent : public Runnable { public: NS_DECL_NSIRUNNABLE explicit ScrollEvent(ScrollFrameHelper* aHelper, bool aDelayed); void Revoke() { mHelper = nullptr; } private: ScrollFrameHelper* mHelper; }; class ScrollFrameHelper::ScrollEndEvent : public Runnable { public: NS_DECL_NSIRUNNABLE explicit ScrollEndEvent(ScrollFrameHelper* aHelper); void Revoke() { mHelper = nullptr; } private: ScrollFrameHelper* mHelper; }; class ScrollFrameHelper::AsyncScrollPortEvent : public Runnable { public: NS_DECL_NSIRUNNABLE explicit AsyncScrollPortEvent(ScrollFrameHelper* helper) : Runnable("ScrollFrameHelper::AsyncScrollPortEvent"), mHelper(helper) {} void Revoke() { mHelper = nullptr; } private: ScrollFrameHelper* mHelper; }; class ScrollFrameHelper::ScrolledAreaEvent : public Runnable { public: NS_DECL_NSIRUNNABLE explicit ScrolledAreaEvent(ScrollFrameHelper* helper) : Runnable("ScrollFrameHelper::ScrolledAreaEvent"), mHelper(helper) {} void Revoke() { mHelper = nullptr; } private: ScrollFrameHelper* mHelper; }; //---------------------------------------------------------------------- //----------nsHTMLScrollFrame------------------------------------------- nsHTMLScrollFrame* NS_NewHTMLScrollFrame(PresShell* aPresShell, ComputedStyle* aStyle, bool aIsRoot) { return new (aPresShell) nsHTMLScrollFrame(aStyle, aPresShell->GetPresContext(), aIsRoot); } NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame) nsHTMLScrollFrame::nsHTMLScrollFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, nsIFrame::ClassID aID, bool aIsRoot) : nsContainerFrame(aStyle, aPresContext, aID), mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot) {} void nsHTMLScrollFrame::ScrollbarActivityStarted() const { if (mHelper.mScrollbarActivity) { mHelper.mScrollbarActivity->ActivityStarted(); } } void nsHTMLScrollFrame::ScrollbarActivityStopped() const { if (mHelper.mScrollbarActivity) { mHelper.mScrollbarActivity->ActivityStopped(); } } nsresult nsHTMLScrollFrame::CreateAnonymousContent( nsTArray& aElements) { return mHelper.CreateAnonymousContent(aElements); } void nsHTMLScrollFrame::AppendAnonymousContentTo( nsTArray& aElements, uint32_t aFilter) { mHelper.AppendAnonymousContentTo(aElements, aFilter); } void nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) { DestroyAbsoluteFrames(aDestructRoot, aPostDestroyData); mHelper.Destroy(aPostDestroyData); nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData); } void nsHTMLScrollFrame::SetInitialChildList(ChildListID aListID, nsFrameList& aChildList) { nsContainerFrame::SetInitialChildList(aListID, aChildList); mHelper.ReloadChildFrames(); } void nsHTMLScrollFrame::AppendFrames(ChildListID aListID, nsFrameList& aFrameList) { NS_ASSERTION(aListID == kPrincipalList, "Only main list supported"); mFrames.AppendFrames(nullptr, aFrameList); mHelper.ReloadChildFrames(); } void nsHTMLScrollFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, const nsLineList::iterator* aPrevFrameLine, nsFrameList& aFrameList) { NS_ASSERTION(aListID == kPrincipalList, "Only main list supported"); NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, "inserting after sibling frame with different parent"); mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); mHelper.ReloadChildFrames(); } void nsHTMLScrollFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) { NS_ASSERTION(aListID == kPrincipalList, "Only main list supported"); mFrames.DestroyFrame(aOldFrame); mHelper.ReloadChildFrames(); } /** HTML scrolling implementation All other things being equal, we prefer layouts with fewer scrollbars showing. */ namespace mozilla { enum class ShowScrollbar : uint8_t { Auto, Always, // Never is a misnomer. We can still get a scrollbar if we need to scroll the // visual viewport inside the layout viewport. Thus this enum is best thought // of as value used by layout, which does not know about the visual viewport. // The visual viewport does not affect any layout sizes, so this is sound. Never, }; static ShowScrollbar ShouldShowScrollbar(StyleOverflow aOverflow) { switch (aOverflow) { case StyleOverflow::Scroll: return ShowScrollbar::Always; case StyleOverflow::Hidden: return ShowScrollbar::Never; default: case StyleOverflow::Auto: return ShowScrollbar::Auto; } } struct MOZ_STACK_CLASS ScrollReflowInput { const ReflowInput& mReflowInput; nsBoxLayoutState mBoxState; ShowScrollbar mHScrollbar; // If the horizontal scrollbar is allowed (even if mHScrollbar == // ShowScrollbar::Never) provided that it is for scrolling the visual viewport // inside the layout viewport only. bool mHScrollbarAllowedForScrollingVVInsideLV = true; ShowScrollbar mVScrollbar; // If the vertical scrollbar is allowed (even if mVScrollbar == // ShowScrollbar::Never) provided that it is for scrolling the visual viewport // inside the layout viewport only. bool mVScrollbarAllowedForScrollingVVInsideLV = true; nsMargin mComputedBorder; // === Filled in by ReflowScrolledFrame === OverflowAreas mContentsOverflowAreas; MOZ_INIT_OUTSIDE_CTOR bool mReflowedContentsWithHScrollbar; MOZ_INIT_OUTSIDE_CTOR bool mReflowedContentsWithVScrollbar; // === Filled in when TryLayout succeeds === // The size of the inside-border area nsSize mInsideBorderSize; // Whether we decided to show the horizontal scrollbar MOZ_INIT_OUTSIDE_CTOR bool mShowHScrollbar; // Whether we decided to show the vertical scrollbar MOZ_INIT_OUTSIDE_CTOR bool mShowVScrollbar; // If mShow(H|V)Scrollbar is true then // mOnlyNeed(V|H)ScrollbarToScrollVVInsideLV indicates if the only reason we // need that scrollbar is to scroll the visual viewport inside the layout // viewport. These scrollbars are special in that even if they are layout // scrollbars they do not take up any layout space. bool mOnlyNeedHScrollbarToScrollVVInsideLV = false; bool mOnlyNeedVScrollbarToScrollVVInsideLV = false; ScrollReflowInput(nsIScrollableFrame* aFrame, const ReflowInput& aReflowInput) : mReflowInput(aReflowInput), // mBoxState is just used for scrollbars so we don't need to // worry about the reflow depth here mBoxState(aReflowInput.mFrame->PresContext(), aReflowInput.mRenderingContext) { ScrollStyles styles = aFrame->GetScrollStyles(); mHScrollbar = ShouldShowScrollbar(styles.mHorizontal); mVScrollbar = ShouldShowScrollbar(styles.mVertical); } }; } // namespace mozilla // XXXldb Can this go away? static nsSize ComputeInsideBorderSize(ScrollReflowInput* aState, const nsSize& aDesiredInsideBorderSize) { // aDesiredInsideBorderSize is the frame size; i.e., it includes // borders and padding (but the scrolled child doesn't have // borders). The scrolled child has the same padding as us. nscoord contentWidth = aState->mReflowInput.ComputedWidth(); if (contentWidth == NS_UNCONSTRAINEDSIZE) { contentWidth = aDesiredInsideBorderSize.width - aState->mReflowInput.ComputedPhysicalPadding().LeftRight(); } nscoord contentHeight = aState->mReflowInput.ComputedHeight(); if (contentHeight == NS_UNCONSTRAINEDSIZE) { contentHeight = aDesiredInsideBorderSize.height - aState->mReflowInput.ComputedPhysicalPadding().TopBottom(); } contentWidth = aState->mReflowInput.ApplyMinMaxWidth(contentWidth); contentHeight = aState->mReflowInput.ApplyMinMaxHeight(contentHeight); return nsSize( contentWidth + aState->mReflowInput.ComputedPhysicalPadding().LeftRight(), contentHeight + aState->mReflowInput.ComputedPhysicalPadding().TopBottom()); } static void GetScrollbarMetrics(nsBoxLayoutState& aState, nsIFrame* aBox, nsSize* aMin, nsSize* aPref) { NS_ASSERTION(aState.GetRenderingContext(), "Must have rendering context in layout state for size " "computations"); if (aMin) { *aMin = aBox->GetXULMinSize(aState); nsIFrame::AddXULMargin(aBox, *aMin); if (aMin->width < 0) { aMin->width = 0; } if (aMin->height < 0) { aMin->height = 0; } } if (aPref) { *aPref = aBox->GetXULPrefSize(aState); nsIFrame::AddXULMargin(aBox, *aPref); if (aPref->width < 0) { aPref->width = 0; } if (aPref->height < 0) { aPref->height = 0; } } } /** * Assuming that we know the metrics for our wrapped frame and * whether the horizontal and/or vertical scrollbars are present, * compute the resulting layout and return true if the layout is * consistent. If the layout is consistent then we fill in the * computed fields of the ScrollReflowInput. * * The layout is consistent when both scrollbars are showing if and only * if they should be showing. A horizontal scrollbar should be showing if all * following conditions are met: * 1) the style is not HIDDEN * 2) our inside-border height is at least the scrollbar height (i.e., the * scrollbar fits vertically) * 3) the style is SCROLL, or the kid's overflow-area XMost is * greater than the scrollport width * * @param aForce if true, then we just assume the layout is consistent. */ bool nsHTMLScrollFrame::TryLayout(ScrollReflowInput* aState, ReflowOutput* aKidMetrics, bool aAssumeHScroll, bool aAssumeVScroll, bool aForce) { if ((aState->mVScrollbar == ShowScrollbar::Never && aAssumeVScroll) || (aState->mHScrollbar == ShowScrollbar::Never && aAssumeHScroll)) { NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!"); return false; } if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar || (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar && ScrolledContentDependsOnHeight(aState))) { if (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar) { nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize( mHelper.mScrolledFrame); } aKidMetrics->mOverflowAreas.Clear(); ROOT_SCROLLBAR_LOG( "TryLayout reflowing scrolled frame with scrollbars h=%d, v=%d\n", aAssumeHScroll, aAssumeVScroll); ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll, aKidMetrics); } nsSize vScrollbarMinSize(0, 0); nsSize vScrollbarPrefSize(0, 0); if (mHelper.mVScrollbarBox) { GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox, &vScrollbarMinSize, &vScrollbarPrefSize); nsScrollbarFrame* scrollbar = do_QueryFrame(mHelper.mVScrollbarBox); scrollbar->SetScrollbarMediatorContent(mContent); } nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0; nsSize hScrollbarMinSize(0, 0); nsSize hScrollbarPrefSize(0, 0); if (mHelper.mHScrollbarBox) { GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, &hScrollbarMinSize, &hScrollbarPrefSize); nsScrollbarFrame* scrollbar = do_QueryFrame(mHelper.mHScrollbarBox); scrollbar->SetScrollbarMediatorContent(mContent); } nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0; // First, compute our inside-border size and scrollport size // XXXldb Can we depend more on ComputeSize here? nsSize kidSize = aState->mReflowInput.mStyleDisplay->IsContainSize() ? nsSize(0, 0) : aKidMetrics->PhysicalSize(); nsSize desiredInsideBorderSize; desiredInsideBorderSize.width = vScrollbarDesiredWidth + kidSize.width; desiredInsideBorderSize.height = hScrollbarDesiredHeight + kidSize.height; aState->mInsideBorderSize = ComputeInsideBorderSize(aState, desiredInsideBorderSize); nsSize layoutSize = mHelper.mIsUsingMinimumScaleSize ? mHelper.mMinimumScaleSize : aState->mInsideBorderSize; const nsSize scrollPortSize = nsSize(std::max(0, layoutSize.width - vScrollbarDesiredWidth), std::max(0, layoutSize.height - hScrollbarDesiredHeight)); if (mHelper.mIsUsingMinimumScaleSize) { mHelper.mICBSize = nsSize( std::max(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth), std::max(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight)); } nsSize visualViewportSize = scrollPortSize; ROOT_SCROLLBAR_LOG("TryLayout with VV %s\n", ToString(visualViewportSize).c_str()); mozilla::PresShell* presShell = PresShell(); // Note: we check for a non-null MobileViepwortManager here, but ideally we // should be able to drop that clause as well. It's just that in some cases // with extension popups the composition size comes back as stale, because // the content viewer is only resized after the popup contents are reflowed. // That case also happens to have no APZ and no MVM, so we use that as a // way to detect the scenario. Bug 1648669 tracks removing this clause. if (mHelper.mIsRoot && presShell->GetMobileViewportManager()) { visualViewportSize = nsLayoutUtils::CalculateCompositionSizeForFrame( this, false, &layoutSize); visualViewportSize = nsSize( std::max(0, visualViewportSize.width - vScrollbarDesiredWidth), std::max(0, visualViewportSize.height - hScrollbarDesiredHeight)); float resolution = presShell->GetResolution(); visualViewportSize.width /= resolution; visualViewportSize.height /= resolution; ROOT_SCROLLBAR_LOG("TryLayout now with VV %s\n", ToString(visualViewportSize).c_str()); } nsRect overflowRect = aState->mContentsOverflowAreas.ScrollableOverflow(); // If the content height expanded by the minimum-scale will be taller than // the scrollable overflow area, we need to expand the area here to tell // properly whether we need to render the overlay vertical scrollbar. // NOTE: This expanded size should NOT be used for non-overley scrollbars // cases since putting the vertical non-overlay scrollbar will make the // content width narrow a little bit, which in turn the minimum scale value // becomes a bit bigger than before, then the vertical scrollbar is no longer // needed, which means the content width becomes the original width, then the // minimum-scale is changed to the original one, and so forth. if (mHelper.UsesOverlayScrollbars() && mHelper.mIsUsingMinimumScaleSize && mHelper.mMinimumScaleSize.height > overflowRect.YMost()) { overflowRect.height += mHelper.mMinimumScaleSize.height - overflowRect.YMost(); } nsRect scrolledRect = mHelper.GetUnsnappedScrolledRectInternal(overflowRect, scrollPortSize); ROOT_SCROLLBAR_LOG( "TryLayout scrolledRect:%s overflowRect:%s scrollportSize:%s\n", ToString(scrolledRect).c_str(), ToString(overflowRect).c_str(), ToString(scrollPortSize).c_str()); nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1); if (!aForce) { nsSize sizeToCompare = visualViewportSize; if (gfxPlatform::UseDesktopZoomingScrollbars()) { sizeToCompare = scrollPortSize; } // If the style is HIDDEN then we already know that aAssumeHScroll is false if (aState->mHScrollbar != ShowScrollbar::Never) { bool wantHScrollbar = aState->mHScrollbar == ShowScrollbar::Always || scrolledRect.XMost() >= sizeToCompare.width + oneDevPixel || scrolledRect.x <= -oneDevPixel; // TODO(emilio): This should probably check this scrollbar's minimum size // in both axes, for consistency? if (aState->mHScrollbar == ShowScrollbar::Auto && scrollPortSize.width < hScrollbarMinSize.width) { wantHScrollbar = false; } ROOT_SCROLLBAR_LOG("TryLayout wants H Scrollbar: %d =? %d\n", wantHScrollbar, aAssumeHScroll); if (wantHScrollbar != aAssumeHScroll) { return false; } } // If the style is HIDDEN then we already know that aAssumeVScroll is false if (aState->mVScrollbar != ShowScrollbar::Never) { bool wantVScrollbar = aState->mVScrollbar == ShowScrollbar::Always || scrolledRect.YMost() >= sizeToCompare.height + oneDevPixel || scrolledRect.y <= -oneDevPixel; // TODO(emilio): This should probably check this scrollbar's minimum size // in both axes, for consistency? if (aState->mVScrollbar == ShowScrollbar::Auto && scrollPortSize.height < vScrollbarMinSize.height) { wantVScrollbar = false; } ROOT_SCROLLBAR_LOG("TryLayout wants V Scrollbar: %d =? %d\n", wantVScrollbar, aAssumeVScroll); if (wantVScrollbar != aAssumeVScroll) { return false; } } } aState->mShowHScrollbar = aAssumeHScroll; aState->mShowVScrollbar = aAssumeVScroll; nsPoint scrollPortOrigin(aState->mComputedBorder.left, aState->mComputedBorder.top); if (!IsScrollbarOnRight()) { nscoord vScrollbarActualWidth = layoutSize.width - scrollPortSize.width; scrollPortOrigin.x += vScrollbarActualWidth; } mHelper.mScrollPort = nsRect(scrollPortOrigin, scrollPortSize); if (mHelper.mIsRoot && gfxPlatform::UseDesktopZoomingScrollbars()) { bool vvChanged = true; // This loop can run at most twice since we can only add a scrollbar once. // At this point we've already decided that this layout is consistent so we // will return true. Scrollbars added here never take up layout space even // if they are layout scrollbars so any changes made here will not make us // return false. while (vvChanged) { vvChanged = false; if (!aState->mShowHScrollbar && aState->mHScrollbarAllowedForScrollingVVInsideLV) { if (mHelper.mScrollPort.width >= visualViewportSize.width + oneDevPixel && visualViewportSize.width >= hScrollbarMinSize.width) { vvChanged = true; visualViewportSize.height -= hScrollbarPrefSize.height; aState->mShowHScrollbar = true; aState->mOnlyNeedHScrollbarToScrollVVInsideLV = true; ROOT_SCROLLBAR_LOG("TryLayout added H scrollbar for VV, VV now %s\n", ToString(visualViewportSize).c_str()); } } if (!aState->mShowVScrollbar && aState->mVScrollbarAllowedForScrollingVVInsideLV) { if (mHelper.mScrollPort.height >= visualViewportSize.height + oneDevPixel && visualViewportSize.height >= vScrollbarMinSize.height) { vvChanged = true; visualViewportSize.width -= vScrollbarPrefSize.width; aState->mShowVScrollbar = true; aState->mOnlyNeedVScrollbarToScrollVVInsideLV = true; ROOT_SCROLLBAR_LOG("TryLayout added V scrollbar for VV, VV now %s\n", ToString(visualViewportSize).c_str()); } } } } return true; } // XXX Height/BSize mismatch needs to be addressed here; check the caller! // Currently this will only behave as expected for horizontal writing modes. // (See bug 1175509.) bool nsHTMLScrollFrame::ScrolledContentDependsOnHeight( ScrollReflowInput* aState) { // Return true if ReflowScrolledFrame is going to do something different // based on the presence of a horizontal scrollbar. return mHelper.mScrolledFrame->HasAnyStateBits( NS_FRAME_CONTAINS_RELATIVE_BSIZE | NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE) || aState->mReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE || aState->mReflowInput.ComputedMinBSize() > 0 || aState->mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE; } void nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowInput* aState, bool aAssumeHScroll, bool aAssumeVScroll, ReflowOutput* aMetrics) { WritingMode wm = mHelper.mScrolledFrame->GetWritingMode(); // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should // be OK LogicalMargin padding = aState->mReflowInput.ComputedLogicalPadding(wm); nscoord availISize = aState->mReflowInput.ComputedISize() + padding.IStartEnd(wm); nscoord computedBSize = aState->mReflowInput.ComputedBSize(); nscoord computedMinBSize = aState->mReflowInput.ComputedMinBSize(); nscoord computedMaxBSize = aState->mReflowInput.ComputedMaxBSize(); if (!ShouldPropagateComputedBSizeToScrolledContent()) { computedBSize = NS_UNCONSTRAINEDSIZE; computedMinBSize = 0; computedMaxBSize = NS_UNCONSTRAINEDSIZE; } if (wm.IsVertical()) { if (aAssumeVScroll) { nsSize vScrollbarPrefSize; GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox, nullptr, &vScrollbarPrefSize); if (computedBSize != NS_UNCONSTRAINEDSIZE) { computedBSize = std::max(0, computedBSize - vScrollbarPrefSize.width); } computedMinBSize = std::max(0, computedMinBSize - vScrollbarPrefSize.width); if (computedMaxBSize != NS_UNCONSTRAINEDSIZE) { computedMaxBSize = std::max(0, computedMaxBSize - vScrollbarPrefSize.width); } } if (aAssumeHScroll) { nsSize hScrollbarPrefSize; GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, nullptr, &hScrollbarPrefSize); availISize = std::max(0, availISize - hScrollbarPrefSize.height); } } else { if (aAssumeHScroll) { nsSize hScrollbarPrefSize; GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, nullptr, &hScrollbarPrefSize); if (computedBSize != NS_UNCONSTRAINEDSIZE) { computedBSize = std::max(0, computedBSize - hScrollbarPrefSize.height); } computedMinBSize = std::max(0, computedMinBSize - hScrollbarPrefSize.height); if (computedMaxBSize != NS_UNCONSTRAINEDSIZE) { computedMaxBSize = std::max(0, computedMaxBSize - hScrollbarPrefSize.height); } } if (aAssumeVScroll) { nsSize vScrollbarPrefSize; GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox, nullptr, &vScrollbarPrefSize); availISize = std::max(0, availISize - vScrollbarPrefSize.width); } } nsPresContext* presContext = PresContext(); // Pass InitFlags::CallerWillInit so we can pass in the correct padding. ReflowInput kidReflowInput(presContext, aState->mReflowInput, mHelper.mScrolledFrame, LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE), Nothing(), ReflowInput::InitFlag::CallerWillInit); const WritingMode kidWM = kidReflowInput.GetWritingMode(); kidReflowInput.Init(presContext, Nothing(), Nothing(), Some(padding.ConvertTo(kidWM, wm))); kidReflowInput.mFlags.mAssumingHScrollbar = aAssumeHScroll; kidReflowInput.mFlags.mAssumingVScrollbar = aAssumeVScroll; kidReflowInput.SetComputedBSize(computedBSize); kidReflowInput.ComputedMinBSize() = computedMinBSize; kidReflowInput.ComputedMaxBSize() = computedMaxBSize; if (aState->mReflowInput.IsBResizeForWM(kidWM)) { kidReflowInput.SetBResize(true); } if (aState->mReflowInput.IsBResizeForPercentagesForWM(kidWM)) { kidReflowInput.mFlags.mIsBResizeForPercentages = true; } // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to // reflect our assumptions while we reflow the child. bool didHaveHorizontalScrollbar = mHelper.mHasHorizontalScrollbar; bool didHaveVerticalScrollbar = mHelper.mHasVerticalScrollbar; mHelper.mHasHorizontalScrollbar = aAssumeHScroll; mHelper.mHasVerticalScrollbar = aAssumeVScroll; nsReflowStatus status; // No need to pass a true container-size to ReflowChild or // FinishReflowChild, because it's only used there when positioning // the frame (i.e. if ReflowChildFlags::NoMoveFrame isn't set) const nsSize dummyContainerSize; ReflowChild(mHelper.mScrolledFrame, presContext, *aMetrics, kidReflowInput, wm, LogicalPoint(wm), dummyContainerSize, ReflowChildFlags::NoMoveFrame, status); mHelper.mHasHorizontalScrollbar = didHaveHorizontalScrollbar; mHelper.mHasVerticalScrollbar = didHaveVerticalScrollbar; // Don't resize or position the view (if any) because we're going to resize // it to the correct size anyway in PlaceScrollArea. Allowing it to // resize here would size it to the natural height of the frame, // which will usually be different from the scrollport height; // invalidating the difference will cause unnecessary repainting. FinishReflowChild( mHelper.mScrolledFrame, presContext, *aMetrics, &kidReflowInput, wm, LogicalPoint(wm), dummyContainerSize, ReflowChildFlags::NoMoveFrame | ReflowChildFlags::NoSizeView); // XXX Some frames (e.g. nsFrameFrame, nsTextFrame) don't // bother setting their mOverflowArea. This is wrong because every frame // should always set mOverflowArea. In fact nsFrameFrame doesn't // support the 'outline' property because of this. Rather than fix the // world right now, just fix up the overflow area if necessary. Note that we // don't check HasOverflowRect() because it could be set even though the // overflow area doesn't include the frame bounds. aMetrics->UnionOverflowAreasWithDesiredBounds(); auto* disp = StyleDisplay(); if (MOZ_UNLIKELY(disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox)) { // If the scrolled frame can be scrolled in the inline axis, inflate its // scrollable overflow areas with its inline-end padding to prevent its // content from being clipped at scroll container's inline-end padding // edge. // // Note: Inflating scrolled frame's overflow areas is generally wrong if the // scrolled frame's children themselves has any scrollable overflow areas. // However, we can only be here in production for