1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "Units.h"
6 #include "mozilla/PresShell.h"
7 #include "mozilla/ViewportFrame.h"
8 #include "mozilla/ViewportUtils.h"
9 #include "mozilla/dom/BrowserChild.h"
10 #include "mozilla/layers/APZCCallbackHelper.h"
11 #include "mozilla/layers/InputAPZContext.h"
12 #include "mozilla/layers/ScrollableLayerGuid.h"
13 #include "nsIContent.h"
14 #include "nsIFrame.h"
15 #include "nsIScrollableFrame.h"
16 #include "nsLayoutUtils.h"
17 #include "nsQueryFrame.h"
18 #include "nsStyleStruct.h"
19 
20 namespace mozilla {
21 
22 using layers::APZCCallbackHelper;
23 using layers::InputAPZContext;
24 using layers::ScrollableLayerGuid;
25 
26 template <typename Units>
GetVisualToLayoutTransform(ScrollableLayerGuid::ViewID aScrollId)27 gfx::Matrix4x4Typed<Units, Units> ViewportUtils::GetVisualToLayoutTransform(
28     ScrollableLayerGuid::ViewID aScrollId) {
29   static_assert(
30       std::is_same_v<Units, CSSPixel> ||
31           std::is_same_v<Units, LayoutDevicePixel>,
32       "GetCallbackTransform() may only be used with CSS or LayoutDevice units");
33 
34   if (aScrollId == ScrollableLayerGuid::NULL_SCROLL_ID) {
35     return {};
36   }
37   nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aScrollId);
38   if (!content || !content->GetPrimaryFrame()) {
39     return {};
40   }
41 
42   // First, scale inversely by the root content document's pres shell
43   // resolution to cancel the scale-to-resolution transform that the
44   // compositor adds to the layer with the pres shell resolution. The points
45   // sent to Gecko by APZ don't have this transform unapplied (unlike other
46   // compositor-side transforms) because Gecko needs it applied when hit
47   // testing against content that's conceptually outside the resolution,
48   // such as scrollbars.
49   float resolution = 1.0f;
50   if (PresShell* presShell =
51           APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
52               content)) {
53     resolution = presShell->GetResolution();
54   }
55 
56   // Now apply the callback-transform. This is only approximately correct,
57   // see the comment on GetCumulativeApzCallbackTransform for details.
58   gfx::PointTyped<Units> transform;
59   CSSPoint transformCSS = nsLayoutUtils::GetCumulativeApzCallbackTransform(
60       content->GetPrimaryFrame());
61   if constexpr (std::is_same_v<Units, CSSPixel>) {
62     transform = transformCSS;
63   } else {  // Units == LayoutDevicePixel
64     transform = transformCSS *
65                 content->GetPrimaryFrame()->PresContext()->CSSToDevPixelScale();
66   }
67 
68   return gfx::Matrix4x4Typed<Units, Units>::Scaling(1 / resolution,
69                                                     1 / resolution, 1)
70       .PostTranslate(transform.x, transform.y, 0);
71 }
72 
GetVisualToLayoutTransform(PresShell * aContext)73 CSSToCSSMatrix4x4 GetVisualToLayoutTransform(PresShell* aContext) {
74   ScrollableLayerGuid::ViewID targetScrollId =
75       InputAPZContext::GetTargetLayerGuid().mScrollId;
76   if (targetScrollId == ScrollableLayerGuid::NULL_SCROLL_ID) {
77     if (nsIFrame* rootScrollFrame = aContext->GetRootScrollFrame()) {
78       targetScrollId =
79           nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent());
80     }
81   }
82   return ViewportUtils::GetVisualToLayoutTransform(targetScrollId);
83 }
84 
VisualToLayout(const nsPoint & aPt,PresShell * aContext)85 nsPoint ViewportUtils::VisualToLayout(const nsPoint& aPt, PresShell* aContext) {
86   auto visualToLayout = mozilla::GetVisualToLayoutTransform(aContext);
87   CSSPoint cssPt = CSSPoint::FromAppUnits(aPt);
88   cssPt = visualToLayout.TransformPoint(cssPt);
89   return CSSPoint::ToAppUnits(cssPt);
90 }
91 
VisualToLayout(const nsRect & aRect,PresShell * aContext)92 nsRect ViewportUtils::VisualToLayout(const nsRect& aRect, PresShell* aContext) {
93   auto visualToLayout = mozilla::GetVisualToLayoutTransform(aContext);
94   CSSRect cssRect = CSSRect::FromAppUnits(aRect);
95   cssRect = visualToLayout.TransformBounds(cssRect);
96   nsRect result = CSSRect::ToAppUnits(cssRect);
97 
98   // In hit testing codepaths, the input rect often has dimensions of one app
99   // units. If we are zoomed in enough, the rounded size of the output rect
100   // can be zero app units, which will fail to Intersect() with anything, and
101   // therefore cause hit testing to fail. To avoid this, we expand the output
102   // rect to one app units.
103   if (!aRect.IsEmpty() && result.IsEmpty()) {
104     result.width = 1;
105     result.height = 1;
106   }
107 
108   return result;
109 }
110 
LayoutToVisual(const nsPoint & aPt,PresShell * aContext)111 nsPoint ViewportUtils::LayoutToVisual(const nsPoint& aPt, PresShell* aContext) {
112   auto visualToLayout = mozilla::GetVisualToLayoutTransform(aContext);
113   CSSPoint cssPt = CSSPoint::FromAppUnits(aPt);
114   auto transformed = visualToLayout.Inverse().TransformPoint(cssPt);
115   return CSSPoint::ToAppUnits(transformed);
116 }
117 
DocumentRelativeLayoutToVisual(const LayoutDevicePoint & aPoint,PresShell * aShell)118 LayoutDevicePoint ViewportUtils::DocumentRelativeLayoutToVisual(
119     const LayoutDevicePoint& aPoint, PresShell* aShell) {
120   ScrollableLayerGuid::ViewID targetScrollId =
121       nsLayoutUtils::ScrollIdForRootScrollFrame(aShell->GetPresContext());
122   auto visualToLayout =
123       ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
124           targetScrollId);
125   return visualToLayout.Inverse().TransformPoint(aPoint);
126 }
127 
DocumentRelativeLayoutToVisual(const LayoutDeviceRect & aRect,PresShell * aShell)128 LayoutDeviceRect ViewportUtils::DocumentRelativeLayoutToVisual(
129     const LayoutDeviceRect& aRect, PresShell* aShell) {
130   ScrollableLayerGuid::ViewID targetScrollId =
131       nsLayoutUtils::ScrollIdForRootScrollFrame(aShell->GetPresContext());
132   auto visualToLayout =
133       ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
134           targetScrollId);
135   return visualToLayout.Inverse().TransformBounds(aRect);
136 }
137 
DocumentRelativeLayoutToVisual(const LayoutDeviceIntRect & aRect,PresShell * aShell)138 LayoutDeviceRect ViewportUtils::DocumentRelativeLayoutToVisual(
139     const LayoutDeviceIntRect& aRect, PresShell* aShell) {
140   return DocumentRelativeLayoutToVisual(IntRectToRect(aRect), aShell);
141 }
142 
DocumentRelativeLayoutToVisual(const CSSRect & aRect,PresShell * aShell)143 CSSRect ViewportUtils::DocumentRelativeLayoutToVisual(const CSSRect& aRect,
144                                                       PresShell* aShell) {
145   ScrollableLayerGuid::ViewID targetScrollId =
146       nsLayoutUtils::ScrollIdForRootScrollFrame(aShell->GetPresContext());
147   auto visualToLayout =
148       ViewportUtils::GetVisualToLayoutTransform(targetScrollId);
149   return visualToLayout.Inverse().TransformBounds(aRect);
150 }
151 
152 template <class SourceUnits, class DestUnits>
TransformPointOrRect(const gfx::Matrix4x4Typed<SourceUnits,DestUnits> & aMatrix,const gfx::PointTyped<SourceUnits> & aPoint)153 gfx::PointTyped<DestUnits> TransformPointOrRect(
154     const gfx::Matrix4x4Typed<SourceUnits, DestUnits>& aMatrix,
155     const gfx::PointTyped<SourceUnits>& aPoint) {
156   return aMatrix.TransformPoint(aPoint);
157 }
158 
159 template <class SourceUnits, class DestUnits>
TransformPointOrRect(const gfx::Matrix4x4Typed<SourceUnits,DestUnits> & aMatrix,const gfx::RectTyped<SourceUnits> & aRect)160 gfx::RectTyped<DestUnits> TransformPointOrRect(
161     const gfx::Matrix4x4Typed<SourceUnits, DestUnits>& aMatrix,
162     const gfx::RectTyped<SourceUnits>& aRect) {
163   return aMatrix.TransformBounds(aRect);
164 }
165 
166 template <class LDPointOrRect>
ConvertToScreenRelativeVisual(const LDPointOrRect & aInput,nsPresContext * aCtx)167 LDPointOrRect ConvertToScreenRelativeVisual(const LDPointOrRect& aInput,
168                                             nsPresContext* aCtx) {
169   MOZ_ASSERT(aCtx);
170 
171   LDPointOrRect layoutToVisual(aInput);
172   nsIFrame* prevRootFrame = nullptr;
173   nsPresContext* prevCtx = nullptr;
174 
175   // Walk up to the rootmost prescontext, transforming as we go.
176   for (nsPresContext* ctx = aCtx; ctx; ctx = ctx->GetParentPresContext()) {
177     PresShell* shell = ctx->PresShell();
178     nsIFrame* rootFrame = shell->GetRootFrame();
179     if (prevRootFrame) {
180       // Convert layoutToVisual from being relative to `prevRootFrame`
181       // to being relative to `rootFrame` (layout space).
182       nscoord apd = prevCtx->AppUnitsPerDevPixel();
183       nsPoint offset = prevRootFrame->GetOffsetToCrossDoc(rootFrame, apd);
184       layoutToVisual += LayoutDevicePoint::FromAppUnits(offset, apd);
185     }
186     if (shell->GetResolution() != 1.0) {
187       // Found the APZ zoom root, so do the layout -> visual conversion.
188       layoutToVisual =
189           ViewportUtils::DocumentRelativeLayoutToVisual(layoutToVisual, shell);
190     }
191 
192     prevRootFrame = rootFrame;
193     prevCtx = ctx;
194   }
195 
196   // If we're in a nested content process, the above traversal will not have
197   // encountered the APZ zoom root. The translation part of the layout-to-visual
198   // transform will be included in |rootScreenRect.TopLeft()|, added below
199   // (that ultimately comes from nsIWidget::WidgetToScreenOffset(), which for an
200   // OOP iframe's widget includes this translation), but the scale part needs to
201   // be computed and added separately.
202   Scale2D enclosingResolution =
203       ViewportUtils::TryInferEnclosingResolution(prevCtx->GetPresShell());
204   if (enclosingResolution != Scale2D{1.0f, 1.0f}) {
205     layoutToVisual = TransformPointOrRect(
206         LayoutDeviceToLayoutDeviceMatrix4x4::Scaling(
207             enclosingResolution.xScale, enclosingResolution.yScale, 1.0f),
208         layoutToVisual);
209   }
210 
211   // Then we do the conversion from the rootmost presContext's root frame (in
212   // visual space) to screen space.
213   LayoutDeviceIntRect rootScreenRect =
214       LayoutDeviceIntRect::FromAppUnitsToNearest(
215           prevRootFrame->GetScreenRectInAppUnits(),
216           prevCtx->AppUnitsPerDevPixel());
217 
218   return layoutToVisual + rootScreenRect.TopLeft();
219 }
220 
ToScreenRelativeVisual(const LayoutDevicePoint & aPt,nsPresContext * aCtx)221 LayoutDevicePoint ViewportUtils::ToScreenRelativeVisual(
222     const LayoutDevicePoint& aPt, nsPresContext* aCtx) {
223   return ConvertToScreenRelativeVisual(aPt, aCtx);
224 }
225 
ToScreenRelativeVisual(const LayoutDeviceRect & aRect,nsPresContext * aCtx)226 LayoutDeviceRect ViewportUtils::ToScreenRelativeVisual(
227     const LayoutDeviceRect& aRect, nsPresContext* aCtx) {
228   return ConvertToScreenRelativeVisual(aRect, aCtx);
229 }
230 
231 // Definitions of the two explicit instantiations forward declared in the header
232 // file. This causes code for these instantiations to be emitted into the object
233 // file for ViewportUtils.cpp.
234 template CSSToCSSMatrix4x4 ViewportUtils::GetVisualToLayoutTransform<CSSPixel>(
235     ScrollableLayerGuid::ViewID);
236 template LayoutDeviceToLayoutDeviceMatrix4x4
237     ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
238         ScrollableLayerGuid::ViewID);
239 
IsZoomedContentRoot(const nsIFrame * aFrame)240 const nsIFrame* ViewportUtils::IsZoomedContentRoot(const nsIFrame* aFrame) {
241   if (!aFrame) {
242     return nullptr;
243   }
244   if (aFrame->Type() == LayoutFrameType::Canvas ||
245       aFrame->Type() == LayoutFrameType::PageSequence) {
246     nsIScrollableFrame* sf = do_QueryFrame(aFrame->GetParent());
247     if (sf && sf->IsRootScrollFrameOfDocument() &&
248         aFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
249       return aFrame->GetParent();
250     }
251   } else if (aFrame->StyleDisplay()->mPosition ==
252              StylePositionProperty::Fixed) {
253     if (ViewportFrame* viewportFrame = do_QueryFrame(aFrame->GetParent())) {
254       if (viewportFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
255         return viewportFrame->PresShell()->GetRootScrollFrame();
256       }
257     }
258   }
259   return nullptr;
260 }
261 
TryInferEnclosingResolution(PresShell * aShell)262 Scale2D ViewportUtils::TryInferEnclosingResolution(PresShell* aShell) {
263   MOZ_ASSERT(aShell && aShell->GetPresContext());
264   MOZ_ASSERT(!aShell->GetPresContext()->GetParentPresContext(),
265              "TryInferEnclosingResolution can only be called for a root pres "
266              "shell within a process");
267   if (dom::BrowserChild* bc = dom::BrowserChild::GetFrom(aShell)) {
268     if (!bc->IsTopLevel()) {
269       // The enclosing resolution is not directly available in the BrowserChild.
270       // The closest thing available is GetChildToParentConversionMatrix(),
271       // which also includes any enclosing CSS transforms.
272       // The behaviour implemented here will not provide an accurate answer
273       // in the presence of CSS transforms, but it tries to do something
274       // reasonable:
275       //  - If there are no enclosing CSS transforms, it will return the
276       //    resolution.
277       //  - If the enclosing transforms contain scales and translations only,
278       //    it will return the resolution times the CSS transform scale
279       //    (choosing the x-scale if they are different).
280       //  - Otherwise, it will return the resolution times a scale component
281       //    of the transform as returned by Matrix4x4Typed::Decompose().
282       //  - If the enclosing transform is sufficiently complex that
283       //    Decompose() returns false, give up and return 1.0.
284       gfx::Point3DTyped<gfx::UnknownUnits> translation;
285       gfx::Quaternion rotation;
286       gfx::Point3DTyped<gfx::UnknownUnits> scale;
287       // Need to call ToUnknownMatrix() because Decompose() doesn't properly
288       // support typed units.
289       if (bc->GetChildToParentConversionMatrix().ToUnknownMatrix().Decompose(
290               translation, rotation, scale)) {
291         return {scale.x, scale.y};
292       }
293     }
294   }
295   return {1.0f, 1.0f};
296 }
297 
298 }  // namespace mozilla
299