summaryrefslogtreecommitdiffstats
path: root/layout/generic/ViewportFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--layout/generic/ViewportFrame.cpp486
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