tor-browser

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

MotionPathUtils.cpp (33425B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/MotionPathUtils.h"
      8 
      9 #include <math.h>
     10 
     11 #include "gfxPlatform.h"
     12 #include "mozilla/RefPtr.h"
     13 #include "mozilla/SVGObserverUtils.h"
     14 #include "mozilla/ShapeUtils.h"
     15 #include "mozilla/dom/SVGGeometryElement.h"
     16 #include "mozilla/dom/SVGPathData.h"
     17 #include "mozilla/dom/SVGViewportElement.h"
     18 #include "mozilla/gfx/2D.h"
     19 #include "mozilla/gfx/Matrix.h"
     20 #include "mozilla/layers/LayersMessages.h"
     21 #include "nsIFrame.h"
     22 #include "nsLayoutUtils.h"
     23 #include "nsStyleTransformMatrix.h"
     24 
     25 namespace mozilla {
     26 
     27 using nsStyleTransformMatrix::TransformReferenceBox;
     28 
     29 /* static */
     30 CSSPoint MotionPathUtils::ComputeAnchorPointAdjustment(const nsIFrame& aFrame) {
     31  if (!aFrame.HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
     32    return {};
     33  }
     34 
     35  auto transformBox = aFrame.StyleDisplay()->mTransformBox;
     36  if (transformBox == StyleTransformBox::ViewBox ||
     37      transformBox == StyleTransformBox::BorderBox) {
     38    return {};
     39  }
     40 
     41  if (aFrame.IsSVGContainerFrame()) {
     42    nsRect boxRect = nsLayoutUtils::ComputeSVGReferenceRect(
     43        const_cast<nsIFrame*>(&aFrame), StyleGeometryBox::FillBox);
     44    return CSSPoint::FromAppUnits(boxRect.TopLeft());
     45  }
     46  return CSSPoint::FromAppUnits(aFrame.GetPosition());
     47 }
     48 
     49 // Convert the StyleCoordBox into the StyleGeometryBox in CSS layout.
     50 // https://drafts.csswg.org/css-box-4/#keywords
     51 static StyleGeometryBox CoordBoxToGeometryBoxInCSSLayout(
     52    StyleCoordBox aCoordBox) {
     53  switch (aCoordBox) {
     54    case StyleCoordBox::ContentBox:
     55      return StyleGeometryBox::ContentBox;
     56    case StyleCoordBox::PaddingBox:
     57      return StyleGeometryBox::PaddingBox;
     58    case StyleCoordBox::BorderBox:
     59      return StyleGeometryBox::BorderBox;
     60    case StyleCoordBox::FillBox:
     61      return StyleGeometryBox::ContentBox;
     62    case StyleCoordBox::StrokeBox:
     63    case StyleCoordBox::ViewBox:
     64      return StyleGeometryBox::BorderBox;
     65  }
     66  MOZ_ASSERT_UNREACHABLE("Unknown coord-box type");
     67  return StyleGeometryBox::BorderBox;
     68 }
     69 
     70 /* static */
     71 const nsIFrame* MotionPathUtils::GetOffsetPathReferenceBox(
     72    const nsIFrame* aFrame, nsRect& aOutputRect) {
     73  const StyleOffsetPath& offsetPath = aFrame->StyleDisplay()->mOffsetPath;
     74  if (offsetPath.IsNone()) {
     75    return nullptr;
     76  }
     77 
     78  if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
     79    MOZ_ASSERT(aFrame->GetContent()->IsSVGElement());
     80    auto* viewportElement =
     81        dom::SVGElement::FromNode(aFrame->GetContent())->GetCtx();
     82    aOutputRect = nsLayoutUtils::ComputeSVGOriginBox(viewportElement);
     83    return viewportElement ? viewportElement->GetPrimaryFrame() : nullptr;
     84  }
     85 
     86  const nsIFrame* containingBlock = aFrame->GetContainingBlock();
     87  const StyleCoordBox coordBox = offsetPath.IsCoordBox()
     88                                     ? offsetPath.AsCoordBox()
     89                                     : offsetPath.AsOffsetPath().coord_box;
     90  aOutputRect = nsLayoutUtils::ComputeHTMLReferenceRect(
     91      containingBlock, CoordBoxToGeometryBoxInCSSLayout(coordBox));
     92  return containingBlock;
     93 }
     94 
     95 /* static */
     96 CSSCoord MotionPathUtils::GetRayContainReferenceSize(nsIFrame* aFrame) {
     97  // We use the border-box size to calculate the reduced path length when using
     98  // "contain" keyword.
     99  // https://drafts.fxtf.org/motion-1/#valdef-ray-contain
    100  //
    101  // Note: Per the spec, border-box is treated as stroke-box in the SVG context,
    102  // https://drafts.csswg.org/css-box-4/#valdef-box-border-box
    103 
    104  // To calculate stroke bounds for an element with `non-scaling-stroke` we
    105  // need to resolve its transform to its outer-svg, but to resolve that
    106  // transform when it has `transform-box:stroke-box` (or `border-box`)
    107  // may require its stroke bounds. There's no ideal way to break this
    108  // cyclical dependency, but we break it by using the FillBox.
    109  // https://github.com/w3c/csswg-drafts/issues/9640
    110 
    111  const auto size = CSSSize::FromAppUnits(
    112      (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)
    113           ? nsLayoutUtils::ComputeSVGReferenceRect(
    114                 aFrame,
    115                 aFrame->StyleSVGReset()->HasNonScalingStroke()
    116                     ? StyleGeometryBox::FillBox
    117                     : StyleGeometryBox::StrokeBox,
    118                 nsLayoutUtils::MayHaveNonScalingStrokeCyclicDependency::Yes)
    119           : nsLayoutUtils::ComputeHTMLReferenceRect(
    120                 aFrame, StyleGeometryBox::BorderBox))
    121          .Size());
    122  return std::max(size.width, size.height);
    123 }
    124 
    125 /* static */
    126 nsTArray<nscoord> MotionPathUtils::ComputeBorderRadii(
    127    const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox) {
    128  const nsRect insetRect = ShapeUtils::ComputeInsetRect(
    129      StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
    130      aCoordBox);
    131  nsTArray<nscoord> result;
    132  nsRectCornerRadii radii;
    133  if (ShapeUtils::ComputeRectRadii(aBorderRadius, aCoordBox, insetRect,
    134                                   radii)) {
    135    result.SetCapacity(8);
    136    for (auto hc : AllPhysicalHalfCorners()) {
    137      result.AppendElement(radii[hc]);
    138    }
    139  }
    140  return result;
    141 }
    142 
    143 // The distance is measured between the origin and the intersection of the ray
    144 // with the reference box of the containing block.
    145 // Note: |aOrigin| and |aContaingBlock| should be in the same coordinate system
    146 // (i.e. the nsIFrame::mRect of the containing block).
    147 // https://drafts.fxtf.org/motion-1/#size-sides
    148 static CSSCoord ComputeSides(const CSSPoint& aOrigin,
    149                             const CSSRect& aContainingBlock,
    150                             const StyleAngle& aAngle) {
    151  const CSSPoint& topLeft = aContainingBlock.TopLeft();
    152  // Given an acute angle |theta| (i.e. |t|) of a right-angled triangle, the
    153  // hypotenuse |h| is the side that connects the two acute angles. The side
    154  // |b| adjacent to |theta| is the side of the triangle that connects |theta|
    155  // to the right angle.
    156  //
    157  // e.g. if the angle |t| is 0 ~ 90 degrees, and b * tan(theta) <= b',
    158  //      h = b / cos(t):
    159  //                       b*tan(t)
    160  //    (topLeft) #--------*-----*--# (aContainingBlock.XMost(), topLeft.y)
    161  //              |        |    /   |
    162  //              |        |   /    |
    163  //              |        b  h     |
    164  //              |        |t/      |
    165  //              |        |/       |
    166  //             (aOrigin) *---b'---* (aContainingBlock.XMost(), aOrigin.y)
    167  //              |        |        |
    168  //              |        |        |
    169  //              |        |        |
    170  //              |        |        |
    171  //              |        |        |
    172  //              #-----------------# (aContainingBlock.XMost(),
    173  //        (topLeft.x,                aContainingBlock.YMost())
    174  //         aContainingBlock.YMost())
    175  const double theta = aAngle.ToRadians();
    176  double sint = std::sin(theta);
    177  double cost = std::cos(theta);
    178 
    179  const double b = cost >= 0 ? aOrigin.y.value - topLeft.y
    180                             : aContainingBlock.YMost() - aOrigin.y.value;
    181  const double bPrime = sint >= 0 ? aContainingBlock.XMost() - aOrigin.x.value
    182                                  : aOrigin.x.value - topLeft.x;
    183  sint = std::fabs(sint);
    184  cost = std::fabs(cost);
    185 
    186  // The trigonometric formula here doesn't work well if |theta| is 0deg or
    187  // 90deg, so we handle these edge cases first.
    188  if (sint < std::numeric_limits<double>::epsilon()) {
    189    // For 0deg (or 180deg), we use |b| directly.
    190    return static_cast<float>(b);
    191  }
    192 
    193  if (cost < std::numeric_limits<double>::epsilon()) {
    194    // For 90deg (or 270deg), we use |bPrime| directly. This can also avoid 0/0
    195    // if both |b| and |cost| are 0.0. (i.e. b / cost).
    196    return static_cast<float>(bPrime);
    197  }
    198 
    199  // Note: The following formula works well only when 0 < theta < 90deg. So we
    200  // handle 0deg and 90deg above first.
    201  //
    202  // If |b * tan(theta)| is larger than |bPrime|, the intersection is
    203  // on the other side, and |b'| is the opposite side of angle |theta| in this
    204  // case.
    205  //
    206  // e.g. If b * tan(theta) > b', h = b' / sin(theta):
    207  //   *----*
    208  //   |    |
    209  //   |   /|
    210  //   b  /t|
    211  //   |t/  |
    212  //   |/   |
    213  //   *-b'-*
    214  if (b * sint > bPrime * cost) {
    215    return bPrime / sint;
    216  }
    217  return b / cost;
    218 }
    219 
    220 // Compute the position of "at <position>" together with offset starting
    221 // position (i.e. offset-position).
    222 static nsPoint ComputePosition(const StylePositionOrAuto& aAtPosition,
    223                               const StyleOffsetPosition& aOffsetPosition,
    224                               const nsRect& aCoordBox,
    225                               const nsPoint& aCurrentCoord) {
    226  if (aAtPosition.IsPosition()) {
    227    // Resolve this by using the <position> to position a 0x0 object area within
    228    // the box’s containing block.
    229    return ShapeUtils::ComputePosition(aAtPosition.AsPosition(), aCoordBox);
    230  }
    231 
    232  MOZ_ASSERT(aAtPosition.IsAuto(), "\"at <position>\" should be omitted");
    233 
    234  // Use the offset starting position of the element, given by offset-position.
    235  // https://drafts.fxtf.org/motion-1/#valdef-ray-at-position
    236  if (aOffsetPosition.IsPosition()) {
    237    return ShapeUtils::ComputePosition(aOffsetPosition.AsPosition(), aCoordBox);
    238  }
    239 
    240  if (aOffsetPosition.IsNormal()) {
    241    // If the element doesn’t have an offset starting position either, it
    242    // behaves as at center.
    243    const StylePosition& center = StylePosition::FromPercentage(0.5);
    244    return ShapeUtils::ComputePosition(center, aCoordBox);
    245  }
    246 
    247  MOZ_ASSERT(aOffsetPosition.IsAuto());
    248  return aCurrentCoord;
    249 }
    250 
    251 static CSSCoord ComputeRayPathLength(const StyleRaySize aRaySizeType,
    252                                     const StyleAngle& aAngle,
    253                                     const CSSPoint& aOrigin,
    254                                     const CSSRect& aContainingBlock) {
    255  if (aRaySizeType == StyleRaySize::Sides) {
    256    // If the initial position is not within the box, the distance is 0.
    257    //
    258    // Note: If the origin is at XMost() (and/or YMost()), we should consider it
    259    // to be inside containing block (because we expect 100% x (or y) coordinate
    260    // is still to be considered inside the containing block.
    261    if (!aContainingBlock.ContainsInclusively(aOrigin)) {
    262      return 0.0;
    263    }
    264 
    265    return ComputeSides(aOrigin, aContainingBlock, aAngle);
    266  }
    267 
    268  // left: the length between the origin and the left side.
    269  // right: the length between the origin and the right side.
    270  // top: the length between the origin and the top side.
    271  // bottom: the lenght between the origin and the bottom side.
    272  const CSSPoint& topLeft = aContainingBlock.TopLeft();
    273  const CSSCoord left = std::abs(aOrigin.x - topLeft.x);
    274  const CSSCoord right = std::abs(aContainingBlock.XMost() - aOrigin.x);
    275  const CSSCoord top = std::abs(aOrigin.y - topLeft.y);
    276  const CSSCoord bottom = std::abs(aContainingBlock.YMost() - aOrigin.y);
    277 
    278  switch (aRaySizeType) {
    279    case StyleRaySize::ClosestSide:
    280      return std::min({left, right, top, bottom});
    281 
    282    case StyleRaySize::FarthestSide:
    283      return std::max({left, right, top, bottom});
    284 
    285    case StyleRaySize::ClosestCorner:
    286    case StyleRaySize::FarthestCorner: {
    287      CSSCoord h = 0;
    288      CSSCoord v = 0;
    289      if (aRaySizeType == StyleRaySize::ClosestCorner) {
    290        h = std::min(left, right);
    291        v = std::min(top, bottom);
    292      } else {
    293        h = std::max(left, right);
    294        v = std::max(top, bottom);
    295      }
    296      return sqrt(h.value * h.value + v.value * v.value);
    297    }
    298    case StyleRaySize::Sides:
    299      MOZ_ASSERT_UNREACHABLE("Unsupported ray size");
    300  }
    301 
    302  return 0.0;
    303 }
    304 
    305 static CSSCoord ComputeRayUsedDistance(
    306    const StyleRayFunction& aRay, const LengthPercentage& aDistance,
    307    const CSSCoord& aPathLength, const CSSCoord& aRayContainReferenceLength) {
    308  CSSCoord usedDistance = aDistance.ResolveToCSSPixels(aPathLength);
    309  if (!aRay.contain) {
    310    return usedDistance;
    311  }
    312 
    313  // The length of the offset path is reduced so that the element stays within
    314  // the containing block even at offset-distance: 100%. Specifically, the
    315  // path’s length is reduced by half the width or half the height of the
    316  // element’s border box, whichever is larger, and floored at zero.
    317  // https://drafts.fxtf.org/motion-1/#valdef-ray-contain
    318  return std::max((usedDistance - aRayContainReferenceLength / 2.0f).value,
    319                  0.0f);
    320 }
    321 
    322 /* static */
    323 Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
    324    const OffsetPathData& aPath, const LengthPercentage& aDistance,
    325    const StyleOffsetRotate& aRotate, const StylePositionOrAuto& aAnchor,
    326    const StyleOffsetPosition& aPosition, const CSSPoint& aTransformOrigin,
    327    TransformReferenceBox& aRefBox, const CSSPoint& aAnchorPointAdjustment) {
    328  if (aPath.IsNone()) {
    329    return Nothing();
    330  }
    331 
    332  // Compute the point and angle for creating the equivalent translate and
    333  // rotate.
    334  double directionAngle = 0.0;
    335  gfx::Point point;
    336  if (aPath.IsShape()) {
    337    const auto& data = aPath.AsShape();
    338    RefPtr<gfx::Path> path = data.mGfxPath;
    339    MOZ_ASSERT(path, "The empty path is not allowed");
    340 
    341    // Per the spec, we have to convert offset distance to pixels, with 100%
    342    // being converted to total length. So here |gfxPath| is built with CSS
    343    // pixel, and we calculate |pathLength| and |computedDistance| with CSS
    344    // pixel as well.
    345    gfx::Float pathLength = path->ComputeLength();
    346    gfx::Float usedDistance =
    347        aDistance.ResolveToCSSPixels(CSSCoord(pathLength));
    348    if (data.mIsClosedLoop) {
    349      // Per the spec, let used offset distance be equal to offset distance
    350      // modulus the total length of the path. If the total length of the path
    351      // is 0, used offset distance is also 0.
    352      usedDistance = pathLength > 0.0 ? fmod(usedDistance, pathLength) : 0.0;
    353      // We make sure |usedDistance| is 0.0 or a positive value.
    354      if (usedDistance < 0.0) {
    355        usedDistance += pathLength;
    356      }
    357    } else {
    358      // Per the spec, for unclosed interval, let used offset distance be equal
    359      // to offset distance clamped by 0 and the total length of the path.
    360      usedDistance = std::clamp(usedDistance, 0.0f, pathLength);
    361    }
    362    gfx::Point tangent;
    363    point = path->ComputePointAtLength(usedDistance, &tangent);
    364    // Basically, |point| should be a relative distance between the current
    365    // position and the target position. The built |path| is in the coordinate
    366    // system of its containing block. Therefore, we have to take the current
    367    // position of this box into account to offset the translation so it's final
    368    // position is not affected by other boxes in the same containing block.
    369    point -= NSPointToPoint(data.mCurrentPosition, AppUnitsPerCSSPixel());
    370    // If the path length is 0, it's unlikely to get a valid tangent angle, e.g.
    371    // it may be (0, 0). And so we may get an undefined value from atan2().
    372    // Therefore, we use 0rad as the default behavior.
    373    directionAngle =
    374        pathLength < std::numeric_limits<gfx::Float>::epsilon()
    375            ? 0.0
    376            : atan2((double)tangent.y, (double)tangent.x);  // in Radian.
    377  } else if (aPath.IsRay()) {
    378    const auto& ray = aPath.AsRay();
    379    MOZ_ASSERT(ray.mRay);
    380 
    381    // Compute the origin, where the ray’s line begins (the 0% position).
    382    // https://drafts.fxtf.org/motion-1/#ray-origin
    383    const CSSPoint origin = CSSPoint::FromAppUnits(ComputePosition(
    384        ray.mRay->position, aPosition, ray.mCoordBox, ray.mCurrentPosition));
    385    const CSSCoord pathLength =
    386        ComputeRayPathLength(ray.mRay->size, ray.mRay->angle, origin,
    387                             CSSRect::FromAppUnits(ray.mCoordBox));
    388    const CSSCoord usedDistance = ComputeRayUsedDistance(
    389        *ray.mRay, aDistance, pathLength, ray.mContainReferenceLength);
    390 
    391    // 0deg pointing up and positive angles representing clockwise rotation.
    392    directionAngle =
    393        StyleAngle{ray.mRay->angle.ToDegrees() - 90.0f}.ToRadians();
    394 
    395    // The vector from the current position of this box to the origin of this
    396    // polar coordinate system.
    397    const gfx::Point vectorToOrigin =
    398        (origin - CSSPoint::FromAppUnits(ray.mCurrentPosition))
    399            .ToUnknownPoint();
    400    // |vectorToOrigin| + The vector from the origin to this polar coordinate,
    401    // (|usedDistance|, |directionAngle|), i.e. the vector from the current
    402    // position to this polar coordinate.
    403    point =
    404        vectorToOrigin +
    405        gfx::Point(usedDistance * static_cast<gfx::Float>(cos(directionAngle)),
    406                   usedDistance * static_cast<gfx::Float>(sin(directionAngle)));
    407  } else {
    408    MOZ_ASSERT_UNREACHABLE("Unsupported offset-path value");
    409    return Nothing();
    410  }
    411 
    412  // If |rotate.auto_| is true, the element should be rotated by the angle of
    413  // the direction (i.e. directional tangent vector) of the offset-path, and the
    414  // computed value of <angle> is added to this.
    415  // Otherwise, the element has a constant clockwise rotation transformation
    416  // applied to it by the specified rotation angle. (i.e. Don't need to
    417  // consider the direction of the path.)
    418  gfx::Float angle = static_cast<gfx::Float>(
    419      (aRotate.auto_ ? directionAngle : 0.0) + aRotate.angle.ToRadians());
    420 
    421  // Compute the offset for motion path translate.
    422  // Bug 1559232: the translate parameters will be adjusted more after we
    423  // support offset-position.
    424  // Per the spec, the default offset-anchor is `auto`, so initialize the anchor
    425  // point to transform-origin.
    426  CSSPoint anchorPoint(aTransformOrigin);
    427  gfx::Point shift;
    428  if (!aAnchor.IsAuto()) {
    429    const auto& pos = aAnchor.AsPosition();
    430    anchorPoint = nsStyleTransformMatrix::Convert2DPosition(
    431        pos.horizontal, pos.vertical, aRefBox);
    432    // We need this value to shift the origin from transform-origin to
    433    // offset-anchor (and vice versa).
    434    // See nsStyleTransformMatrix::ReadTransform for more details.
    435    shift = (anchorPoint - aTransformOrigin).ToUnknownPoint();
    436  }
    437 
    438  anchorPoint += aAnchorPointAdjustment;
    439 
    440  return Some(ResolvedMotionPathData{point - anchorPoint.ToUnknownPoint(),
    441                                     angle, shift});
    442 }
    443 
    444 static inline bool IsClosedLoop(const StyleSVGPathData& aPathData) {
    445  return !aPathData._0.AsSpan().empty() &&
    446         aPathData._0.AsSpan().rbegin()->IsClose();
    447 }
    448 
    449 // Create a path for "inset(0 round X)", where X is the value of border-radius
    450 // on the element that establishes the containing block for this element.
    451 static already_AddRefed<gfx::Path> BuildSimpleInsetPath(
    452    const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox,
    453    gfx::PathBuilder* aPathBuilder) {
    454  if (!aPathBuilder) {
    455    return nullptr;
    456  }
    457 
    458  const nsRect insetRect = ShapeUtils::ComputeInsetRect(
    459      StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
    460      aCoordBox);
    461  nsRectCornerRadii radii;
    462  const bool hasRadii =
    463      ShapeUtils::ComputeRectRadii(aBorderRadius, aCoordBox, insetRect, radii);
    464  return ShapeUtils::BuildRectPath(insetRect, hasRadii ? &radii : nullptr,
    465                                   aCoordBox, AppUnitsPerCSSPixel(),
    466                                   aPathBuilder);
    467 }
    468 
    469 // Create a path for `path("m 0 0")`, which is the default URL path if we cannot
    470 // resolve a SVG shape element.
    471 // https://drafts.fxtf.org/motion-1/#valdef-offset-path-url
    472 static already_AddRefed<gfx::Path> BuildDefaultPathForURL(
    473    gfx::PathBuilder* aBuilder) {
    474  if (!aBuilder) {
    475    return nullptr;
    476  }
    477 
    478  using CommandEndPoint =
    479      StyleCommandEndPoint<StyleShapePosition<StyleCSSFloat>, StyleCSSFloat>;
    480  Array<const StylePathCommand, 1> array(
    481      StylePathCommand::Move(CommandEndPoint::ByCoordinate({0.0, 0.0})));
    482  return SVGPathData::BuildPath(array, aBuilder, StyleStrokeLinecap::Butt, 0.0);
    483 }
    484 
    485 // Generate data for motion path on the main thread.
    486 static OffsetPathData GenerateOffsetPathData(const nsIFrame* aFrame) {
    487  const StyleOffsetPath& offsetPath = aFrame->StyleDisplay()->mOffsetPath;
    488  if (offsetPath.IsNone()) {
    489    return OffsetPathData::None();
    490  }
    491 
    492  // Handle ray().
    493  if (offsetPath.IsRay()) {
    494    nsRect coordBox;
    495    const nsIFrame* containingBlockFrame =
    496        MotionPathUtils::GetOffsetPathReferenceBox(aFrame, coordBox);
    497    return !containingBlockFrame
    498               ? OffsetPathData::None()
    499               : OffsetPathData::Ray(
    500                     offsetPath.AsRay(), std::move(coordBox),
    501                     aFrame->GetOffsetTo(containingBlockFrame),
    502                     MotionPathUtils::GetRayContainReferenceSize(
    503                         const_cast<nsIFrame*>(aFrame)));
    504  }
    505 
    506  // Handle path(). We cache it so we handle it separately.
    507  // FIXME: Bug 1837042, cache gfx::Path for shapes other than path(). Once we
    508  // cache all basic shapes, we can merge this branch into other basic shapes.
    509  if (offsetPath.IsPath()) {
    510    const StyleSVGPathData& pathData = offsetPath.AsSVGPathData();
    511    RefPtr<gfx::Path> gfxPath =
    512        aFrame->GetProperty(nsIFrame::OffsetPathCache());
    513    MOZ_ASSERT(gfxPath || pathData._0.IsEmpty(),
    514               "Should have a valid cached gfx::Path or an empty path string");
    515    // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
    516    // to give it the current box position.
    517    return OffsetPathData::Shape(gfxPath.forget(), {}, IsClosedLoop(pathData));
    518  }
    519 
    520  nsRect coordBox;
    521  const nsIFrame* containingFrame =
    522      MotionPathUtils::GetOffsetPathReferenceBox(aFrame, coordBox);
    523  if (!containingFrame || coordBox.IsEmpty()) {
    524    return OffsetPathData::None();
    525  }
    526  nsPoint currentPosition = aFrame->GetOffsetTo(containingFrame);
    527  RefPtr<gfx::PathBuilder> builder = MotionPathUtils::GetPathBuilder();
    528 
    529  if (offsetPath.IsUrl()) {
    530    dom::SVGGeometryElement* element =
    531        SVGObserverUtils::GetAndObserveGeometry(const_cast<nsIFrame*>(aFrame));
    532    if (!element) {
    533      // Note: This behaves as path("m 0 0") (a <basic-shape>).
    534      RefPtr<gfx::Path> path = BuildDefaultPathForURL(builder);
    535      // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
    536      // to give it the current box position.
    537      return path ? OffsetPathData::Shape(path.forget(), {}, false)
    538                  : OffsetPathData::None();
    539    }
    540 
    541    // We just need this path to calculate the specific point and direction
    542    // angle, so use measuring function and get the benefit of caching the path
    543    // in the SVG shape element.
    544    RefPtr<gfx::Path> path = element->GetOrBuildPathForMeasuring();
    545 
    546    // The built |path| from SVG shape element doesn't take |coordBox| into
    547    // account. It uses the SVG viewport as its coordinate system. So after
    548    // mapping it into the CSS layout, we should use |coordBox| as its viewport
    549    // and user coordinate system. |currentPosition| is based on the border-box
    550    // of the containing block. Therefore, we have to apply an extra translation
    551    // to put it at the correct position based on |coordBox|.
    552    //
    553    // Note: we reuse |OffsetPathData::ShapeData::mCurrentPosition| to include
    554    // this extra translation, so we don't have to add an extra field.
    555    nsPoint positionInCoordBox = currentPosition - coordBox.TopLeft();
    556    return path ? OffsetPathData::Shape(path.forget(),
    557                                        std::move(positionInCoordBox),
    558                                        element->IsClosedLoop())
    559                : OffsetPathData::None();
    560  }
    561 
    562  // The rest part is to handle "<basic-shape> || <coord-box>".
    563  MOZ_ASSERT(offsetPath.IsBasicShapeOrCoordBox());
    564 
    565  const nsStyleDisplay* disp = aFrame->StyleDisplay();
    566  RefPtr<gfx::Path> path =
    567      disp->mOffsetPath.IsCoordBox()
    568          ? BuildSimpleInsetPath(containingFrame->StyleBorder()->mBorderRadius,
    569                                 coordBox, builder)
    570          : MotionPathUtils::BuildPath(
    571                disp->mOffsetPath.AsOffsetPath().path->AsShape(),
    572                disp->mOffsetPosition, coordBox, currentPosition, builder);
    573  return path ? OffsetPathData::Shape(path.forget(), std::move(currentPosition),
    574                                      true)
    575              : OffsetPathData::None();
    576 }
    577 
    578 /* static*/
    579 Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
    580    const nsIFrame* aFrame, TransformReferenceBox& aRefBox) {
    581  MOZ_ASSERT(aFrame);
    582 
    583  const nsStyleDisplay* display = aFrame->StyleDisplay();
    584 
    585  // FIXME: It's possible to refactor the calculation of transform-origin, so we
    586  // could calculate from the caller, and reuse the value in nsDisplayList.cpp.
    587  CSSPoint transformOrigin = nsStyleTransformMatrix::Convert2DPosition(
    588      display->mTransformOrigin.horizontal, display->mTransformOrigin.vertical,
    589      aRefBox);
    590 
    591  return ResolveMotionPath(
    592      GenerateOffsetPathData(aFrame), display->mOffsetDistance,
    593      display->mOffsetRotate, display->mOffsetAnchor, display->mOffsetPosition,
    594      transformOrigin, aRefBox, ComputeAnchorPointAdjustment(*aFrame));
    595 }
    596 
    597 // Generate data for motion path on the compositor thread.
    598 static OffsetPathData GenerateOffsetPathData(
    599    const StyleOffsetPath& aOffsetPath,
    600    const StyleOffsetPosition& aOffsetPosition,
    601    const layers::MotionPathData& aMotionPathData,
    602    gfx::Path* aCachedMotionPath) {
    603  if (aOffsetPath.IsNone()) {
    604    return OffsetPathData::None();
    605  }
    606 
    607  // Handle ray().
    608  if (aOffsetPath.IsRay()) {
    609    return aMotionPathData.coordBox().IsEmpty()
    610               ? OffsetPathData::None()
    611               : OffsetPathData::Ray(
    612                     aOffsetPath.AsRay(), aMotionPathData.coordBox(),
    613                     aMotionPathData.currentPosition(),
    614                     aMotionPathData.rayContainReferenceLength());
    615  }
    616 
    617  // Handle path().
    618  // FIXME: Bug 1837042, cache gfx::Path for shapes other than path().
    619  if (aOffsetPath.IsPath()) {
    620    const StyleSVGPathData& pathData = aOffsetPath.AsSVGPathData();
    621    // If aCachedMotionPath is valid, we have a fixed path.
    622    // This means we have pre-built it already and no need to update.
    623    RefPtr<gfx::Path> path = aCachedMotionPath;
    624    if (!path) {
    625      RefPtr<gfx::PathBuilder> builder =
    626          MotionPathUtils::GetCompositorPathBuilder();
    627      path = MotionPathUtils::BuildSVGPath(pathData, builder);
    628    }
    629    // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
    630    // to give it the current box position.
    631    return OffsetPathData::Shape(path.forget(), {}, IsClosedLoop(pathData));
    632  }
    633 
    634  // The rest part is to handle "<basic-shape> || <coord-box>".
    635  MOZ_ASSERT(aOffsetPath.IsBasicShapeOrCoordBox());
    636 
    637  const nsRect& coordBox = aMotionPathData.coordBox();
    638  if (coordBox.IsEmpty()) {
    639    return OffsetPathData::None();
    640  }
    641 
    642  RefPtr<gfx::PathBuilder> builder =
    643      MotionPathUtils::GetCompositorPathBuilder();
    644  if (!builder) {
    645    return OffsetPathData::None();
    646  }
    647 
    648  RefPtr<gfx::Path> path;
    649  if (aOffsetPath.IsCoordBox()) {
    650    const nsRect insetRect = ShapeUtils::ComputeInsetRect(
    651        StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
    652        coordBox);
    653    const nsTArray<nscoord>& radii = aMotionPathData.coordBoxInsetRadii();
    654    nsRectCornerRadii rectRadii;
    655    if (!radii.IsEmpty()) {
    656      for (auto hc : AllPhysicalHalfCorners()) {
    657        rectRadii[hc] = radii[hc];
    658      }
    659    }
    660    path = ShapeUtils::BuildRectPath(insetRect,
    661                                     radii.IsEmpty() ? nullptr : &rectRadii,
    662                                     coordBox, AppUnitsPerCSSPixel(), builder);
    663  } else {
    664    path = MotionPathUtils::BuildPath(
    665        aOffsetPath.AsOffsetPath().path->AsShape(), aOffsetPosition, coordBox,
    666        aMotionPathData.currentPosition(), builder);
    667  }
    668 
    669  return path ? OffsetPathData::Shape(
    670                    path.forget(), nsPoint(aMotionPathData.currentPosition()),
    671                    true)
    672              : OffsetPathData::None();
    673 }
    674 
    675 /* static */
    676 Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
    677    const StyleOffsetPath* aPath, const StyleLengthPercentage* aDistance,
    678    const StyleOffsetRotate* aRotate, const StylePositionOrAuto* aAnchor,
    679    const StyleOffsetPosition* aPosition,
    680    const Maybe<layers::MotionPathData>& aMotionPathData,
    681    TransformReferenceBox& aRefBox, gfx::Path* aCachedMotionPath) {
    682  if (!aPath) {
    683    return Nothing();
    684  }
    685 
    686  MOZ_ASSERT(aMotionPathData);
    687 
    688  auto zeroOffsetDistance = LengthPercentage::Zero();
    689  auto autoOffsetRotate = StyleOffsetRotate{true, StyleAngle::Zero()};
    690  auto autoOffsetAnchor = StylePositionOrAuto::Auto();
    691  auto autoOffsetPosition = StyleOffsetPosition::Auto();
    692  return ResolveMotionPath(
    693      GenerateOffsetPathData(*aPath,
    694                             aPosition ? *aPosition : autoOffsetPosition,
    695                             *aMotionPathData, aCachedMotionPath),
    696      aDistance ? *aDistance : zeroOffsetDistance,
    697      aRotate ? *aRotate : autoOffsetRotate,
    698      aAnchor ? *aAnchor : autoOffsetAnchor,
    699      aPosition ? *aPosition : autoOffsetPosition, aMotionPathData->origin(),
    700      aRefBox, aMotionPathData->anchorAdjustment());
    701 }
    702 
    703 /* static */
    704 already_AddRefed<gfx::Path> MotionPathUtils::BuildSVGPath(
    705    const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder) {
    706  if (!aPathBuilder) {
    707    return nullptr;
    708  }
    709 
    710  const Span<const StylePathCommand>& path = aPath._0.AsSpan();
    711  return SVGPathData::BuildPath(path, aPathBuilder, StyleStrokeLinecap::Butt,
    712                                0.0);
    713 }
    714 
    715 static already_AddRefed<gfx::Path> BuildShape(
    716    const Span<const StyleShapeCommand>& aShape, gfx::PathBuilder* aPathBuilder,
    717    const nsRect& aCoordBox) {
    718  if (!aPathBuilder) {
    719    return nullptr;
    720  }
    721 
    722  // For motion path, we always use CSSPixel unit to compute the offset
    723  // transform (i.e. motion path transform).
    724  const auto rect = CSSRect::FromAppUnits(aCoordBox);
    725  return SVGPathData::BuildPath(aShape, aPathBuilder, StyleStrokeLinecap::Butt,
    726                                0.0, rect.Size(),
    727                                rect.TopLeft().ToUnknownPoint());
    728 }
    729 
    730 /* static */
    731 already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
    732    const StyleBasicShape& aBasicShape,
    733    const StyleOffsetPosition& aOffsetPosition, const nsRect& aCoordBox,
    734    const nsPoint& aCurrentPosition, gfx::PathBuilder* aPathBuilder) {
    735  if (!aPathBuilder) {
    736    return nullptr;
    737  }
    738 
    739  switch (aBasicShape.tag) {
    740    case StyleBasicShape::Tag::Circle: {
    741      const nsPoint center =
    742          ComputePosition(aBasicShape.AsCircle().position, aOffsetPosition,
    743                          aCoordBox, aCurrentPosition);
    744      return ShapeUtils::BuildCirclePath(aBasicShape, aCoordBox, center,
    745                                         AppUnitsPerCSSPixel(), aPathBuilder);
    746    }
    747    case StyleBasicShape::Tag::Ellipse: {
    748      const nsPoint center =
    749          ComputePosition(aBasicShape.AsEllipse().position, aOffsetPosition,
    750                          aCoordBox, aCurrentPosition);
    751      return ShapeUtils::BuildEllipsePath(aBasicShape, aCoordBox, center,
    752                                          AppUnitsPerCSSPixel(), aPathBuilder);
    753    }
    754    case StyleBasicShape::Tag::Rect:
    755      return ShapeUtils::BuildInsetPath(aBasicShape, aCoordBox,
    756                                        AppUnitsPerCSSPixel(), aPathBuilder);
    757    case StyleBasicShape::Tag::Polygon:
    758      return ShapeUtils::BuildPolygonPath(aBasicShape, aCoordBox,
    759                                          AppUnitsPerCSSPixel(), aPathBuilder);
    760    case StyleBasicShape::Tag::PathOrShape: {
    761      // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
    762      // to also check its containing block as well. For now, we are still
    763      // building its gfx::Path directly by its SVGPathData without other
    764      // reference. https://github.com/w3c/fxtf-drafts/issues/504
    765      const auto& pathOrShape = aBasicShape.AsPathOrShape();
    766      if (pathOrShape.IsPath()) {
    767        return BuildSVGPath(pathOrShape.AsPath().path, aPathBuilder);
    768      }
    769 
    770      // Note that shape() always defines the initial position, i.e. "from x y",
    771      // by its first move command, so |aOffsetPosition|, i.e. offset-position
    772      // property, is ignored.
    773      return BuildShape(pathOrShape.AsShape().commands.AsSpan(), aPathBuilder,
    774                        aCoordBox);
    775    }
    776  }
    777 
    778  return nullptr;
    779 }
    780 
    781 /* static */
    782 already_AddRefed<gfx::PathBuilder> MotionPathUtils::GetPathBuilder() {
    783  // Here we only need to build a valid path for motion path, so
    784  // using the default values of stroke-width, stoke-linecap, and fill-rule
    785  // is fine for now because what we want is to get the point and its normal
    786  // vector along the path, instead of rendering it.
    787  RefPtr<gfx::PathBuilder> builder =
    788      gfxPlatform::GetPlatform()
    789          ->ScreenReferenceDrawTarget()
    790          ->CreatePathBuilder(gfx::FillRule::FILL_WINDING);
    791  return builder.forget();
    792 }
    793 
    794 /* static */
    795 already_AddRefed<gfx::PathBuilder> MotionPathUtils::GetCompositorPathBuilder() {
    796  // FIXME: Perhaps we need a PathBuilder which is independent on the backend.
    797  RefPtr<gfx::PathBuilder> builder =
    798      gfxPlatform::Initialized()
    799          ? gfxPlatform::GetPlatform()
    800                ->ScreenReferenceDrawTarget()
    801                ->CreatePathBuilder(gfx::FillRule::FILL_WINDING)
    802          : gfx::Factory::CreateSimplePathBuilder();
    803  return builder.forget();
    804 }
    805 
    806 }  // namespace mozilla