Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / generic / ViewportFrame.cpp
bloba77ade73ef109626d3500c9d469e974a33ee71f3
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/. */
7 /*
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);
48 if (parent) {
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();
61 if (!kid) {
62 return;
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)) {
73 if (isOpaque) {
74 set.DeleteAll(aBuilder);
76 set.PositionedDescendants()->AppendToTop(list);
80 set.MoveTo(aLists);
83 #ifdef DEBUG
84 /**
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()) {
90 return false;
92 nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aElement);
93 if (browserFrame && browserFrame->GetReallyIsBrowser()) {
94 return false;
96 return true;
98 #endif // DEBUG
100 static void BuildDisplayListForTopLayerFrame(nsDisplayListBuilder* aBuilder,
101 nsIFrame* aFrame,
102 nsDisplayList* aList) {
103 nsRect visible;
104 nsRect dirty;
105 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
106 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
107 nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData =
108 nsDisplayListBuilder::GetOutOfFlowData(aFrame);
109 if (savedOutOfFlowData) {
110 visible =
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) {
141 return false;
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()) {
148 return false;
151 nsDisplayList* children = fixed->GetChildren();
152 if (!children->GetTop() ||
153 children->GetTop()->GetType() != DisplayItemType::TYPE_BACKGROUND_COLOR) {
154 return false;
157 nsDisplayBackgroundColor* child =
158 static_cast<nsDisplayBackgroundColor*>(children->GetTop());
159 if (child->GetActiveScrolledRoot() || child->GetClipChain()) {
160 return false;
163 // Check that the background color is both opaque, and covering the
164 // whole viewport.
165 bool dummy;
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();
177 if (!frame) {
178 continue;
181 if (frame->IsHiddenByContentVisibilityOnAnyAncestor(
182 nsIFrame::IncludeContentVisibility::Hidden)) {
183 continue;
186 // There are two cases where an element in fullscreen is not in
187 // the top layer:
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
194 // the normal path.
195 if (frame->StyleDisplay()->mTopLayer == StyleTopLayer::None) {
196 MOZ_ASSERT(!aBuilder->IsForPainting() ||
197 !ShouldInTopLayerForFullscreen(elem));
198 continue;
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 "
207 "layer");
208 continue;
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);
219 if (aIsOpaque) {
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()) {
237 return nullptr;
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(),
247 false);
248 if (!wrapList) {
249 return nullptr;
251 wrapList->SetOverrideZIndex(
252 std::numeric_limits<decltype(wrapList->ZIndex())>::max());
253 return wrapList;
256 #ifdef DEBUG
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);
278 #endif
280 /* virtual */
281 nscoord ViewportFrame::GetMinISize(gfxContext* aRenderingContext) {
282 nscoord result;
283 DISPLAY_MIN_INLINE_SIZE(this, result);
284 if (mFrames.IsEmpty())
285 result = 0;
286 else
287 result = mFrames.FirstChild()->GetMinISize(aRenderingContext);
289 return result;
292 /* virtual */
293 nscoord ViewportFrame::GetPrefISize(gfxContext* aRenderingContext) {
294 nscoord result;
295 DISPLAY_PREF_INLINE_SIZE(this, result);
296 if (mFrames.IsEmpty())
297 result = 0;
298 else
299 result = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
301 return result;
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 {
328 #ifdef DEBUG
329 nsPoint offset =
330 #endif
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));
343 return rect;
346 void ViewportFrame::Reflow(nsPresContext* aPresContext,
347 ReflowOutput& aDesiredSize,
348 const ReflowInput& aReflowInput,
349 nsReflowStatus& aStatus) {
350 MarkInReflow();
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
357 // ComputedBSize().
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
367 // reflow.
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,
382 availableSpace);
384 // Reflow the frame
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);
392 } else {
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()
406 : kidBSize);
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)) {
440 InvalidateFrame();
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
475 // computed size.
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;
492 return result;
495 #ifdef DEBUG_FRAME_DUMP
496 nsresult ViewportFrame::GetFrameName(nsAString& aResult) const {
497 return MakeFrameName(u"Viewport"_ns, aResult);
499 #endif