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: /* michael@0: * rendering object that is the root of the frame tree, which contains michael@0: * the document's scrollbars and contains fixed-positioned elements michael@0: */ michael@0: michael@0: #include "nsViewportFrame.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsSubDocumentFrame.h" michael@0: #include "nsAbsoluteContainingBlock.h" michael@0: #include "GeckoProfiler.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: nsIFrame* michael@0: NS_NewViewportFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) ViewportFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(ViewportFrame) michael@0: NS_QUERYFRAME_HEAD(ViewportFrame) michael@0: NS_QUERYFRAME_ENTRY(ViewportFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) michael@0: michael@0: void michael@0: ViewportFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: Super::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(this); michael@0: if (parent) { michael@0: nsFrameState state = parent->GetStateBits(); michael@0: michael@0: mState |= state & (NS_FRAME_IN_POPUP); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: ViewportFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: // See which child list to add the frames to michael@0: #ifdef DEBUG michael@0: nsFrame::VerifyDirtyBitSet(aChildList); michael@0: #endif michael@0: return nsContainerFrame::SetInitialChildList(aListID, aChildList); michael@0: } michael@0: michael@0: void michael@0: ViewportFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: PROFILER_LABEL("ViewportFrame", "BuildDisplayList"); michael@0: nsIFrame* kid = mFrames.FirstChild(); michael@0: if (!kid) michael@0: return; michael@0: michael@0: // make the kid's BorderBackground our own. This ensures that the canvas michael@0: // frame's background becomes our own background and therefore appears michael@0: // below negative z-index elements. michael@0: BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); michael@0: } michael@0: michael@0: nsresult michael@0: ViewportFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList || michael@0: aListID == GetAbsoluteListID(), "unexpected child list"); michael@0: NS_ASSERTION(aListID != GetAbsoluteListID() || michael@0: GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!"); michael@0: return nsContainerFrame::AppendFrames(aListID, aFrameList); michael@0: } michael@0: michael@0: nsresult michael@0: ViewportFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList || michael@0: aListID == GetAbsoluteListID(), "unexpected child list"); michael@0: NS_ASSERTION(aListID != GetAbsoluteListID() || michael@0: GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!"); michael@0: return nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList); michael@0: } michael@0: michael@0: nsresult michael@0: ViewportFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList || michael@0: aListID == GetAbsoluteListID(), "unexpected child list"); michael@0: return nsContainerFrame::RemoveFrame(aListID, aOldFrame); michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: ViewportFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nscoord result; michael@0: DISPLAY_MIN_WIDTH(this, result); michael@0: if (mFrames.IsEmpty()) michael@0: result = 0; michael@0: else michael@0: result = mFrames.FirstChild()->GetMinWidth(aRenderingContext); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: ViewportFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nscoord result; michael@0: DISPLAY_PREF_WIDTH(this, result); michael@0: if (mFrames.IsEmpty()) michael@0: result = 0; michael@0: else michael@0: result = mFrames.FirstChild()->GetPrefWidth(aRenderingContext); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsPoint michael@0: ViewportFrame::AdjustReflowStateForScrollbars(nsHTMLReflowState* aReflowState) const michael@0: { michael@0: // Get our prinicpal child frame and see if we're scrollable michael@0: nsIFrame* kidFrame = mFrames.FirstChild(); michael@0: nsIScrollableFrame* scrollingFrame = do_QueryFrame(kidFrame); michael@0: michael@0: if (scrollingFrame) { michael@0: nsMargin scrollbars = scrollingFrame->GetActualScrollbarSizes(); michael@0: aReflowState->SetComputedWidth(aReflowState->ComputedWidth() - michael@0: scrollbars.LeftRight()); michael@0: aReflowState->AvailableWidth() -= scrollbars.LeftRight(); michael@0: aReflowState->SetComputedHeightWithoutResettingResizeFlags( michael@0: aReflowState->ComputedHeight() - scrollbars.TopBottom()); michael@0: return nsPoint(scrollbars.left, scrollbars.top); michael@0: } michael@0: return nsPoint(0, 0); michael@0: } michael@0: michael@0: nsRect michael@0: ViewportFrame::AdjustReflowStateAsContainingBlock(nsHTMLReflowState* aReflowState) const michael@0: { michael@0: #ifdef DEBUG michael@0: nsPoint offset = michael@0: #endif michael@0: AdjustReflowStateForScrollbars(aReflowState); michael@0: michael@0: NS_ASSERTION(GetAbsoluteContainingBlock()->GetChildList().IsEmpty() || michael@0: (offset.x == 0 && offset.y == 0), michael@0: "We don't handle correct positioning of fixed frames with " michael@0: "scrollbars in odd positions"); michael@0: michael@0: // If a scroll position clamping scroll-port size has been set, layout michael@0: // fixed position elements to this size instead of the computed size. michael@0: nsRect rect(0, 0, aReflowState->ComputedWidth(), aReflowState->ComputedHeight()); michael@0: nsIPresShell* ps = PresContext()->PresShell(); michael@0: if (ps->IsScrollPositionClampingScrollPortSizeSet()) { michael@0: rect.SizeTo(ps->GetScrollPositionClampingScrollPortSize()); michael@0: } michael@0: michael@0: // Make sure content document fixed-position margins are respected. michael@0: rect.Deflate(ps->GetContentDocumentFixedPositionMargins()); michael@0: return rect; michael@0: } michael@0: michael@0: nsresult michael@0: ViewportFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("ViewportFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); michael@0: NS_FRAME_TRACE_REFLOW_IN("ViewportFrame::Reflow"); michael@0: michael@0: // Initialize OUT parameters michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: michael@0: // Because |Reflow| sets mComputedHeight on the child to michael@0: // availableHeight. michael@0: AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT); michael@0: michael@0: // Set our size up front, since some parts of reflow depend on it michael@0: // being already set. Note that the computed height may be michael@0: // unconstrained; that's ok. Consumers should watch out for that. michael@0: SetSize(nsSize(aReflowState.ComputedWidth(), aReflowState.ComputedHeight())); michael@0: michael@0: // Reflow the main content first so that the placeholders of the michael@0: // fixed-position frames will be in the right places on an initial michael@0: // reflow. michael@0: nscoord kidHeight = 0; michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (mFrames.NotEmpty()) { michael@0: // Deal with a non-incremental reflow or an incremental reflow michael@0: // targeted at our one-and-only principal child frame. michael@0: if (aReflowState.ShouldReflowAllKids() || michael@0: aReflowState.mFlags.mVResize || michael@0: NS_SUBTREE_DIRTY(mFrames.FirstChild())) { michael@0: // Reflow our one-and-only principal child frame michael@0: nsIFrame* kidFrame = mFrames.FirstChild(); michael@0: nsHTMLReflowMetrics kidDesiredSize(aReflowState); michael@0: nsSize availableSpace(aReflowState.AvailableWidth(), michael@0: aReflowState.AvailableHeight()); michael@0: nsHTMLReflowState kidReflowState(aPresContext, aReflowState, michael@0: kidFrame, availableSpace); michael@0: michael@0: // Reflow the frame michael@0: kidReflowState.SetComputedHeight(aReflowState.ComputedHeight()); michael@0: rv = ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowState, michael@0: 0, 0, 0, aStatus); michael@0: kidHeight = kidDesiredSize.Height(); michael@0: michael@0: FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, nullptr, 0, 0, 0); michael@0: } else { michael@0: kidHeight = mFrames.FirstChild()->GetSize().height; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(aReflowState.AvailableWidth() != NS_UNCONSTRAINEDSIZE, michael@0: "shouldn't happen anymore"); michael@0: michael@0: // Return the max size as our desired size michael@0: aDesiredSize.Width() = aReflowState.AvailableWidth(); michael@0: // Being flowed initially at an unconstrained height means we should michael@0: // return our child's intrinsic size. michael@0: aDesiredSize.Height() = aReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE michael@0: ? aReflowState.ComputedHeight() michael@0: : kidHeight; michael@0: aDesiredSize.SetOverflowAreasToDesiredBounds(); michael@0: michael@0: if (mFrames.NotEmpty()) { michael@0: ConsiderChildOverflow(aDesiredSize.mOverflowAreas, mFrames.FirstChild()); michael@0: } michael@0: michael@0: if (IsAbsoluteContainer()) { michael@0: // Make a copy of the reflow state and change the computed width and height michael@0: // to reflect the available space for the fixed items michael@0: nsHTMLReflowState reflowState(aReflowState); michael@0: michael@0: if (reflowState.AvailableHeight() == NS_UNCONSTRAINEDSIZE) { michael@0: // We have an intrinsic-height document with abs-pos/fixed-pos children. michael@0: // Set the available height and mComputedHeight to our chosen height. michael@0: reflowState.AvailableHeight() = aDesiredSize.Height(); michael@0: // Not having border/padding simplifies things michael@0: NS_ASSERTION(reflowState.ComputedPhysicalBorderPadding() == nsMargin(0,0,0,0), michael@0: "Viewports can't have border/padding"); michael@0: reflowState.SetComputedHeight(aDesiredSize.Height()); michael@0: } michael@0: michael@0: nsRect rect = AdjustReflowStateAsContainingBlock(&reflowState); michael@0: michael@0: // Just reflow all the fixed-pos frames. michael@0: rv = GetAbsoluteContainingBlock()->Reflow(this, aPresContext, reflowState, aStatus, michael@0: rect, michael@0: false, true, true, // XXX could be optimized michael@0: &aDesiredSize.mOverflowAreas); michael@0: } michael@0: michael@0: // If we were dirty then do a repaint michael@0: if (GetStateBits() & NS_FRAME_IS_DIRTY) { michael@0: InvalidateFrame(); michael@0: } michael@0: michael@0: // Clipping is handled by the document container (e.g., nsSubDocumentFrame), michael@0: // so we don't need to change our overflow areas. michael@0: bool overflowChanged = FinishAndStoreOverflow(&aDesiredSize); michael@0: if (overflowChanged) { michael@0: // We may need to alert our container to get it to pick up the michael@0: // overflow change. michael@0: nsSubDocumentFrame* container = static_cast michael@0: (nsLayoutUtils::GetCrossDocParentFrame(this)); michael@0: if (container && !container->ShouldClipSubdocument()) { michael@0: container->PresContext()->PresShell()-> michael@0: FrameNeedsReflow(container, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: michael@0: NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus); michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: return rv; michael@0: } michael@0: michael@0: nsIAtom* michael@0: ViewportFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::viewportFrame; michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: ViewportFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("Viewport"), aResult); michael@0: } michael@0: #endif