1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * rendering object that is the root of the frame tree, which contains
9 * the document's scrollbars and contains fixed-positioned elements
12 #include "mozilla/ViewportFrame.h"
14 #include "mozilla/ComputedStyleInlines.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/ProfilerLabels.h"
17 #include "mozilla/RestyleManager.h"
18 #include "nsGkAtoms.h"
19 #include "nsIScrollableFrame.h"
20 #include "nsAbsoluteContainingBlock.h"
21 #include "nsCanvasFrame.h"
22 #include "nsLayoutUtils.h"
23 #include "nsSubDocumentFrame.h"
24 #include "nsIMozBrowserFrame.h"
25 #include "nsPlaceholderFrame.h"
26 #include "MobileViewportManager.h"
28 using namespace mozilla
;
29 typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags
;
31 ViewportFrame
* NS_NewViewportFrame(PresShell
* aPresShell
,
32 ComputedStyle
* aStyle
) {
33 return new (aPresShell
) ViewportFrame(aStyle
, aPresShell
->GetPresContext());
36 NS_IMPL_FRAMEARENA_HELPERS(ViewportFrame
)
37 NS_QUERYFRAME_HEAD(ViewportFrame
)
38 NS_QUERYFRAME_ENTRY(ViewportFrame
)
39 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
41 void ViewportFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
42 nsIFrame
* aPrevInFlow
) {
43 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
44 // No need to call CreateView() here - the frame ctor will call SetView()
45 // with the ViewManager's root view, so we'll assign it in SetViewInternal().
47 nsIFrame
* parent
= nsLayoutUtils::GetCrossDocParentFrameInProcess(this);
49 nsFrameState state
= parent
->GetStateBits();
51 AddStateBits(state
& (NS_FRAME_IN_POPUP
));
55 void ViewportFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
56 const nsDisplayListSet
& aLists
) {
57 AUTO_PROFILER_LABEL("ViewportFrame::BuildDisplayList",
58 GRAPHICS_DisplayListBuilding
);
60 nsIFrame
* kid
= mFrames
.FirstChild();
65 nsDisplayListCollection
set(aBuilder
);
66 BuildDisplayListForChild(aBuilder
, kid
, set
);
68 // If we have a scrollframe then it takes care of creating the display list
69 // for the top layer, but otherwise we need to do it here.
70 if (!kid
->IsScrollFrame()) {
71 bool isOpaque
= false;
72 if (auto* list
= BuildDisplayListForTopLayer(aBuilder
, &isOpaque
)) {
74 set
.DeleteAll(aBuilder
);
76 set
.PositionedDescendants()->AppendToTop(list
);
85 * Returns whether we are going to put an element in the top layer for
86 * fullscreen. This function should matches the CSS rule in ua.css.
88 static bool ShouldInTopLayerForFullscreen(dom::Element
* aElement
) {
89 if (!aElement
->GetParent()) {
92 nsCOMPtr
<nsIMozBrowserFrame
> browserFrame
= do_QueryInterface(aElement
);
93 if (browserFrame
&& browserFrame
->GetReallyIsBrowser()) {
100 static void BuildDisplayListForTopLayerFrame(nsDisplayListBuilder
* aBuilder
,
102 nsDisplayList
* aList
) {
105 DisplayListClipState::AutoSaveRestore
clipState(aBuilder
);
106 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter
asrSetter(aBuilder
);
107 nsDisplayListBuilder::OutOfFlowDisplayData
* savedOutOfFlowData
=
108 nsDisplayListBuilder::GetOutOfFlowData(aFrame
);
109 if (savedOutOfFlowData
) {
111 savedOutOfFlowData
->GetVisibleRectForFrame(aBuilder
, aFrame
, &dirty
);
112 // This function is called after we've finished building display items for
113 // the root scroll frame. That means that the content clip from the root
114 // scroll frame is no longer on aBuilder. However, we need to make sure
115 // that the display items we build in this function have finite clipped
116 // bounds with respect to the root ASR, so we restore the *combined clip*
117 // that we saved earlier. The combined clip will include the clip from the
118 // root scroll frame.
119 clipState
.SetClipChainForContainingBlockDescendants(
120 savedOutOfFlowData
->mCombinedClipChain
);
121 asrSetter
.SetCurrentActiveScrolledRoot(
122 savedOutOfFlowData
->mContainingBlockActiveScrolledRoot
);
123 asrSetter
.SetCurrentScrollParentId(savedOutOfFlowData
->mScrollParentId
);
125 nsDisplayListBuilder::AutoBuildingDisplayList
buildingForChild(
126 aBuilder
, aFrame
, visible
, dirty
);
128 nsDisplayList
list(aBuilder
);
129 aFrame
->BuildDisplayListForStackingContext(aBuilder
, &list
);
130 aList
->AppendToTop(&list
);
133 static bool BackdropListIsOpaque(ViewportFrame
* aFrame
,
134 nsDisplayListBuilder
* aBuilder
,
135 nsDisplayList
* aList
) {
136 // The common case for ::backdrop elements on the top layer is a single
137 // fixed position container, holding an opaque background color covering
138 // the whole viewport.
139 if (aList
->Length() != 1 ||
140 aList
->GetTop()->GetType() != DisplayItemType::TYPE_FIXED_POSITION
) {
144 // Make sure the fixed position container isn't clipped or scrollable.
145 nsDisplayFixedPosition
* fixed
=
146 static_cast<nsDisplayFixedPosition
*>(aList
->GetTop());
147 if (fixed
->GetActiveScrolledRoot() || fixed
->GetClipChain()) {
151 nsDisplayList
* children
= fixed
->GetChildren();
152 if (!children
->GetTop() ||
153 children
->GetTop()->GetType() != DisplayItemType::TYPE_BACKGROUND_COLOR
) {
157 nsDisplayBackgroundColor
* child
=
158 static_cast<nsDisplayBackgroundColor
*>(children
->GetTop());
159 if (child
->GetActiveScrolledRoot() || child
->GetClipChain()) {
163 // Check that the background color is both opaque, and covering the
166 nsRegion opaque
= child
->GetOpaqueRegion(aBuilder
, &dummy
);
167 return opaque
.Contains(aFrame
->GetRect());
170 nsDisplayWrapList
* ViewportFrame::BuildDisplayListForTopLayer(
171 nsDisplayListBuilder
* aBuilder
, bool* aIsOpaque
) {
172 nsDisplayList
topLayerList(aBuilder
);
174 nsTArray
<dom::Element
*> topLayer
= PresContext()->Document()->GetTopLayer();
175 for (dom::Element
* elem
: topLayer
) {
176 nsIFrame
* frame
= elem
->GetPrimaryFrame();
181 if (frame
->IsHiddenByContentVisibilityOnAnyAncestor(
182 nsIFrame::IncludeContentVisibility::Hidden
)) {
186 // There are two cases where an element in fullscreen is not in
188 // 1. When building display list for purpose other than painting,
189 // it is possible that there is inconsistency between the style
190 // info and the content tree.
191 // 2. This is an element which we are not going to put in the top
192 // layer for fullscreen. See ShouldInTopLayerForFullscreen().
193 // In both cases, we want to skip the frame here and paint it in
195 if (frame
->StyleDisplay()->mTopLayer
== StyleTopLayer::None
) {
196 MOZ_ASSERT(!aBuilder
->IsForPainting() ||
197 !ShouldInTopLayerForFullscreen(elem
));
200 MOZ_ASSERT(ShouldInTopLayerForFullscreen(elem
));
201 // Inner SVG, MathML elements, as well as children of some XUL
202 // elements are not allowed to be out-of-flow. They should not
203 // be handled as top layer element here.
204 if (!frame
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
)) {
205 MOZ_ASSERT(!elem
->GetParent()->IsHTMLElement(),
206 "HTML element should always be out-of-flow if in the top "
210 if (nsIFrame
* backdropPh
=
211 frame
->GetChildList(FrameChildListID::Backdrop
).FirstChild()) {
212 MOZ_ASSERT(!backdropPh
->GetNextSibling(), "more than one ::backdrop?");
213 MOZ_ASSERT(backdropPh
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
),
214 "did you intend to reflow ::backdrop placeholders?");
215 nsIFrame
* backdropFrame
=
216 nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPh
);
217 BuildDisplayListForTopLayerFrame(aBuilder
, backdropFrame
, &topLayerList
);
220 *aIsOpaque
= BackdropListIsOpaque(this, aBuilder
, &topLayerList
);
223 BuildDisplayListForTopLayerFrame(aBuilder
, frame
, &topLayerList
);
226 if (nsCanvasFrame
* canvasFrame
= PresShell()->GetCanvasFrame()) {
227 if (dom::Element
* container
= canvasFrame
->GetCustomContentContainer()) {
228 if (nsIFrame
* frame
= container
->GetPrimaryFrame()) {
229 MOZ_ASSERT(frame
->StyleDisplay()->mTopLayer
!= StyleTopLayer::None
,
230 "ua.css should ensure this");
231 MOZ_ASSERT(frame
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
));
232 BuildDisplayListForTopLayerFrame(aBuilder
, frame
, &topLayerList
);
236 if (topLayerList
.IsEmpty()) {
239 nsPoint offset
= aBuilder
->GetCurrentFrame()->GetOffsetTo(this);
240 nsDisplayListBuilder::AutoBuildingDisplayList
buildingDisplayList(
241 aBuilder
, this, aBuilder
->GetVisibleRect() + offset
,
242 aBuilder
->GetDirtyRect() + offset
);
243 // Wrap the whole top layer in a single item with maximum z-index,
244 // and append it at the very end, so that it stays at the topmost.
245 nsDisplayWrapList
* wrapList
= MakeDisplayItemWithIndex
<nsDisplayWrapper
>(
246 aBuilder
, this, 2, &topLayerList
, aBuilder
->CurrentActiveScrolledRoot(),
251 wrapList
->SetOverrideZIndex(
252 std::numeric_limits
<decltype(wrapList
->ZIndex())>::max());
257 void ViewportFrame::AppendFrames(ChildListID aListID
,
258 nsFrameList
&& aFrameList
) {
259 NS_ASSERTION(aListID
== FrameChildListID::Principal
, "unexpected child list");
260 NS_ASSERTION(GetChildList(aListID
).IsEmpty(), "Shouldn't have any kids!");
261 nsContainerFrame::AppendFrames(aListID
, std::move(aFrameList
));
264 void ViewportFrame::InsertFrames(ChildListID aListID
, nsIFrame
* aPrevFrame
,
265 const nsLineList::iterator
* aPrevFrameLine
,
266 nsFrameList
&& aFrameList
) {
267 NS_ASSERTION(aListID
== FrameChildListID::Principal
, "unexpected child list");
268 NS_ASSERTION(GetChildList(aListID
).IsEmpty(), "Shouldn't have any kids!");
269 nsContainerFrame::InsertFrames(aListID
, aPrevFrame
, aPrevFrameLine
,
270 std::move(aFrameList
));
273 void ViewportFrame::RemoveFrame(DestroyContext
& aContext
, ChildListID aListID
,
274 nsIFrame
* aOldFrame
) {
275 NS_ASSERTION(aListID
== FrameChildListID::Principal
, "unexpected child list");
276 nsContainerFrame::RemoveFrame(aContext
, aListID
, aOldFrame
);
281 nscoord
ViewportFrame::GetMinISize(gfxContext
* aRenderingContext
) {
283 DISPLAY_MIN_INLINE_SIZE(this, result
);
284 if (mFrames
.IsEmpty())
287 result
= mFrames
.FirstChild()->GetMinISize(aRenderingContext
);
293 nscoord
ViewportFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
295 DISPLAY_PREF_INLINE_SIZE(this, result
);
296 if (mFrames
.IsEmpty())
299 result
= mFrames
.FirstChild()->GetPrefISize(aRenderingContext
);
304 nsPoint
ViewportFrame::AdjustReflowInputForScrollbars(
305 ReflowInput
* aReflowInput
) const {
306 // Get our prinicpal child frame and see if we're scrollable
307 nsIFrame
* kidFrame
= mFrames
.FirstChild();
308 nsIScrollableFrame
* scrollingFrame
= do_QueryFrame(kidFrame
);
310 if (scrollingFrame
) {
311 WritingMode wm
= aReflowInput
->GetWritingMode();
312 LogicalMargin
scrollbars(wm
, scrollingFrame
->GetActualScrollbarSizes());
313 aReflowInput
->SetComputedISize(
314 aReflowInput
->ComputedISize() - scrollbars
.IStartEnd(wm
),
315 ReflowInput::ResetResizeFlags::No
);
316 aReflowInput
->SetAvailableISize(aReflowInput
->AvailableISize() -
317 scrollbars
.IStartEnd(wm
));
318 aReflowInput
->SetComputedBSize(
319 aReflowInput
->ComputedBSize() - scrollbars
.BStartEnd(wm
),
320 ReflowInput::ResetResizeFlags::No
);
321 return nsPoint(scrollbars
.Left(wm
), scrollbars
.Top(wm
));
323 return nsPoint(0, 0);
326 nsRect
ViewportFrame::AdjustReflowInputAsContainingBlock(
327 ReflowInput
* aReflowInput
) const {
331 AdjustReflowInputForScrollbars(aReflowInput
);
333 NS_ASSERTION(GetAbsoluteContainingBlock()->GetChildList().IsEmpty() ||
334 (offset
.x
== 0 && offset
.y
== 0),
335 "We don't handle correct positioning of fixed frames with "
336 "scrollbars in odd positions");
338 nsRect
rect(0, 0, aReflowInput
->ComputedWidth(),
339 aReflowInput
->ComputedHeight());
341 rect
.SizeTo(AdjustViewportSizeForFixedPosition(rect
));
346 void ViewportFrame::Reflow(nsPresContext
* aPresContext
,
347 ReflowOutput
& aDesiredSize
,
348 const ReflowInput
& aReflowInput
,
349 nsReflowStatus
& aStatus
) {
351 DO_GLOBAL_REFLOW_COUNT("ViewportFrame");
352 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aDesiredSize
, aStatus
);
353 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
354 NS_FRAME_TRACE_REFLOW_IN("ViewportFrame::Reflow");
356 // Because |Reflow| sets ComputedBSize() on the child to our
358 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE
);
360 // Set our size up front, since some parts of reflow depend on it
361 // being already set. Note that the computed height may be
362 // unconstrained; that's ok. Consumers should watch out for that.
363 SetSize(nsSize(aReflowInput
.ComputedWidth(), aReflowInput
.ComputedHeight()));
365 // Reflow the main content first so that the placeholders of the
366 // fixed-position frames will be in the right places on an initial
368 nscoord kidBSize
= 0;
369 WritingMode wm
= aReflowInput
.GetWritingMode();
371 if (mFrames
.NotEmpty()) {
372 // Deal with a non-incremental reflow or an incremental reflow
373 // targeted at our one-and-only principal child frame.
374 if (aReflowInput
.ShouldReflowAllKids() ||
375 mFrames
.FirstChild()->IsSubtreeDirty()) {
376 // Reflow our one-and-only principal child frame
377 nsIFrame
* kidFrame
= mFrames
.FirstChild();
378 ReflowOutput
kidDesiredSize(aReflowInput
);
379 const WritingMode kidWM
= kidFrame
->GetWritingMode();
380 LogicalSize availableSpace
= aReflowInput
.AvailableSize(kidWM
);
381 ReflowInput
kidReflowInput(aPresContext
, aReflowInput
, kidFrame
,
385 kidReflowInput
.SetComputedBSize(aReflowInput
.ComputedBSize());
386 ReflowChild(kidFrame
, aPresContext
, kidDesiredSize
, kidReflowInput
, 0, 0,
387 ReflowChildFlags::Default
, aStatus
);
388 kidBSize
= kidDesiredSize
.BSize(wm
);
390 FinishReflowChild(kidFrame
, aPresContext
, kidDesiredSize
, &kidReflowInput
,
391 0, 0, ReflowChildFlags::Default
);
393 kidBSize
= LogicalSize(wm
, mFrames
.FirstChild()->GetSize()).BSize(wm
);
397 NS_ASSERTION(aReflowInput
.AvailableISize() != NS_UNCONSTRAINEDSIZE
,
398 "shouldn't happen anymore");
400 // Return the max size as our desired size
401 LogicalSize
maxSize(wm
, aReflowInput
.AvailableISize(),
402 // Being flowed initially at an unconstrained block size
403 // means we should return our child's intrinsic size.
404 aReflowInput
.ComputedBSize() != NS_UNCONSTRAINEDSIZE
405 ? aReflowInput
.ComputedBSize()
407 aDesiredSize
.SetSize(wm
, maxSize
);
408 aDesiredSize
.SetOverflowAreasToDesiredBounds();
410 if (HasAbsolutelyPositionedChildren()) {
411 // Make a copy of the reflow input and change the computed width and height
412 // to reflect the available space for the fixed items
413 ReflowInput
reflowInput(aReflowInput
);
415 if (reflowInput
.AvailableBSize() == NS_UNCONSTRAINEDSIZE
) {
416 // We have an intrinsic-block-size document with abs-pos/fixed-pos
417 // children. Set the available block-size and computed block-size to our
418 // chosen block-size.
419 reflowInput
.SetAvailableBSize(maxSize
.BSize(wm
));
420 // Not having border/padding simplifies things
421 NS_ASSERTION(reflowInput
.ComputedPhysicalBorderPadding() == nsMargin(),
422 "Viewports can't have border/padding");
423 reflowInput
.SetComputedBSize(maxSize
.BSize(wm
));
426 nsRect rect
= AdjustReflowInputAsContainingBlock(&reflowInput
);
427 AbsPosReflowFlags flags
=
428 AbsPosReflowFlags::CBWidthAndHeightChanged
; // XXX could be optimized
429 GetAbsoluteContainingBlock()->Reflow(this, aPresContext
, reflowInput
,
430 aStatus
, rect
, flags
,
431 /* aOverflowAreas = */ nullptr);
434 if (mFrames
.NotEmpty()) {
435 ConsiderChildOverflow(aDesiredSize
.mOverflowAreas
, mFrames
.FirstChild());
438 // If we were dirty then do a repaint
439 if (HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
443 // Clipping is handled by the document container (e.g., nsSubDocumentFrame),
444 // so we don't need to change our overflow areas.
445 FinishAndStoreOverflow(&aDesiredSize
);
447 NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus
);
450 void ViewportFrame::UpdateStyle(ServoRestyleState
& aRestyleState
) {
451 RefPtr
<ComputedStyle
> newStyle
=
452 aRestyleState
.StyleSet().ResolveInheritingAnonymousBoxStyle(
453 Style()->GetPseudoType(), nullptr);
455 MOZ_ASSERT(!GetNextContinuation(), "Viewport has continuations?");
456 SetComputedStyle(newStyle
);
458 UpdateStyleOfOwnedAnonBoxes(aRestyleState
);
461 void ViewportFrame::AppendDirectlyOwnedAnonBoxes(
462 nsTArray
<OwnedAnonBox
>& aResult
) {
463 if (mFrames
.NotEmpty()) {
464 aResult
.AppendElement(mFrames
.FirstChild());
468 nsSize
ViewportFrame::AdjustViewportSizeForFixedPosition(
469 const nsRect
& aViewportRect
) const {
470 nsSize result
= aViewportRect
.Size();
472 mozilla::PresShell
* presShell
= PresShell();
473 // Layout fixed position elements to the visual viewport size if and only if
474 // it has been set and it is larger than the computed size, otherwise use the
476 if (presShell
->IsVisualViewportSizeSet()) {
477 if (presShell
->GetDynamicToolbarState() == DynamicToolbarState::Collapsed
&&
478 result
< presShell
->GetVisualViewportSizeUpdatedByDynamicToolbar()) {
479 // We need to use the viewport size updated by the dynamic toolbar in the
480 // case where the dynamic toolbar is completely hidden.
481 result
= presShell
->GetVisualViewportSizeUpdatedByDynamicToolbar();
482 } else if (result
< presShell
->GetVisualViewportSize()) {
483 result
= presShell
->GetVisualViewportSize();
486 // Expand the size to the layout viewport size if necessary.
487 const nsSize layoutViewportSize
= presShell
->GetLayoutViewportSize();
488 if (result
< layoutViewportSize
) {
489 result
= layoutViewportSize
;
495 #ifdef DEBUG_FRAME_DUMP
496 nsresult
ViewportFrame::GetFrameName(nsAString
& aResult
) const {
497 return MakeFrameName(u
"Viewport"_ns
, aResult
);