summaryrefslogtreecommitdiffstats
path: root/layout/base/ViewportUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/ViewportUtils.cpp')
-rw-r--r--layout/base/ViewportUtils.cpp296
1 files changed, 296 insertions, 0 deletions
diff --git a/layout/base/ViewportUtils.cpp b/layout/base/ViewportUtils.cpp
new file mode 100644
index 0000000000..f0d18510bf
--- /dev/null
+++ b/layout/base/ViewportUtils.cpp
@@ -0,0 +1,296 @@
+/* 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