diff options
Diffstat (limited to '')
-rw-r--r-- | layout/generic/ViewportFrame.cpp | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/layout/generic/ViewportFrame.cpp b/layout/generic/ViewportFrame.cpp new file mode 100644 index 0000000000..12c8561515 --- /dev/null +++ b/layout/generic/ViewportFrame.cpp @@ -0,0 +1,486 @@ +/* -*- 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 that is the root of the frame tree, which contains + * the document's scrollbars and contains fixed-positioned elements + */ + +#include "mozilla/ViewportFrame.h" + +#include "mozilla/ComputedStyleInlines.h" +#include "mozilla/PresShell.h" +#include "mozilla/RestyleManager.h" +#include "nsGkAtoms.h" +#include "nsIScrollableFrame.h" +#include "nsAbsoluteContainingBlock.h" +#include "nsCanvasFrame.h" +#include "nsLayoutUtils.h" +#include "nsSubDocumentFrame.h" +#include "GeckoProfiler.h" +#include "nsIMozBrowserFrame.h" +#include "nsPlaceholderFrame.h" +#include "MobileViewportManager.h" + +using namespace mozilla; +typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags; + +ViewportFrame* NS_NewViewportFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) ViewportFrame(aStyle, aPresShell->GetPresContext()); +} + +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); + // No need to call CreateView() here - the frame ctor will call SetView() + // with the ViewManager's root view, so we'll assign it in SetViewInternal(). + + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(this); + if (parent) { + nsFrameState state = parent->GetStateBits(); + + AddStateBits(state & (NS_FRAME_IN_POPUP)); + } +} + +void ViewportFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + AUTO_PROFILER_LABEL("ViewportFrame::BuildDisplayList", + GRAPHICS_DisplayListBuilding); + + nsIFrame* kid = mFrames.FirstChild(); + if (!kid) { + return; + } + + nsDisplayListCollection set(aBuilder); + BuildDisplayListForChild(aBuilder, kid, set); + + // If we have a scrollframe then it takes care of creating the display list + // for the top layer, but otherwise we need to do it here. + if (!kid->IsScrollFrame()) { + bool isOpaque = false; + if (auto* list = BuildDisplayListForTopLayer(aBuilder, &isOpaque)) { + if (isOpaque) { + set.DeleteAll(aBuilder); + } + set.PositionedDescendants()->AppendToTop(list); + } + } + + set.MoveTo(aLists); +} + +#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(dom::Element* aElement) { + if (!aElement->GetParent()) { + return false; + } + nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aElement); + if (browserFrame && browserFrame->GetReallyIsBrowser()) { + return false; + } + return true; +} +#endif // DEBUG + +static void BuildDisplayListForTopLayerFrame(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) { + nsRect visible; + nsRect dirty; + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder); + nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData = + nsDisplayListBuilder::GetOutOfFlowData(aFrame); + if (savedOutOfFlowData) { + visible = + savedOutOfFlowData->GetVisibleRectForFrame(aBuilder, aFrame, &dirty); + // This function is called after we've finished building display items for + // the root scroll frame. That means that the content clip from the root + // scroll frame is no longer on aBuilder. However, we need to make sure + // that the display items we build in this function have finite clipped + // bounds with respect to the root ASR, so we restore the *combined clip* + // that we saved earlier. The combined clip will include the clip from the + // root scroll frame. + clipState.SetClipChainForContainingBlockDescendants( + savedOutOfFlowData->mCombinedClipChain); + asrSetter.SetCurrentActiveScrolledRoot( + savedOutOfFlowData->mContainingBlockActiveScrolledRoot); + } + nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( + aBuilder, aFrame, visible, dirty); + + nsDisplayList list; + aFrame->BuildDisplayListForStackingContext(aBuilder, &list); + aList->AppendToTop(&list); +} + +static bool BackdropListIsOpaque(ViewportFrame* aFrame, + nsDisplayListBuilder* aBuilder, + nsDisplayList* aList) { + // The common case for ::backdrop elements on the top layer is a single + // fixed position container, holding an opaque background color covering + // the whole viewport. + if (aList->Count() != 1 || + aList->GetTop()->GetType() != DisplayItemType::TYPE_FIXED_POSITION) { + return false; + } + + // Make sure the fixed position container isn't clipped or scrollable. + nsDisplayFixedPosition* fixed = + static_cast<nsDisplayFixedPosition*>(aList->GetTop()); + if (fixed->GetActiveScrolledRoot() || fixed->GetClipChain()) { + return false; + } + + nsDisplayList* children = fixed->GetChildren(); + if (!children->GetTop() || + children->GetTop()->GetType() != DisplayItemType::TYPE_BACKGROUND_COLOR) { + return false; + } + + nsDisplayBackgroundColor* child = + static_cast<nsDisplayBackgroundColor*>(children->GetTop()); + if (child->GetActiveScrolledRoot() || child->GetClipChain()) { + return false; + } + + // Check that the background color is both opaque, and covering the + // whole viewport. + bool dummy; + nsRegion opaque = child->GetOpaqueRegion(aBuilder, &dummy); + return opaque.Contains(aFrame->GetRect()); +} + +nsDisplayWrapList* ViewportFrame::BuildDisplayListForTopLayer( + nsDisplayListBuilder* aBuilder, bool* aIsOpaque) { + nsDisplayList topLayerList; + + nsTArray<dom::Element*> topLayer = PresContext()->Document()->GetTopLayer(); + for (dom::Element* elem : topLayer) { + nsIFrame* frame = elem->GetPrimaryFrame(); + if (!frame) { + continue; + } + // 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 == StyleTopLayer::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->HasAnyStateBits(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->GetNextSibling(), "more than one ::backdrop?"); + MOZ_ASSERT(backdropPh->HasAnyStateBits(NS_FRAME_FIRST_REFLOW), + "did you intend to reflow ::backdrop placeholders?"); + nsIFrame* backdropFrame = + nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPh); + BuildDisplayListForTopLayerFrame(aBuilder, backdropFrame, &topLayerList); + + if (aIsOpaque) { + *aIsOpaque = BackdropListIsOpaque(this, aBuilder, &topLayerList); + } + } + BuildDisplayListForTopLayerFrame(aBuilder, frame, &topLayerList); + } + + if (nsCanvasFrame* canvasFrame = PresShell()->GetCanvasFrame()) { + if (dom::Element* container = canvasFrame->GetCustomContentContainer()) { + if (nsIFrame* frame = container->GetPrimaryFrame()) { + MOZ_ASSERT(frame->StyleDisplay()->mTopLayer != StyleTopLayer::None, + "ua.css should ensure this"); + MOZ_ASSERT(frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); + BuildDisplayListForTopLayerFrame(aBuilder, frame, &topLayerList); + } + } + } + if (topLayerList.IsEmpty()) { + return nullptr; + } + nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(aBuilder, + this); + // 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 = MakeDisplayItemWithIndex<nsDisplayWrapList>( + aBuilder, this, 2, &topLayerList, aBuilder->CurrentActiveScrolledRoot(), + false); + if (!wrapList) { + return nullptr; + } + wrapList->SetOverrideZIndex( + std::numeric_limits<decltype(wrapList->ZIndex())>::max()); + return wrapList; +} + +#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, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList& aFrameList) { + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!"); + nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, + aFrameList); +} + +void ViewportFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) { + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + nsContainerFrame::RemoveFrame(aListID, aOldFrame); +} +#endif + +/* virtual */ +nscoord ViewportFrame::GetMinISize(gfxContext* aRenderingContext) { + nscoord result; + DISPLAY_MIN_INLINE_SIZE(this, result); + if (mFrames.IsEmpty()) + result = 0; + else + result = mFrames.FirstChild()->GetMinISize(aRenderingContext); + + return result; +} + +/* virtual */ +nscoord ViewportFrame::GetPrefISize(gfxContext* aRenderingContext) { + nscoord result; + DISPLAY_PREF_INLINE_SIZE(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"); + + nsRect rect(0, 0, aReflowInput->ComputedWidth(), + aReflowInput->ComputedHeight()); + + rect.SizeTo(AdjustViewportSizeForFixedPosition(rect)); + + 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); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + NS_FRAME_TRACE_REFLOW_IN("ViewportFrame::Reflow"); + + // 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() || + mFrames.FirstChild()->IsSubtreeDirty()) { + // Reflow our one-and-only principal child frame + nsIFrame* kidFrame = mFrames.FirstChild(); + ReflowOutput kidDesiredSize(aReflowInput); + const WritingMode kidWM = kidFrame->GetWritingMode(); + LogicalSize availableSpace = aReflowInput.AvailableSize(kidWM); + ReflowInput kidReflowInput(aPresContext, aReflowInput, kidFrame, + availableSpace); + + // Reflow the frame + kidReflowInput.SetComputedBSize(aReflowInput.ComputedBSize()); + ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, 0, 0, + ReflowChildFlags::Default, aStatus); + kidBSize = kidDesiredSize.BSize(wm); + + FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, &kidReflowInput, + 0, 0, ReflowChildFlags::Default); + } 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 input 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); + AbsPosReflowFlags flags = + AbsPosReflowFlags::CBWidthAndHeightChanged; // XXX could be optimized + GetAbsoluteContainingBlock()->Reflow(this, aPresContext, reflowInput, + aStatus, rect, flags, + /* aOverflowAreas = */ nullptr); + } + + if (mFrames.NotEmpty()) { + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, mFrames.FirstChild()); + } + + // If we were dirty then do a repaint + if (HasAnyStateBits(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. + FinishAndStoreOverflow(&aDesiredSize); + + NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus); + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +void ViewportFrame::UpdateStyle(ServoRestyleState& aRestyleState) { + RefPtr<ComputedStyle> newStyle = + aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle( + Style()->GetPseudoType(), nullptr); + + MOZ_ASSERT(!GetNextContinuation(), "Viewport has continuations?"); + SetComputedStyle(newStyle); + + UpdateStyleOfOwnedAnonBoxes(aRestyleState); +} + +void ViewportFrame::AppendDirectlyOwnedAnonBoxes( + nsTArray<OwnedAnonBox>& aResult) { + if (mFrames.NotEmpty()) { + aResult.AppendElement(mFrames.FirstChild()); + } +} + +nsSize ViewportFrame::AdjustViewportSizeForFixedPosition( + const nsRect& aViewportRect) const { + nsSize result = aViewportRect.Size(); + + mozilla::PresShell* presShell = PresShell(); + // Layout fixed position elements to the visual viewport size if and only if + // it has been set and it is larger than the computed size, otherwise use the + // computed size. + if (presShell->IsVisualViewportSizeSet()) { + if (presShell->GetDynamicToolbarState() == DynamicToolbarState::Collapsed && + result < presShell->GetVisualViewportSizeUpdatedByDynamicToolbar()) { + // We need to use the viewport size updated by the dynamic toolbar in the + // case where the dynamic toolbar is completely hidden. + result = presShell->GetVisualViewportSizeUpdatedByDynamicToolbar(); + } else if (result < presShell->GetVisualViewportSize()) { + result = presShell->GetVisualViewportSize(); + } + } + // Expand the size to the layout viewport size if necessary. + const nsSize layoutViewportSize = presShell->GetLayoutViewportSize(); + if (result < layoutViewportSize) { + result = layoutViewportSize; + } + + return result; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult ViewportFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"Viewport"_ns, aResult); +} +#endif |