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/StaticPrefs_layers.h"
19 #include "mozilla/StaticPrefs_layout.h"
20 #include "nsIScrollableFrame.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::ForScrollFrame(
49 nsIScrollableFrame
* aScrollFrame
, const ScreenMargin
& aMargins
) {
50 CSSPoint visualOffset
;
51 CSSPoint layoutOffset
;
53 nsIFrame
* scrollFrame
= do_QueryFrame(aScrollFrame
);
54 PresShell
* presShell
= scrollFrame
->PresShell();
55 layoutOffset
= CSSPoint::FromAppUnits(aScrollFrame
->GetScrollPosition());
56 if (aScrollFrame
->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 ForScrollFrame(
71 aContent
? nsLayoutUtils::FindScrollableFrameFor(aContent
) : nullptr,
75 ScreenMargin
DisplayPortMargins::GetRelativeToLayoutViewport(
76 ContentGeometryType aGeometryType
, nsIScrollableFrame
* aScrollableFrame
,
77 const CSSToScreenScale2D
& aDisplayportScale
) const {
78 // APZ wants |mMargins| applied relative to the visual viewport.
79 // The main-thread painting code applies margins relative to
80 // the layout viewport. To get the main thread to paint the
81 // area APZ wants, apply a translation between the two. The
82 // magnitude of the translation depends on whether we are
83 // applying the displayport to scrolled or fixed content.
84 CSSPoint scrollDeltaCss
=
85 ComputeAsyncTranslation(aGeometryType
, aScrollableFrame
);
86 ScreenPoint scrollDelta
= scrollDeltaCss
* aDisplayportScale
;
87 ScreenMargin margins
= mMargins
;
88 margins
.left
-= scrollDelta
.x
;
89 margins
.right
+= scrollDelta
.x
;
90 margins
.top
-= scrollDelta
.y
;
91 margins
.bottom
+= scrollDelta
.y
;
95 std::ostream
& operator<<(std::ostream
& aOs
,
96 const DisplayPortMargins
& aMargins
) {
97 if (aMargins
.mVisualOffset
== CSSPoint() &&
98 aMargins
.mLayoutOffset
== CSSPoint()) {
99 aOs
<< aMargins
.mMargins
;
101 aOs
<< "{" << aMargins
.mMargins
<< "," << aMargins
.mVisualOffset
<< ","
102 << aMargins
.mLayoutOffset
<< "}";
107 CSSPoint
DisplayPortMargins::ComputeAsyncTranslation(
108 ContentGeometryType aGeometryType
,
109 nsIScrollableFrame
* aScrollableFrame
) const {
110 // If we are applying the displayport to scrolled content, the
111 // translation is the entire difference between the visual and
113 if (aGeometryType
== ContentGeometryType::Scrolled
) {
114 return mVisualOffset
- mLayoutOffset
;
117 // If we are applying the displayport to fixed content, only
118 // part of the difference between the visual and layout offsets
119 // should be applied. This is because fixed content remains fixed
120 // to the layout viewport, and some of the async delta between
121 // the visual and layout offsets can drag the layout viewport
122 // with it. We want only the remaining delta, i.e. the offset of
123 // the visual viewport relative to the (async-scrolled) layout
125 if (!aScrollableFrame
) {
126 // Displayport on a non-scrolling frame for some reason.
127 // There will be no divergence between the two viewports.
130 // Fixed content is always fixed to an RSF.
131 MOZ_ASSERT(aScrollableFrame
->IsRootScrollFrameOfDocument());
132 nsIFrame
* scrollFrame
= do_QueryFrame(aScrollableFrame
);
133 if (!scrollFrame
->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(scrollFrame
->PresShell()->GetVisualViewportSize())};
149 const CSSRect scrollableRect
= CSSRect::FromAppUnits(
150 nsLayoutUtils::CalculateExpandedScrollableRect(scrollFrame
));
151 CSSRect asyncLayoutViewport
{
153 CSSSize::FromAppUnits(aScrollableFrame
->GetScrollPortRect().Size())};
154 FrameMetrics::KeepLayoutViewportEnclosingVisualViewport(
155 visualViewport
, scrollableRect
, /* out */ asyncLayoutViewport
);
156 return mVisualOffset
- asyncLayoutViewport
.TopLeft();
159 static nsRect
GetDisplayPortFromRectData(nsIContent
* aContent
,
160 DisplayPortPropertyData
* aRectData
) {
161 // In the case where the displayport is set as a rect, we assume it is
162 // already aligned and clamped as necessary. The burden to do that is
163 // on the setter of the displayport. In practice very few places set the
164 // displayport directly as a rect (mostly tests).
165 return aRectData
->mRect
;
168 static nsRect
GetDisplayPortFromMarginsData(
169 nsIContent
* aContent
, DisplayPortMarginsPropertyData
* aMarginsData
,
170 const DisplayPortOptions
& aOptions
) {
171 // In the case where the displayport is set via margins, we apply the margins
172 // to a base rect. Then we align the expanded rect based on the alignment
173 // requested, and finally, clamp it to the size of the scrollable rect.
176 if (nsRect
* baseData
= static_cast<nsRect
*>(
177 aContent
->GetProperty(nsGkAtoms::DisplayPortBase
))) {
180 // In theory we shouldn't get here, but we do sometimes (see bug 1212136).
181 // Fall through for graceful handling.
184 nsIFrame
* frame
= nsLayoutUtils::GetScrollFrameFromContent(aContent
);
186 // Turns out we can't really compute it. Oops. We still should return
189 "Attempting to get a displayport from a content with no primary "
195 if (aContent
->OwnerDoc()->GetRootElement() == aContent
) {
199 nsIScrollableFrame
* scrollableFrame
= frame
->GetScrollTargetFrame();
201 if (scrollableFrame
) {
202 scrollPos
= scrollableFrame
->GetScrollPosition();
205 nsPresContext
* presContext
= frame
->PresContext();
206 int32_t auPerDevPixel
= presContext
->AppUnitsPerDevPixel();
208 LayoutDeviceToScreenScale2D res
=
209 LayoutDeviceToParentLayerScale(
210 presContext
->PresShell()->GetCumulativeResolution()) *
211 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
214 // Calculate the expanded scrollable rect, which we'll be clamping the
216 nsRect expandedScrollableRect
=
217 nsLayoutUtils::CalculateExpandedScrollableRect(frame
);
219 // GetTransformToAncestorScale() can return 0. In this case, just return the
220 // base rect (clamped to the expanded scrollable rect), as other calculations
221 // would run into divisions by zero.
222 if (res
== LayoutDeviceToScreenScale2D(0, 0)) {
223 // Make sure the displayport remains within the scrollable rect.
224 return base
.MoveInsideAndClamp(expandedScrollableRect
- scrollPos
);
227 // First convert the base rect to screen pixels
228 LayoutDeviceToScreenScale2D parentRes
= res
;
230 // the base rect for root scroll frames is specified in the parent document
231 // coordinate space, so it doesn't include the local resolution.
232 float localRes
= presContext
->PresShell()->GetResolution();
233 parentRes
.xScale
/= localRes
;
234 parentRes
.yScale
/= localRes
;
236 ScreenRect screenRect
=
237 LayoutDeviceRect::FromAppUnits(base
, auPerDevPixel
) * parentRes
;
239 // Note on the correctness of applying the alignment in Screen space:
240 // The correct space to apply the alignment in would be Layer space, but
241 // we don't necessarily know the scale to convert to Layer space at this
242 // point because Layout may not yet have chosen the resolution at which to
243 // render (it chooses that in FrameLayerBuilder, but this can be called
244 // during display list building). Therefore, we perform the alignment in
245 // Screen space, which basically assumes that Layout chose to render at
246 // screen resolution; since this is what Layout does most of the time,
247 // this is a good approximation. A proper solution would involve moving
248 // the choosing of the resolution to display-list building time.
249 ScreenSize alignment
;
251 PresShell
* presShell
= presContext
->PresShell();
252 MOZ_ASSERT(presShell
);
254 ScreenMargin margins
= aMarginsData
->mMargins
.GetRelativeToLayoutViewport(
255 aOptions
.mGeometryType
, scrollableFrame
,
256 presContext
->CSSToDevPixelScale() * res
);
258 if (presShell
->IsDisplayportSuppressed() ||
259 aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
)) {
260 alignment
= ScreenSize(1, 1);
262 // Moving the displayport is relatively expensive with WR so we use a larger
263 // alignment that causes the displayport to move less frequently. The
264 // alignment scales up with the size of the base rect so larger scrollframes
265 // use a larger alignment, but we clamp the alignment to a power of two
266 // between 128 and 1024 (inclusive).
267 // This naturally also increases the size of the displayport compared to
268 // always using a 128 alignment, so the displayport multipliers are also
269 // correspondingly smaller when WR is enabled to prevent the displayport
270 // from becoming too big.
272 layers::apz::GetDisplayportAlignmentMultiplier(screenRect
.Size());
273 alignment
= ScreenSize(128 * multiplier
.width
, 128 * multiplier
.height
);
276 // Avoid division by zero.
277 if (alignment
.width
== 0) {
278 alignment
.width
= 128;
280 if (alignment
.height
== 0) {
281 alignment
.height
= 128;
284 // Expand the rect by the margins
285 screenRect
.Inflate(margins
);
287 ScreenPoint scrollPosScreen
=
288 LayoutDevicePoint::FromAppUnits(scrollPos
, auPerDevPixel
) * res
;
290 // Align the display port.
291 screenRect
+= scrollPosScreen
;
292 float x
= alignment
.width
* floor(screenRect
.x
/ alignment
.width
);
293 float y
= alignment
.height
* floor(screenRect
.y
/ alignment
.height
);
294 float w
= alignment
.width
* ceil(screenRect
.width
/ alignment
.width
+ 1);
295 float h
= alignment
.height
* ceil(screenRect
.height
/ alignment
.height
+ 1);
296 screenRect
= ScreenRect(x
, y
, w
, h
);
297 screenRect
-= scrollPosScreen
;
299 // Convert the aligned rect back into app units.
300 nsRect result
= LayoutDeviceRect::ToAppUnits(screenRect
/ res
, auPerDevPixel
);
302 // Make sure the displayport remains within the scrollable rect.
303 result
= result
.MoveInsideAndClamp(expandedScrollableRect
- scrollPos
);
308 static bool GetDisplayPortData(
309 nsIContent
* aContent
, DisplayPortPropertyData
** aOutRectData
,
310 DisplayPortMarginsPropertyData
** aOutMarginsData
) {
311 MOZ_ASSERT(aOutRectData
&& aOutMarginsData
);
313 *aOutRectData
= static_cast<DisplayPortPropertyData
*>(
314 aContent
->GetProperty(nsGkAtoms::DisplayPort
));
315 *aOutMarginsData
= static_cast<DisplayPortMarginsPropertyData
*>(
316 aContent
->GetProperty(nsGkAtoms::DisplayPortMargins
));
318 if (!*aOutRectData
&& !*aOutMarginsData
) {
319 // This content element has no displayport data at all
323 if (*aOutRectData
&& *aOutMarginsData
) {
324 // choose margins if equal priority
325 if ((*aOutRectData
)->mPriority
> (*aOutMarginsData
)->mPriority
) {
326 *aOutMarginsData
= nullptr;
328 *aOutRectData
= nullptr;
332 NS_ASSERTION((*aOutRectData
== nullptr) != (*aOutMarginsData
== nullptr),
333 "Only one of aOutRectData or aOutMarginsData should be set!");
338 static bool GetWasDisplayPortPainted(nsIContent
* aContent
) {
339 DisplayPortPropertyData
* rectData
= nullptr;
340 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
342 if (!GetDisplayPortData(aContent
, &rectData
, &marginsData
)) {
346 return rectData
? rectData
->mPainted
: marginsData
->mPainted
;
349 bool DisplayPortUtils::IsMissingDisplayPortBaseRect(nsIContent
* aContent
) {
350 DisplayPortPropertyData
* rectData
= nullptr;
351 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
353 if (GetDisplayPortData(aContent
, &rectData
, &marginsData
) && marginsData
) {
354 return !aContent
->GetProperty(nsGkAtoms::DisplayPortBase
);
360 static void TranslateFromScrollPortToScrollFrame(nsIContent
* aContent
,
363 if (nsIScrollableFrame
* scrollableFrame
=
364 nsLayoutUtils::FindScrollableFrameFor(aContent
)) {
365 *aRect
+= scrollableFrame
->GetScrollPortRect().TopLeft();
369 static bool GetDisplayPortImpl(nsIContent
* aContent
, nsRect
* aResult
,
370 const DisplayPortOptions
& aOptions
) {
371 DisplayPortPropertyData
* rectData
= nullptr;
372 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
374 if (!GetDisplayPortData(aContent
, &rectData
, &marginsData
)) {
378 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
379 if (frame
&& !frame
->PresShell()->AsyncPanZoomEnabled()) {
384 // We have displayport data, but the caller doesn't want the actual
385 // rect, so we don't need to actually compute it.
389 bool isDisplayportSuppressed
= false;
392 nsPresContext
* presContext
= frame
->PresContext();
393 MOZ_ASSERT(presContext
);
394 PresShell
* presShell
= presContext
->PresShell();
395 MOZ_ASSERT(presShell
);
396 isDisplayportSuppressed
= presShell
->IsDisplayportSuppressed();
401 result
= GetDisplayPortFromRectData(aContent
, rectData
);
402 } else if (isDisplayportSuppressed
||
403 nsLayoutUtils::ShouldDisableApzForElement(aContent
) ||
404 aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
)) {
405 // Note: the above conditions should be in sync with the conditions in
406 // WillUseEmptyDisplayPortMargins.
408 // Make a copy of the margins data but set the margins to empty.
409 // Do not create a new DisplayPortMargins object with
410 // DisplayPortMargins::Empty(), because that will record the visual
411 // and layout scroll offsets in place right now on the DisplayPortMargins,
412 // and those are only meant to be recorded when the margins are stored.
413 DisplayPortMarginsPropertyData noMargins
= *marginsData
;
414 noMargins
.mMargins
.mMargins
= ScreenMargin();
415 result
= GetDisplayPortFromMarginsData(aContent
, &noMargins
, aOptions
);
417 result
= GetDisplayPortFromMarginsData(aContent
, marginsData
, aOptions
);
420 if (aOptions
.mRelativeTo
== DisplayportRelativeTo::ScrollFrame
) {
421 TranslateFromScrollPortToScrollFrame(aContent
, &result
);
428 bool DisplayPortUtils::GetDisplayPort(nsIContent
* aContent
, nsRect
* aResult
,
429 const DisplayPortOptions
& aOptions
) {
430 return GetDisplayPortImpl(aContent
, aResult
, aOptions
);
433 bool DisplayPortUtils::HasDisplayPort(nsIContent
* aContent
) {
434 return GetDisplayPort(aContent
, nullptr);
437 bool DisplayPortUtils::HasPaintedDisplayPort(nsIContent
* aContent
) {
438 DisplayPortPropertyData
* rectData
= nullptr;
439 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
440 GetDisplayPortData(aContent
, &rectData
, &marginsData
);
442 return rectData
->mPainted
;
445 return marginsData
->mPainted
;
450 void DisplayPortUtils::MarkDisplayPortAsPainted(nsIContent
* aContent
) {
451 DisplayPortPropertyData
* rectData
= nullptr;
452 DisplayPortMarginsPropertyData
* marginsData
= nullptr;
453 GetDisplayPortData(aContent
, &rectData
, &marginsData
);
454 MOZ_ASSERT(rectData
|| marginsData
,
455 "MarkDisplayPortAsPainted should only be called for an element "
456 "with a displayport");
458 rectData
->mPainted
= true;
461 marginsData
->mPainted
= true;
465 bool DisplayPortUtils::HasNonMinimalDisplayPort(nsIContent
* aContent
) {
466 return HasDisplayPort(aContent
) &&
467 !aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
);
470 bool DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(nsIContent
* aContent
) {
471 if (!HasDisplayPort(aContent
)) {
474 if (aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
)) {
478 DisplayPortMarginsPropertyData
* currentData
=
479 static_cast<DisplayPortMarginsPropertyData
*>(
480 aContent
->GetProperty(nsGkAtoms::DisplayPortMargins
));
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 (currentData
->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::GetScrollFrameFromContent(aContent
);
517 frame
= do_QueryFrame(frame
->GetScrollTargetFrame());
520 if (changed
&& frame
) {
521 // It is important to call SchedulePaint on the same frame that we set the
522 // dirty rect properties on so we can find the frame later to remove the
524 frame
->SchedulePaint();
526 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
530 if (StaticPrefs::layout_display_list_retain_sc()) {
531 // DisplayListBuildingDisplayPortRect property is not used when retain sc
536 auto* builder
= nsLayoutUtils::GetRetainedDisplayListBuilder(frame
);
542 nsRect
* rect
= frame
->GetProperty(
543 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), &found
);
548 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect
);
549 frame
->SetHasOverrideDirtyRegion(true);
551 DL_LOGV("Adding display port building rect for frame %p\n", frame
);
552 RetainedDisplayListData
* data
= builder
->Data();
553 data
->Flags(frame
) += RetainedDisplayListData::FrameFlag::HasProps
;
555 MOZ_ASSERT(rect
, "this property should only store non-null values");
558 if (aHadDisplayPort
) {
559 // We only need to build a display list for any new areas added
560 nsRegion
newRegion(aNewDisplayPort
);
561 newRegion
.SubOut(aOldDisplayPort
);
562 rect
->UnionRect(*rect
, newRegion
.GetBounds());
564 rect
->UnionRect(*rect
, aNewDisplayPort
);
569 bool DisplayPortUtils::SetDisplayPortMargins(
570 nsIContent
* aContent
, PresShell
* aPresShell
,
571 const DisplayPortMargins
& aMargins
,
572 ClearMinimalDisplayPortProperty aClearMinimalDisplayPortProperty
,
573 uint32_t aPriority
, RepaintMode aRepaintMode
) {
574 MOZ_ASSERT(aContent
);
575 MOZ_ASSERT(aContent
->GetComposedDoc() == aPresShell
->GetDocument());
577 DisplayPortMarginsPropertyData
* currentData
=
578 static_cast<DisplayPortMarginsPropertyData
*>(
579 aContent
->GetProperty(nsGkAtoms::DisplayPortMargins
));
580 if (currentData
&& currentData
->mPriority
> aPriority
) {
584 if (currentData
&& currentData
->mMargins
.mVisualOffset
!= CSSPoint() &&
585 aMargins
.mVisualOffset
== CSSPoint()) {
586 // If we hit this, then it's possible that we're setting a displayport
587 // that is wrong because the old one had a layout/visual adjustment and
588 // the new one does not.
589 MOZ_LOG(sDisplayportLog
, LogLevel::Warning
,
590 ("Dropping visual offset %s",
591 ToString(currentData
->mMargins
.mVisualOffset
).c_str()));
594 nsIFrame
* scrollFrame
= nsLayoutUtils::GetScrollFrameFromContent(aContent
);
596 nsRect oldDisplayPort
;
597 bool hadDisplayPort
= false;
598 bool wasPainted
= GetWasDisplayPortPainted(aContent
);
600 // We only use the two return values from this function to call
601 // InvalidateForDisplayPortChange. InvalidateForDisplayPortChange does
602 // nothing if aContent does not have a frame. So getting the displayport is
603 // useless if the content has no frame, so we avoid calling this to avoid
604 // triggering a warning about not having a frame.
605 hadDisplayPort
= GetDisplayPort(aContent
, &oldDisplayPort
);
608 aContent
->SetProperty(
609 nsGkAtoms::DisplayPortMargins
,
610 new DisplayPortMarginsPropertyData(aMargins
, aPriority
, wasPainted
),
611 nsINode::DeleteProperty
<DisplayPortMarginsPropertyData
>);
613 if (aClearMinimalDisplayPortProperty
==
614 ClearMinimalDisplayPortProperty::Yes
) {
615 if (MOZ_LOG_TEST(sDisplayportLog
, LogLevel::Debug
) &&
616 aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
)) {
617 mozilla::layers::ScrollableLayerGuid::ViewID viewID
=
618 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID
;
619 nsLayoutUtils::FindIDFor(aContent
, &viewID
);
620 MOZ_LOG(sDisplayportLog
, LogLevel::Debug
,
621 ("SetDisplayPortMargins removing MinimalDisplayPort prop on "
622 "scrollId=%" PRIu64
"\n",
625 aContent
->RemoveProperty(nsGkAtoms::MinimalDisplayPort
);
628 nsIScrollableFrame
* scrollableFrame
=
629 scrollFrame
? scrollFrame
->GetScrollTargetFrame() : nullptr;
630 if (!scrollableFrame
) {
634 nsRect newDisplayPort
;
635 DebugOnly
<bool> hasDisplayPort
= GetDisplayPort(aContent
, &newDisplayPort
);
636 MOZ_ASSERT(hasDisplayPort
);
638 if (MOZ_LOG_TEST(sDisplayportLog
, LogLevel::Debug
)) {
639 mozilla::layers::ScrollableLayerGuid::ViewID viewID
=
640 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID
;
641 nsLayoutUtils::FindIDFor(aContent
, &viewID
);
642 if (!hadDisplayPort
) {
643 MOZ_LOG(sDisplayportLog
, LogLevel::Debug
,
644 ("SetDisplayPortMargins %s on scrollId=%" PRIu64
", newDp=%s\n",
645 ToString(aMargins
).c_str(), viewID
,
646 ToString(newDisplayPort
).c_str()));
648 // Use verbose level logging for when an existing displayport got its
650 MOZ_LOG(sDisplayportLog
, LogLevel::Verbose
,
651 ("SetDisplayPortMargins %s on scrollId=%" PRIu64
", newDp=%s\n",
652 ToString(aMargins
).c_str(), viewID
,
653 ToString(newDisplayPort
).c_str()));
657 InvalidateForDisplayPortChange(aContent
, hadDisplayPort
, oldDisplayPort
,
658 newDisplayPort
, aRepaintMode
);
660 scrollableFrame
->TriggerDisplayPortExpiration();
662 // Display port margins changing means that the set of visible frames may
663 // have drastically changed. Check if we should schedule an update.
665 scrollableFrame
->GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
668 bool needVisibilityUpdate
= !hadDisplayPort
;
669 // Check if the total size has changed by a large factor.
670 if (!needVisibilityUpdate
) {
671 if ((newDisplayPort
.width
> 2 * oldDisplayPort
.width
) ||
672 (oldDisplayPort
.width
> 2 * newDisplayPort
.width
) ||
673 (newDisplayPort
.height
> 2 * oldDisplayPort
.height
) ||
674 (oldDisplayPort
.height
> 2 * newDisplayPort
.height
)) {
675 needVisibilityUpdate
= true;
678 // Check if it's moved by a significant amount.
679 if (!needVisibilityUpdate
) {
680 if (nsRect
* baseData
= static_cast<nsRect
*>(
681 aContent
->GetProperty(nsGkAtoms::DisplayPortBase
))) {
682 nsRect base
= *baseData
;
683 if ((std::abs(newDisplayPort
.X() - oldDisplayPort
.X()) > base
.width
) ||
684 (std::abs(newDisplayPort
.XMost() - oldDisplayPort
.XMost()) >
686 (std::abs(newDisplayPort
.Y() - oldDisplayPort
.Y()) > base
.height
) ||
687 (std::abs(newDisplayPort
.YMost() - oldDisplayPort
.YMost()) >
689 needVisibilityUpdate
= true;
693 if (needVisibilityUpdate
) {
694 aPresShell
->ScheduleApproximateFrameVisibilityUpdateNow();
700 void DisplayPortUtils::SetDisplayPortBase(nsIContent
* aContent
,
701 const nsRect
& aBase
) {
702 if (MOZ_LOG_TEST(sDisplayportLog
, LogLevel::Verbose
)) {
703 ViewID viewId
= nsLayoutUtils::FindOrCreateIDFor(aContent
);
704 MOZ_LOG(sDisplayportLog
, LogLevel::Verbose
,
705 ("Setting base rect %s for scrollId=%" PRIu64
"\n",
706 ToString(aBase
).c_str(), viewId
));
708 aContent
->SetProperty(nsGkAtoms::DisplayPortBase
, new nsRect(aBase
),
709 nsINode::DeleteProperty
<nsRect
>);
712 void DisplayPortUtils::SetDisplayPortBaseIfNotSet(nsIContent
* aContent
,
713 const nsRect
& aBase
) {
714 if (!aContent
->GetProperty(nsGkAtoms::DisplayPortBase
)) {
715 SetDisplayPortBase(aContent
, aBase
);
719 void DisplayPortUtils::RemoveDisplayPort(nsIContent
* aContent
) {
720 aContent
->RemoveProperty(nsGkAtoms::DisplayPort
);
721 aContent
->RemoveProperty(nsGkAtoms::DisplayPortMargins
);
724 bool DisplayPortUtils::ViewportHasDisplayPort(nsPresContext
* aPresContext
) {
725 nsIFrame
* rootScrollFrame
= aPresContext
->PresShell()->GetRootScrollFrame();
726 return rootScrollFrame
&& HasDisplayPort(rootScrollFrame
->GetContent());
729 bool DisplayPortUtils::IsFixedPosFrameInDisplayPort(const nsIFrame
* aFrame
) {
730 // Fixed-pos frames are parented by the viewport frame or the page content
731 // frame. We'll assume that printing/print preview don't have displayports for
733 nsIFrame
* parent
= aFrame
->GetParent();
734 if (!parent
|| parent
->GetParent() ||
735 aFrame
->StyleDisplay()->mPosition
!= StylePositionProperty::Fixed
) {
738 return ViewportHasDisplayPort(aFrame
->PresContext());
741 // We want to this return true for the scroll frame, but not the
742 // scrolled frame (which has the same content).
743 bool DisplayPortUtils::FrameHasDisplayPort(nsIFrame
* aFrame
,
744 const nsIFrame
* aScrolledFrame
) {
745 if (!aFrame
->GetContent() || !HasDisplayPort(aFrame
->GetContent())) {
748 nsIScrollableFrame
* sf
= do_QueryFrame(aFrame
);
750 if (aScrolledFrame
&& aScrolledFrame
!= sf
->GetScrolledFrame()) {
758 bool DisplayPortUtils::CalculateAndSetDisplayPortMargins(
759 nsIScrollableFrame
* aScrollFrame
, RepaintMode aRepaintMode
) {
760 nsIFrame
* frame
= do_QueryFrame(aScrollFrame
);
762 nsIContent
* content
= frame
->GetContent();
765 FrameMetrics metrics
=
766 nsLayoutUtils::CalculateBasicFrameMetrics(aScrollFrame
);
767 ScreenMargin displayportMargins
= layers::apz::CalculatePendingDisplayPort(
768 metrics
, ParentLayerPoint(0.0f
, 0.0f
));
769 PresShell
* presShell
= frame
->PresContext()->GetPresShell();
771 DisplayPortMargins margins
=
772 DisplayPortMargins::ForScrollFrame(aScrollFrame
, displayportMargins
);
774 return SetDisplayPortMargins(content
, presShell
, margins
,
775 ClearMinimalDisplayPortProperty::Yes
, 0,
779 bool DisplayPortUtils::MaybeCreateDisplayPort(
780 nsDisplayListBuilder
* aBuilder
, nsIFrame
* aScrollFrame
,
781 nsIScrollableFrame
* aScrollFrameAsScrollable
, RepaintMode aRepaintMode
) {
782 MOZ_ASSERT(aBuilder
->IsPaintingToWindow());
784 nsIContent
* content
= aScrollFrame
->GetContent();
789 // We perform an optimization where we ensure that at least one
790 // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a
791 // displayport. If that's not the case yet, and we are async-scrollable, we
792 // will get a displayport.
793 MOZ_ASSERT(nsLayoutUtils::AsyncPanZoomEnabled(aScrollFrame
));
794 if (!aBuilder
->HaveScrollableDisplayPort() &&
795 aScrollFrameAsScrollable
->WantAsyncScroll()) {
796 bool haveDisplayPort
= HasNonMinimalNonZeroDisplayPort(content
);
797 // If we don't already have a displayport, calculate and set one.
798 if (!haveDisplayPort
) {
799 // We only use the viewId for logging purposes, but create it
800 // unconditionally to minimize impact of enabling logging. If we don't
801 // assign a viewId here it will get assigned later anyway so functionally
802 // there should be no difference.
803 ViewID viewId
= nsLayoutUtils::FindOrCreateIDFor(content
);
805 sDisplayportLog
, LogLevel::Debug
,
806 ("Setting DP on first-encountered scrollId=%" PRIu64
"\n", viewId
));
808 CalculateAndSetDisplayPortMargins(aScrollFrameAsScrollable
, aRepaintMode
);
810 haveDisplayPort
= HasNonMinimalDisplayPort(content
);
811 MOZ_ASSERT(haveDisplayPort
,
812 "should have a displayport after having just set it");
816 // Record that the we now have a scrollable display port.
817 aBuilder
->SetHaveScrollableDisplayPort();
822 void DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
824 nsIFrame
* frame
= aFrame
;
826 frame
= nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame
);
830 nsIScrollableFrame
* scrollAncestor
=
831 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame
);
832 if (!scrollAncestor
) {
835 frame
= do_QueryFrame(scrollAncestor
);
837 MOZ_ASSERT(scrollAncestor
->WantAsyncScroll() ||
838 frame
->PresShell()->GetRootScrollFrame() == frame
);
839 if (nsLayoutUtils::AsyncPanZoomEnabled(frame
) &&
840 !HasDisplayPort(frame
->GetContent())) {
841 SetDisplayPortMargins(frame
->GetContent(), frame
->PresShell(),
842 DisplayPortMargins::Empty(frame
->GetContent()),
843 ClearMinimalDisplayPortProperty::No
, 0,
844 RepaintMode::Repaint
);
849 bool DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered(
850 nsIFrame
* aFrame
, nsDisplayListBuilder
* aBuilder
) {
851 // Don't descend into the tab bar in chrome, it can be very large and does not
852 // contain any async scrollable elements.
853 if (XRE_IsParentProcess() && aFrame
->GetContent() &&
854 aFrame
->GetContent()->GetID() == nsGkAtoms::tabbrowser_arrowscrollbox
) {
857 if (aFrame
->IsScrollContainer()) {
858 if (nsIScrollableFrame
* sf
= do_QueryFrame(aFrame
)) {
859 if (MaybeCreateDisplayPort(aBuilder
, aFrame
, sf
, RepaintMode::Repaint
)) {
860 // If this was the first displayport found in the first scroll frame
861 // encountered, mark the scroll frame with the current paint sequence
862 // number. This is used later to ensure the displayport created is
863 // never expired. When there is a scrollable frame with a first
864 // scrollable sequence number found that does not match the current
865 // paint sequence number (may occur if the dom was mutated in some way),
866 // the value will be reset.
867 sf
->SetIsFirstScrollableFrameSequenceNumber(
868 Some(nsDisplayListBuilder::GetPaintSequenceNumber()));
872 } else if (aFrame
->IsPlaceholderFrame()) {
873 nsPlaceholderFrame
* placeholder
= static_cast<nsPlaceholderFrame
*>(aFrame
);
874 nsIFrame
* oof
= placeholder
->GetOutOfFlowFrame();
875 if (oof
&& !nsLayoutUtils::IsPopup(oof
) &&
876 MaybeCreateDisplayPortInFirstScrollFrameEncountered(oof
, aBuilder
)) {
879 } else if (aFrame
->IsSubDocumentFrame()) {
880 PresShell
* presShell
= static_cast<nsSubDocumentFrame
*>(aFrame
)
881 ->GetSubdocumentPresShellForPainting(0);
882 if (nsIFrame
* root
= presShell
? presShell
->GetRootFrame() : nullptr) {
883 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(root
, aBuilder
)) {
888 if (aFrame
->StyleUIReset()->mMozSubtreeHiddenOnlyVisually
) {
889 // Only descend the visible card of deck / tabpanels
892 for (nsIFrame
* child
: aFrame
->PrincipalChildList()) {
893 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(child
, aBuilder
)) {
900 void DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(
902 nsIFrame
* frame
= aFrame
;
904 frame
= nsLayoutUtils::GetCrossDocParentFrameInProcess(frame
);
908 nsIScrollableFrame
* scrollAncestor
=
909 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame
);
910 if (!scrollAncestor
) {
913 frame
= do_QueryFrame(scrollAncestor
);
918 MOZ_ASSERT(scrollAncestor
->WantAsyncScroll() ||
919 frame
->PresShell()->GetRootScrollFrame() == frame
);
920 if (HasDisplayPort(frame
->GetContent())) {
921 scrollAncestor
->TriggerDisplayPortExpiration();
922 // Stop after the first trigger. If it failed, there's no point in
923 // continuing because all the rest of the frames we encounter are going
924 // to be ancestors of |scrollAncestor| which will keep its displayport.
925 // If the trigger succeeded, we stop because when the trigger executes
926 // it will call this function again to trigger the next ancestor up the
933 Maybe
<nsRect
> DisplayPortUtils::GetRootDisplayportBase(PresShell
* aPresShell
) {
934 DebugOnly
<nsPresContext
*> pc
= aPresShell
->GetPresContext();
935 MOZ_ASSERT(pc
, "this function should be called after PresShell::Init");
936 MOZ_ASSERT(pc
->IsRootContentDocumentCrossProcess() ||
937 !pc
->GetParentPresContext());
939 dom::BrowserChild
* browserChild
= dom::BrowserChild::GetFrom(aPresShell
);
940 if (browserChild
&& !browserChild
->IsTopLevel()) {
941 // If this is an in-process root in on OOP iframe, use the visible rect if
943 return browserChild
->GetVisibleRect();
946 nsIFrame
* frame
= aPresShell
->GetRootScrollFrame();
948 frame
= aPresShell
->GetRootFrame();
953 baseRect
= nsRect(nsPoint(0, 0),
954 nsLayoutUtils::CalculateCompositionSizeForFrame(frame
));
956 baseRect
= nsRect(nsPoint(0, 0),
957 aPresShell
->GetPresContext()->GetVisibleArea().Size());
960 return Some(baseRect
);
963 bool DisplayPortUtils::WillUseEmptyDisplayPortMargins(nsIContent
* aContent
) {
964 MOZ_ASSERT(HasDisplayPort(aContent
));
965 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
970 // Note these conditions should be in sync with the conditions where we use
971 // empty margins to calculate display port in GetDisplayPortImpl
972 return aContent
->GetProperty(nsGkAtoms::MinimalDisplayPort
) ||
973 frame
->PresShell()->IsDisplayportSuppressed() ||
974 nsLayoutUtils::ShouldDisableApzForElement(aContent
);
977 } // namespace mozilla