Bug 1869043 allow a device to be specified with MediaTrackGraph::NotifyWhenDeviceStar...
[gecko.git] / layout / base / DisplayPortUtils.cpp
blobd9471db15381c69cf560b56a28dff68d217b34fc
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"
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::ForScrollFrame(
49 nsIScrollableFrame* aScrollFrame, const ScreenMargin& aMargins) {
50 CSSPoint visualOffset;
51 CSSPoint layoutOffset;
52 if (aScrollFrame) {
53 nsIFrame* scrollFrame = do_QueryFrame(aScrollFrame);
54 PresShell* presShell = scrollFrame->PresShell();
55 layoutOffset = CSSPoint::FromAppUnits(aScrollFrame->GetScrollPosition());
56 if (aScrollFrame->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 ForScrollFrame(
71 aContent ? nsLayoutUtils::FindScrollableFrameFor(aContent) : nullptr,
72 aMargins);
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;
92 return margins;
95 std::ostream& operator<<(std::ostream& aOs,
96 const DisplayPortMargins& aMargins) {
97 if (aMargins.mVisualOffset == CSSPoint() &&
98 aMargins.mLayoutOffset == CSSPoint()) {
99 aOs << aMargins.mMargins;
100 } else {
101 aOs << "{" << aMargins.mMargins << "," << aMargins.mVisualOffset << ","
102 << aMargins.mLayoutOffset << "}";
104 return aOs;
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
112 // layout offsets.
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
124 // viewport.
125 if (!aScrollableFrame) {
126 // Displayport on a non-scrolling frame for some reason.
127 // There will be no divergence between the two viewports.
128 return CSSPoint();
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.
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(scrollFrame->PresShell()->GetVisualViewportSize())};
149 const CSSRect scrollableRect = CSSRect::FromAppUnits(
150 nsLayoutUtils::CalculateExpandedScrollableRect(scrollFrame));
151 CSSRect asyncLayoutViewport{
152 mLayoutOffset,
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.
175 nsRect base;
176 if (nsRect* baseData = static_cast<nsRect*>(
177 aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
178 base = *baseData;
179 } else {
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);
185 if (!frame) {
186 // Turns out we can't really compute it. Oops. We still should return
187 // something sane.
188 NS_WARNING(
189 "Attempting to get a displayport from a content with no primary "
190 "frame!");
191 return base;
194 bool isRoot = false;
195 if (aContent->OwnerDoc()->GetRootElement() == aContent) {
196 isRoot = true;
199 nsIScrollableFrame* scrollableFrame = frame->GetScrollTargetFrame();
200 nsPoint scrollPos;
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(
212 frame);
214 // Calculate the expanded scrollable rect, which we'll be clamping the
215 // displayport to.
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;
229 if (isRoot) {
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);
261 } else {
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.
271 IntSize multiplier =
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);
305 return result;
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
320 return false;
323 if (*aOutRectData && *aOutMarginsData) {
324 // choose margins if equal priority
325 if ((*aOutRectData)->mPriority > (*aOutMarginsData)->mPriority) {
326 *aOutMarginsData = nullptr;
327 } else {
328 *aOutRectData = nullptr;
332 NS_ASSERTION((*aOutRectData == nullptr) != (*aOutMarginsData == nullptr),
333 "Only one of aOutRectData or aOutMarginsData should be set!");
335 return true;
338 static bool GetWasDisplayPortPainted(nsIContent* aContent) {
339 DisplayPortPropertyData* rectData = nullptr;
340 DisplayPortMarginsPropertyData* marginsData = nullptr;
342 if (!GetDisplayPortData(aContent, &rectData, &marginsData)) {
343 return false;
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);
357 return false;
360 static void TranslateFromScrollPortToScrollFrame(nsIContent* aContent,
361 nsRect* aRect) {
362 MOZ_ASSERT(aRect);
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)) {
375 return false;
378 nsIFrame* frame = aContent->GetPrimaryFrame();
379 if (frame && !frame->PresShell()->AsyncPanZoomEnabled()) {
380 return false;
383 if (!aResult) {
384 // We have displayport data, but the caller doesn't want the actual
385 // rect, so we don't need to actually compute it.
386 return true;
389 bool isDisplayportSuppressed = false;
391 if (frame) {
392 nsPresContext* presContext = frame->PresContext();
393 MOZ_ASSERT(presContext);
394 PresShell* presShell = presContext->PresShell();
395 MOZ_ASSERT(presShell);
396 isDisplayportSuppressed = presShell->IsDisplayportSuppressed();
399 nsRect result;
400 if (rectData) {
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);
416 } else {
417 result = GetDisplayPortFromMarginsData(aContent, marginsData, aOptions);
420 if (aOptions.mRelativeTo == DisplayportRelativeTo::ScrollFrame) {
421 TranslateFromScrollPortToScrollFrame(aContent, &result);
424 *aResult = result;
425 return true;
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);
441 if (rectData) {
442 return rectData->mPainted;
444 if (marginsData) {
445 return marginsData->mPainted;
447 return false;
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");
457 if (rectData) {
458 rectData->mPainted = true;
460 if (marginsData) {
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)) {
472 return false;
474 if (aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
475 return false;
478 DisplayPortMarginsPropertyData* currentData =
479 static_cast<DisplayPortMarginsPropertyData*>(
480 aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
482 if (!currentData) {
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 (currentData->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::GetScrollFrameFromContent(aContent);
516 if (frame) {
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
523 // properties.
524 frame->SchedulePaint();
526 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
527 return;
530 if (StaticPrefs::layout_display_list_retain_sc()) {
531 // DisplayListBuildingDisplayPortRect property is not used when retain sc
532 // mode is enabled.
533 return;
536 auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(frame);
537 if (!builder) {
538 return;
541 bool found;
542 nsRect* rect = frame->GetProperty(
543 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), &found);
545 if (!found) {
546 rect = new nsRect();
547 frame->AddProperty(
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;
554 } else {
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());
563 } else {
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) {
581 return false;
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);
599 if (scrollFrame) {
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",
623 viewID));
625 aContent->RemoveProperty(nsGkAtoms::MinimalDisplayPort);
628 nsIScrollableFrame* scrollableFrame =
629 scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
630 if (!scrollableFrame) {
631 return true;
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()));
647 } else {
648 // Use verbose level logging for when an existing displayport got its
649 // margins updated.
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.
664 hadDisplayPort =
665 scrollableFrame->GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
666 &oldDisplayPort);
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()) >
685 base.width) ||
686 (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) ||
687 (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) >
688 base.height)) {
689 needVisibilityUpdate = true;
693 if (needVisibilityUpdate) {
694 aPresShell->ScheduleApproximateFrameVisibilityUpdateNow();
697 return true;
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
732 // their pages!
733 nsIFrame* parent = aFrame->GetParent();
734 if (!parent || parent->GetParent() ||
735 aFrame->StyleDisplay()->mPosition != StylePositionProperty::Fixed) {
736 return false;
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())) {
746 return false;
748 nsIScrollableFrame* sf = do_QueryFrame(aFrame);
749 if (sf) {
750 if (aScrolledFrame && aScrolledFrame != sf->GetScrolledFrame()) {
751 return false;
753 return true;
755 return false;
758 bool DisplayPortUtils::CalculateAndSetDisplayPortMargins(
759 nsIScrollableFrame* aScrollFrame, RepaintMode aRepaintMode) {
760 nsIFrame* frame = do_QueryFrame(aScrollFrame);
761 MOZ_ASSERT(frame);
762 nsIContent* content = frame->GetContent();
763 MOZ_ASSERT(content);
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,
776 aRepaintMode);
779 bool DisplayPortUtils::MaybeCreateDisplayPort(
780 nsDisplayListBuilder* aBuilder, nsIFrame* aScrollFrame,
781 nsIScrollableFrame* aScrollFrameAsScrollable, RepaintMode aRepaintMode) {
782 MOZ_ASSERT(aBuilder->IsPaintingToWindow());
784 nsIContent* content = aScrollFrame->GetContent();
785 if (!content) {
786 return false;
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);
804 MOZ_LOG(
805 sDisplayportLog, LogLevel::Debug,
806 ("Setting DP on first-encountered scrollId=%" PRIu64 "\n", viewId));
808 CalculateAndSetDisplayPortMargins(aScrollFrameAsScrollable, aRepaintMode);
809 #ifdef DEBUG
810 haveDisplayPort = HasNonMinimalDisplayPort(content);
811 MOZ_ASSERT(haveDisplayPort,
812 "should have a displayport after having just set it");
813 #endif
816 // Record that the we now have a scrollable display port.
817 aBuilder->SetHaveScrollableDisplayPort();
818 return true;
820 return false;
822 void DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
823 nsIFrame* aFrame) {
824 nsIFrame* frame = aFrame;
825 while (frame) {
826 frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame);
827 if (!frame) {
828 break;
830 nsIScrollableFrame* scrollAncestor =
831 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame);
832 if (!scrollAncestor) {
833 break;
835 frame = do_QueryFrame(scrollAncestor);
836 MOZ_ASSERT(frame);
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) {
855 return false;
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()));
869 return true;
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)) {
877 return true;
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)) {
884 return true;
888 if (aFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
889 // Only descend the visible card of deck / tabpanels
890 return false;
892 for (nsIFrame* child : aFrame->PrincipalChildList()) {
893 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(child, aBuilder)) {
894 return true;
897 return false;
900 void DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(
901 nsIFrame* aFrame) {
902 nsIFrame* frame = aFrame;
903 while (frame) {
904 frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame);
905 if (!frame) {
906 break;
908 nsIScrollableFrame* scrollAncestor =
909 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame);
910 if (!scrollAncestor) {
911 break;
913 frame = do_QueryFrame(scrollAncestor);
914 MOZ_ASSERT(frame);
915 if (!frame) {
916 break;
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
927 // chain.
928 break;
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
942 // it's been set.
943 return browserChild->GetVisibleRect();
946 nsIFrame* frame = aPresShell->GetRootScrollFrame();
947 if (!frame) {
948 frame = aPresShell->GetRootFrame();
951 nsRect baseRect;
952 if (frame) {
953 baseRect = nsRect(nsPoint(0, 0),
954 nsLayoutUtils::CalculateCompositionSizeForFrame(frame));
955 } else {
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();
966 if (!frame) {
967 return false;
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