forked from mirrors/gecko-dev
		
	 da4b66c796
			
		
	
	
		da4b66c796
		
	
	
	
	
		
			
			Depends on D119696 Differential Revision: https://phabricator.services.mozilla.com/D119697
		
			
				
	
	
		
			296 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "Units.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/ViewportFrame.h"
 | |
| #include "mozilla/ViewportUtils.h"
 | |
| #include "mozilla/dom/BrowserChild.h"
 | |
| #include "mozilla/layers/APZCCallbackHelper.h"
 | |
| #include "mozilla/layers/InputAPZContext.h"
 | |
| #include "mozilla/layers/ScrollableLayerGuid.h"
 | |
| #include "nsIContent.h"
 | |
| #include "nsIFrame.h"
 | |
| #include "nsIScrollableFrame.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsQueryFrame.h"
 | |
| #include "nsStyleStruct.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| using layers::APZCCallbackHelper;
 | |
| using layers::InputAPZContext;
 | |
| using layers::ScrollableLayerGuid;
 | |
| 
 | |
| template <typename Units>
 | |
| gfx::Matrix4x4Typed<Units, Units> ViewportUtils::GetVisualToLayoutTransform(
 | |
|     ScrollableLayerGuid::ViewID aScrollId) {
 | |
|   static_assert(
 | |
|       std::is_same_v<Units, CSSPixel> ||
 | |
|           std::is_same_v<Units, LayoutDevicePixel>,
 | |
|       "GetCallbackTransform() may only be used with CSS or LayoutDevice units");
 | |
| 
 | |
|   if (aScrollId == ScrollableLayerGuid::NULL_SCROLL_ID) {
 | |
|     return {};
 | |
|   }
 | |
|   nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aScrollId);
 | |
|   if (!content || !content->GetPrimaryFrame()) {
 | |
|     return {};
 | |
|   }
 | |
| 
 | |
|   // First, scale inversely by the root content document's pres shell
 | |
|   // resolution to cancel the scale-to-resolution transform that the
 | |
|   // compositor adds to the layer with the pres shell resolution. The points
 | |
|   // sent to Gecko by APZ don't have this transform unapplied (unlike other
 | |
|   // compositor-side transforms) because Gecko needs it applied when hit
 | |
|   // testing against content that's conceptually outside the resolution,
 | |
|   // such as scrollbars.
 | |
|   float resolution = 1.0f;
 | |
|   if (PresShell* presShell =
 | |
|           APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
 | |
|               content)) {
 | |
|     resolution = presShell->GetResolution();
 | |
|   }
 | |
| 
 | |
|   // Now apply the callback-transform. This is only approximately correct,
 | |
|   // see the comment on GetCumulativeApzCallbackTransform for details.
 | |
|   gfx::PointTyped<Units> transform;
 | |
|   CSSPoint transformCSS = nsLayoutUtils::GetCumulativeApzCallbackTransform(
 | |
|       content->GetPrimaryFrame());
 | |
|   if constexpr (std::is_same_v<Units, CSSPixel>) {
 | |
|     transform = transformCSS;
 | |
|   } else {  // Units == LayoutDevicePixel
 | |
|     transform = transformCSS *
 | |
|                 content->GetPrimaryFrame()->PresContext()->CSSToDevPixelScale();
 | |
|   }
 | |
| 
 | |
|   return gfx::Matrix4x4Typed<Units, Units>::Scaling(1 / resolution,
 | |
|                                                     1 / resolution, 1)
 | |
|       .PostTranslate(transform.x, transform.y, 0);
 | |
| }
 | |
| 
 | |
| CSSToCSSMatrix4x4 GetVisualToLayoutTransform(PresShell* aContext) {
 | |
|   ScrollableLayerGuid::ViewID targetScrollId =
 | |
|       InputAPZContext::GetTargetLayerGuid().mScrollId;
 | |
|   if (targetScrollId == ScrollableLayerGuid::NULL_SCROLL_ID) {
 | |
|     if (nsIFrame* rootScrollFrame = aContext->GetRootScrollFrame()) {
 | |
|       targetScrollId =
 | |
|           nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent());
 | |
|     }
 | |
|   }
 | |
