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