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