|   return ViewportUtils::GetVisualToLayoutTransform(targetScrollId);
 | |
| }
 | |
| 
 | |
| nsPoint ViewportUtils::VisualToLayout(const nsPoint& aPt, PresShell* aContext) {
 | |
|   auto visualToLayout = mozilla::GetVisualToLayoutTransform(aContext);
 | |
|   CSSPoint cssPt = CSSPoint::FromAppUnits(aPt);
 | |
|   cssPt = visualToLayout.TransformPoint(cssPt);
 | |
|   return CSSPoint::ToAppUnits(cssPt);
 | |
| }
 | |
| 
 | |
| nsRect ViewportUtils::VisualToLayout(const nsRect& aRect, PresShell* aContext) {
 | |
|   auto visualToLayout = mozilla::GetVisualToLayoutTransform(aContext);
 | |
|   CSSRect cssRect = CSSRect::FromAppUnits(aRect);
 | |
|   cssRect = visualToLayout.TransformBounds(cssRect);
 | |
|   nsRect result = CSSRect::ToAppUnits(cssRect);
 | |
| 
 | |
|   // In hit testing codepaths, the input rect often has dimensions of one app
 | |
|   // units. If we are zoomed in enough, the rounded size of the output rect
 | |
|   // can be zero app units, which will fail to Intersect() with anything, and
 | |
|   // therefore cause hit testing to fail. To avoid this, we expand the output
 | |
|   // rect to one app units.
 | |
|   if (!aRect.IsEmpty() && result.IsEmpty()) {
 | |
|     result.width = 1;
 | |
|     result.height = 1;
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| nsPoint ViewportUtils::LayoutToVisual(const nsPoint& aPt, PresShell* aContext) {
 | |
|   auto visualToLayout = mozilla::GetVisualToLayoutTransform(aContext);
 | |
|   CSSPoint cssPt = CSSPoint::FromAppUnits(aPt);
 | |
|   auto transformed = visualToLayout.Inverse().TransformPoint(cssPt);
 | |
|   return CSSPoint::ToAppUnits(transformed);
 | |
| }
 | |
| 
 | |
| LayoutDevicePoint ViewportUtils::DocumentRelativeLayoutToVisual(
 | |
|     const LayoutDevicePoint& aPoint, PresShell* aShell) {
 | |
|   ScrollableLayerGuid::ViewID targetScrollId =
 | |
|       nsLayoutUtils::ScrollIdForRootScrollFrame(aShell->GetPresContext());
 | |
|   auto visualToLayout =
 | |
|       ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
 | |
|           targetScrollId);
 | |
|   return visualToLayout.Inverse().TransformPoint(aPoint);
 | |
| }
 | |
| 
 | |
| LayoutDeviceRect ViewportUtils::DocumentRelativeLayoutToVisual(
 | |
|     const LayoutDeviceRect& aRect, PresShell* aShell) {
 | |
|   ScrollableLayerGuid::ViewID targetScrollId =
 | |
|       nsLayoutUtils::ScrollIdForRootScrollFrame(aShell->GetPresContext());
 | |
|   auto visualToLayout =
 | |
|       ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
 | |
|           targetScrollId);
 | |
|   return visualToLayout.Inverse().TransformBounds(aRect);
 | |
| }
 | |
| 
 | |
| LayoutDeviceRect ViewportUtils::DocumentRelativeLayoutToVisual(
 | |
|     const LayoutDeviceIntRect& aRect, PresShell* aShell) {
 | |
|   return DocumentRelativeLayoutToVisual(IntRectToRect(aRect), aShell);
 | |
| }
 | |
| 
 | |
| CSSRect ViewportUtils::DocumentRelativeLayoutToVisual(const CSSRect& aRect,
 | |
|                                                       PresShell* aShell) {
 | |
|   ScrollableLayerGuid::ViewID targetScrollId =
 | |
|       nsLayoutUtils::ScrollIdForRootScrollFrame(aShell->GetPresContext());
 | |
|   auto visualToLayout =
 | |
|       ViewportUtils::GetVisualToLayoutTransform(targetScrollId);
 | |
|   return visualToLayout.Inverse().TransformBounds(aRect);
 | |
| }
 | |
