Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/generic/ViewportFrame.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
6
7
/*
8
 * rendering object that is the root of the frame tree, which contains
9
 * the document's scrollbars and contains fixed-positioned elements
10
 */
11
12
#include "mozilla/ViewportFrame.h"
13
14
#include "mozilla/ComputedStyleInlines.h"
15
#include "mozilla/RestyleManager.h"
16
#include "nsGkAtoms.h"
17
#include "nsIScrollableFrame.h"
18
#include "nsSubDocumentFrame.h"
19
#include "nsCanvasFrame.h"
20
#include "nsAbsoluteContainingBlock.h"
21
#include "GeckoProfiler.h"
22
#include "nsIMozBrowserFrame.h"
23
#include "nsPlaceholderFrame.h"
24
25
using namespace mozilla;
26
typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
27
28
ViewportFrame*
29
NS_NewViewportFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
30
0
{
31
0
  return new (aPresShell) ViewportFrame(aStyle);
32
0
}
33
34
NS_IMPL_FRAMEARENA_HELPERS(ViewportFrame)
35
0
NS_QUERYFRAME_HEAD(ViewportFrame)
36
0
  NS_QUERYFRAME_ENTRY(ViewportFrame)
37
0
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
38
39
void
40
ViewportFrame::Init(nsIContent*       aContent,
41
                    nsContainerFrame* aParent,
42
                    nsIFrame*         aPrevInFlow)
43
0
{
44
0
  nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
45
0
  // No need to call CreateView() here - the frame ctor will call SetView()
46
0
  // with the ViewManager's root view, so we'll assign it in SetViewInternal().
47
0
48
0
  nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(this);
49
0
  if (parent) {
50
0
    nsFrameState state = parent->GetStateBits();
51
0
52
0
    AddStateBits(state & (NS_FRAME_IN_POPUP));
53
0
  }
54
0
}
55
56
void
57
ViewportFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
58
                                const nsDisplayListSet& aLists)
59
0
{
60
0
  AUTO_PROFILER_LABEL("ViewportFrame::BuildDisplayList", GRAPHICS);
61
0
62
0
  if (nsIFrame* kid = mFrames.FirstChild()) {
63
0
    // make the kid's BorderBackground our own. This ensures that the canvas
64
0
    // frame's background becomes our own background and therefore appears
65
0
    // below negative z-index elements.
66
0
    BuildDisplayListForChild(aBuilder, kid, aLists);
67
0
  }
68
0
69
0
  nsDisplayList topLayerList;
70
0
  BuildDisplayListForTopLayer(aBuilder, &topLayerList);
71
0
  if (!topLayerList.IsEmpty()) {
72
0
    // Wrap the whole top layer in a single item with maximum z-index,
73
0
    // and append it at the very end, so that it stays at the topmost.
74
0
    nsDisplayWrapList* wrapList =
75
0
      MakeDisplayItem<nsDisplayWrapList>(aBuilder, this, &topLayerList);
76
0
    wrapList->SetOverrideZIndex(
77
0
      std::numeric_limits<decltype(wrapList->ZIndex())>::max());
78
0
    aLists.PositionedDescendants()->AppendToTop(wrapList);
79
0
  }
80
0
}
81
82
#ifdef DEBUG
83
/**
84
 * Returns whether we are going to put an element in the top layer for
85
 * fullscreen. This function should matches the CSS rule in ua.css.
86
 */
87
static bool
88
ShouldInTopLayerForFullscreen(Element* aElement)
89
{
90
  if (!aElement->GetParent()) {
91
    return false;
92
  }
93
  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aElement);
94
  if (browserFrame && browserFrame->GetReallyIsBrowser()) {
95
    return false;
96
  }
97
  return true;
98
}
99
#endif // DEBUG
100
101
static void
102
BuildDisplayListForTopLayerFrame(nsDisplayListBuilder* aBuilder,
103
                                 nsIFrame* aFrame,
104
                                 nsDisplayList* aList)
