Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / layout / base / DisplayPortUtils.cpp
blob835fd4c15e083ed0a84dc4d4dedc107ca1098723
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"
27 #include <ostream>
29 namespace mozilla {
31 using gfx::IntSize;
33 using layers::FrameMetrics;
34 using layers::ScrollableLayerGuid;
36 typedef ScrollableLayerGuid::ViewID ViewID;
38 static LazyLogModule sDisplayportLog("apz.displayport");
40 /* static */
41 DisplayPortMargins DisplayPortMargins::FromAPZ(const ScreenMargin& aMargins,
42 const CSSPoint& aVisualOffset,
43 const CSSPoint& aLayoutOffset) {
44 return DisplayPortMargins{aMargins, aVisualOffset, aLayoutOffset};
47 /* static */
48 DisplayPortMargins DisplayPortMargins::ForScrollContainerFrame(
49 ScrollContainerFrame* aScrollContainerFrame, const ScreenMargin& aMargins) {
50 CSSPoint visualOffset;
51 CSSPoint layoutOffset;
52 if (aScrollContainerFrame) {
53 PresShell* presShell = aScrollContainerFrame->PresShell();
54 layoutOffset =
55 CSSPoint::FromAppUnits(aScrollContainerFrame->GetScrollPosition());
56 if (aScrollContainerFrame->IsRootScrollFrameOfDocument()) {
57 visualOffset =
58 CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset());
60 } else {
61 visualOffset = layoutOffset;
64 return DisplayPortMargins{aMargins, visualOffset, layoutOffset};
67 /* static */
68 DisplayPortMargins DisplayPortMargins::ForContent(
69 nsIContent* aContent, const ScreenMargin& aMargins) {
70 return ForScrollContainerFrame(
71 aContent ? nsLayoutUtils::FindScrollContainerFrameFor(aContent) : nullptr,
72 aMargins);
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;
93 return margins;
96 std::ostream& operator<<(std::ostream& aOs,
97 const DisplayPortMargins& aMargins) {
98 if (aMargins.mVisualOffset == CSSPoint() &&
99 aMargins.mLayoutOffset == CSSPoint()) {
100 aOs << aMargins.mMargins;
101 } else {
102 aOs << "{" << aMargins.mMargins << "," << aMargins.mVisualOffset << ","
103 << aMargins.mLayoutOffset << "}";
105 return aOs;
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
113 // layout offsets.
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
125 // viewport.
126 if (!aScrollContainerFrame) {
127 // Displayport on a non-scrolling frame for some reason.
128 // There will be no divergence between the two viewports.
129 return CSSPoint();
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.
136 return CSSPoint();
138 // Use KeepLayoutViewportEnclosingViewportVisual() to compute
139 // an async layout viewport the way APZ would.
140 const CSSRect visualViewport{
141 mVisualOffset,
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{
153 mLayoutOffset,
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.
176 nsRect base;
177 if (nsRect* baseData = static_cast<nsRect*>(
178 aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
179 base = *baseData;
180 } else {
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);
186 if (!frame) {
187 // Turns out we can't really compute it. Oops. We still should return
188 // something sane.
189 NS_WARNING(
190 "Attempting to get a displayport from a content with no primary "
191 "frame!");
192 return base;
195 bool isRoot = false;
196 if (aContent->OwnerDoc()->GetRootElement() == aContent) {
197 isRoot = true;
200 ScrollContainerFrame* scrollContainerFrame = frame->GetScrollTargetFrame();
201 nsPoint scrollPos;
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(
213 frame);
215 // Calculate the expanded scrollable rect, which we'll be clamping the
216 // displayport to.
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;
230 if (isRoot) {
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);
262 } else {
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.
272 IntSize multiplier =
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);
306 return result;
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
321 return false;
324 if (*aOutRectData && *aOutMarginsData) {
325 // choose margins if equal priority
326 if ((*aOutRectData)->mPriority > (*aOutMarginsData)->mPriority) {
327 *aOutMarginsData = nullptr;
328 } else {
329 *aOutRectData = nullptr;
333 NS_ASSERTION((*aOutRectData == nullptr) != (*aOutMarginsData == nullptr),
334 "Only one of aOutRectData or aOutMarginsData should be set!");
336 return true;
339 static bool GetWasDisplayPortPainted(nsIContent* aContent) {
340 DisplayPortPropertyData* rectData = nullptr;
341 DisplayPortMarginsPropertyData* marginsData = nullptr;
343 if (!GetDisplayPortData(aContent, &rectData, &marginsData)) {
344 return false;
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);
358 return false;
361 static void TranslateFromScrollPortToScrollContainerFrame(nsIContent* aContent,
362 nsRect* aRect) {
363 MOZ_ASSERT(aRect);
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)) {
376 return false;
379 nsIFrame* frame = aContent->GetPrimaryFrame();
380 if (frame && !frame->PresShell()->AsyncPanZoomEnabled()) {
381 return false;
384 if (!aResult) {
385 // We have displayport data, but the caller doesn't want the actual
386 // rect, so we don't need to actually compute it.
387 return true;
390 bool isDisplayportSuppressed = false;
392 if (frame) {
393 nsPresContext* presContext = frame->PresContext();
394 MOZ_ASSERT(presContext);
395 PresShell* presShell = presContext->PresShell();
396 MOZ_ASSERT(presShell);
397 isDisplayportSuppressed = presShell->IsDisplayportSuppressed();
400 nsRect result;
401 if (rectData) {
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);
417 } else {
418 result = GetDisplayPortFromMarginsData(aContent, marginsData, aOptions);
421 if (aOptions.mRelativeTo == DisplayportRelativeTo::ScrollFrame) {
422 TranslateFromScrollPortToScrollContainerFrame(aContent, &result);
425 *aResult = result;
426 return true;
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);
442 if (rectData) {
443 return rectData->mPainted;
445 if (marginsData) {
446 return marginsData->mPainted;
448 return false;
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");
458 if (rectData) {
459 rectData->mPainted = true;
461 if (marginsData) {
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)) {
473 return false;
476 DisplayPortPropertyData* rectData = nullptr;
477 DisplayPortMarginsPropertyData* marginsData = nullptr;
478 if (!GetDisplayPortData(aContent, &rectData, &marginsData)) {
479 return false;
482 if (!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.
486 return true;
489 if (marginsData->mMargins.mMargins != ScreenMargin()) {
490 return true;
493 return false;
496 /* static */
497 bool DisplayPortUtils::GetDisplayPortForVisibilityTesting(nsIContent* aContent,
498 nsRect* aResult) {
499 MOZ_ASSERT(aResult);
500 return GetDisplayPortImpl(
501 aContent, aResult,
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) {
509 return;
512 bool changed =
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
519 // properties.
520 frame->SchedulePaint();
522 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
523 return;
526 if (StaticPrefs::layout_display_list_retain_sc()) {
527 // DisplayListBuildingDisplayPortRect property is not used when retain sc
528 // mode is enabled.
529 return;
532 auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(frame);
533 if (!builder) {
534 return;
537 bool found;
538 nsRect* rect = frame->GetProperty(
539 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), &found);
541 if (!found) {
542 rect = new nsRect();
543 frame->AddProperty(
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;
550 } else {
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());
559 } else {
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) {
577 return false;
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);
596 if (scrollFrame) {
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",
620 viewID));
622 aContent->RemoveProperty(nsGkAtoms::MinimalDisplayPort);
625 ScrollContainerFrame* scrollContainerFrame =
626 scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
627 if (!scrollContainerFrame) {
628 return true;
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()));
644 } else {
645 // Use verbose level logging for when an existing displayport got its
646 // margins updated.
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(
663 &oldDisplayPort);
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()) >
682 base.width) ||
683 (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) ||
684 (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) >
685 base.height)) {
686 needVisibilityUpdate = true;
690 if (needVisibilityUpdate) {
691 aPresShell->ScheduleApproximateFrameVisibilityUpdateNow();
694 return true;
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
731 // their pages!
732 nsIFrame* parent = aFrame->GetParent();
733 if (!parent || parent->GetParent() ||
734 aFrame->StyleDisplay()->mPosition != StylePositionProperty::Fixed) {
735 return false;
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())) {
745 return false;
747 ScrollContainerFrame* sf = do_QueryFrame(aFrame);
748 if (sf) {
749 if (aScrolledFrame && aScrolledFrame != sf->GetScrolledFrame()) {
750 return false;
752 return true;
754 return false;
757 bool DisplayPortUtils::CalculateAndSetDisplayPortMargins(
758 ScrollContainerFrame* aScrollContainerFrame, RepaintMode aRepaintMode) {
759 nsIContent* content = aScrollContainerFrame->GetContent();
760 MOZ_ASSERT(content);
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,
773 aRepaintMode);
776 bool DisplayPortUtils::MaybeCreateDisplayPort(
777 nsDisplayListBuilder* aBuilder, ScrollContainerFrame* aScrollContainerFrame,
778 RepaintMode aRepaintMode) {
779 MOZ_ASSERT(aBuilder->IsPaintingToWindow());
781 nsIContent* content = aScrollContainerFrame->GetContent();
782 if (!content) {
783 return false;
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);
801 MOZ_LOG(
802 sDisplayportLog, LogLevel::Debug,
803 ("Setting DP on first-encountered scrollId=%" PRIu64 "\n", viewId));
805 CalculateAndSetDisplayPortMargins(aScrollContainerFrame, aRepaintMode);
806 #ifdef DEBUG
807 haveDisplayPort = HasNonMinimalDisplayPort(content);
808 MOZ_ASSERT(haveDisplayPort,
809 "should have a displayport after having just set it");
810 #endif
813 // Record that the we now have a scrollable display port.
814 aBuilder->SetHaveScrollableDisplayPort();
815 return true;
817 return false;
819 void DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
820 nsIFrame* aFrame) {
821 nsIFrame* frame = aFrame;
822 while (frame) {
823 frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame);
824 if (!frame) {
825 break;
827 ScrollContainerFrame* scrollAncestor =
828 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame);
829 if (!scrollAncestor) {
830 break;
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) {
851 return false;
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()));
865 return true;
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)) {
872 return true;
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)) {
879 return true;
883 if (aFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
884 // Only descend the visible card of deck / tabpanels
885 return false;
887 for (nsIFrame* child : aFrame->PrincipalChildList()) {
888 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(child, aBuilder)) {
889 return true;
892 return false;
895 void DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(
896 nsIFrame* aFrame) {
897 nsIFrame* frame = aFrame;
898 while (frame) {
899 frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame);
900 if (!frame) {
901 break;
903 ScrollContainerFrame* scrollAncestor =
904 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame);
905 if (!scrollAncestor) {
906 break;
908 frame = scrollAncestor;
909 MOZ_ASSERT(frame);
910 if (!frame) {
911 break;
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
922 // chain.
923 break;
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
937 // it's been set.
938 return browserChild->GetVisibleRect();
941 nsIFrame* frame = aPresShell->GetRootScrollContainerFrame();
942 if (!frame) {
943 frame = aPresShell->GetRootFrame();
946 nsRect baseRect;
947 if (frame) {
948 baseRect = GetDisplayportBase(frame);
949 } else {
950 baseRect = nsRect(nsPoint(0, 0),
951 aPresShell->GetPresContext()->GetVisibleArea().Size());
954 return Some(baseRect);
957 nsRect DisplayPortUtils::GetDisplayportBase(nsIFrame* aFrame) {
958 MOZ_ASSERT(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();
967 if (!frame) {
968 return false;
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