/* -*- 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" #include "nsPlaceholderFrame.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 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, 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_limitsZIndex())>::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 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); } nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(aBuilder, aFrame, dirty, aBuilder->IsAtRootOfPseudoStackingContext()); nsDisplayList list; aFrame->BuildDisplayListForStackingContext(aBuilder, &list); aList->AppendToTop(&list); } void ViewportFrame::BuildDisplayListForTopLayer(nsDisplayListBuilder* aBuilder, nsDisplayList* aList) { nsIDocument* doc = PresContext()->Document(); nsTArray 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(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 (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