105
0
{
106
0
  nsRect visible;
107
0
  nsRect dirty;
108
0
  DisplayListClipState::AutoClipMultiple clipState(aBuilder);
109
0
  nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
110
0
  nsDisplayListBuilder::OutOfFlowDisplayData*
111
0
    savedOutOfFlowData = nsDisplayListBuilder::GetOutOfFlowData(aFrame);
112
0
  if (savedOutOfFlowData) {
113
0
    visible = savedOutOfFlowData->GetVisibleRectForFrame(aBuilder, aFrame, &dirty);
114
0
    // This function is called after we've finished building display items for
115
0
    // the root scroll frame. That means that the content clip from the root
116
0
    // scroll frame is no longer on aBuilder. However, we need to make sure
117
0
    // that the display items we build in this function have finite clipped
118
0
    // bounds with respect to the root ASR, so we restore the *combined clip*
119
0
    // that we saved earlier. The combined clip will include the clip from the
120
0
    // root scroll frame.
121
0
    clipState.SetClipChainForContainingBlockDescendants(
122
0
      savedOutOfFlowData->mCombinedClipChain);
123
0
    clipState.ClipContainingBlockDescendantsExtra(
124
0
      visible + aBuilder->ToReferenceFrame(aFrame), nullptr);
125
0
    asrSetter.SetCurrentActiveScrolledRoot(
126
0
      savedOutOfFlowData->mContainingBlockActiveScrolledRoot);
127
0
  }
128
0
  nsDisplayListBuilder::AutoBuildingDisplayList
129
0
    buildingForChild(aBuilder, aFrame, visible, dirty,
130
0
                     aBuilder->IsAtRootOfPseudoStackingContext());
131
0
132
0
  nsDisplayList list;
133
0
  aFrame->BuildDisplayListForStackingContext(aBuilder, &list);
134
0
  aList->AppendToTop(&list);
135
0
}
136
137
void
138
ViewportFrame::BuildDisplayListForTopLayer(nsDisplayListBuilder* aBuilder,
139
                                           nsDisplayList* aList)
