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