tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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