140
0
{
141
0
  nsIDocument* doc = PresContext()->Document();
142
0
  nsTArray<Element*> fullscreenStack = doc->GetFullscreenStack();
143
0
  for (Element* elem : fullscreenStack) {
144
0
    if (nsIFrame* frame = elem->GetPrimaryFrame()) {
145
0
      // There are two cases where an element in fullscreen is not in
146
0
      // the top layer:
147
0
      // 1. When building display list for purpose other than painting,
148
0
      //    it is possible that there is inconsistency between the style
149
0
      //    info and the content tree.
150
0
      // 2. This is an element which we are not going to put in the top
151
0
      //    layer for fullscreen. See ShouldInTopLayerForFullscreen().
152
0
      // In both cases, we want to skip the frame here and paint it in
153
0
      // the normal path.
154
0
      if (frame->StyleDisplay()->mTopLayer == NS_STYLE_TOP_LAYER_NONE) {
155
0
        MOZ_ASSERT(!aBuilder->IsForPainting() ||
156
0
                   !ShouldInTopLayerForFullscreen(elem));
157
0
        continue;
158
0
      }
159
0
      MOZ_ASSERT(ShouldInTopLayerForFullscreen(elem));
160
0
      // Inner SVG, MathML elements, as well as children of some XUL
161
0
      // elements are not allowed to be out-of-flow. They should not
162
0
      // be handled as top layer element here.
163
0
      if (!(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
164
0
        MOZ_ASSERT(!elem->GetParent()->IsHTMLElement(), "HTML element "
165
0
                   "should always be out-of-flow if in the top layer");
166
0
        continue;
167
0
      }
168
0
      if (nsIFrame* backdropPh =
169
0
          frame->GetChildList(kBackdropList).FirstChild()) {
170
0
        MOZ_ASSERT(backdropPh->IsPlaceholderFrame());
171
0
        MOZ_ASSERT(!backdropPh->GetNextSibling(), "more than one ::backdrop?");
172
0
        MOZ_ASSERT(backdropPh->HasAnyStateBits(NS_FRAME_FIRST_REFLOW),
173
0
                   "did you intend to reflow ::backdrop placeholders?");
174
0
        nsIFrame* backdropFrame =
175
0
          static_cast<nsPlaceholderFrame*>(backdropPh)->GetOutOfFlowFrame();
176
0
        MOZ_ASSERT(backdropFrame);
177
0
        BuildDisplayListForTopLayerFrame(aBuilder, backdropFrame, aList);
178
0
      }
179
0
      BuildDisplayListForTopLayerFrame(aBuilder, frame, aList);
180
0
    }
181
0
  }
182
0
183
0
  nsIPresShell* shell = PresShell();
184
0
  if (nsCanvasFrame* canvasFrame = shell->GetCanvasFrame()) {
185
0
    if (Element* container = canvasFrame->GetCustomContentContainer()) {
186
0
      if (nsIFrame* frame = container->GetPrimaryFrame()) {
187
0
        MOZ_ASSERT(frame->StyleDisplay()->mTopLayer != NS_STYLE_TOP_LAYER_NONE,
188
0
                   "ua.css should ensure this");
189
0
        MOZ_ASSERT(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW);
190
0
        BuildDisplayListForTopLayerFrame(aBuilder, frame, aList);
191
0
      }
192
0
    }
193
0
  }
194
0
}
195
196
#ifdef DEBUG
197
void
198
ViewportFrame::AppendFrames(ChildListID     aListID,
199
                            nsFrameList&    aFrameList)
200
{
201
  NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
202
  NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!");
203
  nsContainerFrame::AppendFrames(aListID, aFrameList);
204
}
205
206
void
207
ViewportFrame::InsertFrames(ChildListID     aListID,
208
                            nsIFrame*       aPrevFrame,
209
                            nsFrameList&    aFrameList)
210
{
211
  NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
212
  NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!");
213
  nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
214
}
215
216
void
217
ViewportFrame::RemoveFrame(ChildListID     aListID,
218
                           nsIFrame*       aOldFrame)
219
{
220
  NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
221
  nsContainerFrame::RemoveFrame(aListID, aOldFrame);
222
}
223
#endif
224
225
/* virtual */ nscoord
226
ViewportFrame::GetMinISize(gfxContext *aRenderingContext)
227
0
{
228
0
  nscoord result;
229
0
  DISPLAY_MIN_INLINE_SIZE(this, result);
230
0
  if (mFrames.IsEmpty())
231
0
    result = 0;
232
0
  else
233
0
    result = mFrames.FirstChild()->GetMinISize(aRenderingContext);
234
0
235
0
  return result;
236
0
}
237
238
/* virtual */ nscoord
239
ViewportFrame::GetPrefISize(gfxContext *aRenderingContext)
240
0
{
241
0
  nscoord result;
242
0
  DISPLAY_PREF_INLINE_SIZE(this, result);
243
0
  if (mFrames.IsEmpty())
244
0
    result = 0;
245
0
  else
246
0
    result = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
247
0
248
0
  return result;
249
0
}
250
251
nsPoint
252
ViewportFrame::AdjustReflowInputForScrollbars(ReflowInput* aReflowInput) const
253
0
{
254
0
  // Get our prinicpal child frame and see if we're scrollable
255
0
  nsIFrame* kidFrame = mFrames.FirstChild();
256
0
  nsIScrollableFrame* scrollingFrame = do_QueryFrame(kidFrame);
257
0
258
0
  if (scrollingFrame) {
259
0
    WritingMode wm = aReflowInput->GetWritingMode();
260
0
    LogicalMargin scrollbars(wm, scrollingFrame->GetActualScrollbarSizes());
261
0
    aReflowInput->SetComputedISize(aReflowInput->ComputedISize() -
262
0
                                   scrollbars.IStartEnd(wm));
263
0
    aReflowInput->AvailableISize() -= scrollbars.IStartEnd(wm);
264
0
    aReflowInput->SetComputedBSizeWithoutResettingResizeFlags(
265
0
      aReflowInput->ComputedBSize() - scrollbars.BStartEnd(wm));
266
0
    return nsPoint(scrollbars.Left(wm), scrollbars.Top(wm));
267
0
  }
268
0
  return nsPoint(0, 0);
269
0
}
270
271
nsRect
272
ViewportFrame::AdjustReflowInputAsContainingBlock(ReflowInput* aReflowInput) const
273
0
{
274
#ifdef DEBUG
275
  nsPoint offset =
276
#endif
277
    AdjustReflowInputForScrollbars(aReflowInput);
278
0
279
0
  NS_ASSERTION(GetAbsoluteContainingBlock()->GetChildList().IsEmpty() ||
280
0
               (offset.x == 0 && offset.y == 0),
281
0
               "We don't handle correct positioning of fixed frames with "
282
0
               "scrollbars in odd positions");
283
0
284
0
  // Layout fixed position elements to the visual viewport size if and only if
285
0
  // it has been set and it is larger than the computed size, otherwise use the
286
0
  // computed size.
287
0
  nsRect rect(0, 0, aReflowInput->ComputedWidth(), aReflowInput->ComputedHeight());
288
0
  nsIPresShell* ps = PresShell();
289
0
  if (ps->IsVisualViewportSizeSet() && rect.Size() < ps->GetVisualViewportSize()) {
290
0
    rect.SizeTo(ps->GetVisualViewportSize());
291
0
  }
292
0
  return rect;
293
0
}
294
295
void
296
ViewportFrame::Reflow(nsPresContext*           aPresContext,
297
                      ReflowOutput&     aDesiredSize,
298
                      const ReflowInput& aReflowInput,
299
                      nsReflowStatus&          aStatus)
300
0
{
301
0
  MarkInReflow();
302
0
  DO_GLOBAL_REFLOW_COUNT("ViewportFrame");
303
0
  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
304
0
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
305
0
  NS_FRAME_TRACE_REFLOW_IN("ViewportFrame::Reflow");
306
0
307
0
  // Because |Reflow| sets ComputedBSize() on the child to our
308
0
  // ComputedBSize().
309
0
  AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
310
0
311
0
  // Set our size up front, since some parts of reflow depend on it
312
0
  // being already set.  Note that the computed height may be
313
0
  // unconstrained; that's ok.  Consumers should watch out for that.
314
0
  SetSize(nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight()));
315
0
316
0
  // Reflow the main content first so that the placeholders of the
317
0
  // fixed-position frames will be in the right places on an initial
318
0
  // reflow.
319
0
  nscoord kidBSize = 0;
320
0
  WritingMode wm = aReflowInput.GetWritingMode();
321
0
322
0
  if (mFrames.NotEmpty()) {
323
0
    // Deal with a non-incremental reflow or an incremental reflow
324
0
    // targeted at our one-and-only principal child frame.
325
0
    if (aReflowInput.ShouldReflowAllKids() ||
326
0
        aReflowInput.IsBResize() ||
327
0
        NS_SUBTREE_DIRTY(mFrames.FirstChild())) {
328
0
      // Reflow our one-and-only principal child frame
329
0
      nsIFrame*           kidFrame = mFrames.FirstChild();
330
0
      ReflowOutput kidDesiredSize(aReflowInput);
331
0
      WritingMode         wm = kidFrame->GetWritingMode();
332
0
      LogicalSize         availableSpace = aReflowInput.AvailableSize(wm);
333
0
      ReflowInput   kidReflowInput(aPresContext, aReflowInput,
334
0
                                         kidFrame, availableSpace);
335
0
336
0
      // Reflow the frame
337
0
      kidReflowInput.SetComputedBSize(aReflowInput.ComputedBSize());
338
0
      ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput,
339
0
                  0, 0, 0, aStatus);
340
0
      kidBSize = kidDesiredSize.BSize(wm);
341
0
342
0
      FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, nullptr, 0, 0, 0);
343
0
    } else {
344
0
      kidBSize = LogicalSize(wm, mFrames.FirstChild()->GetSize()).BSize(wm);
345
0
    }