| 
 | |
| template <class SourceUnits, class DestUnits>
 | |
| gfx::PointTyped<DestUnits> TransformPointOrRect(
 | |
|     const gfx::Matrix4x4Typed<SourceUnits, DestUnits>& aMatrix,
 | |
|     const gfx::PointTyped<SourceUnits>& aPoint) {
 | |
|   return aMatrix.TransformPoint(aPoint);
 | |
| }
 | |
| 
 | |
| template <class SourceUnits, class DestUnits>
 | |
| gfx::RectTyped<DestUnits> TransformPointOrRect(
 | |
|     const gfx::Matrix4x4Typed<SourceUnits, DestUnits>& aMatrix,
 | |
|     const gfx::RectTyped<SourceUnits>& aRect) {
 | |
|   return aMatrix.TransformBounds(aRect);
 | |
| }
 | |
| 
 | |
| template <class LDPointOrRect>
 | |
| LDPointOrRect ConvertToScreenRelativeVisual(const LDPointOrRect& aInput,
 | |
|                                             nsPresContext* aCtx) {
 | |
|   MOZ_ASSERT(aCtx);
 | |
| 
 | |
|   LDPointOrRect layoutToVisual(aInput);
 | |
|   nsIFrame* prevRootFrame = nullptr;
 | |
|   nsPresContext* prevCtx = nullptr;
 | |
| 
 | |
|   // Walk up to the rootmost prescontext, transforming as we go.
 | |
|   for (nsPresContext* ctx = aCtx; ctx; ctx = ctx->GetParentPresContext()) {
 | |
|     PresShell* shell = ctx->PresShell();
 | |
|     nsIFrame* rootFrame = shell->GetRootFrame();
 | |
|     if (prevRootFrame) {
 | |
|       // Convert layoutToVisual from being relative to `prevRootFrame`
 | |
|       // to being relative to `rootFrame` (layout space).
 | |
|       nscoord apd = prevCtx->AppUnitsPerDevPixel();
 | |
|       nsPoint offset = prevRootFrame->GetOffsetToCrossDoc(rootFrame, apd);
 | |
|       layoutToVisual += LayoutDevicePoint::FromAppUnits(offset, apd);
 | |
|     }
 | |
|     if (shell->GetResolution() != 1.0) {
 | |
|       // Found the APZ zoom root, so do the layout -> visual conversion.
 | |
|       layoutToVisual =
 | |
|           ViewportUtils::DocumentRelativeLayoutToVisual(layoutToVisual, shell);
 | |
|     }
 | |
| 
 | |
|     prevRootFrame = rootFrame;
 | |
|     prevCtx = ctx;
 | |
|   }
 | |
| 
 | |
|   // If we're in a nested content process, the above traversal will not have
 | |
|   // encountered the APZ zoom root. The translation part of the layout-to-visual
 | |
|   // transform will be included in |rootScreenRect.TopLeft()|, added below
 | |
|   // (that ultimately comes from nsIWidget::WidgetToScreenOffset(), which for an
 | |
|   // OOP iframe's widget includes this translation), but the scale part needs to
 | |
|   // be computed and added separately.
 | |
|   Scale2D enclosingResolution =
 | |
|       ViewportUtils::TryInferEnclosingResolution(prevCtx->GetPresShell());
 | |
|   if (enclosingResolution != Scale2D{1.0f, 1.0f}) {
 | |
|     layoutToVisual = TransformPointOrRect(
 | |
|         LayoutDeviceToLayoutDeviceMatrix4x4::Scaling(
 | |
|             enclosingResolution.xScale, enclosingResolution.yScale, 1.0f),
 | |
|         layoutToVisual);
 | |
|   }
 | |
| 
 | |
|   // Then we do the conversion from the rootmost presContext's root frame (in
 | |
|   // visual space) to screen space.
 | |
|   LayoutDeviceIntRect rootScreenRect =
 | |
|       LayoutDeviceIntRect::FromAppUnitsToNearest(
 | |
|           prevRootFrame->GetScreenRectInAppUnits(),
 | |
|           prevCtx->AppUnitsPerDevPixel());
 | |
| 
 | |
|   return layoutToVisual + rootScreenRect.TopLeft();
 | |
| }
 | |
