tor-browser

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

nsCSSRenderingGradients.cpp (56596B)


      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 /* utility functions for drawing borders and backgrounds */
      8 
      9 #include "nsCSSRenderingGradients.h"
     10 
     11 #include <tuple>
     12 
     13 #include "Units.h"
     14 #include "gfx2DGlue.h"
     15 #include "gfxContext.h"
     16 #include "gfxGradientCache.h"
     17 #include "gfxUtils.h"
     18 #include "mozilla/ComputedStyle.h"
     19 #include "mozilla/MathAlgorithms.h"
     20 #include "mozilla/ProfilerLabels.h"
     21 #include "mozilla/StaticPrefs_layout.h"
     22 #include "mozilla/gfx/2D.h"
     23 #include "mozilla/gfx/Helpers.h"
     24 #include "mozilla/layers/StackingContextHelper.h"
     25 #include "mozilla/layers/WebRenderLayerManager.h"
     26 #include "mozilla/webrender/WebRenderAPI.h"
     27 #include "mozilla/webrender/WebRenderTypes.h"
     28 #include "nsCSSColorUtils.h"
     29 #include "nsCSSProps.h"
     30 #include "nsLayoutUtils.h"
     31 #include "nsPoint.h"
     32 #include "nsPresContext.h"
     33 #include "nsRect.h"
     34 #include "nsStyleConsts.h"
     35 #include "nsStyleStructInlines.h"
     36 
     37 using namespace mozilla;
     38 using namespace mozilla::gfx;
     39 
     40 static CSSPoint ResolvePosition(const Position& aPos, const CSSSize& aSize) {
     41  CSSCoord h = aPos.horizontal.ResolveToCSSPixels(aSize.width);
     42  CSSCoord v = aPos.vertical.ResolveToCSSPixels(aSize.height);
     43  return CSSPoint(h, v);
     44 }
     45 
     46 // Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
     47 // and a starting point for the gradient line aStart, find the endpoint of
     48 // the gradient line --- the intersection of the gradient line with a line
     49 // perpendicular to aAngle that passes through the farthest corner in the
     50 // direction aAngle.
     51 static CSSPoint ComputeGradientLineEndFromAngle(const CSSPoint& aStart,
     52                                                double aAngle,
     53                                                const CSSSize& aBoxSize) {
     54  double dx = cos(-aAngle);
     55  double dy = sin(-aAngle);
     56  CSSPoint farthestCorner(dx > 0 ? aBoxSize.width : 0,
     57                          dy > 0 ? aBoxSize.height : 0);
     58  CSSPoint delta = farthestCorner - aStart;
     59  double u = delta.x * dy - delta.y * dx;
     60  return farthestCorner + CSSPoint(-u * dy, u * dx);
     61 }
     62 
     63 // Compute the start and end points of the gradient line for a linear gradient.
     64 static std::tuple<CSSPoint, CSSPoint> ComputeLinearGradientLine(
     65    nsPresContext* aPresContext, const StyleGradient& aGradient,
     66    const CSSSize& aBoxSize) {
     67  using X = StyleHorizontalPositionKeyword;
     68  using Y = StyleVerticalPositionKeyword;
     69 
     70  const StyleLineDirection& direction = aGradient.AsLinear().direction;
     71  const bool isModern =
     72      aGradient.AsLinear().compat_mode == StyleGradientCompatMode::Modern;
     73 
     74  CSSPoint center(aBoxSize.width / 2, aBoxSize.height / 2);
     75  switch (direction.tag) {
     76    case StyleLineDirection::Tag::Angle: {
     77      double angle = direction.AsAngle().ToRadians();
     78      if (isModern) {
     79        angle = M_PI_2 - angle;
     80      }
     81      CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
     82      CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end;
     83      return {start, end};
     84    }
     85    case StyleLineDirection::Tag::Vertical: {
     86      CSSPoint start(center.x, 0);
     87      CSSPoint end(center.x, aBoxSize.height);
     88      if (isModern == (direction.AsVertical() == Y::Top)) {
     89        std::swap(start.y, end.y);
     90      }
     91      return {start, end};
     92    }
     93    case StyleLineDirection::Tag::Horizontal: {
     94      CSSPoint start(0, center.y);
     95      CSSPoint end(aBoxSize.width, center.y);
     96      if (isModern == (direction.AsHorizontal() == X::Left)) {
     97        std::swap(start.x, end.x);
     98      }
     99      return {start, end};
    100    }
    101    case StyleLineDirection::Tag::Corner: {
    102      const auto& corner = direction.AsCorner();
    103      const X& h = corner._0;
    104      const Y& v = corner._1;
    105 
    106      if (isModern) {
    107        float xSign = h == X::Right ? 1.0 : -1.0;
    108        float ySign = v == Y::Top ? 1.0 : -1.0;
    109        double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height);
    110        CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
    111        CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end;
    112        return {start, end};
    113      }
    114 
    115      CSSCoord startX = h == X::Left ? 0.0 : aBoxSize.width;
    116      CSSCoord startY = v == Y::Top ? 0.0 : aBoxSize.height;
    117 
    118      CSSPoint start(startX, startY);
    119      CSSPoint end = CSSPoint(aBoxSize.width, aBoxSize.height) - start;
    120      return {start, end};
    121    }
    122    default:
    123      break;
    124  }
    125  MOZ_ASSERT_UNREACHABLE("Unknown line direction");
    126  return {CSSPoint(), CSSPoint()};
    127 }
    128 
    129 using EndingShape = StyleGenericEndingShape<Length, LengthPercentage>;
    130 using RadialGradientRadii =
    131    Variant<StyleShapeExtent, std::pair<CSSCoord, CSSCoord>>;
    132 
    133 static RadialGradientRadii ComputeRadialGradientRadii(const EndingShape& aShape,
    134                                                      const CSSSize& aSize) {
    135  if (aShape.IsCircle()) {
    136    auto& circle = aShape.AsCircle();
    137    if (circle.IsExtent()) {
    138      return RadialGradientRadii(circle.AsExtent());
    139    }
    140    CSSCoord radius = circle.AsRadius().ToCSSPixels();
    141    return RadialGradientRadii(std::make_pair(radius, radius));
    142  }
    143  auto& ellipse = aShape.AsEllipse();
    144  if (ellipse.IsExtent()) {
    145    return RadialGradientRadii(ellipse.AsExtent());
    146  }
    147 
    148  auto& radii = ellipse.AsRadii();
    149  return RadialGradientRadii(
    150      std::make_pair(radii._0.ResolveToCSSPixels(aSize.width),
    151                     radii._1.ResolveToCSSPixels(aSize.height)));
    152 }
    153 
    154 // Compute the start and end points of the gradient line for a radial gradient.
    155 // Also returns the horizontal and vertical radii defining the circle or
    156 // ellipse to use.
    157 static std::tuple<CSSPoint, CSSPoint, CSSCoord, CSSCoord>
    158 ComputeRadialGradientLine(const StyleGradient& aGradient,
    159                          const CSSSize& aBoxSize) {
    160  const auto& radial = aGradient.AsRadial();
    161  const EndingShape& endingShape = radial.shape;
    162  const Position& position = radial.position;
    163  CSSPoint start = ResolvePosition(position, aBoxSize);
    164 
    165  // Compute gradient shape: the x and y radii of an ellipse.
    166  CSSCoord radiusX, radiusY;
    167  CSSCoord leftDistance = Abs(start.x);
    168  CSSCoord rightDistance = Abs(aBoxSize.width - start.x);
    169  CSSCoord topDistance = Abs(start.y);
    170  CSSCoord bottomDistance = Abs(aBoxSize.height - start.y);
    171 
    172  auto radii = ComputeRadialGradientRadii(endingShape, aBoxSize);
    173  if (radii.is<StyleShapeExtent>()) {
    174    switch (radii.as<StyleShapeExtent>()) {
    175      case StyleShapeExtent::ClosestSide:
    176        radiusX = std::min(leftDistance, rightDistance);
    177        radiusY = std::min(topDistance, bottomDistance);
    178        if (endingShape.IsCircle()) {
    179          radiusX = radiusY = std::min(radiusX, radiusY);
    180        }
    181        break;
    182      case StyleShapeExtent::ClosestCorner: {
    183        // Compute x and y distances to nearest corner
    184        CSSCoord offsetX = std::min(leftDistance, rightDistance);
    185        CSSCoord offsetY = std::min(topDistance, bottomDistance);
    186        if (endingShape.IsCircle()) {
    187          radiusX = radiusY = NS_hypot(offsetX, offsetY);
    188        } else {
    189          // maintain aspect ratio
    190          radiusX = offsetX * M_SQRT2;
    191          radiusY = offsetY * M_SQRT2;
    192        }
    193        break;
    194      }
    195      case StyleShapeExtent::FarthestSide:
    196        radiusX = std::max(leftDistance, rightDistance);
    197        radiusY = std::max(topDistance, bottomDistance);
    198        if (endingShape.IsCircle()) {
    199          radiusX = radiusY = std::max(radiusX, radiusY);
    200        }
    201        break;
    202      case StyleShapeExtent::FarthestCorner: {
    203        // Compute x and y distances to nearest corner
    204        CSSCoord offsetX = std::max(leftDistance, rightDistance);
    205        CSSCoord offsetY = std::max(topDistance, bottomDistance);
    206        if (endingShape.IsCircle()) {
    207          radiusX = radiusY = NS_hypot(offsetX, offsetY);
    208        } else {
    209          // maintain aspect ratio
    210          radiusX = offsetX * M_SQRT2;
    211          radiusY = offsetY * M_SQRT2;
    212        }
    213        break;
    214      }
    215      default:
    216        MOZ_ASSERT_UNREACHABLE("Unknown shape extent keyword?");
    217        radiusX = radiusY = 0;
    218    }
    219  } else {
    220    auto pair = radii.as<std::pair<CSSCoord, CSSCoord>>();
    221    radiusX = pair.first;
    222    radiusY = pair.second;
    223  }
    224 
    225  // The gradient line end point is where the gradient line intersects
    226  // the ellipse.
    227  CSSPoint end = start + CSSPoint(radiusX, 0);
    228  return {start, end, radiusX, radiusY};
    229 }
    230 
    231 // Compute the center and the start angle of the conic gradient.
    232 static std::tuple<CSSPoint, float> ComputeConicGradientProperties(
    233    const StyleGradient& aGradient, const CSSSize& aBoxSize) {
    234  const auto& conic = aGradient.AsConic();
    235  const Position& position = conic.position;
    236  float angle = static_cast<float>(conic.angle.ToRadians());
    237  CSSPoint center = ResolvePosition(position, aBoxSize);
    238 
    239  return {center, angle};
    240 }
    241 
    242 static float Interpolate(float aF1, float aF2, float aFrac) {
    243  return aF1 + aFrac * (aF2 - aF1);
    244 }
    245 
    246 static StyleAbsoluteColor Interpolate(const StyleAbsoluteColor& aLeft,
    247                                      const StyleAbsoluteColor& aRight,
    248                                      float aFrac) {
    249  // NOTE: This has to match the interpolation method that WebRender uses which
    250  // right now is sRGB. In the future we should implement interpolation in more
    251  // gradient color-spaces.
    252  static constexpr auto kMethod = StyleColorInterpolationMethod{
    253      StyleColorSpace::Srgb,
    254      StyleHueInterpolationMethod::Shorter,
    255  };
    256  return Servo_InterpolateColor(kMethod, &aLeft, &aRight, aFrac);
    257 }
    258 
    259 static nscoord FindTileStart(nscoord aDirtyCoord, nscoord aTilePos,
    260                             nscoord aTileDim) {
    261  NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension");
    262  double multiples = floor(double(aDirtyCoord - aTilePos) / aTileDim);
    263  return NSToCoordRound(multiples * aTileDim + aTilePos);
    264 }
    265 
    266 static gfxFloat LinearGradientStopPositionForPoint(
    267    const gfxPoint& aGradientStart, const gfxPoint& aGradientEnd,
    268    const gfxPoint& aPoint) {
    269  gfxPoint d = aGradientEnd - aGradientStart;
    270  gfxPoint p = aPoint - aGradientStart;
    271  /**
    272   * Compute a parameter t such that a line perpendicular to the
    273   * d vector, passing through aGradientStart + d*t, also
    274   * passes through aPoint.
    275   *
    276   * t is given by
    277   *   (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
    278   *
    279   * Solving for t we get
    280   *   numerator = d.x*p.x + d.y*p.y
    281   *   denominator = d.x^2 + d.y^2
    282   *   t = numerator/denominator
    283   *
    284   * In nsCSSRendering::PaintGradient we know the length of d
    285   * is not zero.
    286   */
    287  double numerator = d.x.value * p.x.value + d.y.value * p.y.value;
    288  double denominator = d.x.value * d.x.value + d.y.value * d.y.value;
    289  return numerator / denominator;
    290 }
    291 
    292 static bool RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
    293                                           const gfxMatrix& aPatternMatrix,
    294                                           const nsTArray<ColorStop>& aStops,
    295                                           const gfxPoint& aGradientStart,
    296                                           const gfxPoint& aGradientEnd,
    297                                           StyleAbsoluteColor* aOutEdgeColor) {
    298  gfxFloat topLeft = LinearGradientStopPositionForPoint(
    299      aGradientStart, aGradientEnd,
    300      aPatternMatrix.TransformPoint(aRect.TopLeft()));
    301  gfxFloat topRight = LinearGradientStopPositionForPoint(
    302      aGradientStart, aGradientEnd,
    303      aPatternMatrix.TransformPoint(aRect.TopRight()));
    304  gfxFloat bottomLeft = LinearGradientStopPositionForPoint(
    305      aGradientStart, aGradientEnd,
    306      aPatternMatrix.TransformPoint(aRect.BottomLeft()));
    307  gfxFloat bottomRight = LinearGradientStopPositionForPoint(
    308      aGradientStart, aGradientEnd,
    309      aPatternMatrix.TransformPoint(aRect.BottomRight()));
    310 
    311  const ColorStop& firstStop = aStops[0];
    312  if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition &&
    313      bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) {
    314    *aOutEdgeColor = firstStop.mColor;
    315    return true;
    316  }
    317 
    318  const ColorStop& lastStop = aStops.LastElement();
    319  if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition &&
    320      bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) {
    321    *aOutEdgeColor = lastStop.mColor;
    322    return true;
    323  }
    324 
    325  return false;
    326 }
    327 
    328 static void ResolveMidpoints(nsTArray<ColorStop>& stops) {
    329  for (size_t x = 1; x < stops.Length() - 1;) {
    330    if (!stops[x].mIsMidpoint) {
    331      x++;
    332      continue;
    333    }
    334 
    335    const auto& color1 = stops[x - 1].mColor;
    336    const auto& color2 = stops[x + 1].mColor;
    337    float offset1 = stops[x - 1].mPosition;
    338    float offset2 = stops[x + 1].mPosition;
    339    float offset = stops[x].mPosition;
    340    // check if everything coincides. If so, ignore the midpoint.
    341    if (offset - offset1 == offset2 - offset) {
    342      stops.RemoveElementAt(x);
    343      continue;
    344    }
    345 
    346    // Check if we coincide with the left colorstop.
    347    if (offset1 == offset) {
    348      // Morph the midpoint to a regular stop with the color of the next
    349      // color stop.
    350      stops[x].mColor = color2;
    351      stops[x].mIsMidpoint = false;
    352      continue;
    353    }
    354 
    355    // Check if we coincide with the right colorstop.
    356    if (offset2 == offset) {
    357      // Morph the midpoint to a regular stop with the color of the previous
    358      // color stop.
    359      stops[x].mColor = color1;
    360      stops[x].mIsMidpoint = false;
    361      continue;
    362    }
    363 
    364    // Calculate the intermediate color stops per the formula of the CSS
    365    // images spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax
    366    // 9 points were chosen since it is the minimum number of stops that always
    367    // give the smoothest appearace regardless of midpoint position and
    368    // difference in luminance of the end points.
    369    float midpoint = (offset - offset1) / (offset2 - offset1);
    370    ColorStop newStops[9];
    371    if (midpoint > .5f) {
    372      for (size_t y = 0; y < 7; y++) {
    373        newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13;
    374      }
    375 
    376      newStops[7].mPosition = offset + (offset2 - offset) / 3;
    377      newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3;
    378    } else {
    379      newStops[0].mPosition = offset1 + (offset - offset1) / 3;
    380      newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3;
    381 
    382      for (size_t y = 0; y < 7; y++) {
    383        newStops[y + 2].mPosition = offset + (offset2 - offset) * y / 13;
    384      }
    385    }
    386 
    387    // calculate colors
    388    for (auto& newStop : newStops) {
    389      const float relativeOffset =
    390          (newStop.mPosition - offset1) / (offset2 - offset1);
    391      const float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
    392      newStop.mColor = Interpolate(color1, color2, multiplier);
    393    }
    394 
    395    stops.ReplaceElementsAt(x, 1, newStops, 9);
    396    x += 9;
    397  }
    398 }
    399 
    400 static StyleAbsoluteColor TransparentColor(const StyleAbsoluteColor& aColor) {
    401  auto color = aColor;
    402  color.alpha = 0.0f;
    403  return color;
    404 }
    405 
    406 // Adjusts and adds color stops in such a way that drawing the gradient with
    407 // unpremultiplied interpolation looks nearly the same as if it were drawn with
    408 // premultiplied interpolation.
    409 static const float kAlphaIncrementPerGradientStep = 0.1f;
    410 static void ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops) {
    411  for (size_t x = 1; x < aStops.Length(); x++) {
    412    const ColorStop leftStop = aStops[x - 1];
    413    const ColorStop rightStop = aStops[x];
    414 
    415    // if the left and right stop have the same alpha value, we don't need
    416    // to do anything. Hardstops should be instant, and also should never
    417    // require dealing with interpolation.
    418    if (leftStop.mColor.alpha == rightStop.mColor.alpha ||
    419        leftStop.mPosition == rightStop.mPosition) {
    420      continue;
    421    }
    422 
    423    // Is the stop on the left 100% transparent? If so, have it adopt the color
    424    // of the right stop
    425    if (leftStop.mColor.alpha == 0) {
    426      aStops[x - 1].mColor = TransparentColor(rightStop.mColor);
    427      continue;
    428    }
    429 
    430    // Is the stop on the right completely transparent?
    431    // If so, duplicate it and assign it the color on the left.
    432    if (rightStop.mColor.alpha == 0) {
    433      ColorStop newStop = rightStop;
    434      newStop.mColor = TransparentColor(leftStop.mColor);
    435      aStops.InsertElementAt(x, newStop);
    436      x++;
    437      continue;
    438    }
    439 
    440    // Now handle cases where one or both of the stops are partially
    441    // transparent.
    442    if (leftStop.mColor.alpha != 1.0f || rightStop.mColor.alpha != 1.0f) {
    443      // Calculate how many extra steps. We do a step per 10% transparency.
    444      size_t stepCount =
    445          NSToIntFloor(fabsf(leftStop.mColor.alpha - rightStop.mColor.alpha) /
    446                       kAlphaIncrementPerGradientStep);
    447      for (size_t y = 1; y < stepCount; y++) {
    448        float frac = static_cast<float>(y) / stepCount;
    449        ColorStop newStop(
    450            Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false,
    451            Interpolate(leftStop.mColor, rightStop.mColor, frac));
    452        aStops.InsertElementAt(x, newStop);
    453        x++;
    454      }
    455    }
    456  }
    457 }
    458 
    459 static ColorStop InterpolateColorStop(const ColorStop& aFirst,
    460                                      const ColorStop& aSecond,
    461                                      double aPosition,
    462                                      const StyleAbsoluteColor& aDefault) {
    463  MOZ_ASSERT(aFirst.mPosition <= aPosition);
    464  MOZ_ASSERT(aPosition <= aSecond.mPosition);
    465 
    466  double delta = aSecond.mPosition - aFirst.mPosition;
    467  if (delta < 1e-6) {
    468    return ColorStop(aPosition, false, aDefault);
    469  }
    470 
    471  return ColorStop(aPosition, false,
    472                   Interpolate(aFirst.mColor, aSecond.mColor,
    473                               (aPosition - aFirst.mPosition) / delta));
    474 }
    475 
    476 // Clamp and extend the given ColorStop array in-place to fit exactly into the
    477 // range [0, 1].
    478 static void ClampColorStops(nsTArray<ColorStop>& aStops) {
    479  MOZ_ASSERT(aStops.Length() > 0);
    480 
    481  // If all stops are outside the range, then get rid of everything and replace
    482  // with a single colour.
    483  if (aStops.Length() < 2 || aStops[0].mPosition > 1 ||
    484      aStops.LastElement().mPosition < 0) {
    485    const auto c = aStops[0].mPosition > 1 ? aStops[0].mColor
    486                                           : aStops.LastElement().mColor;
    487    aStops.Clear();
    488    aStops.AppendElement(ColorStop(0, false, c));
    489    return;
    490  }
    491 
    492  // Create the 0 and 1 points if they fall in the range of |aStops|, and
    493  // discard all stops outside the range [0, 1].
    494  // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of
    495  // those stops. This should be fine for the current user(s) of this function.
    496  for (size_t i = aStops.Length() - 1; i > 0; i--) {
    497    if (aStops[i - 1].mPosition < 1 && aStops[i].mPosition >= 1) {
    498      // Add a point to position 1.
    499      aStops[i] =
    500          InterpolateColorStop(aStops[i - 1], aStops[i],
    501                               /* aPosition = */ 1, aStops[i - 1].mColor);
    502      // Remove all the elements whose position is greater than 1.
    503      aStops.RemoveLastElements(aStops.Length() - (i + 1));
    504    }
    505    if (aStops[i - 1].mPosition <= 0 && aStops[i].mPosition > 0) {
    506      // Add a point to position 0.
    507      aStops[i - 1] =
    508          InterpolateColorStop(aStops[i - 1], aStops[i],
    509                               /* aPosition = */ 0, aStops[i].mColor);
    510      // Remove all of the preceding stops -- they are all negative.
    511      aStops.RemoveElementsAt(0, i - 1);
    512      break;
    513    }
    514  }
    515 
    516  MOZ_ASSERT(aStops[0].mPosition >= -1e6);
    517  MOZ_ASSERT(aStops.LastElement().mPosition - 1 <= 1e6);
    518 
    519  // The end points won't exist yet if they don't fall in the original range of
    520  // |aStops|. Create them if needed.
    521  if (aStops[0].mPosition > 0) {
    522    aStops.InsertElementAt(0, ColorStop(0, false, aStops[0].mColor));
    523  }
    524  if (aStops.LastElement().mPosition < 1) {
    525    aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor));
    526  }
    527 }
    528 
    529 namespace mozilla {
    530 
    531 template <typename T>
    532 static StyleAbsoluteColor GetSpecifiedColor(
    533    const StyleGenericGradientItem<StyleColor, T>& aItem,
    534    const ComputedStyle& aStyle) {
    535  if (aItem.IsInterpolationHint()) {
    536    return StyleAbsoluteColor::TRANSPARENT_BLACK;
    537  }
    538  const StyleColor& c = aItem.IsSimpleColorStop()
    539                            ? aItem.AsSimpleColorStop()
    540                            : aItem.AsComplexColorStop().color;
    541 
    542  return c.ResolveColor(aStyle.StyleText()->mColor);
    543 }
    544 
    545 static Maybe<double> GetSpecifiedGradientPosition(
    546    const StyleGenericGradientItem<StyleColor, StyleLengthPercentage>& aItem,
    547    CSSCoord aLineLength) {
    548  if (aItem.IsSimpleColorStop()) {
    549    return Nothing();
    550  }
    551 
    552  const LengthPercentage& pos = aItem.IsComplexColorStop()
    553                                    ? aItem.AsComplexColorStop().position
    554                                    : aItem.AsInterpolationHint();
    555 
    556  if (pos.ConvertsToPercentage()) {
    557    return Some(pos.ToPercentage());
    558  }
    559 
    560  if (aLineLength < 1e-6) {
    561    return Some(0.0);
    562  }
    563  return Some(pos.ResolveToCSSPixels(aLineLength) / aLineLength);
    564 }
    565 
    566 // aLineLength argument is unused for conic-gradients.
    567 static Maybe<double> GetSpecifiedGradientPosition(
    568    const StyleGenericGradientItem<StyleColor, StyleAngleOrPercentage>& aItem,
    569    CSSCoord aLineLength) {
    570  if (aItem.IsSimpleColorStop()) {
    571    return Nothing();
    572  }
    573 
    574  const StyleAngleOrPercentage& pos = aItem.IsComplexColorStop()
    575                                          ? aItem.AsComplexColorStop().position
    576                                          : aItem.AsInterpolationHint();
    577 
    578  if (pos.IsPercentage()) {
    579    return Some(pos.AsPercentage()._0);
    580  }
    581 
    582  return Some(pos.AsAngle().ToRadians() / (2 * M_PI));
    583 }
    584 
    585 template <typename T>
    586 static nsTArray<ColorStop> ComputeColorStopsForItems(
    587    ComputedStyle* aComputedStyle,
    588    Span<const StyleGenericGradientItem<StyleColor, T>> aItems,
    589    CSSCoord aLineLength) {
    590  MOZ_ASSERT(!aItems.IsEmpty(),
    591             "The parser should reject gradients with no stops");
    592 
    593  nsTArray<ColorStop> stops(aItems.Length());
    594 
    595  // If there is a run of stops before stop i that did not have specified
    596  // positions, then this is the index of the first stop in that run.
    597  Maybe<size_t> firstUnsetPosition;
    598  for (size_t i = 0; i < aItems.Length(); ++i) {
    599    const auto& stop = aItems[i];
    600    double position;
    601 
    602    Maybe<double> specifiedPosition =
    603        GetSpecifiedGradientPosition(stop, aLineLength);
    604 
    605    if (specifiedPosition) {
    606      position = *specifiedPosition;
    607    } else if (i == 0) {
    608      // First stop defaults to position 0.0
    609      position = 0.0;
    610    } else if (i == aItems.Length() - 1) {
    611      // Last stop defaults to position 1.0
    612      position = 1.0;
    613    } else {
    614      // Other stops with no specified position get their position assigned
    615      // later by interpolation, see below.
    616      // Remember where the run of stops with no specified position starts,
    617      // if it starts here.
    618      if (firstUnsetPosition.isNothing()) {
    619        firstUnsetPosition.emplace(i);
    620      }
    621      MOZ_ASSERT(!stop.IsInterpolationHint(),
    622                 "Interpolation hints always specify position");
    623      auto color = GetSpecifiedColor(stop, *aComputedStyle);
    624      stops.AppendElement(ColorStop(0, false, color));
    625      continue;
    626    }
    627 
    628    if (i > 0) {
    629      // Prevent decreasing stop positions by advancing this position
    630      // to the previous stop position, if necessary
    631      double previousPosition = firstUnsetPosition
    632                                    ? stops[*firstUnsetPosition - 1].mPosition
    633                                    : stops[i - 1].mPosition;
    634      position = std::max(position, previousPosition);
    635    }
    636    auto stopColor = GetSpecifiedColor(stop, *aComputedStyle);
    637    stops.AppendElement(
    638        ColorStop(position, stop.IsInterpolationHint(), stopColor));
    639    if (firstUnsetPosition) {
    640      // Interpolate positions for all stops that didn't have a specified
    641      // position
    642      double p = stops[*firstUnsetPosition - 1].mPosition;
    643      double d = (stops[i].mPosition - p) / (i - *firstUnsetPosition + 1);
    644      for (size_t j = *firstUnsetPosition; j < i; ++j) {
    645        p += d;
    646        stops[j].mPosition = p;
    647      }
    648      firstUnsetPosition.reset();
    649    }
    650  }
    651 
    652  return stops;
    653 }
    654 
    655 static nsTArray<ColorStop> ComputeColorStops(ComputedStyle* aComputedStyle,
    656                                             const StyleGradient& aGradient,
    657                                             CSSCoord aLineLength) {
    658  if (aGradient.IsLinear()) {
    659    return ComputeColorStopsForItems(
    660        aComputedStyle, aGradient.AsLinear().items.AsSpan(), aLineLength);
    661  }
    662  if (aGradient.IsRadial()) {
    663    return ComputeColorStopsForItems(
    664        aComputedStyle, aGradient.AsRadial().items.AsSpan(), aLineLength);
    665  }
    666  return ComputeColorStopsForItems(
    667      aComputedStyle, aGradient.AsConic().items.AsSpan(), aLineLength);
    668 }
    669 
    670 nsCSSGradientRenderer nsCSSGradientRenderer::Create(
    671    nsPresContext* aPresContext, ComputedStyle* aComputedStyle,
    672    const StyleGradient& aGradient, const nsSize& aIntrinsicSize) {
    673  auto srcSize = CSSSize::FromAppUnits(aIntrinsicSize);
    674 
    675  // Compute "gradient line" start and end relative to the intrinsic size of
    676  // the gradient.
    677  CSSPoint lineStart, lineEnd, center;  // center is for conic gradients only
    678  CSSCoord radiusX = 0, radiusY = 0;    // for radial gradients only
    679  float angle = 0.0;                    // for conic gradients only
    680  if (aGradient.IsLinear()) {
    681    std::tie(lineStart, lineEnd) =
    682        ComputeLinearGradientLine(aPresContext, aGradient, srcSize);
    683  } else if (aGradient.IsRadial()) {
    684    std::tie(lineStart, lineEnd, radiusX, radiusY) =
    685        ComputeRadialGradientLine(aGradient, srcSize);
    686  } else {
    687    MOZ_ASSERT(aGradient.IsConic());
    688    std::tie(center, angle) =
    689        ComputeConicGradientProperties(aGradient, srcSize);
    690  }
    691  // Avoid sending Infs or Nans to downwind draw targets.
    692  if (!lineStart.IsFinite() || !lineEnd.IsFinite()) {
    693    lineStart = lineEnd = CSSPoint(0, 0);
    694  }
    695  if (!center.IsFinite()) {
    696    center = CSSPoint(0, 0);
    697  }
    698  CSSCoord lineLength =
    699      NS_hypot(lineEnd.x - lineStart.x, lineEnd.y - lineStart.y);
    700 
    701  // Build color stop array and compute stop positions
    702  nsTArray<ColorStop> stops =
    703      ComputeColorStops(aComputedStyle, aGradient, lineLength);
    704 
    705  ResolveMidpoints(stops);
    706 
    707  nsCSSGradientRenderer renderer;
    708  renderer.mPresContext = aPresContext;
    709  renderer.mGradient = &aGradient;
    710  renderer.mStops = std::move(stops);
    711  renderer.mLineStart = {
    712      aPresContext->CSSPixelsToDevPixels(lineStart.x),
    713      aPresContext->CSSPixelsToDevPixels(lineStart.y),
    714  };
    715  renderer.mLineEnd = {
    716      aPresContext->CSSPixelsToDevPixels(lineEnd.x),
    717      aPresContext->CSSPixelsToDevPixels(lineEnd.y),
    718  };
    719  renderer.mRadiusX = aPresContext->CSSPixelsToDevPixels(radiusX);
    720  renderer.mRadiusY = aPresContext->CSSPixelsToDevPixels(radiusY);
    721  renderer.mCenter = {
    722      aPresContext->CSSPixelsToDevPixels(center.x),
    723      aPresContext->CSSPixelsToDevPixels(center.y),
    724  };
    725  renderer.mAngle = angle;
    726  return renderer;
    727 }
    728 
    729 void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest,
    730                                  const nsRect& aFillArea,
    731                                  const nsSize& aRepeatSize,
    732                                  const CSSIntRect& aSrc,
    733                                  const nsRect& aDirtyRect, float aOpacity) {
    734  AUTO_PROFILER_LABEL("nsCSSGradientRenderer::Paint", GRAPHICS);
    735 
    736  if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
    737    return;
    738  }
    739 
    740  nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
    741 
    742  gfxFloat lineLength =
    743      NS_hypot(mLineEnd.x - mLineStart.x, mLineEnd.y - mLineStart.y);
    744  bool cellContainsFill = aDest.Contains(aFillArea);
    745 
    746  // If a non-repeating linear gradient is axis-aligned and there are no gaps
    747  // between tiles, we can optimise away most of the work by converting to a
    748  // repeating linear gradient and filling the whole destination rect at once.
    749  bool forceRepeatToCoverTiles =
    750      mGradient->IsLinear() &&
    751      (mLineStart.x == mLineEnd.x) != (mLineStart.y == mLineEnd.y) &&
    752      aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height &&
    753      !(mGradient->Repeating()) && !aSrc.IsEmpty() && !cellContainsFill;
    754 
    755  gfxMatrix matrix;
    756  if (forceRepeatToCoverTiles) {
    757    // Length of the source rectangle along the gradient axis.
    758    double rectLen;
    759    // The position of the start of the rectangle along the gradient.
    760    double offset;
    761 
    762    // The gradient line is "backwards". Flip the line upside down to make
    763    // things easier, and then rotate the matrix to turn everything back the
    764    // right way up.
    765    if (mLineStart.x > mLineEnd.x || mLineStart.y > mLineEnd.y) {
    766      std::swap(mLineStart, mLineEnd);
    767      matrix.PreScale(-1, -1);
    768    }
    769 
    770    // Fit the gradient line exactly into the source rect.
    771    // aSrc is relative to aIntrinsincSize.
    772    // srcRectDev will be relative to srcSize, so in the same coordinate space
    773    // as lineStart / lineEnd.
    774    gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect(
    775        CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel);
    776    if (mLineStart.x != mLineEnd.x) {
    777      rectLen = srcRectDev.width;
    778      offset = (srcRectDev.x - mLineStart.x) / lineLength;
    779      mLineStart.x = srcRectDev.x;
    780      mLineEnd.x = srcRectDev.XMost();
    781    } else {
    782      rectLen = srcRectDev.height;
    783      offset = (srcRectDev.y - mLineStart.y) / lineLength;
    784      mLineStart.y = srcRectDev.y;
    785      mLineEnd.y = srcRectDev.YMost();
    786    }
    787 
    788    // Adjust gradient stop positions for the new gradient line.
    789    double scale = lineLength / rectLen;
    790    for (size_t i = 0; i < mStops.Length(); i++) {
    791      mStops[i].mPosition = (mStops[i].mPosition - offset) * fabs(scale);
    792    }
    793 
    794    // Clamp or extrapolate gradient stops to exactly [0, 1].
    795    ClampColorStops(mStops);
    796 
    797    lineLength = rectLen;
    798  }
    799 
    800  // Eliminate negative-position stops if the gradient is radial.
    801  double firstStop = mStops[0].mPosition;
    802  if (mGradient->IsRadial() && firstStop < 0.0) {
    803    if (mGradient->AsRadial().flags & StyleGradientFlags::REPEATING) {
    804      // Choose an instance of the repeated pattern that gives us all positive
    805      // stop-offsets.
    806      double lastStop = mStops[mStops.Length() - 1].mPosition;
    807      double stopDelta = lastStop - firstStop;
    808      // If all the stops are in approximately the same place then logic below
    809      // will kick in that makes us draw just the last stop color, so don't
    810      // try to do anything in that case. We certainly need to avoid
    811      // dividing by zero.
    812      if (stopDelta >= 1e-6) {
    813        double instanceCount = ceil(-firstStop / stopDelta);
    814        // Advance stops by instanceCount multiples of the period of the
    815        // repeating gradient.
    816        double offset = instanceCount * stopDelta;
    817        for (uint32_t i = 0; i < mStops.Length(); i++) {
    818          mStops[i].mPosition += offset;
    819        }
    820      }
    821    } else {
    822      // Move negative-position stops to position 0.0. We may also need
    823      // to set the color of the stop to the color the gradient should have
    824      // at the center of the ellipse.
    825      for (uint32_t i = 0; i < mStops.Length(); i++) {
    826        double pos = mStops[i].mPosition;
    827        if (pos < 0.0) {
    828          mStops[i].mPosition = 0.0;
    829          // If this is the last stop, we don't need to adjust the color,
    830          // it will fill the entire area.
    831          if (i < mStops.Length() - 1) {
    832            double nextPos = mStops[i + 1].mPosition;
    833            // If nextPos is approximately equal to pos, then we don't
    834            // need to adjust the color of this stop because it's
    835            // not going to be displayed.
    836            // If nextPos is negative, we don't need to adjust the color of
    837            // this stop since it's not going to be displayed because
    838            // nextPos will also be moved to 0.0.
    839            if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
    840              // Compute how far the new position 0.0 is along the interval
    841              // between pos and nextPos.
    842              // XXX Color interpolation (in cairo, too) should use the
    843              // CSS 'color-interpolation' property!
    844              float frac = float((0.0 - pos) / (nextPos - pos));
    845              mStops[i].mColor =
    846                  Interpolate(mStops[i].mColor, mStops[i + 1].mColor, frac);
    847            }
    848          }
    849        }
    850      }
    851    }
    852    firstStop = mStops[0].mPosition;
    853    MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets");
    854  }
    855 
    856  if (mGradient->IsRadial() &&
    857      !(mGradient->AsRadial().flags & StyleGradientFlags::REPEATING)) {
    858    // Direct2D can only handle a particular class of radial gradients because
    859    // of the way the it specifies gradients. Setting firstStop to 0, when we
    860    // can, will help us stay on the fast path. Currently we don't do this
    861    // for repeating gradients but we could by adjusting the stop collection
    862    // to start at 0
    863    firstStop = 0;
    864  }
    865 
    866  double lastStop = mStops[mStops.Length() - 1].mPosition;
    867  // Cairo gradients must have stop positions in the range [0, 1]. So,
    868  // stop positions will be normalized below by subtracting firstStop and then
    869  // multiplying by stopScale.
    870  double stopScale;
    871  double stopOrigin = firstStop;
    872  double stopEnd = lastStop;
    873  double stopDelta = lastStop - firstStop;
    874  bool zeroRadius =
    875      mGradient->IsRadial() && (mRadiusX < 1e-6 || mRadiusY < 1e-6);
    876  if (stopDelta < 1e-6 || (!mGradient->IsConic() && lineLength < 1e-6) ||
    877      zeroRadius) {
    878    // Stops are all at the same place. Map all stops to 0.0.
    879    // For repeating radial gradients, or for any radial gradients with
    880    // a zero radius, we need to fill with the last stop color, so just set
    881    // both radii to 0.
    882    if (mGradient->Repeating() || zeroRadius) {
    883      mRadiusX = mRadiusY = 0.0;
    884    }
    885    stopDelta = 0.0;
    886  }
    887 
    888  // Don't normalize non-repeating or degenerate gradients below 0..1
    889  // This keeps the gradient line as large as the box and doesn't
    890  // lets us avoiding having to get padding correct for stops
    891  // at 0 and 1
    892  if (!mGradient->Repeating() || stopDelta == 0.0) {
    893    stopOrigin = std::min(stopOrigin, 0.0);
    894    stopEnd = std::max(stopEnd, 1.0);
    895  }
    896  stopScale = 1.0 / (stopEnd - stopOrigin);
    897 
    898  // Create the gradient pattern.
    899  RefPtr<gfxPattern> gradientPattern;
    900  gfxPoint gradientStart;
    901  gfxPoint gradientEnd;
    902  if (mGradient->IsLinear()) {
    903    // Compute the actual gradient line ends we need to pass to cairo after
    904    // stops have been normalized.
    905    gradientStart = mLineStart + (mLineEnd - mLineStart) * stopOrigin;
    906    gradientEnd = mLineStart + (mLineEnd - mLineStart) * stopEnd;
    907 
    908    if (stopDelta == 0.0) {
    909      // Stops are all at the same place. For repeating gradients, this will
    910      // just paint the last stop color. We don't need to do anything.
    911      // For non-repeating gradients, this should render as two colors, one
    912      // on each "side" of the gradient line segment, which is a point. All
    913      // our stops will be at 0.0; we just need to set the direction vector
    914      // correctly.
    915      gradientEnd = gradientStart + (mLineEnd - mLineStart);
    916    }
    917 
    918    gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
    919                                     gradientEnd.x, gradientEnd.y);
    920  } else if (mGradient->IsRadial()) {
    921    NS_ASSERTION(firstStop >= 0.0,
    922                 "Negative stops not allowed for radial gradients");
    923 
    924    // To form an ellipse, we'll stretch a circle vertically, if necessary.
    925    // So our radii are based on radiusX.
    926    double innerRadius = mRadiusX * stopOrigin;
    927    double outerRadius = mRadiusX * stopEnd;
    928    if (stopDelta == 0.0) {
    929      // Stops are all at the same place.  See above (except we now have
    930      // the inside vs. outside of an ellipse).
    931      outerRadius = innerRadius + 1;
    932    }
    933    gradientPattern = new gfxPattern(mLineStart.x, mLineStart.y, innerRadius,
    934                                     mLineStart.x, mLineStart.y, outerRadius);
    935    if (mRadiusX != mRadiusY) {
    936      // Stretch the circles into ellipses vertically by setting a transform
    937      // in the pattern.
    938      // Recall that this is the transform from user space to pattern space.
    939      // So to stretch the ellipse by factor of P vertically, we scale
    940      // user coordinates by 1/P.
    941      matrix.PreTranslate(mLineStart);
    942      matrix.PreScale(1.0, mRadiusX / mRadiusY);
    943      matrix.PreTranslate(-mLineStart);
    944    }
    945  } else {
    946    gradientPattern =
    947        new gfxPattern(mCenter.x, mCenter.y, mAngle, stopOrigin, stopEnd);
    948  }
    949  // Use a pattern transform to take account of source and dest rects
    950  matrix.PreTranslate(gfxPoint(mPresContext->CSSPixelsToDevPixels(aSrc.x),
    951                               mPresContext->CSSPixelsToDevPixels(aSrc.y)));
    952  matrix.PreScale(
    953      gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.width)) / aDest.width,
    954      gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.height)) / aDest.height);
    955  gradientPattern->SetMatrix(matrix);
    956 
    957  if (stopDelta == 0.0) {
    958    // Non-repeating gradient with all stops in same place -> just add
    959    // first stop and last stop, both at position 0.
    960    // Repeating gradient with all stops in the same place, or radial
    961    // gradient with radius of 0 -> just paint the last stop color.
    962    // We use firstStop offset to keep |stops| with same units (will later
    963    // normalize to 0).
    964    auto firstColor(mStops[0].mColor);
    965    auto lastColor(mStops.LastElement().mColor);
    966    mStops.Clear();
    967 
    968    if (!mGradient->Repeating() && !zeroRadius) {
    969      mStops.AppendElement(ColorStop(firstStop, false, firstColor));
    970    }
    971    mStops.AppendElement(ColorStop(firstStop, false, lastColor));
    972  }
    973 
    974  ResolvePremultipliedAlpha(mStops);
    975 
    976  bool isRepeat = mGradient->Repeating() || forceRepeatToCoverTiles;
    977 
    978  // Now set normalized color stops in pattern.
    979  // Offscreen gradient surface cache (not a tile):
    980  // On some backends (e.g. D2D), the GradientStops object holds an offscreen
    981  // surface which is a lookup table used to evaluate the gradient. This surface
    982  // can use much memory (ram and/or GPU ram) and can be expensive to create. So
    983  // we cache it. The cache key correlates 1:1 with the arguments for
    984  // CreateGradientStops (also the implied backend type) Note that GradientStop
    985  // is a simple struct with a stop value (while GradientStops has the surface).
    986  nsTArray<gfx::GradientStop> rawStops(mStops.Length());
    987  StyleColorInterpolationMethod styleColorInterpolationMethod =
    988      mGradient->ColorInterpolationMethod();
    989  if (styleColorInterpolationMethod.space != StyleColorSpace::Srgb ||
    990      gfxPlatform::GetCMSMode() == CMSMode::All) {
    991    class MOZ_STACK_CLASS GradientStopInterpolator final
    992        : public ColorStopInterpolator<GradientStopInterpolator> {
    993     public:
    994      GradientStopInterpolator(
    995          const nsTArray<ColorStop>& aStops,
    996          const StyleColorInterpolationMethod& aStyleColorInterpolationMethod,
    997          bool aExtend, nsTArray<gfx::GradientStop>& aResult)
    998          : ColorStopInterpolator(aStops, aStyleColorInterpolationMethod,
    999                                  aExtend),
   1000            mStops(aResult) {}
   1001      void CreateStop(float aPosition, gfx::DeviceColor aColor) {
   1002        mStops.AppendElement(gfx::GradientStop{aPosition, aColor});
   1003      }
   1004 
   1005     private:
   1006      nsTArray<gfx::GradientStop>& mStops;
   1007    };
   1008 
   1009    bool extend = !isRepeat && styleColorInterpolationMethod.hue ==
   1010                                   StyleHueInterpolationMethod::Longer;
   1011    GradientStopInterpolator interpolator(mStops, styleColorInterpolationMethod,
   1012                                          extend, rawStops);
   1013    interpolator.CreateStops();
   1014  } else {
   1015    rawStops.SetLength(mStops.Length());
   1016    for (uint32_t i = 0; i < mStops.Length(); i++) {
   1017      rawStops[i].color = ToDeviceColor(mStops[i].mColor);
   1018      rawStops[i].color.a *= aOpacity;
   1019      rawStops[i].offset = stopScale * (mStops[i].mPosition - stopOrigin);
   1020    }
   1021  }
   1022  RefPtr<mozilla::gfx::GradientStops> gs =
   1023      gfxGradientCache::GetOrCreateGradientStops(
   1024          aContext.GetDrawTarget(), rawStops,
   1025          isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
   1026  gradientPattern->SetColorStops(gs);
   1027 
   1028  // Paint gradient tiles. This isn't terribly efficient, but doing it this
   1029  // way is simple and sure to get pixel-snapping right. We could speed things
   1030  // up by drawing tiles into temporary surfaces and copying those to the
   1031  // destination, but after pixel-snapping tiles may not all be the same size.
   1032  nsRect dirty;
   1033  if (!dirty.IntersectRect(aDirtyRect, aFillArea)) {
   1034    return;
   1035  }
   1036 
   1037  gfxRect areaToFill =
   1038      nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
   1039  gfxRect dirtyAreaToFill =
   1040      nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
   1041  dirtyAreaToFill.RoundOut();
   1042 
   1043  Matrix ctm = aContext.CurrentMatrix();
   1044  bool isCTMPreservingAxisAlignedRectangles =
   1045      ctm.PreservesAxisAlignedRectangles();
   1046 
   1047  // xStart/yStart are the top-left corner of the top-left tile.
   1048  nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width);
   1049  nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height);
   1050  nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
   1051  nscoord yEnd =
   1052      forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
   1053 
   1054  if (TryPaintTilesWithExtendMode(aContext, gradientPattern, xStart, yStart,
   1055                                  dirtyAreaToFill, aDest, aRepeatSize,
   1056                                  forceRepeatToCoverTiles)) {
   1057    return;
   1058  }
   1059 
   1060  // x and y are the top-left corner of the tile to draw
   1061  for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) {
   1062    for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) {
   1063      // The coordinates of the tile
   1064      gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
   1065          nsRect(x, y, aDest.width, aDest.height), appUnitsPerDevPixel);
   1066      // The actual area to fill with this tile is the intersection of this
   1067      // tile with the overall area we're supposed to be filling
   1068      gfxRect fillRect =
   1069          forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
   1070      // Try snapping the fill rect. Snap its top-left and bottom-right
   1071      // independently to preserve the orientation.
   1072      gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
   1073      gfxPoint snappedFillRectTopRight = fillRect.TopRight();
   1074      gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
   1075      // Snap three points instead of just two to ensure we choose the
   1076      // correct orientation if there's a reflection.
   1077      if (isCTMPreservingAxisAlignedRectangles &&
   1078          aContext.UserToDevicePixelSnapped(snappedFillRectTopLeft, true) &&
   1079          aContext.UserToDevicePixelSnapped(snappedFillRectBottomRight, true) &&
   1080          aContext.UserToDevicePixelSnapped(snappedFillRectTopRight, true)) {
   1081        if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x ||
   1082            snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) {
   1083          // Nothing to draw; avoid scaling by zero and other weirdness that
   1084          // could put the context in an error state.
   1085          continue;
   1086        }
   1087        // Set the context's transform to the transform that maps fillRect to
   1088        // snappedFillRect. The part of the gradient that was going to
   1089        // exactly fill fillRect will fill snappedFillRect instead.
   1090        gfxMatrix transform = gfxUtils::TransformRectToRect(
   1091            fillRect, snappedFillRectTopLeft, snappedFillRectTopRight,
   1092            snappedFillRectBottomRight);
   1093        aContext.SetMatrixDouble(transform);
   1094      }
   1095      aContext.NewPath();
   1096      aContext.Rectangle(fillRect);
   1097 
   1098      gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
   1099      gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
   1100      auto edgeColor = StyleAbsoluteColor::TRANSPARENT_BLACK;
   1101      if (mGradient->IsLinear() && !isRepeat &&
   1102          RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops,
   1103                                         gradientStart, gradientEnd,
   1104                                         &edgeColor)) {
   1105        edgeColor.alpha *= aOpacity;
   1106        aContext.SetColor(ToSRGBColor(edgeColor));
   1107      } else {
   1108        aContext.SetMatrixDouble(
   1109            aContext.CurrentMatrixDouble().Copy().PreTranslate(
   1110                tileRect.TopLeft()));
   1111        aContext.SetPattern(gradientPattern);
   1112      }
   1113      aContext.Fill();
   1114      aContext.SetMatrix(ctm);
   1115    }
   1116  }
   1117 }
   1118 
   1119 bool nsCSSGradientRenderer::TryPaintTilesWithExtendMode(
   1120    gfxContext& aContext, gfxPattern* aGradientPattern, nscoord aXStart,
   1121    nscoord aYStart, const gfxRect& aDirtyAreaToFill, const nsRect& aDest,
   1122    const nsSize& aRepeatSize, bool aForceRepeatToCoverTiles) {
   1123  // If we have forced a non-repeating gradient to repeat to cover tiles,
   1124  // then it will be faster to just paint it once using that optimization
   1125  if (aForceRepeatToCoverTiles) {
   1126    return false;
   1127  }
   1128 
   1129  nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
   1130 
   1131  // We can only use this fast path if we don't have to worry about pixel
   1132  // snapping, and there is no spacing between tiles. We could handle spacing
   1133  // by increasing the size of tileSurface and leaving it transparent, but I'm
   1134  // not sure it's worth it
   1135  bool canUseExtendModeForTiling = (aXStart % appUnitsPerDevPixel == 0) &&
   1136                                   (aYStart % appUnitsPerDevPixel == 0) &&
   1137                                   (aDest.width % appUnitsPerDevPixel == 0) &&
   1138                                   (aDest.height % appUnitsPerDevPixel == 0) &&
   1139                                   (aRepeatSize.width == aDest.width) &&
   1140                                   (aRepeatSize.height == aDest.height);
   1141 
   1142  if (!canUseExtendModeForTiling) {
   1143    return false;
   1144  }
   1145 
   1146  IntSize tileSize{
   1147      NSAppUnitsToIntPixels(aDest.width, appUnitsPerDevPixel),
   1148      NSAppUnitsToIntPixels(aDest.height, appUnitsPerDevPixel),
   1149  };
   1150 
   1151  // Check whether this is a reasonable surface size and doesn't overflow
   1152  // before doing calculations with the tile size
   1153  if (!Factory::ReasonableSurfaceSize(tileSize)) {
   1154    return false;
   1155  }
   1156 
   1157  // We only want to do this when there are enough tiles to justify the
   1158  // overhead of painting to an offscreen surface. The heuristic here
   1159  // is when we will be painting at least 16 tiles or more, this is kind
   1160  // of arbitrary
   1161  bool shouldUseExtendModeForTiling =
   1162      aDirtyAreaToFill.Area() > (tileSize.width * tileSize.height) * 16.0;
   1163 
   1164  if (!shouldUseExtendModeForTiling) {
   1165    return false;
   1166  }
   1167 
   1168  // Draw the gradient pattern into a surface for our single tile
   1169  RefPtr<gfx::SourceSurface> tileSurface;
   1170  {
   1171    RefPtr<gfx::DrawTarget> tileTarget =
   1172        aContext.GetDrawTarget()->CreateSimilarDrawTarget(
   1173            tileSize, gfx::SurfaceFormat::B8G8R8A8);
   1174    if (!tileTarget || !tileTarget->IsValid()) {
   1175      return false;
   1176    }
   1177 
   1178    {
   1179      gfxContext tileContext(tileTarget);
   1180 
   1181      tileContext.SetPattern(aGradientPattern);
   1182      tileContext.Paint();
   1183    }
   1184 
   1185    tileSurface = tileTarget->Snapshot();
   1186    tileTarget = nullptr;
   1187  }
   1188 
   1189  // Draw the gradient using tileSurface as a repeating pattern masked by
   1190  // the dirtyRect
   1191  Matrix tileTransform = Matrix::Translation(
   1192      NSAppUnitsToFloatPixels(aXStart, appUnitsPerDevPixel),
   1193      NSAppUnitsToFloatPixels(aYStart, appUnitsPerDevPixel));
   1194 
   1195  aContext.NewPath();
   1196  aContext.Rectangle(aDirtyAreaToFill);
   1197  aContext.Fill(SurfacePattern(tileSurface, ExtendMode::REPEAT, tileTransform));
   1198 
   1199  return true;
   1200 }
   1201 
   1202 class MOZ_STACK_CLASS WrColorStopInterpolator
   1203    : public ColorStopInterpolator<WrColorStopInterpolator> {
   1204 public:
   1205  WrColorStopInterpolator(
   1206      const nsTArray<ColorStop>& aStops,
   1207      const StyleColorInterpolationMethod& aStyleColorInterpolationMethod,
   1208      float aOpacity, nsTArray<wr::GradientStop>& aResult, bool aExtend)
   1209      : ColorStopInterpolator(aStops, aStyleColorInterpolationMethod, aExtend),
   1210        mResult(aResult),
   1211        mOpacity(aOpacity),
   1212        mOutputStop(0) {}
   1213 
   1214  void CreateStops() {
   1215    mResult.SetLengthAndRetainStorage(0);
   1216    // We always emit at least two stops (start and end) for each input stop,
   1217    // which avoids ambiguity with incomplete oklch/lch/hsv/hsb color stops for
   1218    // the last stop pair, where the last color stop can't be interpreted on its
   1219    // own because it actually depends on the previous stop.
   1220    mResult.SetLength(mStops.Length() * 2 + kFullRangeExtraStops);
   1221    mOutputStop = 0;
   1222    ColorStopInterpolator::CreateStops();
   1223    mResult.SetLength(mOutputStop);
   1224  }
   1225 
   1226  void CreateStop(float aPosition, DeviceColor aColor) {
   1227    if (mOutputStop < mResult.Capacity()) {
   1228      mResult[mOutputStop].color = wr::ToColorF(aColor);
   1229      mResult[mOutputStop].color.a *= mOpacity;
   1230      mResult[mOutputStop].offset = aPosition;
   1231      mOutputStop++;
   1232    }
   1233  }
   1234 
   1235 private:
   1236  nsTArray<wr::GradientStop>& mResult;
   1237  float mOpacity;
   1238  uint32_t mOutputStop;
   1239 };
   1240 
   1241 void nsCSSGradientRenderer::BuildWebRenderParameters(
   1242    float aOpacity, wr::ExtendMode& aMode, nsTArray<wr::GradientStop>& aStops,
   1243    LayoutDevicePoint& aLineStart, LayoutDevicePoint& aLineEnd,
   1244    LayoutDeviceSize& aGradientRadius, LayoutDevicePoint& aGradientCenter,
   1245    float& aGradientAngle) {
   1246  aMode =
   1247      mGradient->Repeating() ? wr::ExtendMode::Repeat : wr::ExtendMode::Clamp;
   1248 
   1249  // If the interpolation space is not sRGB, or if color management is active,
   1250  // we need to add additional stops so that the sRGB interpolation in WebRender
   1251  // still closely approximates the correct curves.  We prefer avoiding this if
   1252  // the gradient is simple because WebRender has fast rendering of linear
   1253  // gradients with 2 stops (which represent >99% of all gradients on the web).
   1254  //
   1255  // WebRender doesn't have easy access to StyleAbsoluteColor and CMS display
   1256  // color correction, so we just expand the gradient stop table significantly
   1257  // so that gamma and hue interpolation errors become imperceptible.
   1258  //
   1259  // This always turns into 128 pairs of stops inside WebRender as an
   1260  // implementation detail, so the number of stops we generate here should have
   1261  // very little impact on performance as the texture upload is always the same,
   1262  // except for the special linear gradient 2-stop case, and it is gpucache so
   1263  // if it does not change it is not re-uploaded.
   1264  //
   1265  // Color management bugs that this addresses:
   1266  // * https://bugzilla.mozilla.org/show_bug.cgi?id=939387
   1267  // * https://bugzilla.mozilla.org/show_bug.cgi?id=1248178
   1268  StyleColorInterpolationMethod styleColorInterpolationMethod =
   1269      mGradient->ColorInterpolationMethod();
   1270  // For colorspaces supported by WebRender (Srgb, Hsl, Hwb) we technically do
   1271  // not need to add extra stops, but the only one of those colorspaces that
   1272  // appears frequently is Srgb, and Srgb still needs extra stops if CMS is
   1273  // enabled.  Hsl/Hwb need extra stops if StyleHueInterpolationMethod is not
   1274  // Shorter, or if CMS is enabled.
   1275  //
   1276  // It's probably best to keep this logic as simple as possible, see
   1277  // https://bugzilla.mozilla.org/show_bug.cgi?id=1885716 for an example of
   1278  // what can happen if we try to be clever here.
   1279  if (styleColorInterpolationMethod.space != StyleColorSpace::Srgb ||
   1280      gfxPlatform::GetCMSMode() == CMSMode::All) {
   1281    // For the specific case of longer hue interpolation on a CSS non-repeating
   1282    // gradient, we have to pretend there is another stop at position=1.0 that
   1283    // duplicates the last stop, this is probably only used for things like a
   1284    // color wheel.  No such problem for SVG as it doesn't have that complexity.
   1285    bool extend = aMode == wr::ExtendMode::Clamp &&
   1286                  styleColorInterpolationMethod.hue ==
   1287                      StyleHueInterpolationMethod::Longer;
   1288    WrColorStopInterpolator interpolator(mStops, styleColorInterpolationMethod,
   1289                                         aOpacity, aStops, extend);
   1290    interpolator.CreateStops();
   1291  } else {
   1292    aStops.SetLength(mStops.Length());
   1293    for (uint32_t i = 0; i < mStops.Length(); i++) {
   1294      aStops[i].color = wr::ToColorF(ToDeviceColor(mStops[i].mColor));
   1295      aStops[i].color.a *= aOpacity;
   1296      aStops[i].offset = (float)mStops[i].mPosition;
   1297    }
   1298  }
   1299 
   1300  aLineStart = LayoutDevicePoint(mLineStart.x, mLineStart.y);
   1301  aLineEnd = LayoutDevicePoint(mLineEnd.x, mLineEnd.y);
   1302  aGradientRadius = LayoutDeviceSize(mRadiusX, mRadiusY);
   1303  aGradientCenter = LayoutDevicePoint(mCenter.x, mCenter.y);
   1304  aGradientAngle = mAngle;
   1305 }
   1306 
   1307 void nsCSSGradientRenderer::BuildWebRenderDisplayItems(
   1308    wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc,
   1309    const nsRect& aDest, const nsRect& aFillArea, const nsSize& aRepeatSize,
   1310    const CSSIntRect& aSrc, bool aIsBackfaceVisible, float aOpacity) {
   1311  if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
   1312    return;
   1313  }
   1314 
   1315  wr::ExtendMode extendMode;
   1316  nsTArray<wr::GradientStop> stops;
   1317  LayoutDevicePoint lineStart;
   1318  LayoutDevicePoint lineEnd;
   1319  LayoutDeviceSize gradientRadius;
   1320  LayoutDevicePoint gradientCenter;
   1321  float gradientAngle;
   1322  BuildWebRenderParameters(aOpacity, extendMode, stops, lineStart, lineEnd,
   1323                           gradientRadius, gradientCenter, gradientAngle);
   1324 
   1325  nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
   1326 
   1327  nsPoint firstTile =
   1328      nsPoint(FindTileStart(aFillArea.x, aDest.x, aRepeatSize.width),
   1329              FindTileStart(aFillArea.y, aDest.y, aRepeatSize.height));
   1330 
   1331  // Translate the parameters into device coordinates
   1332  LayoutDeviceRect clipBounds =
   1333      LayoutDevicePixel::FromAppUnits(aFillArea, appUnitsPerDevPixel);
   1334  LayoutDeviceRect firstTileBounds = LayoutDevicePixel::FromAppUnits(
   1335      nsRect(firstTile, aDest.Size()), appUnitsPerDevPixel);
   1336  LayoutDeviceSize tileRepeat =
   1337      LayoutDevicePixel::FromAppUnits(aRepeatSize, appUnitsPerDevPixel);
   1338 
   1339  // Calculate the bounds of the gradient display item, which starts at the
   1340  // first tile and extends to the end of clip bounds
   1341  LayoutDevicePoint tileToClip =
   1342      clipBounds.BottomRight() - firstTileBounds.TopLeft();
   1343  LayoutDeviceRect gradientBounds = LayoutDeviceRect(
   1344      firstTileBounds.TopLeft(), LayoutDeviceSize(tileToClip.x, tileToClip.y));
   1345 
   1346  // Calculate the tile spacing, which is the repeat size minus the tile size
   1347  LayoutDeviceSize tileSpacing = tileRepeat - firstTileBounds.Size();
   1348 
   1349  // srcTransform is used for scaling the gradient to match aSrc
   1350  LayoutDeviceRect srcTransform = LayoutDeviceRect(
   1351      nsPresContext::CSSPixelsToAppUnits(aSrc.x),
   1352      nsPresContext::CSSPixelsToAppUnits(aSrc.y),
   1353      aDest.width / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.width)),
   1354      aDest.height / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.height)));
   1355 
   1356  lineStart.x = (lineStart.x - srcTransform.x) * srcTransform.width;
   1357  lineStart.y = (lineStart.y - srcTransform.y) * srcTransform.height;
   1358 
   1359  gradientCenter.x = (gradientCenter.x - srcTransform.x) * srcTransform.width;
   1360  gradientCenter.y = (gradientCenter.y - srcTransform.y) * srcTransform.height;
   1361 
   1362  if (mGradient->IsLinear()) {
   1363    lineEnd.x = (lineEnd.x - srcTransform.x) * srcTransform.width;
   1364    lineEnd.y = (lineEnd.y - srcTransform.y) * srcTransform.height;
   1365 
   1366    aBuilder.PushLinearGradient(
   1367        mozilla::wr::ToLayoutRect(gradientBounds),
   1368        mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible,
   1369        mozilla::wr::ToLayoutPoint(lineStart),
   1370        mozilla::wr::ToLayoutPoint(lineEnd), stops, extendMode,
   1371        mozilla::wr::ToLayoutSize(firstTileBounds.Size()),
   1372        mozilla::wr::ToLayoutSize(tileSpacing));
   1373  } else if (mGradient->IsRadial()) {
   1374    gradientRadius.width *= srcTransform.width;
   1375    gradientRadius.height *= srcTransform.height;
   1376 
   1377    aBuilder.PushRadialGradient(
   1378        mozilla::wr::ToLayoutRect(gradientBounds),
   1379        mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible,
   1380        mozilla::wr::ToLayoutPoint(lineStart),
   1381        mozilla::wr::ToLayoutSize(gradientRadius), stops, extendMode,
   1382        mozilla::wr::ToLayoutSize(firstTileBounds.Size()),
   1383        mozilla::wr::ToLayoutSize(tileSpacing));
   1384  } else {
   1385    MOZ_ASSERT(mGradient->IsConic());
   1386    aBuilder.PushConicGradient(
   1387        mozilla::wr::ToLayoutRect(gradientBounds),
   1388        mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible,
   1389        mozilla::wr::ToLayoutPoint(gradientCenter), gradientAngle, stops,
   1390        extendMode, mozilla::wr::ToLayoutSize(firstTileBounds.Size()),
   1391        mozilla::wr::ToLayoutSize(tileSpacing));
   1392  }
   1393 }
   1394 
   1395 }  // namespace mozilla