Bug 1726704 [wpt PR 30103] - Add more extensive tests for .canShare(), a=testonly
[gecko.git] / layout / base / ViewportUtils.cpp
blobf0d18510bf7a9e1008d8c3178b1e4acefdd1623a
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/. */
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"
20 namespace mozilla {
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) {
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");
34 if (aScrollId == ScrollableLayerGuid::NULL_SCROLL_ID) {
35 return {};
37 nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aScrollId);
38 if (!content || !content->GetPrimaryFrame()) {
39 return {};
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();
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,
69 1 / resolution, 1)
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()) {
78 targetScrollId =
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()) {
104 result.width = 1;
105 result.height = 1;
108 return result;
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>(
124 targetScrollId);
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>(
134 targetScrollId);
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,
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);
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) {
169 MOZ_ASSERT(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();
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);
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);
192 prevRootFrame = rootFrame;
193 prevCtx = ctx;
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);
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) {
241 if (!aFrame) {
242 return nullptr;
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();
259 return nullptr;
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 if (bc->GetChildToParentConversionMatrix().Decompose(translation,
288 rotation, scale)) {
289 return {scale.x, scale.y};
293 return {1.0f, 1.0f};
296 } // namespace mozilla