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 "mozilla/ScrollContainerFrame.h"
14 #include "nsIContent.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::Matrix4x4TypedFlagged
<Units
, Units
>
28 ViewportUtils::GetVisualToLayoutTransform(
29 ScrollableLayerGuid::ViewID aScrollId
) {
31 std::is_same_v
<Units
, CSSPixel
> ||
32 std::is_same_v
<Units
, LayoutDevicePixel
>,
33 "GetCallbackTransform() may only be used with CSS or LayoutDevice units");
35 if (aScrollId
== ScrollableLayerGuid::NULL_SCROLL_ID
) {
38 nsCOMPtr
<nsIContent
> content
= nsLayoutUtils::FindContentFor(aScrollId
);
39 if (!content
|| !content
->GetPrimaryFrame()) {
43 // First, scale inversely by the root content document's pres shell
44 // resolution to cancel the scale-to-resolution transform that the
45 // compositor adds to the layer with the pres shell resolution. The points
46 // sent to Gecko by APZ don't have this transform unapplied (unlike other
47 // compositor-side transforms) because Gecko needs it applied when hit
48 // testing against content that's conceptually outside the resolution,
49 // such as scrollbars.
50 float resolution
= 1.0f
;
51 if (PresShell
* presShell
=
52 APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
54 resolution
= presShell
->GetResolution();
57 // Now apply the callback-transform. This is only approximately correct,
58 // see the comment on GetCumulativeApzCallbackTransform for details.
59 gfx::PointTyped
<Units
> transform
;
60 CSSPoint transformCSS
= nsLayoutUtils::GetCumulativeApzCallbackTransform(
61 content
->GetPrimaryFrame());
62 if constexpr (std::is_same_v
<Units
, CSSPixel
>) {
63 transform
= transformCSS
;
64 } else { // Units == LayoutDevicePixel
65 transform
= transformCSS
*
66 content
->GetPrimaryFrame()->PresContext()->CSSToDevPixelScale();
69 return gfx::Matrix4x4TypedFlagged
<Units
, Units
>::Scaling(1 / resolution
,
71 .PostTranslate(transform
.x
, transform
.y
, 0);
74 CSSToCSSMatrix4x4Flagged
GetVisualToLayoutTransform(PresShell
* aContext
) {
75 ScrollableLayerGuid::ViewID targetScrollId
=
76 InputAPZContext::GetTargetLayerGuid().mScrollId
;
77 if (targetScrollId
== ScrollableLayerGuid::NULL_SCROLL_ID
) {
78 if (nsIFrame
* rootScrollContainerFrame
=
79 aContext
->GetRootScrollContainerFrame()) {
80 targetScrollId
= nsLayoutUtils::FindOrCreateIDFor(
81 rootScrollContainerFrame
->GetContent());
84 return ViewportUtils::GetVisualToLayoutTransform(targetScrollId
);
87 nsPoint
ViewportUtils::VisualToLayout(const nsPoint
& aPt
, PresShell
* aContext
) {
88 auto visualToLayout
= mozilla::GetVisualToLayoutTransform(aContext
);
89 CSSPoint cssPt
= CSSPoint::FromAppUnits(aPt
);
90 cssPt
= visualToLayout
.TransformPoint(cssPt
);
91 return CSSPoint::ToAppUnits(cssPt
);
94 nsRect
ViewportUtils::VisualToLayout(const nsRect
& aRect
, PresShell
* aContext
) {
95 auto visualToLayout
= mozilla::GetVisualToLayoutTransform(aContext
);
96 CSSRect cssRect
= CSSRect::FromAppUnits(aRect
);
97 cssRect
= visualToLayout
.TransformBounds(cssRect
);
98 nsRect result
= CSSRect::ToAppUnits(cssRect
);
100 // In hit testing codepaths, the input rect often has dimensions of one app
101 // units. If we are zoomed in enough, the rounded size of the output rect
102 // can be zero app units, which will fail to Intersect() with anything, and
103 // therefore cause hit testing to fail. To avoid this, we expand the output
104 // rect to one app units.
105 if (!aRect
.IsEmpty() && result
.IsEmpty()) {
113 nsPoint
ViewportUtils::LayoutToVisual(const nsPoint
& aPt
, PresShell
* aContext
) {
114 auto visualToLayout
= mozilla::GetVisualToLayoutTransform(aContext
);
115 CSSPoint cssPt
= CSSPoint::FromAppUnits(aPt
);
116 auto transformed
= visualToLayout
.Inverse().TransformPoint(cssPt
);
117 return CSSPoint::ToAppUnits(transformed
);
120 LayoutDevicePoint
ViewportUtils::DocumentRelativeLayoutToVisual(
121 const LayoutDevicePoint
& aPoint
, PresShell
* aShell
) {
122 ScrollableLayerGuid::ViewID targetScrollId
=
123 nsLayoutUtils::ScrollIdForRootScrollFrame(aShell
->GetPresContext());
124 auto visualToLayout
=
125 ViewportUtils::GetVisualToLayoutTransform
<LayoutDevicePixel
>(
127 return visualToLayout
.Inverse().TransformPoint(aPoint
);
130 LayoutDeviceRect
ViewportUtils::DocumentRelativeLayoutToVisual(
131 const LayoutDeviceRect
& aRect
, PresShell
* aShell
) {
132 ScrollableLayerGuid::ViewID targetScrollId
=
133 nsLayoutUtils::ScrollIdForRootScrollFrame(aShell
->GetPresContext());
134 auto visualToLayout
=
135 ViewportUtils::GetVisualToLayoutTransform
<LayoutDevicePixel
>(
137 return visualToLayout
.Inverse().TransformBounds(aRect
);
140 LayoutDeviceRect
ViewportUtils::DocumentRelativeLayoutToVisual(
141 const LayoutDeviceIntRect
& aRect
, PresShell
* aShell
) {
142 return DocumentRelativeLayoutToVisual(IntRectToRect(aRect
), aShell
);
145 CSSRect
ViewportUtils::DocumentRelativeLayoutToVisual(const CSSRect
& aRect
,
147 ScrollableLayerGuid::ViewID targetScrollId
=
148 nsLayoutUtils::ScrollIdForRootScrollFrame(aShell
->GetPresContext());
149 auto visualToLayout
=
150 ViewportUtils::GetVisualToLayoutTransform(targetScrollId
);
151 return visualToLayout
.Inverse().TransformBounds(aRect
);
154 template <class SourceUnits
, class DestUnits
>
155 gfx::PointTyped
<DestUnits
> TransformPointOrRect(
156 const gfx::Matrix4x4Typed
<SourceUnits
, DestUnits
>& aMatrix
,
157 const gfx::PointTyped
<SourceUnits
>& aPoint
) {
158 return aMatrix
.TransformPoint(aPoint
);
161 template <class SourceUnits
, class DestUnits
>
162 gfx::RectTyped
<DestUnits
> TransformPointOrRect(
163 const gfx::Matrix4x4Typed
<SourceUnits
, DestUnits
>& aMatrix
,
164 const gfx::RectTyped
<SourceUnits
>& aRect
) {
165 return aMatrix
.TransformBounds(aRect
);
168 template <class LDPointOrRect
>
169 LDPointOrRect
ConvertToScreenRelativeVisual(const LDPointOrRect
& aInput
,
170 nsPresContext
* aCtx
) {
173 LDPointOrRect
layoutToVisual(aInput
);
174 nsIFrame
* prevRootFrame
= nullptr;
175 nsPresContext
* prevCtx
= nullptr;
177 // Walk up to the rootmost prescontext, transforming as we go.
178 for (nsPresContext
* ctx
= aCtx
; ctx
; ctx
= ctx
->GetParentPresContext()) {
179 PresShell
* shell
= ctx
->PresShell();
180 nsIFrame
* rootFrame
= shell
->GetRootFrame();
182 // Convert layoutToVisual from being relative to `prevRootFrame`
183 // to being relative to `rootFrame` (layout space).
184 nscoord apd
= prevCtx
->AppUnitsPerDevPixel();
185 nsPoint offset
= prevRootFrame
->GetOffsetToCrossDoc(rootFrame
, apd
);
186 layoutToVisual
+= LayoutDevicePoint::FromAppUnits(offset
, apd
);
188 if (shell
->GetResolution() != 1.0) {
189 // Found the APZ zoom root, so do the layout -> visual conversion.
191 ViewportUtils::DocumentRelativeLayoutToVisual(layoutToVisual
, shell
);
194 prevRootFrame
= rootFrame
;
198 // If we're in a nested content process, the above traversal will not have
199 // encountered the APZ zoom root. The translation part of the layout-to-visual
200 // transform will be included in |rootScreenRect.TopLeft()|, added below
201 // (that ultimately comes from nsIWidget::WidgetToScreenOffset(), which for an
202 // OOP iframe's widget includes this translation), but the scale part needs to
203 // be computed and added separately.
204 Scale2D enclosingResolution
=
205 ViewportUtils::TryInferEnclosingResolution(prevCtx
->GetPresShell());
206 if (enclosingResolution
!= Scale2D
{1.0f
, 1.0f
}) {
207 layoutToVisual
= TransformPointOrRect(
208 LayoutDeviceToLayoutDeviceMatrix4x4::Scaling(
209 enclosingResolution
.xScale
, enclosingResolution
.yScale
, 1.0f
),
213 // Then we do the conversion from the rootmost presContext's root frame (in
214 // visual space) to screen space.
215 LayoutDeviceIntRect rootScreenRect
=
216 LayoutDeviceIntRect::FromAppUnitsToNearest(
217 prevRootFrame
->GetScreenRectInAppUnits(),
218 prevCtx
->AppUnitsPerDevPixel());
220 return layoutToVisual
+ rootScreenRect
.TopLeft();
223 LayoutDevicePoint
ViewportUtils::ToScreenRelativeVisual(
224 const LayoutDevicePoint
& aPt
, nsPresContext
* aCtx
) {
225 return ConvertToScreenRelativeVisual(aPt
, aCtx
);
228 LayoutDeviceRect
ViewportUtils::ToScreenRelativeVisual(
229 const LayoutDeviceRect
& aRect
, nsPresContext
* aCtx
) {
230 return ConvertToScreenRelativeVisual(aRect
, aCtx
);
233 // Definitions of the two explicit instantiations forward declared in the header
234 // file. This causes code for these instantiations to be emitted into the object
235 // file for ViewportUtils.cpp.
236 template CSSToCSSMatrix4x4Flagged
ViewportUtils::GetVisualToLayoutTransform
<
237 CSSPixel
>(ScrollableLayerGuid::ViewID
);
238 template LayoutDeviceToLayoutDeviceMatrix4x4Flagged
239 ViewportUtils::GetVisualToLayoutTransform
<LayoutDevicePixel
>(
240 ScrollableLayerGuid::ViewID
);
242 const nsIFrame
* ViewportUtils::IsZoomedContentRoot(const nsIFrame
* aFrame
) {
246 if (aFrame
->Type() == LayoutFrameType::Canvas
||
247 aFrame
->Type() == LayoutFrameType::PageSequence
) {
248 ScrollContainerFrame
* sf
= do_QueryFrame(aFrame
->GetParent());
249 if (sf
&& sf
->IsRootScrollFrameOfDocument() &&
250 aFrame
->PresContext()->IsRootContentDocumentCrossProcess()) {
251 return aFrame
->GetParent();
253 } else if (aFrame
->StyleDisplay()->mPosition
==
254 StylePositionProperty::Fixed
) {
255 if (ViewportFrame
* viewportFrame
= do_QueryFrame(aFrame
->GetParent())) {
256 if (viewportFrame
->PresContext()->IsRootContentDocumentCrossProcess()) {
257 return viewportFrame
->PresShell()->GetRootScrollContainerFrame();
264 Scale2D
ViewportUtils::TryInferEnclosingResolution(PresShell
* aShell
) {
265 if (!XRE_IsContentProcess()) {
268 MOZ_ASSERT(aShell
&& aShell
->GetPresContext());
269 MOZ_ASSERT(!aShell
->GetPresContext()->GetParentPresContext(),
270 "TryInferEnclosingResolution can only be called for a root pres "
271 "shell within a process");
272 if (dom::BrowserChild
* bc
= dom::BrowserChild::GetFrom(aShell
)) {
273 if (!bc
->IsTopLevel()) {
274 // The enclosing resolution is not directly available in the BrowserChild.
275 // The closest thing available is GetChildToParentConversionMatrix(),
276 // which also includes any enclosing CSS transforms.
277 // The behaviour implemented here will not provide an accurate answer
278 // in the presence of CSS transforms, but it tries to do something
280 // - If there are no enclosing CSS transforms, it will return the
282 // - If the enclosing transforms contain scales and translations only,
283 // it will return the resolution times the CSS transform scale
284 // (choosing the x-scale if they are different).
285 // - Otherwise, it will return the resolution times a scale component
286 // of the transform as returned by Matrix4x4Typed::Decompose().
287 // - If the enclosing transform is sufficiently complex that
288 // Decompose() returns false, give up and return 1.0.
289 gfx::Point3DTyped
<gfx::UnknownUnits
> translation
;
290 gfx::Quaternion rotation
;
291 gfx::Point3DTyped
<gfx::UnknownUnits
> scale
;
292 if (bc
->GetChildToParentConversionMatrix().Decompose(translation
,
294 return {scale
.x
, scale
.y
};
301 } // namespace mozilla