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 #include "DisplayPortUtils.h"
8
9 #include "FrameMetrics.h"
10 #include "Layers.h"
11 #include "mozilla/dom/BrowserChild.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/gfx/gfxVars.h"
14 #include "mozilla/gfx/Point.h"
15 #include "mozilla/layers/APZCCallbackHelper.h"
16 #include "mozilla/layers/APZPublicUtils.h"
17 #include "mozilla/layers/CompositorBridgeChild.h"
18 #include "mozilla/layers/LayersMessageUtils.h"
19 #include "mozilla/layers/PAPZ.h"
20 #include "mozilla/layers/RepaintRequest.h"
21 #include "mozilla/PresShell.h"
22 #include "mozilla/StaticPrefs_layers.h"
23 #include "nsDeckFrame.h"
24 #include "nsIScrollableFrame.h"
25 #include "nsLayoutUtils.h"
26 #include "nsPlaceholderFrame.h"
27 #include "nsSubDocumentFrame.h"
28 #include "RetainedDisplayListBuilder.h"
29 #include "WindowRenderer.h"
30
31 #include <ostream>
32
33 namespace mozilla {
34
35 using gfx::gfxVars;
36 using gfx::IntSize;
37
38 using layers::APZCCallbackHelper;
39 using layers::FrameMetrics;
40 using layers::LayerManager;
41 using layers::RepaintRequest;
42 using layers::ScrollableLayerGuid;
43
44 typedef ScrollableLayerGuid::ViewID ViewID;
45
46 static LazyLogModule sDisplayportLog("apz.displayport");
47
48 /* static */
FromAPZ(const ScreenMargin & aMargins,const CSSPoint & aVisualOffset,const CSSPoint & aLayoutOffset,const CSSToScreenScale2D & aScale)49 DisplayPortMargins DisplayPortMargins::FromAPZ(
50 const ScreenMargin& aMargins, const CSSPoint& aVisualOffset,
51 const CSSPoint& aLayoutOffset, const CSSToScreenScale2D& aScale) {
52 return DisplayPortMargins{aMargins, aVisualOffset, aLayoutOffset, aScale};
53 }
54
ComputeDisplayportScale(nsIScrollableFrame * aScrollFrame)55 CSSToScreenScale2D ComputeDisplayportScale(nsIScrollableFrame* aScrollFrame) {
56 // The calculation here is equivalent to
57 // CalculateBasicFrameMetrics(aScrollFrame).DisplayportPixelsPerCSSPixel(),
58 // but we don't calculate the entire frame metrics.
59 if (!aScrollFrame) {
60 return CSSToScreenScale2D(1.0, 1.0);
61 }
62 nsIFrame* frame = do_QueryFrame(aScrollFrame);
63 MOZ_ASSERT(frame);
64 nsPresContext* presContext = frame->PresContext();
65 PresShell* presShell = presContext->PresShell();
66
67 return presContext->CSSToDevPixelScale() *
68 LayoutDeviceToLayerScale(presShell->GetCumulativeResolution()) *
69 LayerToParentLayerScale(1.0) *
70 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
71 frame);
72 }
73
74 /* static */
ForScrollFrame(nsIScrollableFrame * aScrollFrame,const ScreenMargin & aMargins,const Maybe<CSSToScreenScale2D> & aScale)75 DisplayPortMargins DisplayPortMargins::ForScrollFrame(
76 nsIScrollableFrame* aScrollFrame, const ScreenMargin& aMargins,
77 const Maybe<CSSToScreenScale2D>& aScale) {
78 CSSPoint visualOffset;
79 CSSPoint layoutOffset;
80 if (aScrollFrame) {
81 nsIFrame* scrollFrame = do_QueryFrame(aScrollFrame);
82 PresShell* presShell = scrollFrame->PresShell();
83 layoutOffset = CSSPoint::FromAppUnits(aScrollFrame->GetScrollPosition());
84 if (aScrollFrame->IsRootScrollFrameOfDocument()) {
85 visualOffset =
86 CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset());
87
88 } else {
89 visualOffset = layoutOffset;
90 }
91 }
92 return DisplayPortMargins{aMargins, visualOffset, layoutOffset,
93 aScale.valueOrFrom([&] {
94 return ComputeDisplayportScale(aScrollFrame);
95 })};
96 }
97
98 /* static */
ForContent(nsIContent * aContent,const ScreenMargin & aMargins)99 DisplayPortMargins DisplayPortMargins::ForContent(
100 nsIContent* aContent, const ScreenMargin& aMargins) {
101 return ForScrollFrame(
102 aContent ? nsLayoutUtils::FindScrollableFrameFor(aContent) : nullptr,
103 aMargins, Nothing());
104 }
105
GetRelativeToLayoutViewport(ContentGeometryType aGeometryType,nsIScrollableFrame * aScrollableFrame) const106 ScreenMargin DisplayPortMargins::GetRelativeToLayoutViewport(
107 ContentGeometryType aGeometryType,
108 nsIScrollableFrame* aScrollableFrame) const {
109 // APZ wants |mMargins| applied relative to the visual viewport.
110 // The main-thread painting code applies margins relative to
111 // the layout viewport. To get the main thread to paint the
112 // area APZ wants, apply a translation between the two. The
113 // magnitude of the translation depends on whether we are
114 // applying the displayport to scrolled or fixed content.
115 CSSPoint scrollDeltaCss =
116 ComputeAsyncTranslation(aGeometryType, aScrollableFrame);
117 ScreenPoint scrollDelta = scrollDeltaCss * mScale;
118 ScreenMargin margins = mMargins;
119 margins.left -= scrollDelta.x;
120 margins.right += scrollDelta.x;
121 margins.top -= scrollDelta.y;
122 margins.bottom += scrollDelta.y;
123 return margins;
124 }
125
operator <<(std::ostream & aOs,const DisplayPortMargins & aMargins)126 std::ostream& operator<<(std::ostream& aOs,
127 const DisplayPortMargins& aMargins) {
128 if (aMargins.mVisualOffset == CSSPoint() &&
129 aMargins.mLayoutOffset == CSSPoint()) {
130 aOs << aMargins.mMargins;
131 } else {
132 aOs << "{" << aMargins.mMargins << "," << aMargins.mVisualOffset << ","
133 << aMargins.mLayoutOffset << "}";
134 }
135 return aOs;
136 }
137
ComputeAsyncTranslation(ContentGeometryType aGeometryType,nsIScrollableFrame * aScrollableFrame) const138 CSSPoint DisplayPortMargins::ComputeAsyncTranslation(
139 ContentGeometryType aGeometryType,
140 nsIScrollableFrame* aScrollableFrame) const {
141 // If we are applying the displayport to scrolled content, the
142 // translation is the entire difference between the visual and
143 // layout offsets.
144 if (aGeometryType == ContentGeometryType::Scrolled) {
145 return mVisualOffset - mLayoutOffset;
146 }
147
148 // If we are applying the displayport to fixed content, only
149 // part of the difference between the visual and layout offsets
150 // should be applied. This is because fixed content remains fixed
151 // to the layout viewport, and some of the async delta between
152 // the visual and layout offsets can drag the layout viewport
153 // with it. We want only the remaining delta, i.e. the offset of
154 // the visual viewport relative to the (async-scrolled) layout
155 // viewport.
156 if (!aScrollableFrame) {
157 // Displayport on a non-scrolling frame for some reason.
158 // There will be no divergence between the two viewports.
159 return CSSPoint();
160 }
161 // Fixed content is always fixed to an RSF.
162 MOZ_ASSERT(aScrollableFrame->IsRootScrollFrameOfDocument());
163 nsIFrame* scrollFrame = do_QueryFrame(aScrollableFrame);
164 if (!scrollFrame->PresShell()->IsVisualViewportSizeSet()) {
165 // Zooming is disabled, so the layout viewport tracks the
166 // visual viewport completely.
167 return CSSPoint();
168 }
169 // Use KeepLayoutViewportEnclosingViewportVisual() to compute
170 // an async layout viewport the way APZ would.
171 const CSSRect visualViewport{
172 mVisualOffset,
173 // TODO: There are probably some edge cases here around async zooming
174 // that are not currently being handled properly. For proper handling,
175 // we'd likely need to save APZ's async zoom when populating
176 // mVisualOffset, and using it to adjust the visual viewport size here.
177 // Note that any incorrectness caused by this will only occur transiently
178 // during async zooming.
179 CSSSize::FromAppUnits(scrollFrame->PresShell()->GetVisualViewportSize())};
180 const CSSRect scrollableRect = CSSRect::FromAppUnits(
181 nsLayoutUtils::CalculateExpandedScrollableRect(scrollFrame));
182 CSSRect asyncLayoutViewport{
183 mLayoutOffset,
184 CSSSize::FromAppUnits(aScrollableFrame->GetScrollPortRect().Size())};
185 FrameMetrics::KeepLayoutViewportEnclosingVisualViewport(
186 visualViewport, scrollableRect, /* out */ asyncLayoutViewport);
187 return mVisualOffset - asyncLayoutViewport.TopLeft();
188 }
189
ApplyRectMultiplier(nsRect aRect,float aMultiplier)190 static nsRect ApplyRectMultiplier(nsRect aRect, float aMultiplier) {
191 if (aMultiplier == 1.0f) {
192 return aRect;
193 }
194 float newWidth = aRect.width * aMultiplier;
195 float newHeight = aRect.height * aMultiplier;
196 float newX = aRect.x - ((newWidth - aRect.width) / 2.0f);
197 float newY = aRect.y - ((newHeight - aRect.height) / 2.0f);
198 // Rounding doesn't matter too much here, do a round-in
199 return nsRect(ceil(newX), ceil(newY), floor(newWidth), floor(newHeight));
200 }
201
GetDisplayPortFromRectData(nsIContent * aContent,DisplayPortPropertyData * aRectData,float aMultiplier)202 static nsRect GetDisplayPortFromRectData(nsIContent* aContent,
203 DisplayPortPropertyData* aRectData,
204 float aMultiplier) {
205 // In the case where the displayport is set as a rect, we assume it is
206 // already aligned and clamped as necessary. The burden to do that is
207 // on the setter of the displayport. In practice very few places set the
208 // displayport directly as a rect (mostly tests). We still do need to
209 // expand it by the multiplier though.
210 return ApplyRectMultiplier(aRectData->mRect, aMultiplier);
211 }
212
GetDisplayPortFromMarginsData(nsIContent * aContent,DisplayPortMarginsPropertyData * aMarginsData,float aMultiplier,const DisplayPortOptions & aOptions)213 static nsRect GetDisplayPortFromMarginsData(
214 nsIContent* aContent, DisplayPortMarginsPropertyData* aMarginsData,
215 float aMultiplier, const DisplayPortOptions& aOptions) {
216 // In the case where the displayport is set via margins, we apply the margins
217 // to a base rect. Then we align the expanded rect based on the alignment
218 // requested, further expand the rect by the multiplier, and finally, clamp it
219 // to the size of the scrollable rect.
220
221 nsRect base;
222 if (nsRect* baseData = static_cast<nsRect*>(
223 aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
224 base = *baseData;
225 } else {
226 // In theory we shouldn't get here, but we do sometimes (see bug 1212136).
227 // Fall through for graceful handling.
228 }
229
230 nsIFrame* frame = nsLayoutUtils::GetScrollFrameFromContent(aContent);
231 if (!frame) {
232 // Turns out we can't really compute it. Oops. We still should return
233 // something sane. Note that since we can't clamp the rect without a
234 // frame, we don't apply the multiplier either as it can cause the result
235 // to leak outside the scrollable area.
236 NS_WARNING(
237 "Attempting to get a displayport from a content with no primary "
238 "frame!");
239 return base;
240 }
241
242 bool isRoot = false;
243 if (aContent->OwnerDoc()->GetRootElement() == aContent) {
244 isRoot = true;
245 }
246
247 nsIScrollableFrame* scrollableFrame = frame->GetScrollTargetFrame();
248 nsPoint scrollPos;
249 if (scrollableFrame) {
250 scrollPos = scrollableFrame->GetScrollPosition();
251 }
252
253 nsPresContext* presContext = frame->PresContext();
254 int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
255
256 LayoutDeviceToScreenScale2D res =
257 LayoutDeviceToParentLayerScale(
258 presContext->PresShell()->GetCumulativeResolution()) *
259 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
260 frame);
261
262 // Calculate the expanded scrollable rect, which we'll be clamping the
263 // displayport to.
264 nsRect expandedScrollableRect =
265 nsLayoutUtils::CalculateExpandedScrollableRect(frame);
266
267 // GetTransformToAncestorScale() can return 0. In this case, just return the
268 // base rect (clamped to the expanded scrollable rect), as other calculations
269 // would run into divisions by zero.
270 if (res == LayoutDeviceToScreenScale2D(0, 0)) {
271 // Make sure the displayport remains within the scrollable rect.
272 return base.MoveInsideAndClamp(expandedScrollableRect - scrollPos);
273 }
274
275 // First convert the base rect to screen pixels
276 LayoutDeviceToScreenScale2D parentRes = res;
277 if (isRoot) {
278 // the base rect for root scroll frames is specified in the parent document
279 // coordinate space, so it doesn't include the local resolution.
280 float localRes = presContext->PresShell()->GetResolution();
281 parentRes.xScale /= localRes;
282 parentRes.yScale /= localRes;
283 }
284 ScreenRect screenRect =
285 LayoutDeviceRect::FromAppUnits(base, auPerDevPixel) * parentRes;
286
287 // Note on the correctness of applying the alignment in Screen space:
288 // The correct space to apply the alignment in would be Layer space, but
289 // we don't necessarily know the scale to convert to Layer space at this
290 // point because Layout may not yet have chosen the resolution at which to
291 // render (it chooses that in FrameLayerBuilder, but this can be called
292 // during display list building). Therefore, we perform the alignment in
293 // Screen space, which basically assumes that Layout chose to render at
294 // screen resolution; since this is what Layout does most of the time,
295 // this is a good approximation. A proper solution would involve moving
296 // the choosing of the resolution to display-list building time.
297 ScreenSize alignment;
298
299 PresShell* presShell = presContext->PresShell();
300 MOZ_ASSERT(presShell);
301
302 ScreenMargin margins = aMarginsData->mMargins.GetRelativeToLayoutViewport(
303 aOptions.mGeometryType, scrollableFrame);
304
305 if (presShell->IsDisplayportSuppressed() ||
306 aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
307 alignment = ScreenSize(1, 1);
308 } else {
309 // Moving the displayport is relatively expensive with WR so we use a larger
310 // alignment that causes the displayport to move less frequently. The
311 // alignment scales up with the size of the base rect so larger scrollframes
312 // use a larger alignment, but we clamp the alignment to a power of two
313 // between 128 and 1024 (inclusive).
314 // This naturally also increases the size of the displayport compared to
315 // always using a 128 alignment, so the displayport multipliers are also
316 // correspondingly smaller when WR is enabled to prevent the displayport
317 // from becoming too big.
318 IntSize multiplier =
319 layers::apz::GetDisplayportAlignmentMultiplier(screenRect.Size());
320 alignment = ScreenSize(128 * multiplier.width, 128 * multiplier.height);
321 }
322
323 // Avoid division by zero.
324 if (alignment.width == 0) {
325 alignment.width = 128;
326 }
327 if (alignment.height == 0) {
328 alignment.height = 128;
329 }
330
331 // Expand the rect by the margins
332 screenRect.Inflate(margins);
333
334 ScreenPoint scrollPosScreen =
335 LayoutDevicePoint::FromAppUnits(scrollPos, auPerDevPixel) * res;
336
337 // Align the display port.
338 screenRect += scrollPosScreen;
339 float x = alignment.width * floor(screenRect.x / alignment.width);
340 float y = alignment.height * floor(screenRect.y / alignment.height);
341 float w = alignment.width * ceil(screenRect.width / alignment.width + 1);
342 float h = alignment.height * ceil(screenRect.height / alignment.height + 1);
343 screenRect = ScreenRect(x, y, w, h);
344 screenRect -= scrollPosScreen;
345
346 // Convert the aligned rect back into app units.
347 nsRect result = LayoutDeviceRect::ToAppUnits(screenRect / res, auPerDevPixel);
348
349 // If we have non-zero margins, expand the displayport for the low-res buffer
350 // if that's what we're drawing. If we have zero margins, we want the
351 // displayport to reflect the scrollport.
352 if (margins != ScreenMargin()) {
353 result = ApplyRectMultiplier(result, aMultiplier);
354 }
355
356 // Make sure the displayport remains within the scrollable rect.
357 result = result.MoveInsideAndClamp(expandedScrollableRect - scrollPos);
358
359 return result;
360 }
361
GetDisplayPortData(nsIContent * aContent,DisplayPortPropertyData ** aOutRectData,DisplayPortMarginsPropertyData ** aOutMarginsData)362 static bool GetDisplayPortData(
363 nsIContent* aContent, DisplayPortPropertyData** aOutRectData,
364 DisplayPortMarginsPropertyData** aOutMarginsData) {
365 MOZ_ASSERT(aOutRectData && aOutMarginsData);
366
367 *aOutRectData = static_cast<DisplayPortPropertyData*>(
368 aContent->GetProperty(nsGkAtoms::DisplayPort));
369 *aOutMarginsData = static_cast<DisplayPortMarginsPropertyData*>(
370 aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
371
372 if (!*aOutRectData && !*aOutMarginsData) {
373 // This content element has no displayport data at all
374 return false;
375 }
376
377 if (*aOutRectData && *aOutMarginsData) {
378 // choose margins if equal priority
379 if ((*aOutRectData)->mPriority > (*aOutMarginsData)->mPriority) {
380 *aOutMarginsData = nullptr;
381 } else {
382 *aOutRectData = nullptr;
383 }
384 }
385
386 NS_ASSERTION((*aOutRectData == nullptr) != (*aOutMarginsData == nullptr),
387 "Only one of aOutRectData or aOutMarginsData should be set!");
388
389 return true;
390 }
391
GetWasDisplayPortPainted(nsIContent * aContent)392 static bool GetWasDisplayPortPainted(nsIContent* aContent) {
393 DisplayPortPropertyData* rectData = nullptr;
394 DisplayPortMarginsPropertyData* marginsData = nullptr;
395
396 if (!GetDisplayPortData(aContent, &rectData, &marginsData)) {
397 return false;
398 }
399
400 return rectData ? rectData->mPainted : marginsData->mPainted;
401 }
402
IsMissingDisplayPortBaseRect(nsIContent * aContent)403 bool DisplayPortUtils::IsMissingDisplayPortBaseRect(nsIContent* aContent) {
404 DisplayPortPropertyData* rectData = nullptr;
405 DisplayPortMarginsPropertyData* marginsData = nullptr;
406
407 if (GetDisplayPortData(aContent, &rectData, &marginsData) && marginsData) {
408 return !aContent->GetProperty(nsGkAtoms::DisplayPortBase);
409 }
410
411 return false;
412 }
413
TranslateFromScrollPortToScrollFrame(nsIContent * aContent,nsRect * aRect)414 static void TranslateFromScrollPortToScrollFrame(nsIContent* aContent,
415 nsRect* aRect) {
416 MOZ_ASSERT(aRect);
417 if (nsIScrollableFrame* scrollableFrame =
418 nsLayoutUtils::FindScrollableFrameFor(aContent)) {
419 *aRect += scrollableFrame->GetScrollPortRect().TopLeft();
420 }
421 }
422
GetDisplayPortImpl(nsIContent * aContent,nsRect * aResult,float aMultiplier,const DisplayPortOptions & aOptions)423 static bool GetDisplayPortImpl(nsIContent* aContent, nsRect* aResult,
424 float aMultiplier,
425 const DisplayPortOptions& aOptions) {
426 DisplayPortPropertyData* rectData = nullptr;
427 DisplayPortMarginsPropertyData* marginsData = nullptr;
428
429 if (!GetDisplayPortData(aContent, &rectData, &marginsData)) {
430 return false;
431 }
432
433 nsIFrame* frame = aContent->GetPrimaryFrame();
434 if (frame && !frame->PresShell()->AsyncPanZoomEnabled()) {
435 return false;
436 }
437
438 if (!aResult) {
439 // We have displayport data, but the caller doesn't want the actual
440 // rect, so we don't need to actually compute it.
441 return true;
442 }
443
444 bool isDisplayportSuppressed = false;
445
446 if (frame) {
447 nsPresContext* presContext = frame->PresContext();
448 MOZ_ASSERT(presContext);
449 PresShell* presShell = presContext->PresShell();
450 MOZ_ASSERT(presShell);
451 isDisplayportSuppressed = presShell->IsDisplayportSuppressed();
452 }
453
454 nsRect result;
455 if (rectData) {
456 result = GetDisplayPortFromRectData(aContent, rectData, aMultiplier);
457 } else if (isDisplayportSuppressed ||
458 nsLayoutUtils::ShouldDisableApzForElement(aContent) ||
459 aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
460 // Make a copy of the margins data but set the margins to empty.
461 // Do not create a new DisplayPortMargins object with
462 // DisplayPortMargins::Empty(), because that will record the visual
463 // and layout scroll offsets in place right now on the DisplayPortMargins,
464 // and those are only meant to be recorded when the margins are stored.
465 DisplayPortMarginsPropertyData noMargins = *marginsData;
466 noMargins.mMargins.mMargins = ScreenMargin();
467 result = GetDisplayPortFromMarginsData(aContent, &noMargins, aMultiplier,
468 aOptions);
469 } else {
470 result = GetDisplayPortFromMarginsData(aContent, marginsData, aMultiplier,
471 aOptions);
472 }
473
474 if (aOptions.mRelativeTo == DisplayportRelativeTo::ScrollFrame) {
475 TranslateFromScrollPortToScrollFrame(aContent, &result);
476 }
477
478 *aResult = result;
479 return true;
480 }
481
GetDisplayPort(nsIContent * aContent,nsRect * aResult,const DisplayPortOptions & aOptions)482 bool DisplayPortUtils::GetDisplayPort(nsIContent* aContent, nsRect* aResult,
483 const DisplayPortOptions& aOptions) {
484 float multiplier = StaticPrefs::layers_low_precision_buffer()
485 ? 1.0f / StaticPrefs::layers_low_precision_resolution()
486 : 1.0f;
487 return GetDisplayPortImpl(aContent, aResult, multiplier, aOptions);
488 }
489
HasDisplayPort(nsIContent * aContent)490 bool DisplayPortUtils::HasDisplayPort(nsIContent* aContent) {
491 return GetDisplayPort(aContent, nullptr);
492 }
493
HasPaintedDisplayPort(nsIContent * aContent)494 bool DisplayPortUtils::HasPaintedDisplayPort(nsIContent* aContent) {
495 DisplayPortPropertyData* rectData = nullptr;
496 DisplayPortMarginsPropertyData* marginsData = nullptr;
497 GetDisplayPortData(aContent, &rectData, &marginsData);
498 if (rectData) {
499 return rectData->mPainted;
500 }
501 if (marginsData) {
502 return marginsData->mPainted;
503 }
504 return false;
505 }
506
MarkDisplayPortAsPainted(nsIContent * aContent)507 void DisplayPortUtils::MarkDisplayPortAsPainted(nsIContent* aContent) {
508 DisplayPortPropertyData* rectData = nullptr;
509 DisplayPortMarginsPropertyData* marginsData = nullptr;
510 GetDisplayPortData(aContent, &rectData, &marginsData);
511 MOZ_ASSERT(rectData || marginsData,
512 "MarkDisplayPortAsPainted should only be called for an element "
513 "with a displayport");
514 if (rectData) {
515 rectData->mPainted = true;
516 }
517 if (marginsData) {
518 marginsData->mPainted = true;
519 }
520 }
521
HasNonMinimalDisplayPort(nsIContent * aContent)522 bool DisplayPortUtils::HasNonMinimalDisplayPort(nsIContent* aContent) {
523 return HasDisplayPort(aContent) &&
524 !aContent->GetProperty(nsGkAtoms::MinimalDisplayPort);
525 }
526
HasNonMinimalNonZeroDisplayPort(nsIContent * aContent)527 bool DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(nsIContent* aContent) {
528 if (!HasDisplayPort(aContent)) {
529 return false;
530 }
531 if (aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
532 return false;
533 }
534
535 DisplayPortMarginsPropertyData* currentData =
536 static_cast<DisplayPortMarginsPropertyData*>(
537 aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
538
539 if (!currentData) {
540 // We have a display port, so if we don't have margin data we must have rect
541 // data. We consider such as non zero and non minimal, it's probably not too
542 // important as display port rects are only used in tests.
543 return true;
544 }
545
546 if (currentData->mMargins.mMargins != ScreenMargin()) {
547 return true;
548 }
549
550 return false;
551 }
552
553 /* static */
GetDisplayPortForVisibilityTesting(nsIContent * aContent,nsRect * aResult)554 bool DisplayPortUtils::GetDisplayPortForVisibilityTesting(nsIContent* aContent,
555 nsRect* aResult) {
556 MOZ_ASSERT(aResult);
557 return GetDisplayPortImpl(
558 aContent, aResult, 1.0f,
559 DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame));
560 }
561
InvalidateForDisplayPortChange(nsIContent * aContent,bool aHadDisplayPort,const nsRect & aOldDisplayPort,const nsRect & aNewDisplayPort,RepaintMode aRepaintMode)562 void DisplayPortUtils::InvalidateForDisplayPortChange(
563 nsIContent* aContent, bool aHadDisplayPort, const nsRect& aOldDisplayPort,
564 const nsRect& aNewDisplayPort, RepaintMode aRepaintMode) {
565 if (aRepaintMode != RepaintMode::Repaint) {
566 return;
567 }
568
569 bool changed =
570 !aHadDisplayPort || !aOldDisplayPort.IsEqualEdges(aNewDisplayPort);
571
572 nsIFrame* frame = nsLayoutUtils::GetScrollFrameFromContent(aContent);
573 if (frame) {
574 frame = do_QueryFrame(frame->GetScrollTargetFrame());
575 }
576
577 if (changed && frame) {
578 // It is important to call SchedulePaint on the same frame that we set the
579 // dirty rect properties on so we can find the frame later to remove the
580 // properties.
581 frame->SchedulePaint();
582
583 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled() ||
584 !nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(frame)) {
585 return;
586 }
587
588 if (StaticPrefs::layout_display_list_retain_sc()) {
589 // DisplayListBuildingDisplayPortRect property is not used when retain sc
590 // mode is enabled.
591 return;
592 }
593
594 bool found;
595 nsRect* rect = frame->GetProperty(
596 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), &found);
597
598 if (!found) {
599 rect = new nsRect();
600 frame->AddProperty(
601 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
602 frame->SetHasOverrideDirtyRegion(true);
603
604 nsIFrame* rootFrame = frame->PresShell()->GetRootFrame();
605 MOZ_ASSERT(rootFrame);
606
607 RetainedDisplayListData* data =
608 GetOrSetRetainedDisplayListData(rootFrame);
609 data->Flags(frame) += RetainedDisplayListData::FrameFlag::HasProps;
610 } else {
611 MOZ_ASSERT(rect, "this property should only store non-null values");
612 }
613
614 if (aHadDisplayPort) {
615 // We only need to build a display list for any new areas added
616 nsRegion newRegion(aNewDisplayPort);
617 newRegion.SubOut(aOldDisplayPort);
618 rect->UnionRect(*rect, newRegion.GetBounds());
619 } else {
620 rect->UnionRect(*rect, aNewDisplayPort);
621 }
622 }
623 }
624
SetDisplayPortMargins(nsIContent * aContent,PresShell * aPresShell,const DisplayPortMargins & aMargins,ClearMinimalDisplayPortProperty aClearMinimalDisplayPortProperty,uint32_t aPriority,RepaintMode aRepaintMode)625 bool DisplayPortUtils::SetDisplayPortMargins(
626 nsIContent* aContent, PresShell* aPresShell,
627 const DisplayPortMargins& aMargins,
628 ClearMinimalDisplayPortProperty aClearMinimalDisplayPortProperty,
629 uint32_t aPriority, RepaintMode aRepaintMode) {
630 MOZ_ASSERT(aContent);
631 MOZ_ASSERT(aContent->GetComposedDoc() == aPresShell->GetDocument());
632
633 DisplayPortMarginsPropertyData* currentData =
634 static_cast<DisplayPortMarginsPropertyData*>(
635 aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
636 if (currentData && currentData->mPriority > aPriority) {
637 return false;
638 }
639
640 if (currentData && currentData->mMargins.mVisualOffset != CSSPoint() &&
641 aMargins.mVisualOffset == CSSPoint()) {
642 // If we hit this, then it's possible that we're setting a displayport
643 // that is wrong because the old one had a layout/visual adjustment and
644 // the new one does not.
645 MOZ_LOG(sDisplayportLog, LogLevel::Warning,
646 ("Dropping visual offset %s",
647 ToString(currentData->mMargins.mVisualOffset).c_str()));
648 }
649
650 nsIFrame* scrollFrame = nsLayoutUtils::GetScrollFrameFromContent(aContent);
651
652 nsRect oldDisplayPort;
653 bool hadDisplayPort = false;
654 bool wasPainted = GetWasDisplayPortPainted(aContent);
655 if (scrollFrame) {
656 // We only use the two return values from this function to call
657 // InvalidateForDisplayPortChange. InvalidateForDisplayPortChange does
658 // nothing if aContent does not have a frame. So getting the displayport is
659 // useless if the content has no frame, so we avoid calling this to avoid
660 // triggering a warning about not having a frame.
661 hadDisplayPort = GetDisplayPort(aContent, &oldDisplayPort);
662 }
663
664 aContent->SetProperty(
665 nsGkAtoms::DisplayPortMargins,
666 new DisplayPortMarginsPropertyData(aMargins, aPriority, wasPainted),
667 nsINode::DeleteProperty<DisplayPortMarginsPropertyData>);
668
669 if (aClearMinimalDisplayPortProperty ==
670 ClearMinimalDisplayPortProperty::Yes) {
671 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug) &&
672 aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
673 mozilla::layers::ScrollableLayerGuid::ViewID viewID =
674 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
675 nsLayoutUtils::FindIDFor(aContent, &viewID);
676 MOZ_LOG(sDisplayportLog, LogLevel::Debug,
677 ("SetDisplayPortMargins removing MinimalDisplayPort prop on "
678 "scrollId=%" PRIu64 "\n",
679 viewID));
680 }
681 aContent->RemoveProperty(nsGkAtoms::MinimalDisplayPort);
682 }
683
684 nsIScrollableFrame* scrollableFrame =
685 scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
686 if (!scrollableFrame) {
687 return true;
688 }
689
690 nsRect newDisplayPort;
691 DebugOnly<bool> hasDisplayPort = GetDisplayPort(aContent, &newDisplayPort);
692 MOZ_ASSERT(hasDisplayPort);
693
694 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) {
695 mozilla::layers::ScrollableLayerGuid::ViewID viewID =
696 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
697 nsLayoutUtils::FindIDFor(aContent, &viewID);
698 if (!hadDisplayPort) {
699 MOZ_LOG(sDisplayportLog, LogLevel::Debug,
700 ("SetDisplayPortMargins %s on scrollId=%" PRIu64 ", newDp=%s\n",
701 ToString(aMargins).c_str(), viewID,
702 ToString(newDisplayPort).c_str()));
703 } else {
704 // Use verbose level logging for when an existing displayport got its
705 // margins updated.
706 MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
707 ("SetDisplayPortMargins %s on scrollId=%" PRIu64 ", newDp=%s\n",
708 ToString(aMargins).c_str(), viewID,
709 ToString(newDisplayPort).c_str()));
710 }
711 }
712
713 InvalidateForDisplayPortChange(aContent, hadDisplayPort, oldDisplayPort,
714 newDisplayPort, aRepaintMode);
715
716 scrollableFrame->TriggerDisplayPortExpiration();
717
718 // Display port margins changing means that the set of visible frames may
719 // have drastically changed. Check if we should schedule an update.
720 hadDisplayPort =
721 scrollableFrame->GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
722 &oldDisplayPort);
723
724 bool needVisibilityUpdate = !hadDisplayPort;
725 // Check if the total size has changed by a large factor.
726 if (!needVisibilityUpdate) {
727 if ((newDisplayPort.width > 2 * oldDisplayPort.width) ||
728 (oldDisplayPort.width > 2 * newDisplayPort.width) ||
729 (newDisplayPort.height > 2 * oldDisplayPort.height) ||
730 (oldDisplayPort.height > 2 * newDisplayPort.height)) {
731 needVisibilityUpdate = true;
732 }
733 }
734 // Check if it's moved by a significant amount.
735 if (!needVisibilityUpdate) {
736 if (nsRect* baseData = static_cast<nsRect*>(
737 aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
738 nsRect base = *baseData;
739 if ((std::abs(newDisplayPort.X() - oldDisplayPort.X()) > base.width) ||
740 (std::abs(newDisplayPort.XMost() - oldDisplayPort.XMost()) >
741 base.width) ||
742 (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) ||
743 (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) >
744 base.height)) {
745 needVisibilityUpdate = true;
746 }
747 }
748 }
749 if (needVisibilityUpdate) {
750 aPresShell->ScheduleApproximateFrameVisibilityUpdateNow();
751 }
752
753 return true;
754 }
755
SetDisplayPortBase(nsIContent * aContent,const nsRect & aBase)756 void DisplayPortUtils::SetDisplayPortBase(nsIContent* aContent,
757 const nsRect& aBase) {
758 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Verbose)) {
759 ViewID viewId = nsLayoutUtils::FindOrCreateIDFor(aContent);
760 MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
761 ("Setting base rect %s for scrollId=%" PRIu64 "\n",
762 ToString(aBase).c_str(), viewId));
763 }
764 aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase),
765 nsINode::DeleteProperty<nsRect>);
766 }
767
SetDisplayPortBaseIfNotSet(nsIContent * aContent,const nsRect & aBase)768 void DisplayPortUtils::SetDisplayPortBaseIfNotSet(nsIContent* aContent,
769 const nsRect& aBase) {
770 if (!aContent->GetProperty(nsGkAtoms::DisplayPortBase)) {
771 SetDisplayPortBase(aContent, aBase);
772 }
773 }
774
RemoveDisplayPort(nsIContent * aContent)775 void DisplayPortUtils::RemoveDisplayPort(nsIContent* aContent) {
776 aContent->RemoveProperty(nsGkAtoms::DisplayPort);
777 aContent->RemoveProperty(nsGkAtoms::DisplayPortMargins);
778 }
779
ViewportHasDisplayPort(nsPresContext * aPresContext)780 bool DisplayPortUtils::ViewportHasDisplayPort(nsPresContext* aPresContext) {
781 nsIFrame* rootScrollFrame = aPresContext->PresShell()->GetRootScrollFrame();
782 return rootScrollFrame && HasDisplayPort(rootScrollFrame->GetContent());
783 }
784
IsFixedPosFrameInDisplayPort(const nsIFrame * aFrame)785 bool DisplayPortUtils::IsFixedPosFrameInDisplayPort(const nsIFrame* aFrame) {
786 // Fixed-pos frames are parented by the viewport frame or the page content
787 // frame. We'll assume that printing/print preview don't have displayports for
788 // their pages!
789 nsIFrame* parent = aFrame->GetParent();
790 if (!parent || parent->GetParent() ||
791 aFrame->StyleDisplay()->mPosition != StylePositionProperty::Fixed) {
792 return false;
793 }
794 return ViewportHasDisplayPort(aFrame->PresContext());
795 }
796
797 // We want to this return true for the scroll frame, but not the
798 // scrolled frame (which has the same content).
FrameHasDisplayPort(nsIFrame * aFrame,const nsIFrame * aScrolledFrame)799 bool DisplayPortUtils::FrameHasDisplayPort(nsIFrame* aFrame,
800 const nsIFrame* aScrolledFrame) {
801 if (!aFrame->GetContent() || !HasDisplayPort(aFrame->GetContent())) {
802 return false;
803 }
804 nsIScrollableFrame* sf = do_QueryFrame(aFrame);
805 if (sf) {
806 if (aScrolledFrame && aScrolledFrame != sf->GetScrolledFrame()) {
807 return false;
808 }
809 return true;
810 }
811 return false;
812 }
813
CalculateAndSetDisplayPortMargins(nsIScrollableFrame * aScrollFrame,RepaintMode aRepaintMode)814 bool DisplayPortUtils::CalculateAndSetDisplayPortMargins(
815 nsIScrollableFrame* aScrollFrame, RepaintMode aRepaintMode) {
816 nsIFrame* frame = do_QueryFrame(aScrollFrame);
817 MOZ_ASSERT(frame);
818 nsIContent* content = frame->GetContent();
819 MOZ_ASSERT(content);
820
821 FrameMetrics metrics =
822 nsLayoutUtils::CalculateBasicFrameMetrics(aScrollFrame);
823 ScreenMargin displayportMargins = layers::apz::CalculatePendingDisplayPort(
824 metrics, ParentLayerPoint(0.0f, 0.0f));
825 PresShell* presShell = frame->PresContext()->GetPresShell();
826
827 DisplayPortMargins margins = DisplayPortMargins::ForScrollFrame(
828 aScrollFrame, displayportMargins,
829 Some(metrics.DisplayportPixelsPerCSSPixel()));
830
831 return SetDisplayPortMargins(content, presShell, margins,
832 ClearMinimalDisplayPortProperty::Yes, 0,
833 aRepaintMode);
834 }
835
MaybeCreateDisplayPort(nsDisplayListBuilder * aBuilder,nsIFrame * aScrollFrame,RepaintMode aRepaintMode)836 bool DisplayPortUtils::MaybeCreateDisplayPort(nsDisplayListBuilder* aBuilder,
837 nsIFrame* aScrollFrame,
838 RepaintMode aRepaintMode) {
839 nsIContent* content = aScrollFrame->GetContent();
840 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollFrame);
841 if (!content || !scrollableFrame) {
842 return false;
843 }
844
845 bool haveDisplayPort = HasNonMinimalNonZeroDisplayPort(content);
846
847 // We perform an optimization where we ensure that at least one
848 // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a
849 // displayport. If that's not the case yet, and we are async-scrollable, we
850 // will get a displayport.
851 if (aBuilder->IsPaintingToWindow() &&
852 nsLayoutUtils::AsyncPanZoomEnabled(aScrollFrame) &&
853 !aBuilder->HaveScrollableDisplayPort() &&
854 scrollableFrame->WantAsyncScroll()) {
855 // If we don't already have a displayport, calculate and set one.
856 if (!haveDisplayPort) {
857 // We only use the viewId for logging purposes, but create it
858 // unconditionally to minimize impact of enabling logging. If we don't
859 // assign a viewId here it will get assigned later anyway so functionally
860 // there should be no difference.
861 ViewID viewId = nsLayoutUtils::FindOrCreateIDFor(content);
862 MOZ_LOG(
863 sDisplayportLog, LogLevel::Debug,
864 ("Setting DP on first-encountered scrollId=%" PRIu64 "\n", viewId));
865
866 CalculateAndSetDisplayPortMargins(scrollableFrame, aRepaintMode);
867 #ifdef DEBUG
868 haveDisplayPort = HasNonMinimalDisplayPort(content);
869 MOZ_ASSERT(haveDisplayPort,
870 "should have a displayport after having just set it");
871 #endif
872 }
873
874 // Record that the we now have a scrollable display port.
875 aBuilder->SetHaveScrollableDisplayPort();
876 return true;
877 }
878 return false;
879 }
SetZeroMarginDisplayPortOnAsyncScrollableAncestors(nsIFrame * aFrame)880 void DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
881 nsIFrame* aFrame) {
882 nsIFrame* frame = aFrame;
883 while (frame) {
884 frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame);
885 if (!frame) {
886 break;
887 }
888 nsIScrollableFrame* scrollAncestor =
889 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame);
890 if (!scrollAncestor) {
891 break;
892 }
893 frame = do_QueryFrame(scrollAncestor);
894 MOZ_ASSERT(frame);
895 MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
896 frame->PresShell()->GetRootScrollFrame() == frame);
897 if (nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
898 !HasDisplayPort(frame->GetContent())) {
899 SetDisplayPortMargins(frame->GetContent(), frame->PresShell(),
900 DisplayPortMargins::Empty(frame->GetContent()),
901 ClearMinimalDisplayPortProperty::No, 0,
902 RepaintMode::Repaint);
903 }
904 }
905 }
906
MaybeCreateDisplayPortInFirstScrollFrameEncountered(nsIFrame * aFrame,nsDisplayListBuilder * aBuilder)907 bool DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered(
908 nsIFrame* aFrame, nsDisplayListBuilder* aBuilder) {
909 // Don't descend into the tab bar in chrome, it can be very large and does not
910 // contain any async scrollable elements.
911 if (XRE_IsParentProcess() && aFrame->GetContent() &&
912 aFrame->GetContent()->GetID() == nsGkAtoms::tabbrowser_arrowscrollbox) {
913 return false;
914 }
915
916 nsIScrollableFrame* sf = do_QueryFrame(aFrame);
917 if (sf) {
918 if (MaybeCreateDisplayPort(aBuilder, aFrame, RepaintMode::Repaint)) {
919 return true;
920 }
921 }
922 if (aFrame->IsPlaceholderFrame()) {
923 nsPlaceholderFrame* placeholder = static_cast<nsPlaceholderFrame*>(aFrame);
924 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(
925 placeholder->GetOutOfFlowFrame(), aBuilder)) {
926 return true;
927 }
928 }
929 if (aFrame->IsSubDocumentFrame()) {
930 PresShell* presShell = static_cast<nsSubDocumentFrame*>(aFrame)
931 ->GetSubdocumentPresShellForPainting(0);
932 nsIFrame* root = presShell ? presShell->GetRootFrame() : nullptr;
933 if (root) {
934 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(root, aBuilder)) {
935 return true;
936 }
937 }
938 }
939 if (aFrame->IsDeckFrame()) {
940 // only descend the visible card of a decks
941 nsIFrame* child = static_cast<nsDeckFrame*>(aFrame)->GetSelectedBox();
942 if (child) {
943 return MaybeCreateDisplayPortInFirstScrollFrameEncountered(child,
944 aBuilder);
945 }
946 }
947
948 for (nsIFrame* child : aFrame->PrincipalChildList()) {
949 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(child, aBuilder)) {
950 return true;
951 }
952 }
953
954 return false;
955 }
956
ExpireDisplayPortOnAsyncScrollableAncestor(nsIFrame * aFrame)957 void DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(
958 nsIFrame* aFrame) {
959 nsIFrame* frame = aFrame;
960 while (frame) {
961 frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame);
962 if (!frame) {
963 break;
964 }
965 nsIScrollableFrame* scrollAncestor =
966 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame);
967 if (!scrollAncestor) {
968 break;
969 }
970 frame = do_QueryFrame(scrollAncestor);
971 MOZ_ASSERT(frame);
972 if (!frame) {
973 break;
974 }
975 MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
976 frame->PresShell()->GetRootScrollFrame() == frame);
977 if (HasDisplayPort(frame->GetContent())) {
978 scrollAncestor->TriggerDisplayPortExpiration();
979 // Stop after the first trigger. If it failed, there's no point in
980 // continuing because all the rest of the frames we encounter are going
981 // to be ancestors of |scrollAncestor| which will keep its displayport.
982 // If the trigger succeeded, we stop because when the trigger executes
983 // it will call this function again to trigger the next ancestor up the
984 // chain.
985 break;
986 }
987 }
988 }
989
GetRootDisplayportBase(PresShell * aPresShell)990 Maybe<nsRect> DisplayPortUtils::GetRootDisplayportBase(PresShell* aPresShell) {
991 DebugOnly<nsPresContext*> pc = aPresShell->GetPresContext();
992 MOZ_ASSERT(pc, "this function should be called after PresShell::Init");
993 MOZ_ASSERT(pc->IsRootContentDocumentCrossProcess() ||
994 !pc->GetParentPresContext());
995
996 dom::BrowserChild* browserChild = dom::BrowserChild::GetFrom(aPresShell);
997 if (browserChild && !browserChild->IsTopLevel()) {
998 // If this is an in-process root in on OOP iframe, use the visible rect if
999 // it's been set.
1000 return browserChild->GetVisibleRect();
1001 }
1002
1003 nsIFrame* frame = aPresShell->GetRootScrollFrame();
1004 if (!frame) {
1005 frame = aPresShell->GetRootFrame();
1006 }
1007
1008 nsRect baseRect;
1009 if (frame) {
1010 baseRect = nsRect(nsPoint(0, 0),
1011 nsLayoutUtils::CalculateCompositionSizeForFrame(frame));
1012 } else {
1013 baseRect = nsRect(nsPoint(0, 0),
1014 aPresShell->GetPresContext()->GetVisibleArea().Size());
1015 }
1016
1017 return Some(baseRect);
1018 }
1019
1020 } // namespace mozilla
1021