346
0
  }
347
0
348
0
  NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE,
349
0
               "shouldn't happen anymore");
350
0
351
0
  // Return the max size as our desired size
352
0
  LogicalSize maxSize(wm, aReflowInput.AvailableISize(),
353
0
                      // Being flowed initially at an unconstrained block size
354
0
                      // means we should return our child's intrinsic size.
355
0
                      aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE
356
0
                        ? aReflowInput.ComputedBSize()
357
0
                        : kidBSize);
358
0
  aDesiredSize.SetSize(wm, maxSize);
359
0
  aDesiredSize.SetOverflowAreasToDesiredBounds();
360
0
361
0
  if (HasAbsolutelyPositionedChildren()) {
362
0
    // Make a copy of the reflow state and change the computed width and height
363
0
    // to reflect the available space for the fixed items
364
0
    ReflowInput reflowInput(aReflowInput);
365
0
366
0
    if (reflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
367
0
      // We have an intrinsic-height document with abs-pos/fixed-pos children.
368
0
      // Set the available height and mComputedHeight to our chosen height.
369
0
      reflowInput.AvailableBSize() = maxSize.BSize(wm);
370
0
      // Not having border/padding simplifies things
371
0
      NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0,0,0,0),
372
0
                   "Viewports can't have border/padding");
