1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DisplayPortUtils.h"
9 #include "FrameMetrics.h"
10 #include "mozilla/dom/BrowserChild.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/gfx/Point.h"
13 #include "mozilla/layers/APZPublicUtils.h"
14 #include "mozilla/layers/CompositorBridgeChild.h"
15 #include "mozilla/layers/LayersMessageUtils.h"
16 #include "mozilla/layers/PAPZ.h"
17 #include "mozilla/PresShell.h"
18 #include "mozilla/ScrollContainerFrame.h"
19 #include "mozilla/StaticPrefs_layers.h"
20 #include "mozilla/StaticPrefs_layout.h"
21 #include "nsLayoutUtils.h"
22 #include "nsPlaceholderFrame.h"
23 #include "nsSubDocumentFrame.h"
24 #include "RetainedDisplayListBuilder.h"
25 #include "WindowRenderer.h"
33 using layers::FrameMetrics
;
34 using layers::ScrollableLayerGuid
;
36 typedef ScrollableLayerGuid::ViewID ViewID
;
38 static LazyLogModule
sDisplayportLog("apz.displayport");
41 DisplayPortMargins
DisplayPortMargins::FromAPZ(const ScreenMargin
& aMargins
,
42 const CSSPoint
& aVisualOffset
,
43 const CSSPoint
& aLayoutOffset
) {
44 return DisplayPortMargins
{aMargins
, aVisualOffset
, aLayoutOffset
};
48 DisplayPortMargins
DisplayPortMargins::ForScrollContainerFrame(
49 ScrollContainerFrame
* aScrollContainerFrame
, const ScreenMargin
& aMargins
) {
50 CSSPoint visualOffset
;
51 CSSPoint layoutOffset
;
52 if (aScrollContainerFrame
) {
53 PresShell
* presShell
= aScrollContainerFrame
->PresShell();
55 CSSPoint::FromAppUnits(aScrollContainerFrame
->GetScrollPosition());
56 if (aScrollContainerFrame
->IsRootScrollFrameOfDocument()) {
58 CSSPoint::FromAppUnits(presShell
->GetVisualViewportOffset());
61 visualOffset
= layoutOffset
;
64 return DisplayPortMargins
{aMargins
, visualOffset
, layoutOffset
};
68 DisplayPortMargins
DisplayPortMargins::ForContent(
69 nsIContent
* aContent
, const ScreenMargin
& aMargins
) {
70 return ForScrollContainerFrame(
71 aContent
? nsLayoutUtils::FindScrollContainerFrameFor(aContent
) : nullptr,
75 ScreenMargin
DisplayPortMargins::GetRelativeToLayoutViewport(
76 ContentGeometryType aGeometryType
,
77 ScrollContainerFrame
* aScrollContainerFrame
,
78 const CSSToScreenScale2D
& aDisplayportScale
) const {
79 // APZ wants |mMargins| applied relative to the visual viewport.
80 // The main-thread painting code applies margins relative to
81 // the layout viewport. To get the main thread to paint the
82 // area APZ wants, apply a translation between the two. The
83 // magnitude of the translation depends on whether we are
84 // applying the displayport to scrolled or fixed content.
85 CSSPoint scrollDeltaCss
=
86 ComputeAsyncTranslation(aGeometryType
, aScrollContainerFrame
);
87 ScreenPoint scrollDelta
= scrollDeltaCss
* aDisplayportScale
;
88 ScreenMargin margins
= mMargins
;
89 margins
.left
-= scrollDelta
.x
;
90 margins
.right
+= scrollDelta
.x
;
91 margins
.top
-= scrollDelta
.y
;
92 margins
.bottom
+= scrollDelta
.y
;
96 std::ostream
& operator<<(std::ostream
& aOs
,
97 const DisplayPortMargins
& aMargins
) {
98 if (aMargins
.mVisualOffset
== CSSPoint() &&
99 aMargins
.mLayoutOffset
== CSSPoint()) {
100 aOs
<< aMargins
.mMargins
;
102 aOs
<< "{" << aMargins
.mMargins
<< "," << aMargins
.mVisualOffset
<< ","
103 << aMargins
.mLayoutOffset
<< "}";
108 CSSPoint
DisplayPortMargins::ComputeAsyncTranslation(
109 ContentGeometryType aGeometryType
,
110 ScrollContainerFrame
* aScrollContainerFrame
) const {
111 // If we are applying the displayport to scrolled content, the
112 // translation is the entire difference between the visual and
114 if (aGeometryType
== ContentGeometryType::Scrolled
) {
115 return mVisualOffset
- mLayoutOffset
;
118 // If we are applying the displayport to fixed content, only
119 // part of the difference between the visual and layout offsets
120 // should be applied. This is because fixed content remains fixed
121 // to the layout viewport, and some of the async delta between
122 // the visual and layout offsets can drag the layout viewport
123 // with it. We want only the remaining delta, i.e. the offset of
124 // the visual viewport relative to the (async-scrolled) layout
126 if (!aScrollContainerFrame
) {
127 // Displayport on a non-scrolling frame for some reason.
128 // There will be no divergence between the two viewports.
131 // Fixed content is always fixed to an RSF.
132 MOZ_ASSERT(aScrollContainerFrame
->IsRootScrollFrameOfDocument());
133 if (!aScrollContainerFrame
->PresShell()->IsVisualViewportSizeSet()) {
134 // Zooming is disabled, so the layout viewport tracks the
135 // visual viewport completely.
138 // Use KeepLayoutViewportEnclosingViewportVisual() to compute
139 // an async layout viewport the way APZ would.
140 const CSSRect visualViewport
{
142 // TODO: There are probably some edge cases here around async zooming
143 // that are not currently being handled properly. For proper handling,
144 // we'd likely need to save APZ's async zoom when populating
145 // mVisualOffset, and using it to adjust the visual viewport size here.
146 // Note that any incorrectness caused by this will only occur transiently
147 // during async zooming.
148 CSSSize::FromAppUnits(
149 aScrollContainerFrame
->PresShell()->GetVisualViewportSize())};
150 const CSSRect scrollableRect
= CSSRect::FromAppUnits(
151 nsLayoutUtils::CalculateExpandedScrollableRect(aScrollContainerFrame
));
152 CSSRect asyncLayoutViewport
{
154 CSSSize::FromAppUnits(aScrollContainerFrame
->GetScrollPortRect().Size())};
155 FrameMetrics::KeepLayoutViewportEnclosingVisualViewport(
156 visualViewport
, scrollableRect
, /* out */ asyncLayoutViewport
);
157 return mVisualOffset
- asyncLayoutViewport
.TopLeft();
160 static nsRect
GetDisplayPortFromRectData(nsIContent
* aContent
,
161 DisplayPortPropertyData
* aRectData
) {
162 // In the case where the displayport is set as a rect, we assume it is
163 // already aligned and clamped as necessary. The burden to do that is
164 // on the setter of the displayport. In practice very few places set the
165 // displayport directly as a rect (mostly tests).
166 return aRectData
->mRect
;
169 static nsRect
GetDisplayPortFromMarginsData(
170 nsIContent
* aContent
, DisplayPortMarginsPropertyData
* aMarginsData
,
171 const DisplayPortOptions
& aOptions
) {
172 // In the case where the displayport is set via margins, we apply the margins
173 // to a base rect. Then we align the expanded rect based on the alignment
174 // requested, and finally, clamp it to the size of the scrollable rect.
177 if (nsRect
* baseData
= static_cast<nsRect
*>(
178 aContent
->GetProperty(nsGkAtoms::DisplayPortBase
))) {
181 // In theory we shouldn't get here, but we do sometimes (see bug 1212136).
182 // Fall through for graceful handling.
185 nsIFrame
* frame
= nsLayoutUtils::GetScrollContainerFrameFromContent(aContent
);
187 // Turns out we can't really compute it. Oops. We still should return
190 "Attempting to get a displayport from a content with no primary "
196 if (aContent
->OwnerDoc()->GetRootElement() == aContent
) {
200 ScrollContainerFrame
* scrollContainerFrame
= frame
->GetScrollTargetFrame();
202 if (scrollContainerFrame
) {
203 scrollPos
= scrollContainerFrame
->GetScrollPosition();
206 nsPresContext
* presContext
= frame
->PresContext();
207 int32_t auPerDevPixel
= presContext
->AppUnitsPerDevPixel();
209 LayoutDeviceToScreenScale2D res
=
210 LayoutDeviceToParentLayerScale(
211 presContext
->PresShell()->GetCumulativeResolution()) *
212 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
215 // Calculate the expanded scrollable rect, which we'll be clamping the
217 nsRect expandedScrollableRect
=
218 nsLayoutUtils::CalculateExpandedScrollableRect(frame
);
220 // GetTransformToAncestorScale() can return 0. In this case, just return the
221 // base rect (clamped to the expanded scrollable rect), as other calculations
222 // would run into divisions by zero.
223 if (res
== LayoutDeviceToScreenScale2D(0, 0)) {
224 // Make sure the displayport remains within the scrollable rect.
225 return base
.MoveInsideAndClamp(expandedScrollableRect
- scrollPos
);
228 // First convert the base rect to screen pixels
229 LayoutDeviceToScreenScale2D parentRes
= res
;
231 // the base rect for root scroll frames is specified in the parent document
232 // coordinate space, so it doesn't include the local resolution.
233 float localRes
= presContext
->PresShell()->GetResolution();
234 parentRes
.xScale
/= localRes
;
235 parentRes
.yScale
/= localRes
;
237 ScreenRect screenRect
=
238 LayoutDeviceRect::FromAppUnits(base
, auPerDevPixel
) * parentRes
;
240 // Note on the correctness of applying the alignment in Screen space:
241 // The correct space to apply the alignment in would be Layer space, but
242 // we don't necessarily know the scale to convert to Layer space at this
243 // point because Layout may not yet have chosen the resolution at which to
244 // render (it chooses that in FrameLayerBuilder, but this can be called
245 // during display list building). Therefore, we perform the alignment in
246 // Screen space, which basically assumes that Layout chose to render at
247 // screen resolution; since this is what Layout does most of the time,
248 // this is a good approximation. A proper solution would involve moving
249 // the choosing of the resolution to display-list building time.
250 ScreenSize alignment
;
252 PresShell
* presShell
= presContext
->PresShell();
253 MOZ_ASSERT(presShell
);
255 ScreenMargin margins
= aMarginsData
->mMargins
.GetRelativeToLayoutViewport(
256 aOptions
.mGeometryType
, scrollContainerFrame
,
257 presContext
->CSSToDevPixelScale() * res
);
259 if (presShell
->IsDisplayportSuppressed() ||
260 aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
)) {
261 alignment
= ScreenSize(1, 1);
263 // Moving the displayport is relatively expensive with WR so we use a larger
264 // alignment that causes the displayport to move less frequently. The
265 // alignment scales up with the size of the base rect so larger scrollframes
266 // use a larger alignment, but we clamp the alignment to a power of two
267 // between 128 and 1024 (inclusive).
268 // This naturally also increases the size of the displayport compared to
269 // always using a 128 alignment, so the displayport multipliers are also
270 // correspondingly smaller when WR is enabled to prevent the displayport
271 // from becoming too big.
273 layers::apz::GetDisplayportAlignmentMultiplier(screenRect
.Size());
274 alignment
= ScreenSize(128 * multiplier
.width
, 128 * multiplier
.height
);
277 // Avoid division by zero.
278 if (alignment
.width
== 0) {
279 alignment
.width
= 128;
281 if (alignment
.height
== 0) {
282 alignment
.height
= 128;
285 // Expand the rect by the margins
286 screenRect
.Inflate(margins
);
288 ScreenPoint scrollPosScreen
=
289 LayoutDevicePoint::FromAppUnits(scrollPos
, auPerDevPixel
) * res
;
291 // Align the display port.
292 screenRect
+= scrollPosScreen
;
293 float x
= alignment
.width
* floor(screenRect
.x
/ alignment
.width
);
294 float y
= alignment
.height
* floor(screenRect
.y
/ alignment
.height
);
295 float w
= alignment
.width
* ceil(screenRect
.width
/ alignment
.width
+ 1);
296 float h
= alignment
.height
* ceil(screenRect
.height
/ alignment
.height
+ 1);
297 screenRect
= ScreenRect(x
, y
, w
, h
);
298 screenRect
-= scrollPosScreen
;
300 // Convert the aligned rect back into app units.
301 nsRect result
= LayoutDeviceRect::ToAppUnits(screenRect
/ res
, auPerDevPixel
);
303 // Make sure the displayport remains within the scrollable rect.
304 result
= result
.MoveInsideAndClamp(expandedScrollableRect
- scrollPos
);
309 static bool GetDisplayPortData(
310 nsIContent
* aContent
, DisplayPortPropertyData
** aOutRectData
,
311 DisplayPortMarginsPropertyData
** aOutMarginsData
) {
312 MOZ_ASSERT(aOutRectData
&& aOutMarginsData
);
314 *aOutRectData
= static_cast<DisplayPortPropertyData
*>(
315 aContent
->GetProperty(nsGkAtoms::DisplayPort
));
316 *aOutMarginsData
= static_cast<DisplayPortMarginsPropertyData
*>(
317 aContent
->GetProperty(nsGkAtoms::DisplayPortMargins
));
319 if (!*aOutRectData
&& !*aOutMarginsData
) {
320 // This content element has no displayport data at all
324 if (*aOutRectData
&& *aOutMarginsData
) {
325 // choose margins if equal priority
326 if ((*aOutRectData
)->mPriority
> (*aOutMarginsData
)->mPriority
) {
327 *aOutMarginsData
= nullptr;
329 *aOutRectData
= nullptr;
333 NS_ASSERTION((*aOutRectData
== nullptr) != (*aOutMarginsData
== nullptr),
334 "Only one of aOutRectData or aOutMarginsData should be set!");
339 static bool GetWasDisplayPortPainted(nsIContent
* aContent
) {
340 DisplayPortPropertyData
* rectData
= nullptr;
341 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
343 if (!GetDisplayPortData(aContent
, &rectData
, &marginsData
)) {
347 return rectData
? rectData
->mPainted
: marginsData
->mPainted
;
350 bool DisplayPortUtils::IsMissingDisplayPortBaseRect(nsIContent
* aContent
) {
351 DisplayPortPropertyData
* rectData
= nullptr;
352 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
354 if (GetDisplayPortData(aContent
, &rectData
, &marginsData
) && marginsData
) {
355 return !aContent
->GetProperty(nsGkAtoms::DisplayPortBase
);
361 static void TranslateFromScrollPortToScrollContainerFrame(nsIContent
* aContent
,
364 if (ScrollContainerFrame
* scrollContainerFrame
=
365 nsLayoutUtils::FindScrollContainerFrameFor(aContent
)) {
366 *aRect
+= scrollContainerFrame
->GetScrollPortRect().TopLeft();
370 static bool GetDisplayPortImpl(nsIContent
* aContent
, nsRect
* aResult
,
371 const DisplayPortOptions
& aOptions
) {
372 DisplayPortPropertyData
* rectData
= nullptr;
373 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
375 if (!GetDisplayPortData(aContent
, &rectData
, &marginsData
)) {
379 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
380 if (frame
&& !frame
->PresShell()->AsyncPanZoomEnabled()) {
385 // We have displayport data, but the caller doesn't want the actual
386 // rect, so we don't need to actually compute it.
390 bool isDisplayportSuppressed
= false;
393 nsPresContext
* presContext
= frame
->PresContext();
394 MOZ_ASSERT(presContext
);
395 PresShell
* presShell
= presContext
->PresShell();
396 MOZ_ASSERT(presShell
);
397 isDisplayportSuppressed
= presShell
->IsDisplayportSuppressed();
402 result
= GetDisplayPortFromRectData(aContent
, rectData
);
403 } else if (isDisplayportSuppressed
||
404 nsLayoutUtils::ShouldDisableApzForElement(aContent
) ||
405 aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
)) {
406 // Note: the above conditions should be in sync with the conditions in
407 // WillUseEmptyDisplayPortMargins.
409 // Make a copy of the margins data but set the margins to empty.
410 // Do not create a new DisplayPortMargins object with
411 // DisplayPortMargins::Empty(), because that will record the visual
412 // and layout scroll offsets in place right now on the DisplayPortMargins,
413 // and those are only meant to be recorded when the margins are stored.
414 DisplayPortMarginsPropertyData noMargins
= *marginsData
;
415 noMargins
.mMargins
.mMargins
= ScreenMargin();
416 result
= GetDisplayPortFromMarginsData(aContent
, &noMargins
, aOptions
);
418 result
= GetDisplayPortFromMarginsData(aContent
, marginsData
, aOptions
);
421 if (aOptions
.mRelativeTo
== DisplayportRelativeTo::ScrollFrame
) {
422 TranslateFromScrollPortToScrollContainerFrame(aContent
, &result
);
429 bool DisplayPortUtils::GetDisplayPort(nsIContent
* aContent
, nsRect
* aResult
,
430 const DisplayPortOptions
& aOptions
) {
431 return GetDisplayPortImpl(aContent
, aResult
, aOptions
);
434 bool DisplayPortUtils::HasDisplayPort(nsIContent
* aContent
) {
435 return GetDisplayPort(aContent
, nullptr);
438 bool DisplayPortUtils::HasPaintedDisplayPort(nsIContent
* aContent
) {
439 DisplayPortPropertyData
* rectData
= nullptr;
440 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
441 GetDisplayPortData(aContent
, &rectData
, &marginsData
);
443 return rectData
->mPainted
;
446 return marginsData
->mPainted
;
451 void DisplayPortUtils::MarkDisplayPortAsPainted(nsIContent
* aContent
) {
452 DisplayPortPropertyData
* rectData
= nullptr;
453 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
454 GetDisplayPortData(aContent
, &rectData
, &marginsData
);
455 MOZ_ASSERT(rectData
|| marginsData
,
456 "MarkDisplayPortAsPainted should only be called for an element "
457 "with a displayport");
459 rectData
->mPainted
= true;
462 marginsData
->mPainted
= true;
466 bool DisplayPortUtils::HasNonMinimalDisplayPort(nsIContent
* aContent
) {
467 return !aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
) &&
468 HasDisplayPort(aContent
);
471 bool DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(nsIContent
* aContent
) {
472 if (aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
)) {
476 DisplayPortPropertyData
* rectData
= nullptr;
477 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
478 if (!GetDisplayPortData(aContent
, &rectData
, &marginsData
)) {
483 // We have a display port, so if we don't have margin data we must have rect
484 // data. We consider such as non zero and non minimal, it's probably not too
485 // important as display port rects are only used in tests.
489 if (marginsData
->mMargins
.mMargins
!= ScreenMargin()) {
497 bool DisplayPortUtils::GetDisplayPortForVisibilityTesting(nsIContent
* aContent
,
500 return GetDisplayPortImpl(
502 DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame
));
505 void DisplayPortUtils::InvalidateForDisplayPortChange(
506 nsIContent
* aContent
, bool aHadDisplayPort
, const nsRect
& aOldDisplayPort
,
507 const nsRect
& aNewDisplayPort
, RepaintMode aRepaintMode
) {
508 if (aRepaintMode
!= RepaintMode::Repaint
) {
513 !aHadDisplayPort
|| !aOldDisplayPort
.IsEqualEdges(aNewDisplayPort
);
515 nsIFrame
* frame
= nsLayoutUtils::FindScrollContainerFrameFor(aContent
);
516 if (changed
&& frame
) {
517 // It is important to call SchedulePaint on the same frame that we set the
518 // dirty rect properties on so we can find the frame later to remove the
520 frame
->SchedulePaint();
522 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
526 if (StaticPrefs::layout_display_list_retain_sc()) {
527 // DisplayListBuildingDisplayPortRect property is not used when retain sc
532 auto* builder
= nsLayoutUtils::GetRetainedDisplayListBuilder(frame
);
538 nsRect
* rect
= frame
->GetProperty(
539 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), &found
);
544 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect
);
545 frame
->SetHasOverrideDirtyRegion(true);
547 DL_LOGV("Adding display port building rect for frame %p\n", frame
);
548 RetainedDisplayListData
* data
= builder
->Data();
549 data
->Flags(frame
) += RetainedDisplayListData::FrameFlag::HasProps
;
551 MOZ_ASSERT(rect
, "this property should only store non-null values");
554 if (aHadDisplayPort
) {
555 // We only need to build a display list for any new areas added
556 nsRegion
newRegion(aNewDisplayPort
);
557 newRegion
.SubOut(aOldDisplayPort
);
558 rect
->UnionRect(*rect
, newRegion
.GetBounds());
560 rect
->UnionRect(*rect
, aNewDisplayPort
);
565 bool DisplayPortUtils::SetDisplayPortMargins(
566 nsIContent
* aContent
, PresShell
* aPresShell
,
567 const DisplayPortMargins
& aMargins
,
568 ClearMinimalDisplayPortProperty aClearMinimalDisplayPortProperty
,
569 uint32_t aPriority
, RepaintMode aRepaintMode
) {
570 MOZ_ASSERT(aContent
);
571 MOZ_ASSERT(aContent
->GetComposedDoc() == aPresShell
->GetDocument());
573 DisplayPortMarginsPropertyData
* currentData
=
574 static_cast<DisplayPortMarginsPropertyData
*>(
575 aContent
->GetProperty(nsGkAtoms::DisplayPortMargins
));
576 if (currentData
&& currentData
->mPriority
> aPriority
) {
580 if (currentData
&& currentData
->mMargins
.mVisualOffset
!= CSSPoint() &&
581 aMargins
.mVisualOffset
== CSSPoint()) {
582 // If we hit this, then it's possible that we're setting a displayport
583 // that is wrong because the old one had a layout/visual adjustment and
584 // the new one does not.
585 MOZ_LOG(sDisplayportLog
, LogLevel::Warning
,
586 ("Dropping visual offset %s",
587 ToString(currentData
->mMargins
.mVisualOffset
).c_str()));
590 nsIFrame
* scrollFrame
=
591 nsLayoutUtils::GetScrollContainerFrameFromContent(aContent
);
593 nsRect oldDisplayPort
;
594 bool hadDisplayPort
= false;
595 bool wasPainted
= GetWasDisplayPortPainted(aContent
);
597 // We only use the two return values from this function to call
598 // InvalidateForDisplayPortChange. InvalidateForDisplayPortChange does
599 // nothing if aContent does not have a frame. So getting the displayport is
600 // useless if the content has no frame, so we avoid calling this to avoid
601 // triggering a warning about not having a frame.
602 hadDisplayPort
= GetDisplayPort(aContent
, &oldDisplayPort
);
605 aContent
->SetProperty(
606 nsGkAtoms::DisplayPortMargins
,
607 new DisplayPortMarginsPropertyData(aMargins
, aPriority
, wasPainted
),
608 nsINode::DeleteProperty
<DisplayPortMarginsPropertyData
>);
610 if (aClearMinimalDisplayPortProperty
==
611 ClearMinimalDisplayPortProperty::Yes
) {
612 if (MOZ_LOG_TEST(sDisplayportLog
, LogLevel::Debug
) &&
613 aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
)) {
614 mozilla::layers::ScrollableLayerGuid::ViewID viewID
=
615 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID
;
616 nsLayoutUtils::FindIDFor(aContent
, &viewID
);
617 MOZ_LOG(sDisplayportLog
, LogLevel::Debug
,
618 ("SetDisplayPortMargins removing MinimalDisplayPort prop on "
619 "scrollId=%" PRIu64
"\n",
622 aContent
->RemoveProperty(nsGkAtoms::MinimalDisplayPort
);
625 ScrollContainerFrame
* scrollContainerFrame
=
626 scrollFrame
? scrollFrame
->GetScrollTargetFrame() : nullptr;
627 if (!scrollContainerFrame
) {
631 nsRect newDisplayPort
;
632 DebugOnly
<bool> hasDisplayPort
= GetDisplayPort(aContent
, &newDisplayPort
);
633 MOZ_ASSERT(hasDisplayPort
);
635 if (MOZ_LOG_TEST(sDisplayportLog
, LogLevel::Debug
)) {
636 mozilla::layers::ScrollableLayerGuid::ViewID viewID
=
637 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID
;
638 nsLayoutUtils::FindIDFor(aContent
, &viewID
);
639 if (!hadDisplayPort
) {
640 MOZ_LOG(sDisplayportLog
, LogLevel::Debug
,
641 ("SetDisplayPortMargins %s on scrollId=%" PRIu64
", newDp=%s\n",
642 ToString(aMargins
).c_str(), viewID
,
643 ToString(newDisplayPort
).c_str()));
645 // Use verbose level logging for when an existing displayport got its
647 MOZ_LOG(sDisplayportLog
, LogLevel::Verbose
,
648 ("SetDisplayPortMargins %s on scrollId=%" PRIu64
", newDp=%s\n",
649 ToString(aMargins
).c_str(), viewID
,
650 ToString(newDisplayPort
).c_str()));
654 InvalidateForDisplayPortChange(aContent
, hadDisplayPort
, oldDisplayPort
,
655 newDisplayPort
, aRepaintMode
);
657 scrollContainerFrame
->TriggerDisplayPortExpiration();
659 // Display port margins changing means that the set of visible frames may
660 // have drastically changed. Check if we should schedule an update.
661 hadDisplayPort
= scrollContainerFrame
662 ->GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
665 bool needVisibilityUpdate
= !hadDisplayPort
;
666 // Check if the total size has changed by a large factor.
667 if (!needVisibilityUpdate
) {
668 if ((newDisplayPort
.width
> 2 * oldDisplayPort
.width
) ||
669 (oldDisplayPort
.width
> 2 * newDisplayPort
.width
) ||
670 (newDisplayPort
.height
> 2 * oldDisplayPort
.height
) ||
671 (oldDisplayPort
.height
> 2 * newDisplayPort
.height
)) {
672 needVisibilityUpdate
= true;
675 // Check if it's moved by a significant amount.
676 if (!needVisibilityUpdate
) {
677 if (nsRect
* baseData
= static_cast<nsRect
*>(
678 aContent
->GetProperty(nsGkAtoms::DisplayPortBase
))) {
679 nsRect base
= *baseData
;
680 if ((std::abs(newDisplayPort
.X() - oldDisplayPort
.X()) > base
.width
) ||
681 (std::abs(newDisplayPort
.XMost() - oldDisplayPort
.XMost()) >
683 (std::abs(newDisplayPort
.Y() - oldDisplayPort
.Y()) > base
.height
) ||
684 (std::abs(newDisplayPort
.YMost() - oldDisplayPort
.YMost()) >
686 needVisibilityUpdate
= true;
690 if (needVisibilityUpdate
) {
691 aPresShell
->ScheduleApproximateFrameVisibilityUpdateNow();
697 void DisplayPortUtils::SetDisplayPortBase(nsIContent
* aContent
,
698 const nsRect
& aBase
) {
699 if (MOZ_LOG_TEST(sDisplayportLog
, LogLevel::Verbose
)) {
700 ViewID viewId
= nsLayoutUtils::FindOrCreateIDFor(aContent
);
701 MOZ_LOG(sDisplayportLog
, LogLevel::Verbose
,
702 ("Setting base rect %s for scrollId=%" PRIu64
"\n",
703 ToString(aBase
).c_str(), viewId
));
705 aContent
->SetProperty(nsGkAtoms::DisplayPortBase
, new nsRect(aBase
),
706 nsINode::DeleteProperty
<nsRect
>);
709 void DisplayPortUtils::SetDisplayPortBaseIfNotSet(nsIContent
* aContent
,
710 const nsRect
& aBase
) {
711 if (!aContent
->GetProperty(nsGkAtoms::DisplayPortBase
)) {
712 SetDisplayPortBase(aContent
, aBase
);
716 void DisplayPortUtils::RemoveDisplayPort(nsIContent
* aContent
) {
717 aContent
->RemoveProperty(nsGkAtoms::DisplayPort
);
718 aContent
->RemoveProperty(nsGkAtoms::DisplayPortMargins
);
721 bool DisplayPortUtils::ViewportHasDisplayPort(nsPresContext
* aPresContext
) {
722 nsIFrame
* rootScrollContainerFrame
=
723 aPresContext
->PresShell()->GetRootScrollContainerFrame();
724 return rootScrollContainerFrame
&&
725 HasDisplayPort(rootScrollContainerFrame
->GetContent());
728 bool DisplayPortUtils::IsFixedPosFrameInDisplayPort(const nsIFrame
* aFrame
) {
729 // Fixed-pos frames are parented by the viewport frame or the page content
730 // frame. We'll assume that printing/print preview don't have displayports for
732 nsIFrame
* parent
= aFrame
->GetParent();
733 if (!parent
|| parent
->GetParent() ||
734 aFrame
->StyleDisplay()->mPosition
!= StylePositionProperty::Fixed
) {
737 return ViewportHasDisplayPort(aFrame
->PresContext());
740 // We want to this return true for the scroll frame, but not the
741 // scrolled frame (which has the same content).
742 bool DisplayPortUtils::FrameHasDisplayPort(nsIFrame
* aFrame
,
743 const nsIFrame
* aScrolledFrame
) {
744 if (!aFrame
->GetContent() || !HasDisplayPort(aFrame
->GetContent())) {
747 ScrollContainerFrame
* sf
= do_QueryFrame(aFrame
);
749 if (aScrolledFrame
&& aScrolledFrame
!= sf
->GetScrolledFrame()) {
757 bool DisplayPortUtils::CalculateAndSetDisplayPortMargins(
758 ScrollContainerFrame
* aScrollContainerFrame
, RepaintMode aRepaintMode
) {
759 nsIContent
* content
= aScrollContainerFrame
->GetContent();
762 FrameMetrics metrics
=
763 nsLayoutUtils::CalculateBasicFrameMetrics(aScrollContainerFrame
);
764 ScreenMargin displayportMargins
= layers::apz::CalculatePendingDisplayPort(
765 metrics
, ParentLayerPoint(0.0f
, 0.0f
));
766 PresShell
* presShell
= aScrollContainerFrame
->PresShell();
768 DisplayPortMargins margins
= DisplayPortMargins::ForScrollContainerFrame(
769 aScrollContainerFrame
, displayportMargins
);
771 return SetDisplayPortMargins(content
, presShell
, margins
,
772 ClearMinimalDisplayPortProperty::Yes
, 0,
776 bool DisplayPortUtils::MaybeCreateDisplayPort(
777 nsDisplayListBuilder
* aBuilder
, ScrollContainerFrame
* aScrollContainerFrame
,
778 RepaintMode aRepaintMode
) {
779 MOZ_ASSERT(aBuilder
->IsPaintingToWindow());
781 nsIContent
* content
= aScrollContainerFrame
->GetContent();
786 // We perform an optimization where we ensure that at least one
787 // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a
788 // displayport. If that's not the case yet, and we are async-scrollable, we
789 // will get a displayport.
790 MOZ_ASSERT(nsLayoutUtils::AsyncPanZoomEnabled(aScrollContainerFrame
));
791 if (!aBuilder
->HaveScrollableDisplayPort() &&
792 aScrollContainerFrame
->WantAsyncScroll()) {
793 bool haveDisplayPort
= HasNonMinimalNonZeroDisplayPort(content
);
794 // If we don't already have a displayport, calculate and set one.
795 if (!haveDisplayPort
) {
796 // We only use the viewId for logging purposes, but create it
797 // unconditionally to minimize impact of enabling logging. If we don't
798 // assign a viewId here it will get assigned later anyway so functionally
799 // there should be no difference.
800 ViewID viewId
= nsLayoutUtils::FindOrCreateIDFor(content
);
802 sDisplayportLog
, LogLevel::Debug
,
803 ("Setting DP on first-encountered scrollId=%" PRIu64
"\n", viewId
));
805 CalculateAndSetDisplayPortMargins(aScrollContainerFrame
, aRepaintMode
);
807 haveDisplayPort
= HasNonMinimalDisplayPort(content
);
808 MOZ_ASSERT(haveDisplayPort
,
809 "should have a displayport after having just set it");
813 // Record that the we now have a scrollable display port.
814 aBuilder
->SetHaveScrollableDisplayPort();
819 void DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
821 nsIFrame
* frame
= aFrame
;
823 frame
= nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame
);
827 ScrollContainerFrame
* scrollAncestor
=
828 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame
);
829 if (!scrollAncestor
) {
832 frame
= scrollAncestor
;
833 MOZ_ASSERT(scrollAncestor
->WantAsyncScroll() ||
834 frame
->PresShell()->GetRootScrollContainerFrame() == frame
);
835 if (nsLayoutUtils::AsyncPanZoomEnabled(frame
) &&
836 !HasDisplayPort(frame
->GetContent())) {
837 SetDisplayPortMargins(frame
->GetContent(), frame
->PresShell(),
838 DisplayPortMargins::Empty(frame
->GetContent()),
839 ClearMinimalDisplayPortProperty::No
, 0,
840 RepaintMode::Repaint
);
845 bool DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered(
846 nsIFrame
* aFrame
, nsDisplayListBuilder
* aBuilder
) {
847 // Don't descend into the tab bar in chrome, it can be very large and does not
848 // contain any async scrollable elements.
849 if (XRE_IsParentProcess() && aFrame
->GetContent() &&
850 aFrame
->GetContent()->GetID() == nsGkAtoms::tabbrowser_arrowscrollbox
) {
853 if (aFrame
->IsScrollContainerOrSubclass()) {
854 auto* sf
= static_cast<ScrollContainerFrame
*>(aFrame
);
855 if (MaybeCreateDisplayPort(aBuilder
, sf
, RepaintMode::Repaint
)) {
856 // If this was the first displayport found in the first scroll container
857 // frame encountered, mark the scroll container frame with the current
858 // paint sequence number. This is used later to ensure the displayport
859 // created is never expired. When there is a scrollable frame with a first
860 // scrollable sequence number found that does not match the current paint
861 // sequence number (may occur if the dom was mutated in some way), the
862 // value will be reset.
863 sf
->SetIsFirstScrollableFrameSequenceNumber(
864 Some(nsDisplayListBuilder::GetPaintSequenceNumber()));
867 } else if (aFrame
->IsPlaceholderFrame()) {
868 nsPlaceholderFrame
* placeholder
= static_cast<nsPlaceholderFrame
*>(aFrame
);
869 nsIFrame
* oof
= placeholder
->GetOutOfFlowFrame();
870 if (oof
&& !nsLayoutUtils::IsPopup(oof
) &&
871 MaybeCreateDisplayPortInFirstScrollFrameEncountered(oof
, aBuilder
)) {
874 } else if (aFrame
->IsSubDocumentFrame()) {
875 PresShell
* presShell
= static_cast<nsSubDocumentFrame
*>(aFrame
)
876 ->GetSubdocumentPresShellForPainting(0);
877 if (nsIFrame
* root
= presShell
? presShell
->GetRootFrame() : nullptr) {
878 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(root
, aBuilder
)) {
883 if (aFrame
->StyleUIReset()->mMozSubtreeHiddenOnlyVisually
) {
884 // Only descend the visible card of deck / tabpanels
887 for (nsIFrame
* child
: aFrame
->PrincipalChildList()) {
888 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(child
, aBuilder
)) {
895 void DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(
897 nsIFrame
* frame
= aFrame
;
899 frame
= nsLayoutUtils::GetCrossDocParentFrameInProcess(frame
);
903 ScrollContainerFrame
* scrollAncestor
=
904 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame
);
905 if (!scrollAncestor
) {
908 frame
= scrollAncestor
;
913 MOZ_ASSERT(scrollAncestor
->WantAsyncScroll() ||
914 frame
->PresShell()->GetRootScrollContainerFrame() == frame
);
915 if (HasDisplayPort(frame
->GetContent())) {
916 scrollAncestor
->TriggerDisplayPortExpiration();
917 // Stop after the first trigger. If it failed, there's no point in
918 // continuing because all the rest of the frames we encounter are going
919 // to be ancestors of |scrollAncestor| which will keep its displayport.
920 // If the trigger succeeded, we stop because when the trigger executes
921 // it will call this function again to trigger the next ancestor up the
928 Maybe
<nsRect
> DisplayPortUtils::GetRootDisplayportBase(PresShell
* aPresShell
) {
929 DebugOnly
<nsPresContext
*> pc
= aPresShell
->GetPresContext();
930 MOZ_ASSERT(pc
, "this function should be called after PresShell::Init");
931 MOZ_ASSERT(pc
->IsRootContentDocumentCrossProcess() ||
932 !pc
->GetParentPresContext());
934 dom::BrowserChild
* browserChild
= dom::BrowserChild::GetFrom(aPresShell
);
935 if (browserChild
&& !browserChild
->IsTopLevel()) {
936 // If this is an in-process root in on OOP iframe, use the visible rect if
938 return browserChild
->GetVisibleRect();
941 nsIFrame
* frame
= aPresShell
->GetRootScrollContainerFrame();
943 frame
= aPresShell
->GetRootFrame();
948 baseRect
= GetDisplayportBase(frame
);
950 baseRect
= nsRect(nsPoint(0, 0),
951 aPresShell
->GetPresContext()->GetVisibleArea().Size());
954 return Some(baseRect
);
957 nsRect
DisplayPortUtils::GetDisplayportBase(nsIFrame
* aFrame
) {
960 return nsRect(nsPoint(),
961 nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame
));
964 bool DisplayPortUtils::WillUseEmptyDisplayPortMargins(nsIContent
* aContent
) {
965 MOZ_ASSERT(HasDisplayPort(aContent
));
966 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
971 // Note these conditions should be in sync with the conditions where we use
972 // empty margins to calculate display port in GetDisplayPortImpl
973 return aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
) ||
974 frame
->PresShell()->IsDisplayportSuppressed() ||
975 nsLayoutUtils::ShouldDisableApzForElement(aContent
);
978 } // namespace mozilla