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/. */
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"
15 #include "nsIScrollableFrame.h"
16 #include "nsLayoutUtils.h"
17 #include "nsQueryFrame.h"
18 #include "nsStyleStruct.h"
22 using layers::APZCCallbackHelper
;
23 using layers::InputAPZContext
;
24 using layers::ScrollableLayerGuid
;
26 template <typename Units
>
27 gfx::Matrix4x4Typed
<Units
, Units
> ViewportUtils::GetVisualToLayoutTransform(
28 ScrollableLayerGuid::ViewID aScrollId
) {
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");
34 if (aScrollId
== ScrollableLayerGuid::NULL_SCROLL_ID
) {
37 nsCOMPtr
<nsIContent
> content
= nsLayoutUtils::FindContentFor(aScrollId
);
38 if (!content
|| !content
->GetPrimaryFrame()) {
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(
53 resolution
= presShell
->GetResolution();
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();
68 return gfx::Matrix4x4Typed
<Units
, Units
>::Scaling(1 / resolution
,
70 .PostTranslate(transform
.x
, transform
.y
, 0);
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()) {
79 nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame
->GetContent());
82 return ViewportUtils::GetVisualToLayoutTransform(targetScrollId
);
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
);
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
);
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()) {
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
);
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
>(
125 return visualToLayout
.Inverse().TransformPoint(aPoint
);
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
>(
135 return visualToLayout
.Inverse().TransformBounds(aRect
);
138 LayoutDeviceRect
ViewportUtils::DocumentRelativeLayoutToVisual(
139 const LayoutDeviceIntRect
& aRect
, PresShell
* aShell
) {
140 return DocumentRelativeLayoutToVisual(IntRectToRect(aRect
), aShell
);
143 CSSRect
ViewportUtils::DocumentRelativeLayoutToVisual(const CSSRect
& aRect
,
145 ScrollableLayerGuid::ViewID targetScrollId
=
146 nsLayoutUtils::ScrollIdForRootScrollFrame(aShell
->GetPresContext());
147 auto visualToLayout
=
148 ViewportUtils::GetVisualToLayoutTransform(targetScrollId
);
149 return visualToLayout
.Inverse().TransformBounds(aRect
);
152 template <class SourceUnits
, class DestUnits
>
153 gfx::PointTyped
<DestUnits
> TransformPointOrRect(
154 const gfx::Matrix4x4Typed
<SourceUnits
, DestUnits
>& aMatrix
,
155 const gfx::PointTyped
<SourceUnits
>& aPoint
) {
156 return aMatrix
.TransformPoint(aPoint
);
159 template <class SourceUnits
, class DestUnits
>
160 gfx::RectTyped
<DestUnits
> TransformPointOrRect(
161 const gfx::Matrix4x4Typed
<SourceUnits
, DestUnits
>& aMatrix
,
162 const gfx::RectTyped
<SourceUnits
>& aRect
) {
163 return aMatrix
.TransformBounds(aRect
);
166 template <class LDPointOrRect
>
167 LDPointOrRect
ConvertToScreenRelativeVisual(const LDPointOrRect
& aInput
,
168 nsPresContext
* aCtx
) {
171 LDPointOrRect
layoutToVisual(aInput
);
172 nsIFrame
* prevRootFrame
= nullptr;
173 nsPresContext
* prevCtx
= nullptr;
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();
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
);
186 if (shell
->GetResolution() != 1.0) {
187 // Found the APZ zoom root, so do the layout -> visual conversion.
189 ViewportUtils::DocumentRelativeLayoutToVisual(layoutToVisual
, shell
);
192 prevRootFrame
= rootFrame
;
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
),
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());
218 return layoutToVisual
+ rootScreenRect
.TopLeft();
221 LayoutDevicePoint
ViewportUtils::ToScreenRelativeVisual(
222 const LayoutDevicePoint
& aPt
, nsPresContext
* aCtx
) {
223 return ConvertToScreenRelativeVisual(aPt
, aCtx
);
226 LayoutDeviceRect
ViewportUtils::ToScreenRelativeVisual(
227 const LayoutDeviceRect
& aRect
, nsPresContext
* aCtx
) {
228 return ConvertToScreenRelativeVisual(aRect
, aCtx
);
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
);
240 const nsIFrame
* ViewportUtils::IsZoomedContentRoot(const nsIFrame
* aFrame
) {
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();
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();
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
275 // - If there are no enclosing CSS transforms, it will return the
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 if (bc
->GetChildToParentConversionMatrix().Decompose(translation
,
289 return {scale
.x
, scale
.y
};
296 } // namespace mozilla