1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 /*
7 * rendering object that is the root of the frame tree, which contains
8 * the document's scrollbars and contains fixed-positioned elements
9 */
10
11 #include "nsViewportFrame.h"
12 #include "nsGkAtoms.h"
13 #include "nsIScrollableFrame.h"
14 #include "nsSubDocumentFrame.h"
15 #include "nsCanvasFrame.h"
16 #include "nsAbsoluteContainingBlock.h"
17 #include "GeckoProfiler.h"
18 #include "nsIMozBrowserFrame.h"
19
20 using namespace mozilla;
21 typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
22
23 ViewportFrame*
NS_NewViewportFrame(nsIPresShell * aPresShell,nsStyleContext * aContext)24 NS_NewViewportFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
25 {
26 return new (aPresShell) ViewportFrame(aContext);
27 }
28
29 NS_IMPL_FRAMEARENA_HELPERS(ViewportFrame)
NS_QUERYFRAME_HEAD(ViewportFrame)30 NS_QUERYFRAME_HEAD(ViewportFrame)
31 NS_QUERYFRAME_ENTRY(ViewportFrame)
32 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
33
34 void
35 ViewportFrame::Init(nsIContent* aContent,
36 nsContainerFrame* aParent,
37 nsIFrame* aPrevInFlow)
38 {
39 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
40
41 nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(this);
42 if (parent) {
43 nsFrameState state = parent->GetStateBits();
44
45 mState |= state & (NS_FRAME_IN_POPUP);
46 }
47 }
48
49 void
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsRect & aDirtyRect,const nsDisplayListSet & aLists)50 ViewportFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
51 const nsRect& aDirtyRect,
52 const nsDisplayListSet& aLists)
53 {
54 PROFILER_LABEL("ViewportFrame", "BuildDisplayList",
55 js::ProfileEntry::Category::GRAPHICS);
56
57 if (nsIFrame* kid = mFrames.FirstChild()) {
58 // make the kid's BorderBackground our own. This ensures that the canvas
59 // frame's background becomes our own background and therefore appears
60 // below negative z-index elements.
61 BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
62 }
63
64 nsDisplayList topLayerList;
65 BuildDisplayListForTopLayer(aBuilder, &topLayerList);
66 if (!topLayerList.IsEmpty()) {
67 // Wrap the whole top layer in a single item with maximum z-index,
68 // and append it at the very end, so that it stays at the topmost.
69 nsDisplayWrapList* wrapList =
70 new (aBuilder) nsDisplayWrapList(aBuilder, this, &topLayerList);
71 wrapList->SetOverrideZIndex(
72 std::numeric_limits<decltype(wrapList->ZIndex())>::max());
73 aLists.PositionedDescendants()->AppendNewToTop(wrapList);
74 }
75 }
76
77 #ifdef DEBUG
78 /**
79 * Returns whether we are going to put an element in the top layer for
80 * fullscreen. This function should matches the CSS rule in ua.css.
81 */
82 static bool
ShouldInTopLayerForFullscreen(Element * aElement)83 ShouldInTopLayerForFullscreen(Element* aElement)
84 {
85 if (!aElement->GetParent()) {
86 return false;
87 }
88 nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aElement);
89 if (browserFrame && browserFrame->GetReallyIsBrowserOrApp()) {
90 return false;
91 }
92 return true;
93 }
94 #endif // DEBUG
95
96 static void
BuildDisplayListForTopLayerFrame(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,nsDisplayList * aList)97 BuildDisplayListForTopLayerFrame(nsDisplayListBuilder* aBuilder,
98 nsIFrame* aFrame,
99 nsDisplayList* aList)
100 {
101 nsRect dirty;
102 DisplayListClipState::AutoClipMultiple clipState(aBuilder);
103 nsDisplayListBuilder::OutOfFlowDisplayData*
104 savedOutOfFlowData = nsDisplayListBuilder::GetOutOfFlowData(aFrame);
105 if (savedOutOfFlowData) {
106 dirty = savedOutOfFlowData->mDirtyRect;
107 clipState.SetClipForContainingBlockDescendants(
108 &savedOutOfFlowData->mContainingBlockClip);
109 clipState.SetScrollClipForContainingBlockDescendants(
110 aBuilder, savedOutOfFlowData->mContainingBlockScrollClip);
111 }
112 nsDisplayList list;
113 aFrame->BuildDisplayListForStackingContext(aBuilder, dirty, &list);
114 aList->AppendToTop(&list);
115 }
116
117 void
BuildDisplayListForTopLayer(nsDisplayListBuilder * aBuilder,nsDisplayList * aList)118 ViewportFrame::BuildDisplayListForTopLayer(nsDisplayListBuilder* aBuilder,
119 nsDisplayList* aList)
120 {
121 nsIDocument* doc = PresContext()->Document();
122 nsTArray<Element*> fullscreenStack = doc->GetFullscreenStack();
123 for (Element* elem : fullscreenStack) {
124 if (nsIFrame* frame = elem->GetPrimaryFrame()) {
125 // There are two cases where an element in fullscreen is not in
126 // the top layer:
127 // 1. When building display list for purpose other than painting,
128 // it is possible that there is inconsistency between the style
129 // info and the content tree.
130 // 2. This is an element which we are not going to put in the top
131 // layer for fullscreen. See ShouldInTopLayerForFullscreen().
132 // In both cases, we want to skip the frame here and paint it in
133 // the normal path.
134 if (frame->StyleDisplay()->mTopLayer == NS_STYLE_TOP_LAYER_NONE) {
135 MOZ_ASSERT(!aBuilder->IsForPainting() ||
136 !ShouldInTopLayerForFullscreen(elem));
137 continue;
138 }
139 MOZ_ASSERT(ShouldInTopLayerForFullscreen(elem));
140 // Inner SVG, MathML elements, as well as children of some XUL
141 // elements are not allowed to be out-of-flow. They should not
142 // be handled as top layer element here.
143 if (!(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
144 MOZ_ASSERT(!elem->GetParent()->IsHTMLElement(), "HTML element "
145 "should always be out-of-flow if in the top layer");
146 continue;
147 }
148 if (nsIFrame* backdropPh =
149 frame->GetChildList(kBackdropList).FirstChild()) {
150 MOZ_ASSERT(backdropPh->GetType() == nsGkAtoms::placeholderFrame);
151 nsIFrame* backdropFrame =
152 static_cast<nsPlaceholderFrame*>(backdropPh)->GetOutOfFlowFrame();
153 MOZ_ASSERT(backdropFrame);
154 BuildDisplayListForTopLayerFrame(aBuilder, backdropFrame, aList);
155 }
156 BuildDisplayListForTopLayerFrame(aBuilder, frame, aList);
157 }
158 }
159
160 nsIPresShell* shell = PresContext()->PresShell();
161 if (nsCanvasFrame* canvasFrame = shell->GetCanvasFrame()) {
162 if (Element* container = canvasFrame->GetCustomContentContainer()) {
163 if (nsIFrame* frame = container->GetPrimaryFrame()) {
164 BuildDisplayListForTopLayerFrame(aBuilder, frame, aList);
165 }
166 }
167 }
168 }
169
170 #ifdef DEBUG
171 void
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)172 ViewportFrame::AppendFrames(ChildListID aListID,
173 nsFrameList& aFrameList)
174 {
175 NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
176 NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!");
177 nsContainerFrame::AppendFrames(aListID, aFrameList);
178 }
179
180 void
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,nsFrameList & aFrameList)181 ViewportFrame::InsertFrames(ChildListID aListID,
182 nsIFrame* aPrevFrame,
183 nsFrameList& aFrameList)
184 {
185 NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
186 NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!");
187 nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
188 }
189
190 void
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)191 ViewportFrame::RemoveFrame(ChildListID aListID,
192 nsIFrame* aOldFrame)
193 {
194 NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
195 nsContainerFrame::RemoveFrame(aListID, aOldFrame);
196 }
197 #endif
198
199 /* virtual */ nscoord
GetMinISize(nsRenderingContext * aRenderingContext)200 ViewportFrame::GetMinISize(nsRenderingContext *aRenderingContext)
201 {
202 nscoord result;
203 DISPLAY_MIN_WIDTH(this, result);
204 if (mFrames.IsEmpty())
205 result = 0;
206 else
207 result = mFrames.FirstChild()->GetMinISize(aRenderingContext);
208
209 return result;
210 }
211
212 /* virtual */ nscoord
GetPrefISize(nsRenderingContext * aRenderingContext)213 ViewportFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
214 {
215 nscoord result;
216 DISPLAY_PREF_WIDTH(this, result);
217 if (mFrames.IsEmpty())
218 result = 0;
219 else
220 result = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
221
222 return result;
223 }
224
225 nsPoint
AdjustReflowInputForScrollbars(ReflowInput * aReflowInput) const226 ViewportFrame::AdjustReflowInputForScrollbars(ReflowInput* aReflowInput) const
227 {
228 // Get our prinicpal child frame and see if we're scrollable
229 nsIFrame* kidFrame = mFrames.FirstChild();
230 nsIScrollableFrame* scrollingFrame = do_QueryFrame(kidFrame);
231
232 if (scrollingFrame) {
233 WritingMode wm = aReflowInput->GetWritingMode();
234 LogicalMargin scrollbars(wm, scrollingFrame->GetActualScrollbarSizes());
235 aReflowInput->SetComputedISize(aReflowInput->ComputedISize() -
236 scrollbars.IStartEnd(wm));
237 aReflowInput->AvailableISize() -= scrollbars.IStartEnd(wm);
238 aReflowInput->SetComputedBSizeWithoutResettingResizeFlags(
239 aReflowInput->ComputedBSize() - scrollbars.BStartEnd(wm));
240 return nsPoint(scrollbars.Left(wm), scrollbars.Top(wm));
241 }
242 return nsPoint(0, 0);
243 }
244
245 nsRect
AdjustReflowInputAsContainingBlock(ReflowInput * aReflowInput) const246 ViewportFrame::AdjustReflowInputAsContainingBlock(ReflowInput* aReflowInput) const
247 {
248 #ifdef DEBUG
249 nsPoint offset =
250 #endif
251 AdjustReflowInputForScrollbars(aReflowInput);
252
253 NS_ASSERTION(GetAbsoluteContainingBlock()->GetChildList().IsEmpty() ||
254 (offset.x == 0 && offset.y == 0),
255 "We don't handle correct positioning of fixed frames with "
256 "scrollbars in odd positions");
257
258 // If a scroll position clamping scroll-port size has been set, layout
259 // fixed position elements to this size instead of the computed size.
260 nsRect rect(0, 0, aReflowInput->ComputedWidth(), aReflowInput->ComputedHeight());
261 nsIPresShell* ps = PresContext()->PresShell();
262 if (ps->IsScrollPositionClampingScrollPortSizeSet()) {
263 rect.SizeTo(ps->GetScrollPositionClampingScrollPortSize());
264 }
265
266 return rect;
267 }
268
269 void
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)270 ViewportFrame::Reflow(nsPresContext* aPresContext,
271 ReflowOutput& aDesiredSize,
272 const ReflowInput& aReflowInput,
273 nsReflowStatus& aStatus)
274 {
275 MarkInReflow();
276 DO_GLOBAL_REFLOW_COUNT("ViewportFrame");
277 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
278 NS_FRAME_TRACE_REFLOW_IN("ViewportFrame::Reflow");
279
280 // Initialize OUT parameters
281 aStatus = NS_FRAME_COMPLETE;
282
283 // Because |Reflow| sets ComputedBSize() on the child to our
284 // ComputedBSize().
285 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
286
287 // Set our size up front, since some parts of reflow depend on it
288 // being already set. Note that the computed height may be
289 // unconstrained; that's ok. Consumers should watch out for that.
290 SetSize(nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight()));
291
292 // Reflow the main content first so that the placeholders of the
293 // fixed-position frames will be in the right places on an initial
294 // reflow.
295 nscoord kidBSize = 0;
296 WritingMode wm = aReflowInput.GetWritingMode();
297
298 if (mFrames.NotEmpty()) {
299 // Deal with a non-incremental reflow or an incremental reflow
300 // targeted at our one-and-only principal child frame.
301 if (aReflowInput.ShouldReflowAllKids() ||
302 aReflowInput.IsBResize() ||
303 NS_SUBTREE_DIRTY(mFrames.FirstChild())) {
304 // Reflow our one-and-only principal child frame
305 nsIFrame* kidFrame = mFrames.FirstChild();
306 ReflowOutput kidDesiredSize(aReflowInput);
307 WritingMode wm = kidFrame->GetWritingMode();
308 LogicalSize availableSpace = aReflowInput.AvailableSize(wm);
309 ReflowInput kidReflowInput(aPresContext, aReflowInput,
310 kidFrame, availableSpace);
311
312 // Reflow the frame
313 kidReflowInput.SetComputedBSize(aReflowInput.ComputedBSize());
314 ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput,
315 0, 0, 0, aStatus);
316 kidBSize = kidDesiredSize.BSize(wm);
317
318 FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, nullptr, 0, 0, 0);
319 } else {
320 kidBSize = LogicalSize(wm, mFrames.FirstChild()->GetSize()).BSize(wm);
321 }
322 }
323
324 NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE,
325 "shouldn't happen anymore");
326
327 // Return the max size as our desired size
328 LogicalSize maxSize(wm, aReflowInput.AvailableISize(),
329 // Being flowed initially at an unconstrained block size
330 // means we should return our child's intrinsic size.
331 aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE
332 ? aReflowInput.ComputedBSize()
333 : kidBSize);
334 aDesiredSize.SetSize(wm, maxSize);
335 aDesiredSize.SetOverflowAreasToDesiredBounds();
336
337 if (HasAbsolutelyPositionedChildren()) {
338 // Make a copy of the reflow state and change the computed width and height
339 // to reflect the available space for the fixed items
340 ReflowInput reflowInput(aReflowInput);
341
342 if (reflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
343 // We have an intrinsic-height document with abs-pos/fixed-pos children.
344 // Set the available height and mComputedHeight to our chosen height.
345 reflowInput.AvailableBSize() = maxSize.BSize(wm);
346 // Not having border/padding simplifies things
347 NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0,0,0,0),
348 "Viewports can't have border/padding");
349 reflowInput.SetComputedBSize(maxSize.BSize(wm));
350 }
351
352 nsRect rect = AdjustReflowInputAsContainingBlock(&reflowInput);
353 nsOverflowAreas* overflowAreas = &aDesiredSize.mOverflowAreas;
354 nsIScrollableFrame* rootScrollFrame =
355 aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
356 if (rootScrollFrame && !rootScrollFrame->IsIgnoringViewportClipping()) {
357 overflowAreas = nullptr;
358 }
359 AbsPosReflowFlags flags =
360 AbsPosReflowFlags::eCBWidthAndHeightChanged; // XXX could be optimized
361 GetAbsoluteContainingBlock()->Reflow(this, aPresContext, reflowInput, aStatus,
362 rect, flags, overflowAreas);
363 }
364
365 if (mFrames.NotEmpty()) {
366 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, mFrames.FirstChild());
367 }
368
369 // If we were dirty then do a repaint
370 if (GetStateBits() & NS_FRAME_IS_DIRTY) {
371 InvalidateFrame();
372 }
373
374 // Clipping is handled by the document container (e.g., nsSubDocumentFrame),
375 // so we don't need to change our overflow areas.
376 bool overflowChanged = FinishAndStoreOverflow(&aDesiredSize);
377 if (overflowChanged) {
378 // We may need to alert our container to get it to pick up the
379 // overflow change.
380 nsSubDocumentFrame* container = static_cast<nsSubDocumentFrame*>
381 (nsLayoutUtils::GetCrossDocParentFrame(this));
382 if (container && !container->ShouldClipSubdocument()) {
383 container->PresContext()->PresShell()->
384 FrameNeedsReflow(container, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
385 }
386 }
387
388 NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus);
389 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
390 }
391
392 bool
ComputeCustomOverflow(nsOverflowAreas & aOverflowAreas)393 ViewportFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
394 {
395 nsIScrollableFrame* rootScrollFrame =
396 PresContext()->PresShell()->GetRootScrollFrameAsScrollable();
397 if (rootScrollFrame && !rootScrollFrame->IsIgnoringViewportClipping()) {
398 return false;
399 }
400
401 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
402 }
403
404 nsIAtom*
GetType() const405 ViewportFrame::GetType() const
406 {
407 return nsGkAtoms::viewportFrame;
408 }
409
410 #ifdef DEBUG_FRAME_DUMP
411 nsresult
GetFrameName(nsAString & aResult) const412 ViewportFrame::GetFrameName(nsAString& aResult) const
413 {
414 return MakeFrameName(NS_LITERAL_STRING("Viewport"), aResult);
415 }
416 #endif
417