Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 */
* 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/ProfilerLabels.h"
#include "mozilla/RestyleManager.h"
#include "nsGkAtoms.h"
#include "nsIScrollableFrame.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsCanvasFrame.h"
#include "nsLayoutUtils.h"
#include "nsSubDocumentFrame.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());
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::GetCrossDocParentFrameInProcess(this);
if (parent) {
nsFrameState state = parent->GetStateBits();
AddStateBits(state & (NS_FRAME_IN_POPUP));
void ViewportFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) {
nsIFrame* kid = mFrames.FirstChild();
if (!kid) {
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) {
#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) {
return !!aElement->GetParent();
#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 =
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.
nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
aBuilder, aFrame, visible, dirty);
nsDisplayList list(aBuilder);
aFrame->BuildDisplayListForStackingContext(aBuilder, &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->Length() != 1 ||
aList->GetTop()->GetType() != DisplayItemType::TYPE_FIXED_POSITION) {
return false;
// Make sure the fixed position container isn't clipped or scrollable.
nsDisplayFixedPosition* fixed =
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 =
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(aBuilder);
nsTArray<dom::Element*> topLayer = PresContext()->Document()->GetTopLayer();
for (dom::Element* elem : topLayer) {
nsIFrame* frame = elem->GetPrimaryFrame();
if (!frame) {
if (frame->IsHiddenByContentVisibilityOnAnyAncestor(
nsIFrame::IncludeContentVisibility::Hidden)) {
// 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() ||
// 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)) {
"HTML element should always be out-of-flow if in the top "
if (nsIFrame* backdropPh =
frame->GetChildList(FrameChildListID::Backdrop).FirstChild()) {
MOZ_ASSERT(!backdropPh->GetNextSibling(), "more than one ::backdrop?");
"did you intend to reflow ::backdrop placeholders?");
nsIFrame* backdropFrame =
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");
BuildDisplayListForTopLayerFrame(aBuilder, frame, &topLayerList);
if (topLayerList.IsEmpty()) {
return nullptr;
nsPoint offset = aBuilder->GetCurrentFrame()->GetOffsetTo(this);
nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
aBuilder, this, aBuilder->GetVisibleRect() + offset,
aBuilder->GetDirtyRect() + offset);
// 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<nsDisplayWrapper>(
aBuilder, this, 2, &topLayerList, aBuilder->CurrentActiveScrolledRoot(),
if (!wrapList) {
return nullptr;
return wrapList;
#ifdef DEBUG
void ViewportFrame::AppendFrames(ChildListID aListID,
nsFrameList&& aFrameList) {
NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!");
nsContainerFrame::AppendFrames(aListID, std::move(aFrameList));
void ViewportFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
const nsLineList::iterator* aPrevFrameLine,
nsFrameList&& aFrameList) {
NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!");
nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
void ViewportFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID,
nsIFrame* aOldFrame) {
NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
/* virtual */
nscoord ViewportFrame::GetMinISize(gfxContext* aRenderingContext) {
return mFrames.IsEmpty()
? 0
: mFrames.FirstChild()->GetMinISize(aRenderingContext);
/* virtual */
nscoord ViewportFrame::GetPrefISize(gfxContext* aRenderingContext) {
return mFrames.IsEmpty()
? 0
: mFrames.FirstChild()->GetPrefISize(aRenderingContext);
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->ComputedISize() - scrollbars.IStartEnd(wm),
aReflowInput->SetAvailableISize(aReflowInput->AvailableISize() -
aReflowInput->ComputedBSize() - scrollbars.BStartEnd(wm),
return nsPoint(scrollbars.Left(wm), scrollbars.Top(wm));
return nsPoint(0, 0);
nsRect ViewportFrame::AdjustReflowInputAsContainingBlock(
ReflowInput* aReflowInput) const {
const nsPoint origin = AdjustReflowInputForScrollbars(aReflowInput);
nsRect rect(origin, aReflowInput->ComputedPhysicalSize());
return rect;
void ViewportFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
// Because |Reflow| sets ComputedBSize() on the child to our
// ComputedBSize().
// 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.
// 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,
// Reflow the frame
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);
"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);
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-block-size document with abs-pos/fixed-pos
// children. Set the available block-size and computed block-size to our
// chosen block-size.
// Not having border/padding simplifies things
NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(),
"Viewports can't have border/padding");
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)) {
// Clipping is handled by the document container (e.g., nsSubDocumentFrame),
// so we don't need to change our overflow areas.
NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus);
void ViewportFrame::UpdateStyle(ServoRestyleState& aRestyleState) {
RefPtr<ComputedStyle> newStyle =
Style()->GetPseudoType(), nullptr);
MOZ_ASSERT(!GetNextContinuation(), "Viewport has continuations?");
void ViewportFrame::AppendDirectlyOwnedAnonBoxes(
nsTArray<OwnedAnonBox>& aResult) {
if (mFrames.NotEmpty()) {
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;
nsresult ViewportFrame::GetFrameName(nsAString& aResult) const {
return MakeFrameName(u"Viewport"_ns, aResult);