373
0
      reflowInput.SetComputedBSize(maxSize.BSize(wm));
374
0
    }
375
0
376
0
    nsRect rect = AdjustReflowInputAsContainingBlock(&reflowInput);
377
0
    nsOverflowAreas* overflowAreas = &aDesiredSize.mOverflowAreas;
378
0
    nsIScrollableFrame* rootScrollFrame =
379
0
                    aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
380
0
    if (rootScrollFrame && !rootScrollFrame->IsIgnoringViewportClipping()) {
381
0
      overflowAreas = nullptr;
382
0
    }
383
0
    AbsPosReflowFlags flags =
384
0
      AbsPosReflowFlags::eCBWidthAndHeightChanged; // XXX could be optimized
385
0
    GetAbsoluteContainingBlock()->Reflow(this, aPresContext, reflowInput, aStatus,
386
0
                                         rect, flags, overflowAreas);
387
0
  }
388
0
389
0
  if (mFrames.NotEmpty()) {
390
0
    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, mFrames.FirstChild());
391
0
  }
392
0
393
0
  // If we were dirty then do a repaint
394
0
  if (GetStateBits() & NS_FRAME_IS_DIRTY) {
395
0
    InvalidateFrame();
396
0
  }
397
0
398
0
  // Clipping is handled by the document container (e.g., nsSubDocumentFrame),
399
0
  // so we don't need to change our overflow areas.
400
0
  bool overflowChanged = FinishAndStoreOverflow(&aDesiredSize);
401
0
  if (overflowChanged) {
402
0
    // We may need to alert our container to get it to pick up the
403
0
    // overflow change.
404
0
    nsSubDocumentFrame* container = static_cast<nsSubDocumentFrame*>
405
0
      (nsLayoutUtils::GetCrossDocParentFrame(this));
406
0
    if (container && !container->ShouldClipSubdocument()) {
407
0
      container->PresShell()->
408
0
        FrameNeedsReflow(container, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
409
0
    }
410
0
  }
411
0
412
0
  NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus);
413
0
  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
414
0
}
415
416
bool
417
ViewportFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
418
0
{
419
0
  nsIScrollableFrame* rootScrollFrame =
420
0
    PresShell()->GetRootScrollFrameAsScrollable();
421
0
  if (rootScrollFrame && !rootScrollFrame->IsIgnoringViewportClipping()) {
422
0
    return false;
423
0
  }
424
0
425
0
  return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
426
0
}
427
428
void
429
ViewportFrame::UpdateStyle(ServoRestyleState& aRestyleState)
430
0
{
431
0
 nsAtom* pseudo = Style()->GetPseudo();
432
0
  RefPtr<ComputedStyle> newStyle =
433
0
    aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(pseudo, nullptr);
434
0
435
0
  // We're special because we have a null GetContent(), so don't call things
436
0
  // like UpdateStyleOfOwnedChildFrame that try to append changes for the
437
0
  // content to the change list.  Nor do we computed a changehint, since we have
438
0
  // no way to apply it anyway.
439
0
  newStyle->ResolveSameStructsAs(Style());
440
0
441
0
  MOZ_ASSERT(!GetNextContinuation(), "Viewport has continuations?");
442
0
  SetComputedStyle(newStyle);
443
0
444
0
  UpdateStyleOfOwnedAnonBoxes(aRestyleState);
445
0
}
446
447
void
448
ViewportFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult)
449
0
{
450
0
  if (mFrames.NotEmpty()) {
451
0
    aResult.AppendElement(mFrames.FirstChild());
452
0
  }
453
0
}
454
455
#ifdef DEBUG_FRAME_DUMP
456
nsresult
457
ViewportFrame::GetFrameName(nsAString& aResult) const
458
{
459
  return MakeFrameName(NS_LITERAL_STRING("Viewport"), aResult);
460
}
461
#endif