Merge autoland to mozilla-central. a=merge
[gecko.git] / layout / base / ViewportUtils.cpp
blobe34b808b53fa5c433dd1686b414693da5951d23e
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 "mozilla/ScrollContainerFrame.h"
14 #include "nsIContent.h"
15 #include "nsIFrame.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::Matrix4x4TypedFlagged<Units, Units>
28 ViewportUtils::GetVisualToLayoutTransform(
29 ScrollableLayerGuid::ViewID aScrollId) {
30 static_assert(
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) {
36 return {};
38 nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aScrollId);
39 if (!content || !content->GetPrimaryFrame()) {
40 return {};
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(
53 content)) {
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,
70 1 / resolution, 1)
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()) {
106 result.width = 1;
107 result.height = 1;
110 return result;
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>(
126 targetScrollId);
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>(
136 targetScrollId);
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,
146 PresShell* aShell) {
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) {
171 MOZ_ASSERT(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();
181 if (prevRootFrame) {
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.
190 layoutToVisual =
191 ViewportUtils::DocumentRelativeLayoutToVisual(layoutToVisual, shell);
194 prevRootFrame = rootFrame;
195 prevCtx = ctx;
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),
210 layoutToVisual);
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) {
243 if (!aFrame) {
244 return nullptr;
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();
261 return nullptr;
264 Scale2D ViewportUtils::TryInferEnclosingResolution(PresShell* aShell) {
265 if (!XRE_IsContentProcess()) {
266 return {1.0f, 1.0f};
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
279 // reasonable:
280 // - If there are no enclosing CSS transforms, it will return the
281 // resolution.
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,
293 rotation, scale)) {
294 return {scale.x, scale.y};
298 return {1.0f, 1.0f};
301 } // namespace mozilla