diff options
Diffstat (limited to 'layout/generic/nsViewportFrame.cpp')
-rw-r--r-- | layout/generic/nsViewportFrame.cpp | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/layout/generic/nsViewportFrame.cpp b/layout/generic/nsViewportFrame.cpp new file mode 100644 index 000000000..39491a0ed --- /dev/null +++ b/layout/generic/nsViewportFrame.cpp @@ -0,0 +1,416 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 that is the root of the frame tree, which contains + * the document's scrollbars and contains fixed-positioned elements + */ + +#include "nsViewportFrame.h" +#include "nsGkAtoms.h" +#include "nsIScrollableFrame.h" +#include "nsSubDocumentFrame.h" +#include "nsCanvasFrame.h" +#include "nsAbsoluteContainingBlock.h" +#include "GeckoProfiler.h" +#include "nsIMozBrowserFrame.h" + +using namespace mozilla; +typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags; + +ViewportFrame* +NS_NewViewportFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) ViewportFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(ViewportFrame) +NS_QUERYFRAME_HEAD(ViewportFrame) + NS_QUERYFRAME_ENTRY(ViewportFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +void +ViewportFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); + + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(this); + if (parent) { + nsFrameState state = parent->GetStateBits(); + + mState |= state & (NS_FRAME_IN_POPUP); + } +} + +void +ViewportFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + PROFILER_LABEL("ViewportFrame", "BuildDisplayList", + js::ProfileEntry::Category::GRAPHICS); + + if (nsIFrame* kid = mFrames.FirstChild()) { + // make the kid's BorderBackground our own. This ensures that the canvas + // frame's background becomes our own background and therefore appears + // below negative z-index elements. + BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); + } + + nsDisplayList topLayerList; + BuildDisplayListForTopLayer(aBuilder, &topLayerList); + if (!topLayerList.IsEmpty()) { + // Wrap the whole top layer in a single item with maximum z-index, + // and append it at the very end, so that it stays at the topmost. + nsDisplayWrapList* wrapList = + new (aBuilder) nsDisplayWrapList(aBuilder, this, &topLayerList); + wrapList->SetOverrideZIndex( + std::numeric_limits<decltype(wrapList->ZIndex())>::max()); + aLists.PositionedDescendants()->AppendNewToTop(wrapList); + } +} + +#ifdef DEBUG +/** + * Returns whether we are going to put an element in the top layer for + * fullscreen. This function should matches the CSS rule in ua.css. + */ +static bool +ShouldInTopLayerForFullscreen(Element* aElement) +{ + if (!aElement->GetParent()) { + return false; + } + nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aElement); + if (browserFrame && browserFrame->GetReallyIsBrowserOrApp()) { + return false; + } + return true; +} +#endif // DEBUG + +static void +BuildDisplayListForTopLayerFrame(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) +{ + nsRect dirty; + DisplayListClipState::AutoClipMultiple clipState(aBuilder); + nsDisplayListBuilder::OutOfFlowDisplayData* + savedOutOfFlowData = nsDisplayListBuilder::GetOutOfFlowData(aFrame); + if (savedOutOfFlowData) { + dirty = savedOutOfFlowData->mDirtyRect; + clipState.SetClipForContainingBlockDescendants( + &savedOutOfFlowData->mContainingBlockClip); + clipState.SetScrollClipForContainingBlockDescendants( + aBuilder, savedOutOfFlowData->mContainingBlockScrollClip); + } + nsDisplayList list; + aFrame->BuildDisplayListForStackingContext(aBuilder, dirty, &list); + aList->AppendToTop(&list); +} + +void +ViewportFrame::BuildDisplayListForTopLayer(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList) +{ + nsIDocument* doc = PresContext()->Document(); + nsTArray<Element*> fullscreenStack = doc->GetFullscreenStack(); + for (Element* elem : fullscreenStack) { + if (nsIFrame* frame = elem->GetPrimaryFrame()) { + // There are two cases where an element in fullscreen is not in + // the top layer: + // 1. When building display list for purpose other than painting, + // it is possible that there is inconsistency between the style + // info and the content tree. + // 2. This is an element which we are not going to put in the top + // layer for fullscreen. See ShouldInTopLayerForFullscreen(). + // In both cases, we want to skip the frame here and paint it in + // the normal path. + if (frame->StyleDisplay()->mTopLayer == NS_STYLE_TOP_LAYER_NONE) { + MOZ_ASSERT(!aBuilder->IsForPainting() || + !ShouldInTopLayerForFullscreen(elem)); + continue; + } + MOZ_ASSERT(ShouldInTopLayerForFullscreen(elem)); + // Inner SVG, MathML elements, as well as children of some XUL + // elements are not allowed to be out-of-flow. They should not + // be handled as top layer element here. + if (!(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { + MOZ_ASSERT(!elem->GetParent()->IsHTMLElement(), "HTML element " + "should always be out-of-flow if in the top layer"); + continue; + } + if (nsIFrame* backdropPh = + frame->GetChildList(kBackdropList).FirstChild()) { + MOZ_ASSERT(backdropPh->GetType() == nsGkAtoms::placeholderFrame); + nsIFrame* backdropFrame = + static_cast<nsPlaceholderFrame*>(backdropPh)->GetOutOfFlowFrame(); + MOZ_ASSERT(backdropFrame); + BuildDisplayListForTopLayerFrame(aBuilder, backdropFrame, aList); + } + BuildDisplayListForTopLayerFrame(aBuilder, frame, aList); + } + } + + nsIPresShell* shell = PresContext()->PresShell(); + if (nsCanvasFrame* canvasFrame = shell->GetCanvasFrame()) { + if (Element* container = canvasFrame->GetCustomContentContainer()) { + if (nsIFrame* frame = container->GetPrimaryFrame()) { + BuildDisplayListForTopLayerFrame(aBuilder, frame, aList); + } + } + } +} + +#ifdef DEBUG +void +ViewportFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!"); + nsContainerFrame::AppendFrames(aListID, aFrameList); +} + +void +ViewportFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!"); + nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList); +} + +void +ViewportFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + nsContainerFrame::RemoveFrame(aListID, aOldFrame); +} +#endif + +/* virtual */ nscoord +ViewportFrame::GetMinISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + if (mFrames.IsEmpty()) + result = 0; + else + result = mFrames.FirstChild()->GetMinISize(aRenderingContext); + + return result; +} + +/* virtual */ nscoord +ViewportFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + if (mFrames.IsEmpty()) + result = 0; + else + result = mFrames.FirstChild()->GetPrefISize(aRenderingContext); + + return result; +} + +nsPoint +ViewportFrame::AdjustReflowInputForScrollbars(ReflowInput* aReflowInput) const +{ + // Get our prinicpal child frame and see if we're scrollable + nsIFrame* kidFrame = mFrames.FirstChild(); + nsIScrollableFrame* scrollingFrame = do_QueryFrame(kidFrame); + + if (scrollingFrame) { + WritingMode wm = aReflowInput->GetWritingMode(); + LogicalMargin scrollbars(wm, scrollingFrame->GetActualScrollbarSizes()); + aReflowInput->SetComputedISize(aReflowInput->ComputedISize() - + scrollbars.IStartEnd(wm)); + aReflowInput->AvailableISize() -= scrollbars.IStartEnd(wm); + aReflowInput->SetComputedBSizeWithoutResettingResizeFlags( + aReflowInput->ComputedBSize() - scrollbars.BStartEnd(wm)); + return nsPoint(scrollbars.Left(wm), scrollbars.Top(wm)); + } + return nsPoint(0, 0); +} + +nsRect +ViewportFrame::AdjustReflowInputAsContainingBlock(ReflowInput* aReflowInput) const +{ +#ifdef DEBUG + nsPoint offset = +#endif + AdjustReflowInputForScrollbars(aReflowInput); + + NS_ASSERTION(GetAbsoluteContainingBlock()->GetChildList().IsEmpty() || + (offset.x == 0 && offset.y == 0), + "We don't handle correct positioning of fixed frames with " + "scrollbars in odd positions"); + + // If a scroll position clamping scroll-port size has been set, layout + // fixed position elements to this size instead of the computed size. + nsRect rect(0, 0, aReflowInput->ComputedWidth(), aReflowInput->ComputedHeight()); + nsIPresShell* ps = PresContext()->PresShell(); + if (ps->IsScrollPositionClampingScrollPortSizeSet()) { + rect.SizeTo(ps->GetScrollPositionClampingScrollPortSize()); + } + + return rect; +} + +void +ViewportFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("ViewportFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + NS_FRAME_TRACE_REFLOW_IN("ViewportFrame::Reflow"); + + // Initialize OUT parameters + aStatus = NS_FRAME_COMPLETE; + + // Because |Reflow| sets ComputedBSize() on the child to our + // ComputedBSize(). + AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); + + // Set our size up front, since some parts of reflow depend on it + // being already set. Note that the computed height may be + // unconstrained; that's ok. Consumers should watch out for that. + SetSize(nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight())); + + // Reflow the main content first so that the placeholders of the + // fixed-position frames will be in the right places on an initial + // reflow. + nscoord kidBSize = 0; + WritingMode wm = aReflowInput.GetWritingMode(); + + if (mFrames.NotEmpty()) { + // Deal with a non-incremental reflow or an incremental reflow + // targeted at our one-and-only principal child frame. + if (aReflowInput.ShouldReflowAllKids() || + aReflowInput.IsBResize() || + NS_SUBTREE_DIRTY(mFrames.FirstChild())) { + // Reflow our one-and-only principal child frame + nsIFrame* kidFrame = mFrames.FirstChild(); + ReflowOutput kidDesiredSize(aReflowInput); + WritingMode wm = kidFrame->GetWritingMode(); + LogicalSize availableSpace = aReflowInput.AvailableSize(wm); + ReflowInput kidReflowInput(aPresContext, aReflowInput, + kidFrame, availableSpace); + + // Reflow the frame + kidReflowInput.SetComputedBSize(aReflowInput.ComputedBSize()); + ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, + 0, 0, 0, aStatus); + kidBSize = kidDesiredSize.BSize(wm); + + FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, nullptr, 0, 0, 0); + } else { + kidBSize = LogicalSize(wm, mFrames.FirstChild()->GetSize()).BSize(wm); + } + } + + NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE, + "shouldn't happen anymore"); + + // Return the max size as our desired size + LogicalSize maxSize(wm, aReflowInput.AvailableISize(), + // Being flowed initially at an unconstrained block size + // means we should return our child's intrinsic size. + aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE + ? aReflowInput.ComputedBSize() + : kidBSize); + aDesiredSize.SetSize(wm, maxSize); + aDesiredSize.SetOverflowAreasToDesiredBounds(); + + if (HasAbsolutelyPositionedChildren()) { + // Make a copy of the reflow state and change the computed width and height + // to reflect the available space for the fixed items + ReflowInput reflowInput(aReflowInput); + + if (reflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { + // We have an intrinsic-height document with abs-pos/fixed-pos children. + // Set the available height and mComputedHeight to our chosen height. + reflowInput.AvailableBSize() = maxSize.BSize(wm); + // Not having border/padding simplifies things + NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0,0,0,0), + "Viewports can't have border/padding"); + reflowInput.SetComputedBSize(maxSize.BSize(wm)); + } + + nsRect rect = AdjustReflowInputAsContainingBlock(&reflowInput); + nsOverflowAreas* overflowAreas = &aDesiredSize.mOverflowAreas; + nsIScrollableFrame* rootScrollFrame = + aPresContext->PresShell()->GetRootScrollFrameAsScrollable(); + if (rootScrollFrame && !rootScrollFrame->IsIgnoringViewportClipping()) { + overflowAreas = nullptr; + } + AbsPosReflowFlags flags = + AbsPosReflowFlags::eCBWidthAndHeightChanged; // XXX could be optimized + GetAbsoluteContainingBlock()->Reflow(this, aPresContext, reflowInput, aStatus, + rect, flags, overflowAreas); + } + + if (mFrames.NotEmpty()) { + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, mFrames.FirstChild()); + } + + // If we were dirty then do a repaint + if (GetStateBits() & NS_FRAME_IS_DIRTY) { + InvalidateFrame(); + } + + // Clipping is handled by the document container (e.g., nsSubDocumentFrame), + // so we don't need to change our overflow areas. + bool overflowChanged = FinishAndStoreOverflow(&aDesiredSize); + if (overflowChanged) { + // We may need to alert our container to get it to pick up the + // overflow change. + nsSubDocumentFrame* container = static_cast<nsSubDocumentFrame*> + (nsLayoutUtils::GetCrossDocParentFrame(this)); + if (container && !container->ShouldClipSubdocument()) { + container->PresContext()->PresShell()-> + FrameNeedsReflow(container, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); + } + } + + NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus); + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +bool +ViewportFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) +{ + nsIScrollableFrame* rootScrollFrame = + PresContext()->PresShell()->GetRootScrollFrameAsScrollable(); + if (rootScrollFrame && !rootScrollFrame->IsIgnoringViewportClipping()) { + return false; + } + + return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); +} + +nsIAtom* +ViewportFrame::GetType() const +{ + return nsGkAtoms::viewportFrame; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +ViewportFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("Viewport"), aResult); +} +#endif |