| 
 | |
| LayoutDevicePoint ViewportUtils::ToScreenRelativeVisual(
 | |
|     const LayoutDevicePoint& aPt, nsPresContext* aCtx) {
 | |
|   return ConvertToScreenRelativeVisual(aPt, aCtx);
 | |
| }
 | |
| 
 | |
| LayoutDeviceRect ViewportUtils::ToScreenRelativeVisual(
 | |
|     const LayoutDeviceRect& aRect, nsPresContext* aCtx) {
 | |
|   return ConvertToScreenRelativeVisual(aRect, aCtx);
 | |
| }
 | |
| 
 | |
| // Definitions of the two explicit instantiations forward declared in the header
 | |
| // file. This causes code for these instantiations to be emitted into the object
 | |
| // file for ViewportUtils.cpp.
 | |
| template CSSToCSSMatrix4x4 ViewportUtils::GetVisualToLayoutTransform<CSSPixel>(
 | |
|     ScrollableLayerGuid::ViewID);
 | |
| template LayoutDeviceToLayoutDeviceMatrix4x4
 | |
|     ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
 | |
|         ScrollableLayerGuid::ViewID);
 | |
| 
 | |
| const nsIFrame* ViewportUtils::IsZoomedContentRoot(const nsIFrame* aFrame) {
 | |
|   if (!aFrame) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (aFrame->Type() == LayoutFrameType::Canvas ||
 | |
|       aFrame->Type() == LayoutFrameType::PageSequence) {
 | |
|     nsIScrollableFrame* sf = do_QueryFrame(aFrame->GetParent());
 | |
|     if (sf && sf->IsRootScrollFrameOfDocument() &&
 | |
|         aFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
 | |
|       return aFrame->GetParent();
 | |
|     }
 | |
|   } else if (aFrame->StyleDisplay()->mPosition ==
 | |
|              StylePositionProperty::Fixed) {
 | |
|     if (ViewportFrame* viewportFrame = do_QueryFrame(aFrame->GetParent())) {
 | |
|       if (viewportFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
 | |
|         return viewportFrame->PresShell()->GetRootScrollFrame();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| Scale2D ViewportUtils::TryInferEnclosingResolution(PresShell* aShell) {
 | |
|   MOZ_ASSERT(aShell && aShell->GetPresContext());
 | |
|   MOZ_ASSERT(!aShell->GetPresContext()->GetParentPresContext(),
 | |
|              "TryInferEnclosingResolution can only be called for a root pres "
 | |
|              "shell within a process");
 | |
|   if (dom::BrowserChild* bc = dom::BrowserChild::GetFrom(aShell)) {
 | |
|     if (!bc->IsTopLevel()) {
 | |
|       // The enclosing resolution is not directly available in the BrowserChild.
 | |
|       // The closest thing available is GetChildToParentConversionMatrix(),
 | |
|       // which also includes any enclosing CSS transforms.
 | |
|       // The behaviour implemented here will not provide an accurate answer
 | |
|       // in the presence of CSS transforms, but it tries to do something
 | |
|       // reasonable:
 | |
|       //  - If there are no enclosing CSS transforms, it will return the
 | |
|       //    resolution.
 | |
|       //  - If the enclosing transforms contain scales and translations only,
 | |
|       //    it will return the resolution times the CSS transform scale
 | |
|       //    (choosing the x-scale if they are different).
 | |
|       //  - Otherwise, it will return the resolution times a scale component
 | |
|       //    of the transform as returned by Matrix4x4Typed::Decompose().
 | |
|       //  - If the enclosing transform is sufficiently complex that
 | |
|       //    Decompose() returns false, give up and return 1.0.
 | |
|       gfx::Point3DTyped<gfx::UnknownUnits> translation;
 | |
|       gfx::Quaternion rotation;
 | |
|       gfx::Point3DTyped<gfx::UnknownUnits> scale;
 | |
|       if (bc->GetChildToParentConversionMatrix().Decompose(translation,
 | |
|                                                            rotation, scale)) {
 | |
|         return {scale.x, scale.y};
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return {1.0f, 1.0f};
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 |