tor-browser

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

nsCSSRenderingBorders.cpp (137085B)


      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 "nsCSSRenderingBorders.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "BorderConsts.h"
     12 #include "DashedCornerFinder.h"
     13 #include "DottedCornerFinder.h"
     14 #include "ImageRegion.h"
     15 #include "gfx2DGlue.h"
     16 #include "gfxGradientCache.h"
     17 #include "gfxUtils.h"
     18 #include "mozilla/ProfilerLabels.h"
     19 #include "mozilla/Range.h"
     20 #include "mozilla/gfx/2D.h"
     21 #include "mozilla/gfx/Helpers.h"
     22 #include "mozilla/gfx/PathHelpers.h"
     23 #include "mozilla/image/WebRenderImageProvider.h"
     24 #include "mozilla/layers/RenderRootStateManager.h"
     25 #include "mozilla/layers/StackingContextHelper.h"
     26 #include "mozilla/layers/WebRenderLayerManager.h"
     27 #include "nsCSSColorUtils.h"
     28 #include "nsCSSRendering.h"
     29 #include "nsCSSRenderingGradients.h"
     30 #include "nsClassHashtable.h"
     31 #include "nsContentUtils.h"
     32 #include "nsDisplayList.h"
     33 #include "nsExpirationTracker.h"
     34 #include "nsIScriptError.h"
     35 #include "nsLayoutUtils.h"
     36 #include "nsPresContext.h"
     37 #include "nsStyleConsts.h"
     38 #include "nsStyleStruct.h"
     39 
     40 using namespace mozilla;
     41 using namespace mozilla::gfx;
     42 using namespace mozilla::image;
     43 
     44 #define MAX_COMPOSITE_BORDER_WIDTH LayoutDeviceIntCoord(10000)
     45 
     46 /**
     47 * nsCSSRendering::PaintBorder
     48 * nsCSSRendering::PaintOutline
     49 *   -> DrawBorders
     50 *
     51 * DrawBorders
     52 *   -> Ability to use specialized approach?
     53 *      |- Draw using specialized function
     54 *   |- separate corners?
     55 *   |- dashed side mask
     56 *   |
     57 *   -> can border be drawn in 1 pass? (e.g., solid border same color all
     58 * around)
     59 *      |- DrawBorderSides with all 4 sides
     60 *   -> more than 1 pass?
     61 *      |- for each corner
     62 *         |- clip to DoCornerClipSubPath
     63 *         |- for each side adjacent to corner
     64 *            |- clip to GetSideClipSubPath
     65 *            |- DrawBorderSides with one side
     66 *      |- for each side
     67 *         |- GetSideClipWithoutCornersRect
     68 *         |- DrawDashedOrDottedSide || DrawBorderSides with one side
     69 */
     70 
     71 static void ComputeBorderCornerDimensions(const Margin& aBorderWidths,
     72                                          const RectCornerRadii& aRadii,
     73                                          RectCornerRadii* aDimsResult);
     74 
     75 // given a side index, get the previous and next side index
     76 #define NEXT_SIDE(_s) mozilla::Side(((_s) + 1) & 3)
     77 #define PREV_SIDE(_s) mozilla::Side(((_s) + 3) & 3)
     78 
     79 // given a corner index, get the previous and next corner index
     80 #define NEXT_CORNER(_s) Corner(((_s) + 1) & 3)
     81 #define PREV_CORNER(_s) Corner(((_s) + 3) & 3)
     82 
     83 // from the given base color and the background color, turn
     84 // color into a color for the given border pattern style
     85 static sRGBColor MakeBorderColor(nscolor aColor,
     86                                 BorderColorStyle aBorderColorStyle);
     87 
     88 // Given a line index (an index starting from the outside of the
     89 // border going inwards) and an array of line styles, calculate the
     90 // color that that stripe of the border should be rendered in.
     91 static sRGBColor ComputeColorForLine(uint32_t aLineIndex,
     92                                     const BorderColorStyle* aBorderColorStyle,
     93                                     uint32_t aBorderColorStyleCount,
     94                                     nscolor aBorderColor);
     95 
     96 static bool IsZeroSize(const Size& sz) {
     97  return sz.width == 0.0 || sz.height == 0.0;
     98 }
     99 
    100 /* static */
    101 bool nsCSSBorderRenderer::AllCornersZeroSize(const RectCornerRadii& corners) {
    102  return corners.IsEmpty();
    103 }
    104 
    105 static mozilla::Side GetHorizontalSide(Corner aCorner) {
    106  return (aCorner == C_TL || aCorner == C_TR) ? eSideTop : eSideBottom;
    107 }
    108 
    109 static mozilla::Side GetVerticalSide(Corner aCorner) {
    110  return (aCorner == C_TL || aCorner == C_BL) ? eSideLeft : eSideRight;
    111 }
    112 
    113 static Corner GetCWCorner(mozilla::Side aSide) {
    114  return Corner(NEXT_SIDE(aSide));
    115 }
    116 
    117 static Corner GetCCWCorner(mozilla::Side aSide) { return Corner(aSide); }
    118 
    119 static bool IsSingleSide(mozilla::SideBits aSides) {
    120  return aSides == SideBits::eTop || aSides == SideBits::eRight ||
    121         aSides == SideBits::eBottom || aSides == SideBits::eLeft;
    122 }
    123 
    124 static bool IsHorizontalSide(mozilla::Side aSide) {
    125  return aSide == eSideTop || aSide == eSideBottom;
    126 }
    127 
    128 typedef enum {
    129  // Normal solid square corner.  Will be rectangular, the size of the
    130  // adjacent sides.  If the corner has a border radius, the corner
    131  // will always be solid, since we don't do dotted/dashed etc.
    132  CORNER_NORMAL,
    133 
    134  // Paint the corner in whatever style is not dotted/dashed of the
    135  // adjacent corners.
    136  CORNER_SOLID,
    137 
    138  // Paint the corner as a dot, the size of the bigger of the adjacent
    139  // sides.
    140  CORNER_DOT
    141 } CornerStyle;
    142 
    143 nsCSSBorderRenderer::nsCSSBorderRenderer(
    144    nsPresContext* aPresContext, DrawTarget* aDrawTarget,
    145    const Rect& aDirtyRect, Rect& aOuterRect,
    146    const StyleBorderStyle* aBorderStyles, const Margin& aBorderWidths,
    147    RectCornerRadii& aBorderRadii, const nscolor* aBorderColors,
    148    bool aBackfaceIsVisible, const Maybe<Rect>& aClipRect)
    149    : mPresContext(aPresContext),
    150      mDrawTarget(aDrawTarget),
    151      mDirtyRect(aDirtyRect),
    152      mOuterRect(aOuterRect),
    153      mBorderWidths(aBorderWidths),
    154      mBorderRadii(aBorderRadii),
    155      mBackfaceIsVisible(aBackfaceIsVisible),
    156      mLocalClip(aClipRect) {
    157  PodCopy(mBorderStyles, aBorderStyles, 4);
    158  PodCopy(mBorderColors, aBorderColors, 4);
    159  mInnerRect = mOuterRect;
    160  mInnerRect.Deflate(Margin(
    161      mBorderStyles[0] != StyleBorderStyle::None ? mBorderWidths.top.value : 0,
    162      mBorderStyles[1] != StyleBorderStyle::None ? mBorderWidths.right.value
    163                                                 : 0,
    164      mBorderStyles[2] != StyleBorderStyle::None ? mBorderWidths.bottom.value
    165                                                 : 0,
    166      mBorderStyles[3] != StyleBorderStyle::None ? mBorderWidths.left.value
    167                                                 : 0));
    168 
    169  ComputeBorderCornerDimensions(mBorderWidths, mBorderRadii,
    170                                &mBorderCornerDimensions);
    171 
    172  mOneUnitBorder = mBorderWidths.IsAll(1.0f);
    173  mNoBorderRadius = AllCornersZeroSize(mBorderRadii);
    174  mAllBordersSameStyle = AreBorderSideFinalStylesSame(SideBits::eAll);
    175  mAllBordersSameWidth = mBorderWidths.IsAllEqual();
    176  mAvoidStroke = false;
    177 }
    178 
    179 /* static */
    180 void nsCSSBorderRenderer::ComputeInnerRadii(const RectCornerRadii& aRadii,
    181                                            const Margin& aBorderSizes,
    182                                            RectCornerRadii* aInnerRadiiRet) {
    183  *aInnerRadiiRet = aRadii;
    184  aInnerRadiiRet->AdjustInwards(aBorderSizes);
    185 }
    186 
    187 /* static */
    188 void nsCSSBorderRenderer::ComputeOuterRadii(const RectCornerRadii& aRadii,
    189                                            const Margin& aBorderSizes,
    190                                            RectCornerRadii* aOuterRadiiRet) {
    191  *aOuterRadiiRet = aRadii;
    192  aOuterRadiiRet->AdjustOutwards(aBorderSizes);
    193 }
    194 
    195 /*static*/ void ComputeBorderCornerDimensions(const Margin& aBorderWidths,
    196                                              const RectCornerRadii& aRadii,
    197                                              RectCornerRadii* aDimsRet) {
    198  Float leftWidth = aBorderWidths.left;
    199  Float topWidth = aBorderWidths.top;
    200  Float rightWidth = aBorderWidths.right;
    201  Float bottomWidth = aBorderWidths.bottom;
    202 
    203  if (nsCSSBorderRenderer::AllCornersZeroSize(aRadii)) {
    204    // These will always be in pixel units from CSS
    205    (*aDimsRet)[C_TL] = Size(leftWidth, topWidth);
    206    (*aDimsRet)[C_TR] = Size(rightWidth, topWidth);
    207    (*aDimsRet)[C_BR] = Size(rightWidth, bottomWidth);
    208    (*aDimsRet)[C_BL] = Size(leftWidth, bottomWidth);
    209  } else {
    210    // Always round up to whole pixels for the corners; it's safe to
    211    // make the corners bigger than necessary, and this way we ensure
    212    // that we avoid seams.
    213    (*aDimsRet)[C_TL] = Size(ceil(std::max(leftWidth, aRadii[C_TL].width)),
    214                             ceil(std::max(topWidth, aRadii[C_TL].height)));
    215    (*aDimsRet)[C_TR] = Size(ceil(std::max(rightWidth, aRadii[C_TR].width)),
    216                             ceil(std::max(topWidth, aRadii[C_TR].height)));
    217    (*aDimsRet)[C_BR] = Size(ceil(std::max(rightWidth, aRadii[C_BR].width)),
    218                             ceil(std::max(bottomWidth, aRadii[C_BR].height)));
    219    (*aDimsRet)[C_BL] = Size(ceil(std::max(leftWidth, aRadii[C_BL].width)),
    220                             ceil(std::max(bottomWidth, aRadii[C_BL].height)));
    221  }
    222 }
    223 
    224 bool nsCSSBorderRenderer::AreBorderSideFinalStylesSame(
    225    mozilla::SideBits aSides) {
    226  NS_ASSERTION(aSides != SideBits::eNone &&
    227                   (aSides & ~SideBits::eAll) == SideBits::eNone,
    228               "AreBorderSidesSame: invalid whichSides!");
    229 
    230  /* First check if the specified styles and colors are the same for all sides
    231   */
    232  int firstStyle = 0;
    233  for (const auto i : mozilla::AllPhysicalSides()) {
    234    if (firstStyle == i) {
    235      if ((static_cast<mozilla::SideBits>(1 << i) & aSides) ==
    236          SideBits::eNone) {
    237        firstStyle++;
    238      }
    239      continue;
    240    }
    241 
    242    if ((static_cast<mozilla::SideBits>(1 << i) & aSides) == SideBits::eNone) {
    243      continue;
    244    }
    245 
    246    if (mBorderStyles[firstStyle] != mBorderStyles[i] ||
    247        mBorderColors[firstStyle] != mBorderColors[i]) {
    248      return false;
    249    }
    250  }
    251 
    252  /* Then if it's one of the two-tone styles and we're not
    253   * just comparing the TL or BR sides */
    254  switch (mBorderStyles[firstStyle]) {
    255    case StyleBorderStyle::Groove:
    256    case StyleBorderStyle::Ridge:
    257    case StyleBorderStyle::Inset:
    258    case StyleBorderStyle::Outset:
    259      return ((aSides & ~(SideBits::eTop | SideBits::eLeft)) ==
    260                  SideBits::eNone ||
    261              (aSides & ~(SideBits::eBottom | SideBits::eRight)) ==
    262                  SideBits::eNone);
    263    default:
    264      return true;
    265  }
    266 }
    267 
    268 bool nsCSSBorderRenderer::IsSolidCornerStyle(StyleBorderStyle aStyle,
    269                                             Corner aCorner) {
    270  switch (aStyle) {
    271    case StyleBorderStyle::Solid:
    272      return true;
    273 
    274    case StyleBorderStyle::Inset:
    275    case StyleBorderStyle::Outset:
    276      return (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
    277 
    278    case StyleBorderStyle::Groove:
    279    case StyleBorderStyle::Ridge:
    280      return mOneUnitBorder &&
    281             (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
    282 
    283    case StyleBorderStyle::Double:
    284      return mOneUnitBorder;
    285 
    286    default:
    287      return false;
    288  }
    289 }
    290 
    291 bool nsCSSBorderRenderer::IsCornerMergeable(Corner aCorner) {
    292  // Corner between dotted borders with same width and small radii is
    293  // merged into single dot.
    294  //
    295  //  widthH / 2.0
    296  // |<---------->|
    297  // |            |
    298  // |radius.width|
    299  // |<--->|      |
    300  // |     |      |
    301  // |    _+------+------------+-----
    302  // |  /      ###|###         |
    303  // |/    #######|#######     |
    304  // +   #########|#########   |
    305  // |  ##########|##########  |
    306  // | ###########|########### |
    307  // | ###########|########### |
    308  // |############|############|
    309  // +------------+############|
    310  // |#########################|
    311  // | ####################### |
    312  // | ####################### |
    313  // |  #####################  |
    314  // |   ###################   |
    315  // |     ###############     |
    316  // |         #######         |
    317  // +-------------------------+----
    318  // |                         |
    319  // |                         |
    320  mozilla::Side sideH(GetHorizontalSide(aCorner));
    321  mozilla::Side sideV(GetVerticalSide(aCorner));
    322  StyleBorderStyle styleH = mBorderStyles[sideH];
    323  StyleBorderStyle styleV = mBorderStyles[sideV];
    324  if (styleH != styleV || styleH != StyleBorderStyle::Dotted) {
    325    return false;
    326  }
    327 
    328  Float widthH = mBorderWidths.Side(sideH);
    329  Float widthV = mBorderWidths.Side(sideV);
    330  if (widthH != widthV) {
    331    return false;
    332  }
    333 
    334  Size radius = mBorderRadii[aCorner];
    335  return IsZeroSize(radius) ||
    336         (radius.width < widthH / 2.0f && radius.height < widthH / 2.0f);
    337 }
    338 
    339 BorderColorStyle nsCSSBorderRenderer::BorderColorStyleForSolidCorner(
    340    StyleBorderStyle aStyle, Corner aCorner) {
    341  // note that this function assumes that the corner is already solid,
    342  // as per the earlier function
    343  switch (aStyle) {
    344    case StyleBorderStyle::Solid:
    345    case StyleBorderStyle::Double:
    346      return BorderColorStyleSolid;
    347 
    348    case StyleBorderStyle::Inset:
    349    case StyleBorderStyle::Groove:
    350      if (aCorner == eCornerTopLeft) {
    351        return BorderColorStyleDark;
    352      } else if (aCorner == eCornerBottomRight) {
    353        return BorderColorStyleLight;
    354      }
    355      break;
    356 
    357    case StyleBorderStyle::Outset:
    358    case StyleBorderStyle::Ridge:
    359      if (aCorner == eCornerTopLeft) {
    360        return BorderColorStyleLight;
    361      } else if (aCorner == eCornerBottomRight) {
    362        return BorderColorStyleDark;
    363      }
    364      break;
    365    default:
    366      return BorderColorStyleNone;
    367  }
    368 
    369  return BorderColorStyleNone;
    370 }
    371 
    372 Rect nsCSSBorderRenderer::GetCornerRect(Corner aCorner) {
    373  Point offset(0.f, 0.f);
    374 
    375  if (aCorner == C_TR || aCorner == C_BR) {
    376    offset.x = mOuterRect.Width() - mBorderCornerDimensions[aCorner].width;
    377  }
    378  if (aCorner == C_BR || aCorner == C_BL) {
    379    offset.y = mOuterRect.Height() - mBorderCornerDimensions[aCorner].height;
    380  }
    381 
    382  return Rect(mOuterRect.TopLeft() + offset, mBorderCornerDimensions[aCorner]);
    383 }
    384 
    385 Rect nsCSSBorderRenderer::GetSideClipWithoutCornersRect(mozilla::Side aSide) {
    386  Point offset(0.f, 0.f);
    387 
    388  // The offset from the outside rect to the start of this side's
    389  // box.  For the top and bottom sides, the height of the box
    390  // must be the border height; the x start must take into account
    391  // the corner size (which may be bigger than the right or left
    392  // side's width).  The same applies to the right and left sides.
    393  if (aSide == eSideTop) {
    394    offset.x = mBorderCornerDimensions[C_TL].width;
    395  } else if (aSide == eSideRight) {
    396    offset.x = mOuterRect.Width() - mBorderWidths.right;
    397    offset.y = mBorderCornerDimensions[C_TR].height;
    398  } else if (aSide == eSideBottom) {
    399    offset.x = mBorderCornerDimensions[C_BL].width;
    400    offset.y = mOuterRect.Height() - mBorderWidths.bottom;
    401  } else if (aSide == eSideLeft) {
    402    offset.y = mBorderCornerDimensions[C_TL].height;
    403  }
    404 
    405  // The sum of the width & height of the corners adjacent to the
    406  // side.  This relies on the relationship between side indexing and
    407  // corner indexing; that is, 0 == SIDE_TOP and 0 == CORNER_TOP_LEFT,
    408  // with both proceeding clockwise.
    409  Size sideCornerSum = mBorderCornerDimensions[GetCCWCorner(aSide)] +
    410                       mBorderCornerDimensions[GetCWCorner(aSide)];
    411  Rect rect(mOuterRect.TopLeft() + offset, mOuterRect.Size() - sideCornerSum);
    412 
    413  if (IsHorizontalSide(aSide)) {
    414    rect.height = mBorderWidths.Side(aSide);
    415  } else {
    416    rect.width = mBorderWidths.Side(aSide);
    417  }
    418 
    419  return rect;
    420 }
    421 
    422 // The side border type and the adjacent border types are
    423 // examined and one of the different types of clipping (listed
    424 // below) is selected.
    425 
    426 typedef enum {
    427  // clip to the trapezoid formed by the corners of the
    428  // inner and outer rectangles for the given side
    429  //
    430  // +---------------
    431  // |\%%%%%%%%%%%%%%
    432  // |  \%%%%%%%%%%%%
    433  // |   \%%%%%%%%%%%
    434  // |     \%%%%%%%%%
    435  // |      +--------
    436  // |      |
    437  // |      |
    438  SIDE_CLIP_TRAPEZOID,
    439 
    440  // clip to the trapezoid formed by the outer rectangle
    441  // corners and the center of the region, making sure
    442  // that diagonal lines all go directly from the outside
    443  // corner to the inside corner, but that they then continue on
    444  // to the middle.
    445  //
    446  // This is needed for correctly clipping rounded borders,
    447  // which might extend past the SIDE_CLIP_TRAPEZOID trap.
    448  //
    449  // +-------__--+---
    450  //  \%%%%_-%%%%%%%%
    451  //    \+-%%%%%%%%%%
    452  //    / \%%%%%%%%%%
    453  //   /   \%%%%%%%%%
    454  //  |     +%%_-+---
    455  // |        +%%%%%%
    456  // |       / \%%%%%
    457  // +      +    \%%%
    458  // |      |      +-
    459  SIDE_CLIP_TRAPEZOID_FULL,
    460 
    461  // clip to the rectangle formed by the given side including corner.
    462  // This is used by the non-dotted side next to dotted side.
    463  //
    464  // +---------------
    465  // |%%%%%%%%%%%%%%%
    466  // |%%%%%%%%%%%%%%%
    467  // |%%%%%%%%%%%%%%%
    468  // |%%%%%%%%%%%%%%%
    469  // +------+--------
    470  // |      |
    471  // |      |
    472  SIDE_CLIP_RECTANGLE_CORNER,
    473 
    474  // clip to the rectangle formed by the given side excluding corner.
    475  // This is used by the dotted side next to non-dotted side.
    476  //
    477  // +------+--------
    478  // |      |%%%%%%%%
    479  // |      |%%%%%%%%
    480  // |      |%%%%%%%%
    481  // |      |%%%%%%%%
    482  // |      +--------
    483  // |      |
    484  // |      |
    485  SIDE_CLIP_RECTANGLE_NO_CORNER,
    486 } SideClipType;
    487 
    488 // Given three points, p0, p1, and midPoint, move p1 further in to the
    489 // rectangle (of which aMidPoint is the center) so that it reaches the
    490 // closer of the horizontal or vertical lines intersecting the midpoint,
    491 // while maintaing the slope of the line.  If p0 and p1 are the same,
    492 // just move p1 to midPoint (since there's no slope to maintain).
    493 // FIXME: Extending only to the midpoint isn't actually sufficient for
    494 // boxes with asymmetric radii.
    495 static void MaybeMoveToMidPoint(Point& aP0, Point& aP1,
    496                                const Point& aMidPoint) {
    497  Point ps = aP1 - aP0;
    498 
    499  if (ps.x == 0.0) {
    500    if (ps.y == 0.0) {
    501      aP1 = aMidPoint;
    502    } else {
    503      aP1.y = aMidPoint.y;
    504    }
    505  } else {
    506    if (ps.y == 0.0) {
    507      aP1.x = aMidPoint.x;
    508    } else {
    509      Float k =
    510          std::min((aMidPoint.x - aP0.x) / ps.x, (aMidPoint.y - aP0.y) / ps.y);
    511      aP1 = aP0 + ps * k;
    512    }
    513  }
    514 }
    515 
    516 already_AddRefed<Path> nsCSSBorderRenderer::GetSideClipSubPath(
    517    mozilla::Side aSide) {
    518  // the clip proceeds clockwise from the top left corner;
    519  // so "start" in each case is the start of the region from that side.
    520  //
    521  // the final path will be formed like:
    522  // s0 ------- e0
    523  // |         /
    524  // s1 ----- e1
    525  //
    526  // that is, the second point will always be on the inside
    527 
    528  Point start[2];
    529  Point end[2];
    530 
    531 #define IS_DOTTED(_s) ((_s) == StyleBorderStyle::Dotted)
    532  bool isDotted = IS_DOTTED(mBorderStyles[aSide]);
    533  bool startIsDotted = IS_DOTTED(mBorderStyles[PREV_SIDE(aSide)]);
    534  bool endIsDotted = IS_DOTTED(mBorderStyles[NEXT_SIDE(aSide)]);
    535 #undef IS_DOTTED
    536 
    537  SideClipType startType = SIDE_CLIP_TRAPEZOID;
    538  SideClipType endType = SIDE_CLIP_TRAPEZOID;
    539 
    540  if (!IsZeroSize(mBorderRadii[GetCCWCorner(aSide)])) {
    541    startType = SIDE_CLIP_TRAPEZOID_FULL;
    542  } else if (startIsDotted && !isDotted) {
    543    startType = SIDE_CLIP_RECTANGLE_CORNER;
    544  } else if (!startIsDotted && isDotted) {
    545    startType = SIDE_CLIP_RECTANGLE_NO_CORNER;
    546  }
    547 
    548  if (!IsZeroSize(mBorderRadii[GetCWCorner(aSide)])) {
    549    endType = SIDE_CLIP_TRAPEZOID_FULL;
    550  } else if (endIsDotted && !isDotted) {
    551    endType = SIDE_CLIP_RECTANGLE_CORNER;
    552  } else if (!endIsDotted && isDotted) {
    553    endType = SIDE_CLIP_RECTANGLE_NO_CORNER;
    554  }
    555 
    556  Point midPoint = mInnerRect.Center();
    557 
    558  start[0] = mOuterRect.CCWCorner(aSide);
    559  start[1] = mInnerRect.CCWCorner(aSide);
    560 
    561  end[0] = mOuterRect.CWCorner(aSide);
    562  end[1] = mInnerRect.CWCorner(aSide);
    563 
    564  if (startType == SIDE_CLIP_TRAPEZOID_FULL) {
    565    MaybeMoveToMidPoint(start[0], start[1], midPoint);
    566  } else if (startType == SIDE_CLIP_RECTANGLE_CORNER) {
    567    if (IsHorizontalSide(aSide)) {
    568      start[1] =
    569          Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
    570    } else {
    571      start[1] =
    572          Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
    573    }
    574  } else if (startType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
    575    if (IsHorizontalSide(aSide)) {
    576      start[0] =
    577          Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
    578    } else {
    579      start[0] =
    580          Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
    581    }
    582  }
    583 
    584  if (endType == SIDE_CLIP_TRAPEZOID_FULL) {
    585    MaybeMoveToMidPoint(end[0], end[1], midPoint);
    586  } else if (endType == SIDE_CLIP_RECTANGLE_CORNER) {
    587    if (IsHorizontalSide(aSide)) {
    588      end[1] =
    589          Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
    590    } else {
    591      end[1] =
    592          Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
    593    }
    594  } else if (endType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
    595    if (IsHorizontalSide(aSide)) {
    596      end[0] =
    597          Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
    598    } else {
    599      end[0] =
    600          Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
    601    }
    602  }
    603 
    604  RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
    605  builder->MoveTo(start[0]);
    606  builder->LineTo(end[0]);
    607  builder->LineTo(end[1]);
    608  builder->LineTo(start[1]);
    609  builder->Close();
    610  return builder->Finish();
    611 }
    612 
    613 Point nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::Side aSide,
    614                                                  Corner aCorner,
    615                                                  bool* aIsUnfilled,
    616                                                  Float aDotOffset) {
    617  // Calculate the end point of the side for dashed/dotted border, that is also
    618  // the end point of the corner curve.  The point is specified by aSide and
    619  // aCorner. (e.g. eSideTop and C_TL means the left end of border-top)
    620  //
    621  //
    622  //  aCorner        aSide
    623  //         +--------------------
    624  //         |
    625  //         |
    626  //         |         +----------
    627  //         |    the end point
    628  //         |
    629  //         |         +----------
    630  //         |         |
    631  //         |         |
    632  //         |         |
    633  //
    634  // The position of the point depends on the border-style, border-width, and
    635  // border-radius of the side, corner, and the adjacent side beyond the corner,
    636  // to make those sides (and corner) interact well.
    637  //
    638  // If the style of aSide is dotted and the dot at the point should be
    639  // unfilled, true is stored to *aIsUnfilled, otherwise false is stored.
    640 
    641  const Float signsList[4][2] = {
    642      {+1.0f, +1.0f}, {-1.0f, +1.0f}, {-1.0f, -1.0f}, {+1.0f, -1.0f}};
    643  const Float(&signs)[2] = signsList[aCorner];
    644 
    645  *aIsUnfilled = false;
    646 
    647  Point P = mOuterRect.AtCorner(aCorner);
    648  StyleBorderStyle style = mBorderStyles[aSide];
    649  Float borderWidth = mBorderWidths.Side(aSide);
    650  Size dim = mBorderCornerDimensions[aCorner];
    651  bool isHorizontal = IsHorizontalSide(aSide);
    652  //
    653  //    aCorner      aSide
    654  //           +--------------
    655  //           |
    656  //           |   +----------
    657  //           |   |
    658  // otherSide |   |
    659  //           |   |
    660  mozilla::Side otherSide = ((uint8_t)aSide == (uint8_t)aCorner)
    661                                ? PREV_SIDE(aSide)
    662                                : NEXT_SIDE(aSide);
    663  StyleBorderStyle otherStyle = mBorderStyles[otherSide];
    664  Float otherBorderWidth = mBorderWidths.Side(otherSide);
    665  Size radius = mBorderRadii[aCorner];
    666  if (IsZeroSize(radius)) {
    667    radius.width = 0.0f;
    668    radius.height = 0.0f;
    669  }
    670  if (style == StyleBorderStyle::Dotted) {
    671    // Offset the dot's location along the side toward the corner by a
    672    // multiple of its width.
    673    if (isHorizontal) {
    674      P.x -= signs[0] * aDotOffset * borderWidth;
    675    } else {
    676      P.y -= signs[1] * aDotOffset * borderWidth;
    677    }
    678  }
    679  if (style == StyleBorderStyle::Dotted &&
    680      otherStyle == StyleBorderStyle::Dotted) {
    681    if (borderWidth == otherBorderWidth) {
    682      if (radius.width < borderWidth / 2.0f &&
    683          radius.height < borderWidth / 2.0f) {
    684        // Two dots are merged into one and placed at the corner.
    685        //
    686        //  borderWidth / 2.0
    687        // |<---------->|
    688        // |            |
    689        // |radius.width|
    690        // |<--->|      |
    691        // |     |      |
    692        // |    _+------+------------+-----
    693        // |  /      ###|###         |
    694        // |/    #######|#######     |
    695        // +   #########|#########   |
    696        // |  ##########|##########  |
    697        // | ###########|########### |
    698        // | ###########|########### |
    699        // |############|############|
    700        // +------------+############|
    701        // |########### P ###########|
    702        // | ####################### |
    703        // | ####################### |
    704        // |  #####################  |
    705        // |   ###################   |
    706        // |     ###############     |
    707        // |         #######         |
    708        // +-------------------------+----
    709        // |                         |
    710        // |                         |
    711        P.x += signs[0] * borderWidth / 2.0f;
    712        P.y += signs[1] * borderWidth / 2.0f;
    713      } else {
    714        // Two dots are drawn separately.
    715        //
    716        //    borderWidth * 1.5
    717        //   |<------------>|
    718        //   |              |
    719        //   |radius.width  |
    720        //   |<----->|      |
    721        //   |       |      |
    722        //   |    _--+-+----+---
    723        //   |  _-     |  ##|##
    724        //   | /       | ###|###
    725        //   |/        |####|####
    726        //   |         |####+####
    727        //   |         |### P ###
    728        //   +         | ###|###
    729        //   |         |  ##|##
    730        //   +---------+----+---
    731        //   |  #####  |
    732        //   | ####### |
    733        //   |#########|
    734        //   +----+----+
    735        //   |#########|
    736        //   | ####### |
    737        //   |  #####  |
    738        //   |         |
    739        //
    740        // There should be enough gap between 2 dots even if radius.width is
    741        // small but larger than borderWidth / 2.0.  borderWidth * 1.5 is the
    742        // value that there's imaginally unfilled dot at the corner.  The
    743        // unfilled dot may overflow from the outer curve, but filled dots
    744        // doesn't, so this could be acceptable solution at least for now.
    745        // We may have to find better model/value.
    746        //
    747        //    imaginally unfilled dot at the corner
    748        //        |
    749        //        v    +----+---
    750        //      *****  |  ##|##
    751        //     ******* | ###|###
    752        //    *********|####|####
    753        //    *********|####+####
    754        //    *********|### P ###
    755        //     ******* | ###|###
    756        //      *****  |  ##|##
    757        //   +---------+----+---
    758        //   |  #####  |
    759        //   | ####### |
    760        //   |#########|
    761        //   +----+----+
    762        //   |#########|
    763        //   | ####### |
    764        //   |  #####  |
    765        //   |         |
    766        Float minimum = borderWidth * 1.5f;
    767        if (isHorizontal) {
    768          P.x += signs[0] * std::max(radius.width, minimum);
    769          P.y += signs[1] * borderWidth / 2.0f;
    770        } else {
    771          P.x += signs[0] * borderWidth / 2.0f;
    772          P.y += signs[1] * std::max(radius.height, minimum);
    773        }
    774      }
    775 
    776      return P;
    777    }
    778 
    779    if (borderWidth < otherBorderWidth) {
    780      // This side is smaller than other side, other side draws the corner.
    781      //
    782      //  otherBorderWidth + borderWidth / 2.0
    783      // |<---------->|
    784      // |            |
    785      // +---------+--+--------
    786      // |  #####  | *|*  ###
    787      // | ####### |**|**#####
    788      // |#########|**+**##+##
    789      // |####+####|* P *#####
    790      // |#########| ***  ###
    791      // | ####### +-----------
    792      // |  #####  |  ^
    793      // |         |  |
    794      // |         | first dot is not filled
    795      // |         |
    796      //
    797      //      radius.width
    798      // |<----------------->|
    799      // |                   |
    800      // |             ___---+-------------
    801      // |         __--     #|#       ###
    802      // |       _-        ##|##     #####
    803      // |     /           ##+##     ##+##
    804      // |   /             # P #     #####
    805      // |  |               #|#       ###
    806      // | |             __--+-------------
    807      // ||            _-    ^
    808      // ||           /      |
    809      // |           /      first dot is filled
    810      // |          |
    811      // |          |
    812      // |  #####  |
    813      // | ####### |
    814      // |#########|
    815      // +----+----+
    816      // |#########|
    817      // | ####### |
    818      // |  #####  |
    819      Float minimum = otherBorderWidth + borderWidth / 2.0f;
    820      if (isHorizontal) {
    821        if (radius.width < minimum) {
    822          *aIsUnfilled = true;
    823          P.x += signs[0] * minimum;
    824        } else {
    825          P.x += signs[0] * radius.width;
    826        }
    827        P.y += signs[1] * borderWidth / 2.0f;
    828      } else {
    829        P.x += signs[0] * borderWidth / 2.0f;
    830        if (radius.height < minimum) {
    831          *aIsUnfilled = true;
    832          P.y += signs[1] * minimum;
    833        } else {
    834          P.y += signs[1] * radius.height;
    835        }
    836      }
    837 
    838      return P;
    839    }
    840 
    841    // This side is larger than other side, this side draws the corner.
    842    //
    843    //  borderWidth / 2.0
    844    // |<-->|
    845    // |    |
    846    // +----+---------------------
    847    // |  ##|##           #####
    848    // | ###|###         #######
    849    // |####|####       #########
    850    // |####+####       ####+####
    851    // |### P ###       #########
    852    // | #######         #######
    853    // |  #####           #####
    854    // +-----+---------------------
    855    // | *** |
    856    // |*****|
    857    // |**+**| <-- first dot in other side is not filled
    858    // |*****|
    859    // | *** |
    860    // | ### |
    861    // |#####|
    862    // |##+##|
    863    // |#####|
    864    // | ### |
    865    // |     |
    866    if (isHorizontal) {
    867      P.x += signs[0] * std::max(radius.width, borderWidth / 2.0f);
    868      P.y += signs[1] * borderWidth / 2.0f;
    869    } else {
    870      P.x += signs[0] * borderWidth / 2.0f;
    871      P.y += signs[1] * std::max(radius.height, borderWidth / 2.0f);
    872    }
    873    return P;
    874  }
    875 
    876  if (style == StyleBorderStyle::Dotted) {
    877    // If only this side is dotted, other side draws the corner.
    878    //
    879    //  otherBorderWidth + borderWidth / 2.0
    880    // |<------->|
    881    // |         |
    882    // +------+--+--------
    883    // |##  ##| *|*  ###
    884    // |##  ##|**|**#####
    885    // |##  ##|**+**##+##
    886    // |##  ##|* P *#####
    887    // |##  ##| ***  ###
    888    // |##  ##+-----------
    889    // |##  ##|  ^
    890    // |##  ##|  |
    891    // |##  ##| first dot is not filled
    892    // |##  ##|
    893    //
    894    //      radius.width
    895    // |<----------------->|
    896    // |                   |
    897    // |             ___---+-------------
    898    // |         __--     #|#       ###
    899    // |       _-        ##|##     #####
    900    // |     /           ##+##     ##+##
    901    // |   /             # P #     #####
    902    // |  |               #|#       ###
    903    // | |             __--+-------------
    904    // ||            _-    ^
    905    // ||          /       |
    906    // |         /        first dot is filled
    907    // |        |
    908    // |       |
    909    // |      |
    910    // |      |
    911    // |      |
    912    // +------+
    913    // |##  ##|
    914    // |##  ##|
    915    // |##  ##|
    916    Float minimum = otherBorderWidth + borderWidth / 2.0f;
    917    if (isHorizontal) {
    918      if (radius.width < minimum) {
    919        *aIsUnfilled = true;
    920        P.x += signs[0] * minimum;
    921      } else {
    922        P.x += signs[0] * radius.width;
    923      }
    924      P.y += signs[1] * borderWidth / 2.0f;
    925    } else {
    926      P.x += signs[0] * borderWidth / 2.0f;
    927      if (radius.height < minimum) {
    928        *aIsUnfilled = true;
    929        P.y += signs[1] * minimum;
    930      } else {
    931        P.y += signs[1] * radius.height;
    932      }
    933    }
    934    return P;
    935  }
    936 
    937  if (otherStyle == StyleBorderStyle::Dotted && IsZeroSize(radius)) {
    938    // If other side is dotted and radius=0, draw side to the end of corner.
    939    //
    940    //   +-------------------------------
    941    //   |##########          ##########
    942    // P +##########          ##########
    943    //   |##########          ##########
    944    //   +-----+-------------------------
    945    //   | *** |
    946    //   |*****|
    947    //   |**+**| <-- first dot in other side is not filled
    948    //   |*****|
    949    //   | *** |
    950    //   | ### |
    951    //   |#####|
    952    //   |##+##|
    953    //   |#####|
    954    //   | ### |
    955    //   |     |
    956    if (isHorizontal) {
    957      P.y += signs[1] * borderWidth / 2.0f;
    958    } else {
    959      P.x += signs[0] * borderWidth / 2.0f;
    960    }
    961    return P;
    962  }
    963 
    964  // Other cases.
    965  //
    966  //  dim.width
    967  // |<----------------->|
    968  // |                   |
    969  // |             ___---+------------------
    970  // |         __--      |#######        ###
    971  // |       _-        P +#######        ###
    972  // |     /             |#######        ###
    973  // |   /          __---+------------------
    974  // |  |       __--
    975  // | |       /
    976  // ||      /
    977  // ||     |
    978  // |     |
    979  // |    |
    980  // |   |
    981  // |   |
    982  // +-+-+
    983  // |###|
    984  // |###|
    985  // |###|
    986  // |###|
    987  // |###|
    988  // |   |
    989  // |   |
    990  if (isHorizontal) {
    991    P.x += signs[0] * dim.width;
    992    P.y += signs[1] * borderWidth / 2.0f;
    993  } else {
    994    P.x += signs[0] * borderWidth / 2.0f;
    995    P.y += signs[1] * dim.height;
    996  }
    997 
    998  return P;
    999 }
   1000 
   1001 void nsCSSBorderRenderer::GetOuterAndInnerBezier(Bezier* aOuterBezier,
   1002                                                 Bezier* aInnerBezier,
   1003                                                 Corner aCorner) {
   1004  // Return bezier control points for outer and inner curve for given corner.
   1005  //
   1006  //               ___---+ outer curve
   1007  //           __--      |
   1008  //         _-          |
   1009  //       /             |
   1010  //     /               |
   1011  //    |                |
   1012  //   |             __--+ inner curve
   1013  //  |            _-
   1014  //  |           /
   1015  // |           /
   1016  // |          |
   1017  // |          |
   1018  // |         |
   1019  // |         |
   1020  // |         |
   1021  // +---------+
   1022 
   1023  mozilla::Side sideH(GetHorizontalSide(aCorner));
   1024  mozilla::Side sideV(GetVerticalSide(aCorner));
   1025 
   1026  Size outerCornerSize(ceil(mBorderRadii[aCorner].width),
   1027                       ceil(mBorderRadii[aCorner].height));
   1028  Size innerCornerSize(ceil(std::max(0.0f, mBorderRadii[aCorner].width -
   1029                                               mBorderWidths.Side(sideV))),
   1030                       ceil(std::max(0.0f, mBorderRadii[aCorner].height -
   1031                                               mBorderWidths.Side(sideH))));
   1032 
   1033  GetBezierPointsForCorner(aOuterBezier, aCorner, mOuterRect.AtCorner(aCorner),
   1034                           outerCornerSize);
   1035 
   1036  GetBezierPointsForCorner(aInnerBezier, aCorner, mInnerRect.AtCorner(aCorner),
   1037                           innerCornerSize);
   1038 }
   1039 
   1040 void nsCSSBorderRenderer::FillSolidBorder(const Rect& aOuterRect,
   1041                                          const Rect& aInnerRect,
   1042                                          const RectCornerRadii& aBorderRadii,
   1043                                          const Margin& aBorderSizes,
   1044                                          SideBits aSides,
   1045                                          const ColorPattern& aColor) {
   1046  // Note that this function is allowed to draw more than just the
   1047  // requested sides.
   1048 
   1049  // If we have a border radius, do full rounded rectangles
   1050  // and fill, regardless of what sides we're asked to draw.
   1051  if (!AllCornersZeroSize(aBorderRadii)) {
   1052    RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
   1053 
   1054    RectCornerRadii innerRadii;
   1055    ComputeInnerRadii(aBorderRadii, aBorderSizes, &innerRadii);
   1056 
   1057    // do the outer border
   1058    AppendRoundedRectToPath(builder, aOuterRect, aBorderRadii, true);
   1059 
   1060    // then do the inner border CCW
   1061    AppendRoundedRectToPath(builder, aInnerRect, innerRadii, false);
   1062 
   1063    RefPtr<Path> path = builder->Finish();
   1064 
   1065    mDrawTarget->Fill(path, aColor);
   1066    return;
   1067  }
   1068 
   1069  // If we're asked to draw all sides of an equal-sized border,
   1070  // stroking is fastest.  This is a fairly common path, but partial
   1071  // sides is probably second in the list -- there are a bunch of
   1072  // common border styles, such as inset and outset, that are
   1073  // top-left/bottom-right split.
   1074  if (aSides == SideBits::eAll && aBorderSizes.IsAllEqual() && !mAvoidStroke) {
   1075    Float strokeWidth = aBorderSizes.top.value;
   1076    Rect r(aOuterRect);
   1077    r.Deflate(strokeWidth / 2.f);
   1078    mDrawTarget->StrokeRect(r, aColor, StrokeOptions(strokeWidth));
   1079    return;
   1080  }
   1081 
   1082  // Otherwise, we have unequal sized borders or we're only
   1083  // drawing some sides; create rectangles for each side
   1084  // and fill them.
   1085 
   1086  Rect r[4];
   1087 
   1088  // compute base rects for each side
   1089  if (aSides & SideBits::eTop) {
   1090    r[eSideTop] = Rect(aOuterRect.X(), aOuterRect.Y(), aOuterRect.Width(),
   1091                       aBorderSizes.top);
   1092  }
   1093 
   1094  if (aSides & SideBits::eBottom) {
   1095    r[eSideBottom] =
   1096        Rect(aOuterRect.X(), aOuterRect.YMost() - aBorderSizes.bottom,
   1097             aOuterRect.Width(), aBorderSizes.bottom);
   1098  }
   1099 
   1100  if (aSides & SideBits::eLeft) {
   1101    r[eSideLeft] = Rect(aOuterRect.X(), aOuterRect.Y(), aBorderSizes.left,
   1102                        aOuterRect.Height());
   1103  }
   1104 
   1105  if (aSides & SideBits::eRight) {
   1106    r[eSideRight] =
   1107        Rect(aOuterRect.XMost() - aBorderSizes.right, aOuterRect.Y(),
   1108             aBorderSizes.right, aOuterRect.Height());
   1109  }
   1110 
   1111  // If two sides meet at a corner that we're rendering, then
   1112  // make sure that we adjust one of the sides to avoid overlap.
   1113  // This is especially important in the case of colors with
   1114  // an alpha channel.
   1115 
   1116  if ((aSides & (SideBits::eTop | SideBits::eLeft)) ==
   1117      (SideBits::eTop | SideBits::eLeft)) {
   1118    // adjust the left's top down a bit
   1119    r[eSideLeft].y += aBorderSizes.top;
   1120    r[eSideLeft].height -= aBorderSizes.top;
   1121  }
   1122 
   1123  if ((aSides & (SideBits::eTop | SideBits::eRight)) ==
   1124      (SideBits::eTop | SideBits::eRight)) {
   1125    // adjust the top's left a bit
   1126    r[eSideTop].width -= aBorderSizes.right;
   1127  }
   1128 
   1129  if ((aSides & (SideBits::eBottom | SideBits::eRight)) ==
   1130      (SideBits::eBottom | SideBits::eRight)) {
   1131    // adjust the right's bottom a bit
   1132    r[eSideRight].height -= aBorderSizes.bottom;
   1133  }
   1134 
   1135  if ((aSides & (SideBits::eBottom | SideBits::eLeft)) ==
   1136      (SideBits::eBottom | SideBits::eLeft)) {
   1137    // adjust the bottom's left a bit
   1138    r[eSideBottom].x += aBorderSizes.left;
   1139    r[eSideBottom].width -= aBorderSizes.left;
   1140  }
   1141 
   1142  // Filling these one by one is faster than filling them all at once.
   1143  for (uint32_t i = 0; i < 4; i++) {
   1144    if (aSides & static_cast<mozilla::SideBits>(1 << i)) {
   1145      MaybeSnapToDevicePixels(r[i], *mDrawTarget, true);
   1146      mDrawTarget->FillRect(r[i], aColor);
   1147    }
   1148  }
   1149 }
   1150 
   1151 sRGBColor MakeBorderColor(nscolor aColor, BorderColorStyle aBorderColorStyle) {
   1152  nscolor colors[2];
   1153  int k = 0;
   1154 
   1155  switch (aBorderColorStyle) {
   1156    case BorderColorStyleNone:
   1157      return sRGBColor(0.f, 0.f, 0.f, 0.f);  // transparent black
   1158 
   1159    case BorderColorStyleLight:
   1160      k = 1;
   1161      [[fallthrough]];
   1162    case BorderColorStyleDark:
   1163      NS_GetSpecial3DColors(colors, aColor);
   1164      return sRGBColor::FromABGR(colors[k]);
   1165 
   1166    case BorderColorStyleSolid:
   1167    default:
   1168      return sRGBColor::FromABGR(aColor);
   1169  }
   1170 }
   1171 
   1172 sRGBColor ComputeColorForLine(uint32_t aLineIndex,
   1173                              const BorderColorStyle* aBorderColorStyle,
   1174                              uint32_t aBorderColorStyleCount,
   1175                              nscolor aBorderColor) {
   1176  NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given");
   1177 
   1178  return MakeBorderColor(aBorderColor, aBorderColorStyle[aLineIndex]);
   1179 }
   1180 
   1181 void nsCSSBorderRenderer::DrawBorderSides(mozilla::SideBits aSides) {
   1182  if (aSides == SideBits::eNone ||
   1183      (aSides & ~SideBits::eAll) != SideBits::eNone) {
   1184    NS_WARNING("DrawBorderSides: invalid sides!");
   1185    return;
   1186  }
   1187 
   1188  StyleBorderStyle borderRenderStyle = StyleBorderStyle::None;
   1189  nscolor borderRenderColor;
   1190 
   1191  uint32_t borderColorStyleCount = 0;
   1192  BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3];
   1193  BorderColorStyle* borderColorStyle = nullptr;
   1194 
   1195  for (const auto i : mozilla::AllPhysicalSides()) {
   1196    if ((aSides & static_cast<mozilla::SideBits>(1 << i)) == SideBits::eNone) {
   1197      continue;
   1198    }
   1199    borderRenderStyle = mBorderStyles[i];
   1200    borderRenderColor = mBorderColors[i];
   1201    break;
   1202  }
   1203 
   1204  if (borderRenderStyle == StyleBorderStyle::None ||
   1205      borderRenderStyle == StyleBorderStyle::Hidden) {
   1206    return;
   1207  }
   1208 
   1209  if (borderRenderStyle == StyleBorderStyle::Dashed ||
   1210      borderRenderStyle == StyleBorderStyle::Dotted) {
   1211    // Draw each corner separately, with the given side's color.
   1212    if (aSides & SideBits::eTop) {
   1213      DrawDashedOrDottedCorner(eSideTop, C_TL);
   1214    } else if (aSides & SideBits::eLeft) {
   1215      DrawDashedOrDottedCorner(eSideLeft, C_TL);
   1216    }
   1217 
   1218    if (aSides & SideBits::eTop) {
   1219      DrawDashedOrDottedCorner(eSideTop, C_TR);
   1220    } else if (aSides & SideBits::eRight) {
   1221      DrawDashedOrDottedCorner(eSideRight, C_TR);
   1222    }
   1223 
   1224    if (aSides & SideBits::eBottom) {
   1225      DrawDashedOrDottedCorner(eSideBottom, C_BL);
   1226    } else if (aSides & SideBits::eLeft) {
   1227      DrawDashedOrDottedCorner(eSideLeft, C_BL);
   1228    }
   1229 
   1230    if (aSides & SideBits::eBottom) {
   1231      DrawDashedOrDottedCorner(eSideBottom, C_BR);
   1232    } else if (aSides & SideBits::eRight) {
   1233      DrawDashedOrDottedCorner(eSideRight, C_BR);
   1234    }
   1235    return;
   1236  }
   1237 
   1238  // The borderColorStyle array goes from the outer to the inner style.
   1239  //
   1240  // If the border width is 1, we need to change the borderRenderStyle
   1241  // a bit to make sure that we get the right colors -- e.g. 'ridge'
   1242  // with a 1px border needs to look like solid, not like 'outset'.
   1243  if (mOneUnitBorder && (borderRenderStyle == StyleBorderStyle::Ridge ||
   1244                         borderRenderStyle == StyleBorderStyle::Groove ||
   1245                         borderRenderStyle == StyleBorderStyle::Double)) {
   1246    borderRenderStyle = StyleBorderStyle::Solid;
   1247  }
   1248 
   1249  switch (borderRenderStyle) {
   1250    case StyleBorderStyle::Solid:
   1251      borderColorStyleTopLeft[0] = BorderColorStyleSolid;
   1252 
   1253      borderColorStyleBottomRight[0] = BorderColorStyleSolid;
   1254 
   1255      borderColorStyleCount = 1;
   1256      break;
   1257 
   1258    case StyleBorderStyle::Groove:
   1259      borderColorStyleTopLeft[0] = BorderColorStyleDark;
   1260      borderColorStyleTopLeft[1] = BorderColorStyleLight;
   1261 
   1262      borderColorStyleBottomRight[0] = BorderColorStyleLight;
   1263      borderColorStyleBottomRight[1] = BorderColorStyleDark;
   1264 
   1265      borderColorStyleCount = 2;
   1266      break;
   1267 
   1268    case StyleBorderStyle::Ridge:
   1269      borderColorStyleTopLeft[0] = BorderColorStyleLight;
   1270      borderColorStyleTopLeft[1] = BorderColorStyleDark;
   1271 
   1272      borderColorStyleBottomRight[0] = BorderColorStyleDark;
   1273      borderColorStyleBottomRight[1] = BorderColorStyleLight;
   1274 
   1275      borderColorStyleCount = 2;
   1276      break;
   1277 
   1278    case StyleBorderStyle::Double:
   1279      borderColorStyleTopLeft[0] = BorderColorStyleSolid;
   1280      borderColorStyleTopLeft[1] = BorderColorStyleNone;
   1281      borderColorStyleTopLeft[2] = BorderColorStyleSolid;
   1282 
   1283      borderColorStyleBottomRight[0] = BorderColorStyleSolid;
   1284      borderColorStyleBottomRight[1] = BorderColorStyleNone;
   1285      borderColorStyleBottomRight[2] = BorderColorStyleSolid;
   1286 
   1287      borderColorStyleCount = 3;
   1288      break;
   1289 
   1290    case StyleBorderStyle::Inset:
   1291      borderColorStyleTopLeft[0] = BorderColorStyleDark;
   1292      borderColorStyleBottomRight[0] = BorderColorStyleLight;
   1293 
   1294      borderColorStyleCount = 1;
   1295      break;
   1296 
   1297    case StyleBorderStyle::Outset:
   1298      borderColorStyleTopLeft[0] = BorderColorStyleLight;
   1299      borderColorStyleBottomRight[0] = BorderColorStyleDark;
   1300 
   1301      borderColorStyleCount = 1;
   1302      break;
   1303 
   1304    default:
   1305      MOZ_ASSERT_UNREACHABLE("Unhandled border style!!");
   1306      break;
   1307  }
   1308 
   1309  // The only way to get to here is by having a
   1310  // borderColorStyleCount < 1 or > 3; this should never happen,
   1311  // since -moz-border-colors doesn't get handled here.
   1312  NS_ASSERTION(borderColorStyleCount > 0 && borderColorStyleCount < 4,
   1313               "Non-border-colors case with borderColorStyleCount < 1 or > 3; "
   1314               "what happened?");
   1315 
   1316  // The caller should never give us anything with a mix
   1317  // of TL/BR if the border style would require a
   1318  // TL/BR split.
   1319  if (aSides & (SideBits::eBottom | SideBits::eRight)) {
   1320    borderColorStyle = borderColorStyleBottomRight;
   1321  } else {
   1322    borderColorStyle = borderColorStyleTopLeft;
   1323  }
   1324 
   1325  // Distribute the border across the available space.
   1326  Margin borderWidths[3];
   1327 
   1328  if (borderColorStyleCount == 1) {
   1329    borderWidths[0] = mBorderWidths;
   1330  } else if (borderColorStyleCount == 2) {
   1331    // with 2 color styles, any extra pixel goes to the outside
   1332    for (const auto i : mozilla::AllPhysicalSides()) {
   1333      borderWidths[0].Side(i) = int32_t(mBorderWidths.Side(i)) / 2 +
   1334                                int32_t(mBorderWidths.Side(i)) % 2;
   1335      borderWidths[1].Side(i) = int32_t(mBorderWidths.Side(i)) / 2;
   1336    }
   1337  } else if (borderColorStyleCount == 3) {
   1338    // with 3 color styles, any extra pixel (or lack of extra pixel)
   1339    // goes to the middle
   1340    for (const auto i : mozilla::AllPhysicalSides()) {
   1341      if (mBorderWidths.Side(i) == 1.0) {
   1342        borderWidths[0].Side(i) = 1.f;
   1343        borderWidths[1].Side(i) = borderWidths[2].Side(i) = 0.f;
   1344      } else {
   1345        int32_t rest = int32_t(mBorderWidths.Side(i)) % 3;
   1346        borderWidths[0].Side(i) = borderWidths[2].Side(i) =
   1347            borderWidths[1].Side(i) =
   1348                (int32_t(mBorderWidths.Side(i)) - rest) / 3;
   1349 
   1350        if (rest == 1) {
   1351          borderWidths[1].Side(i) += 1.f;
   1352        } else if (rest == 2) {
   1353          borderWidths[0].Side(i) += 1.f;
   1354          borderWidths[2].Side(i) += 1.f;
   1355        }
   1356      }
   1357    }
   1358  }
   1359 
   1360  // make a copy that we can modify
   1361  RectCornerRadii radii = mBorderRadii;
   1362 
   1363  Rect soRect(mOuterRect);
   1364  Rect siRect(mOuterRect);
   1365 
   1366  // If adjacent side is dotted and radius=0, draw side to the end of corner.
   1367  //
   1368  // +--------------------------------
   1369  // |################################
   1370  // |
   1371  // |################################
   1372  // +-----+--------------------------
   1373  // |     |
   1374  // |     |
   1375  // |     |
   1376  // |     |
   1377  // |     |
   1378  // | ### |
   1379  // |#####|
   1380  // |#####|
   1381  // |#####|
   1382  // | ### |
   1383  // |     |
   1384  bool noMarginTop = false;
   1385  bool noMarginRight = false;
   1386  bool noMarginBottom = false;
   1387  bool noMarginLeft = false;
   1388 
   1389  // If there is at least one dotted side, every side is rendered separately.
   1390  if (IsSingleSide(aSides)) {
   1391    if (aSides == SideBits::eTop) {
   1392      if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted &&
   1393          IsZeroSize(mBorderRadii[C_TR])) {
   1394        noMarginRight = true;
   1395      }
   1396      if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted &&
   1397          IsZeroSize(mBorderRadii[C_TL])) {
   1398        noMarginLeft = true;
   1399      }
   1400    } else if (aSides == SideBits::eRight) {
   1401      if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted &&
   1402          IsZeroSize(mBorderRadii[C_TR])) {
   1403        noMarginTop = true;
   1404      }
   1405      if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted &&
   1406          IsZeroSize(mBorderRadii[C_BR])) {
   1407        noMarginBottom = true;
   1408      }
   1409    } else if (aSides == SideBits::eBottom) {
   1410      if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted &&
   1411          IsZeroSize(mBorderRadii[C_BR])) {
   1412        noMarginRight = true;
   1413      }
   1414      if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted &&
   1415          IsZeroSize(mBorderRadii[C_BL])) {
   1416        noMarginLeft = true;
   1417      }
   1418    } else {
   1419      if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted &&
   1420          IsZeroSize(mBorderRadii[C_TL])) {
   1421        noMarginTop = true;
   1422      }
   1423      if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted &&
   1424          IsZeroSize(mBorderRadii[C_BL])) {
   1425        noMarginBottom = true;
   1426      }
   1427    }
   1428  }
   1429 
   1430  for (unsigned int i = 0; i < borderColorStyleCount; i++) {
   1431    // walk siRect inwards at the start of the loop to get the
   1432    // correct inner rect.
   1433    //
   1434    // If noMarginTop is false:
   1435    //   --------------------+
   1436    //                      /|
   1437    //                     / |
   1438    //                    L  |
   1439    //   ----------------+   |
   1440    //                   |   |
   1441    //                   |   |
   1442    //
   1443    // If noMarginTop is true:
   1444    //   ----------------+<--+
   1445    //                   |   |
   1446    //                   |   |
   1447    //                   |   |
   1448    //                   |   |
   1449    //                   |   |
   1450    //                   |   |
   1451    siRect.Deflate(Margin(noMarginTop ? 0 : borderWidths[i].top.value,
   1452                          noMarginRight ? 0 : borderWidths[i].right.value,
   1453                          noMarginBottom ? 0 : borderWidths[i].bottom.value,
   1454                          noMarginLeft ? 0 : borderWidths[i].left.value));
   1455 
   1456    if (borderColorStyle[i] != BorderColorStyleNone) {
   1457      sRGBColor c = ComputeColorForLine(
   1458          i, borderColorStyle, borderColorStyleCount, borderRenderColor);
   1459      ColorPattern color(ToDeviceColor(c));
   1460 
   1461      FillSolidBorder(soRect, siRect, radii, borderWidths[i], aSides, color);
   1462    }
   1463 
   1464    ComputeInnerRadii(radii, borderWidths[i], &radii);
   1465 
   1466    // And now soRect is the same as siRect, for the next line in.
   1467    soRect = siRect;
   1468  }
   1469 }
   1470 
   1471 void nsCSSBorderRenderer::SetupDashedOptions(StrokeOptions* aStrokeOptions,
   1472                                             Float aDash[2],
   1473                                             mozilla::Side aSide,
   1474                                             Float aBorderLength,
   1475                                             bool isCorner) {
   1476  MOZ_ASSERT(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
   1477                 mBorderStyles[aSide] == StyleBorderStyle::Dotted,
   1478             "Style should be dashed or dotted.");
   1479 
   1480  StyleBorderStyle style = mBorderStyles[aSide];
   1481  Float borderWidth = mBorderWidths.Side(aSide);
   1482 
   1483  // Dashed line starts and ends with half segment in most case.
   1484  //
   1485  // __--+---+---+---+---+---+---+---+---+--__
   1486  //     |###|   |   |###|###|   |   |###|
   1487  //     |###|   |   |###|###|   |   |###|
   1488  //     |###|   |   |###|###|   |   |###|
   1489  // __--+---+---+---+---+---+---+---+---+--__
   1490  //
   1491  // If radius=0 and other side is either dotted or 0-width, it starts or ends
   1492  // with full segment.
   1493  //
   1494  // +---+---+---+---+---+---+---+---+---+---+
   1495  // |###|###|   |   |###|###|   |   |###|###|
   1496  // |###|###|   |   |###|###|   |   |###|###|
   1497  // |###|###|   |   |###|###|   |   |###|###|
   1498  // +---++--+---+---+---+---+---+---+--++---+
   1499  // |    |                             |    |
   1500  // |    |                             |    |
   1501  // |    |                             |    |
   1502  // |    |                             |    |
   1503  // | ## |                             | ## |
   1504  // |####|                             |####|
   1505  // |####|                             |####|
   1506  // | ## |                             | ## |
   1507  // |    |                             |    |
   1508  bool fullStart = false, fullEnd = false;
   1509  Float halfDash;
   1510  if (style == StyleBorderStyle::Dashed) {
   1511    // If either end of the side is not connecting onto a corner then we want a
   1512    // full dash at that end.
   1513    //
   1514    // Note that in the case that a corner is empty, either the adjacent side
   1515    // has zero width, or else DrawBorders() set the corner to be empty
   1516    // (it does that if the adjacent side has zero length and the border widths
   1517    // of this and the adjacent sides are thin enough that the corner will be
   1518    // insignificantly small).
   1519 
   1520    if (mBorderRadii[GetCCWCorner(aSide)].IsEmpty() &&
   1521        (mBorderCornerDimensions[GetCCWCorner(aSide)].IsEmpty() ||
   1522         mBorderStyles[PREV_SIDE(aSide)] == StyleBorderStyle::Dotted ||
   1523         // XXX why this <=1 check?
   1524         borderWidth <= 1.0f)) {
   1525      fullStart = true;
   1526    }
   1527 
   1528    if (mBorderRadii[GetCWCorner(aSide)].IsEmpty() &&
   1529        (mBorderCornerDimensions[GetCWCorner(aSide)].IsEmpty() ||
   1530         mBorderStyles[NEXT_SIDE(aSide)] == StyleBorderStyle::Dotted)) {
   1531      fullEnd = true;
   1532    }
   1533 
   1534    halfDash = borderWidth * DOT_LENGTH * DASH_LENGTH / 2.0f;
   1535  } else {
   1536    halfDash = borderWidth * DOT_LENGTH / 2.0f;
   1537  }
   1538 
   1539  if (style == StyleBorderStyle::Dashed && aBorderLength > 0.0f) {
   1540    // The number of half segments, with maximum dash length.
   1541    int32_t count = floor(aBorderLength / halfDash);
   1542    Float minHalfDash = borderWidth * DOT_LENGTH / 2.0f;
   1543 
   1544    if (fullStart && fullEnd) {
   1545      // count should be 4n + 2
   1546      //
   1547      //   1 +       4       +        4      + 1
   1548      //
   1549      // |   |               |               |   |
   1550      // +---+---+---+---+---+---+---+---+---+---+
   1551      // |###|###|   |   |###|###|   |   |###|###|
   1552      // |###|###|   |   |###|###|   |   |###|###|
   1553      // |###|###|   |   |###|###|   |   |###|###|
   1554      // +---+---+---+---+---+---+---+---+---+---+
   1555 
   1556      // If border is too short, draw solid line.
   1557      if (aBorderLength < 6.0f * minHalfDash) {
   1558        return;
   1559      }
   1560 
   1561      if (count % 4 == 0) {
   1562        count += 2;
   1563      } else if (count % 4 == 1) {
   1564        count += 1;
   1565      } else if (count % 4 == 3) {
   1566        count += 3;
   1567      }
   1568    } else if (fullStart || fullEnd) {
   1569      // count should be 4n + 1
   1570      //
   1571      //   1 +       4       +        4
   1572      //
   1573      // |   |               |               |
   1574      // +---+---+---+---+---+---+---+---+---+
   1575      // |###|###|   |   |###|###|   |   |###|
   1576      // |###|###|   |   |###|###|   |   |###|
   1577      // |###|###|   |   |###|###|   |   |###|
   1578      // +---+---+---+---+---+---+---+---+---+
   1579      //
   1580      //         4       +        4      + 1
   1581      //
   1582      // |               |               |   |
   1583      // +---+---+---+---+---+---+---+---+---+
   1584      // |###|   |   |###|###|   |   |###|###|
   1585      // |###|   |   |###|###|   |   |###|###|
   1586      // |###|   |   |###|###|   |   |###|###|
   1587      // +---+---+---+---+---+---+---+---+---+
   1588 
   1589      // If border is too short, draw solid line.
   1590      if (aBorderLength < 5.0f * minHalfDash) {
   1591        return;
   1592      }
   1593 
   1594      if (count % 4 == 0) {
   1595        count += 1;
   1596      } else if (count % 4 == 2) {
   1597        count += 3;
   1598      } else if (count % 4 == 3) {
   1599        count += 2;
   1600      }
   1601    } else {
   1602      // count should be 4n
   1603      //
   1604      //         4       +        4
   1605      //
   1606      // |               |               |
   1607      // +---+---+---+---+---+---+---+---+
   1608      // |###|   |   |###|###|   |   |###|
   1609      // |###|   |   |###|###|   |   |###|
   1610      // |###|   |   |###|###|   |   |###|
   1611      // +---+---+---+---+---+---+---+---+
   1612 
   1613      // If border is too short, draw solid line.
   1614      if (aBorderLength < 4.0f * minHalfDash) {
   1615        return;
   1616      }
   1617 
   1618      if (count % 4 == 1) {
   1619        count += 3;
   1620      } else if (count % 4 == 2) {
   1621        count += 2;
   1622      } else if (count % 4 == 3) {
   1623        count += 1;
   1624      }
   1625    }
   1626    halfDash = aBorderLength / count;
   1627  }
   1628 
   1629  Float fullDash = halfDash * 2.0f;
   1630 
   1631  aDash[0] = fullDash;
   1632  aDash[1] = fullDash;
   1633 
   1634  if (style == StyleBorderStyle::Dashed && fullDash > 1.0f) {
   1635    if (!fullStart) {
   1636      // Draw half segments on both ends.
   1637      aStrokeOptions->mDashOffset = halfDash;
   1638    }
   1639  } else if (style != StyleBorderStyle::Dotted && isCorner) {
   1640    // If side ends with filled full segment, corner should start with unfilled
   1641    // full segment. Not needed for dotted corners, as they overlap one dot with
   1642    // the side's end.
   1643    //
   1644    //     corner            side
   1645    //   ------------>|<---------------------------
   1646    //                |
   1647    //          __+---+---+---+---+---+---+---+---+
   1648    //       _+-  |   |###|###|   |   |###|###|   |
   1649    //     /##|   |   |###|###|   |   |###|###|   |
   1650    //    +####|   |  |###|###|   |   |###|###|   |
   1651    //   /#\####| _+--+---+---+---+---+---+---+---+
   1652    //  |####\##+-
   1653    //  |#####+-
   1654    //  +--###/
   1655    //  |  --+
   1656    aStrokeOptions->mDashOffset = fullDash;
   1657  }
   1658 
   1659  aStrokeOptions->mDashPattern = aDash;
   1660  aStrokeOptions->mDashLength = 2;
   1661 
   1662  PrintAsFormatString("dash: %f %f\n", aDash[0], aDash[1]);
   1663 }
   1664 
   1665 static Float GetBorderLength(mozilla::Side aSide, const Point& aStart,
   1666                             const Point& aEnd) {
   1667  if (aSide == eSideTop) {
   1668    return aEnd.x - aStart.x;
   1669  }
   1670  if (aSide == eSideRight) {
   1671    return aEnd.y - aStart.y;
   1672  }
   1673  if (aSide == eSideBottom) {
   1674    return aStart.x - aEnd.x;
   1675  }
   1676  return aStart.y - aEnd.y;
   1677 }
   1678 
   1679 void nsCSSBorderRenderer::DrawDashedOrDottedSide(mozilla::Side aSide) {
   1680  // Draw dashed/dotted side with following approach.
   1681  //
   1682  // dashed side
   1683  //   Draw dashed line along the side, with appropriate dash length and gap
   1684  //   to make the side symmetric as far as possible.  Dash length equals to
   1685  //   the gap, and the ratio of the dash length to border-width is the maximum
   1686  //   value in in [1, 3] range.
   1687  //   In most case, line ends with half segment, to joint with corner easily.
   1688  //   If adjacent side is dotted or 0px and border-radius for the corner
   1689  //   between them is 0, the line ends with full segment.
   1690  //   (see comment for GetStraightBorderPoint for more detail)
   1691  //
   1692  // dotted side
   1693  //   If border-width <= 2.0, draw 1:1 dashed line.
   1694  //   Otherwise, draw circles along the side, with appropriate gap that makes
   1695  //   the side symmetric as far as possible.  The ratio of the gap to
   1696  //   border-width is the maximum value in [0.5, 1] range in most case.
   1697  //   if the side is too short and there's only 2 dots, it can be more smaller.
   1698  //   If there's no space to place 2 dots at the side, draw single dot at the
   1699  //   middle of the side.
   1700  //   In most case, line ends with filled dot, to joint with corner easily,
   1701  //   If adjacent side is dotted with larger border-width, or other style,
   1702  //   the line ends with unfilled dot.
   1703  //   (see comment for GetStraightBorderPoint for more detail)
   1704 
   1705  NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
   1706                   mBorderStyles[aSide] == StyleBorderStyle::Dotted,
   1707               "Style should be dashed or dotted.");
   1708 
   1709  Float borderWidth = mBorderWidths.Side(aSide);
   1710  if (borderWidth == 0.0f) {
   1711    return;
   1712  }
   1713 
   1714  if (mBorderStyles[aSide] == StyleBorderStyle::Dotted && borderWidth > 2.0f) {
   1715    DrawDottedSideSlow(aSide);
   1716    return;
   1717  }
   1718 
   1719  nscolor borderColor = mBorderColors[aSide];
   1720  bool ignored;
   1721  // Get the start and end points of the side, ensuring that any dot origins get
   1722  // pushed outward to account for stroking.
   1723  Point start =
   1724      GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &ignored, 0.5f);
   1725  Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &ignored, 0.5f);
   1726  if (borderWidth < 2.0f) {
   1727    // Round start to draw dot on each pixel.
   1728    if (IsHorizontalSide(aSide)) {
   1729      start.x = round(start.x);
   1730    } else {
   1731      start.y = round(start.y);
   1732    }
   1733  }
   1734 
   1735  Float borderLength = GetBorderLength(aSide, start, end);
   1736  if (borderLength < 0.0f) {
   1737    return;
   1738  }
   1739 
   1740  StrokeOptions strokeOptions(borderWidth);
   1741  Float dash[2];
   1742  SetupDashedOptions(&strokeOptions, dash, aSide, borderLength, false);
   1743 
   1744  // For dotted sides that can merge with their prior dotted sides, advance the
   1745  // dash offset to measure the distance around the combined path. This prevents
   1746  // two dots from bunching together at a corner.
   1747  mozilla::Side mergeSide = aSide;
   1748  while (IsCornerMergeable(GetCCWCorner(mergeSide))) {
   1749    mergeSide = PREV_SIDE(mergeSide);
   1750    // If we looped all the way around, measure starting at the top side, since
   1751    // we need to pick a fixed location to start measuring distance from still.
   1752    if (mergeSide == aSide) {
   1753      mergeSide = eSideTop;
   1754      break;
   1755    }
   1756  }
   1757  while (mergeSide != aSide) {
   1758    // Measure the length of the merged side starting from a possibly
   1759    // unmergeable corner up to the merged corner. A merged corner effectively
   1760    // has no border radius, so we can just use the cheaper AtCorner to find the
   1761    // end point.
   1762    Float mergeLength =
   1763        GetBorderLength(mergeSide,
   1764                        GetStraightBorderPoint(
   1765                            mergeSide, GetCCWCorner(mergeSide), &ignored, 0.5f),
   1766                        mOuterRect.AtCorner(GetCWCorner(mergeSide)));
   1767    // Add in the merged side length. Also offset the dash progress by an extra
   1768    // dot's width to avoid drawing a dot that would overdraw where the merged
   1769    // side would have ended in a gap, i.e. O_O_
   1770    //                                    O
   1771    strokeOptions.mDashOffset += mergeLength + borderWidth;
   1772    mergeSide = NEXT_SIDE(mergeSide);
   1773  }
   1774 
   1775  DrawOptions drawOptions;
   1776  if (mBorderStyles[aSide] == StyleBorderStyle::Dotted) {
   1777    drawOptions.mAntialiasMode = AntialiasMode::NONE;
   1778  }
   1779 
   1780  mDrawTarget->StrokeLine(start, end, ColorPattern(ToDeviceColor(borderColor)),
   1781                          strokeOptions, drawOptions);
   1782 }
   1783 
   1784 void nsCSSBorderRenderer::DrawDottedSideSlow(mozilla::Side aSide) {
   1785  // Draw each circles separately for dotted with borderWidth > 2.0.
   1786  // Dashed line with CapStyle::ROUND doesn't render perfect circles.
   1787 
   1788  NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted,
   1789               "Style should be dotted.");
   1790 
   1791  Float borderWidth = mBorderWidths.Side(aSide);
   1792  if (borderWidth == 0.0f) {
   1793    return;
   1794  }
   1795 
   1796  nscolor borderColor = mBorderColors[aSide];
   1797  bool isStartUnfilled, isEndUnfilled;
   1798  Point start =
   1799      GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &isStartUnfilled);
   1800  Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &isEndUnfilled);
   1801  enum {
   1802    // Corner is not mergeable.
   1803    NO_MERGE,
   1804 
   1805    // Corner between different colors.
   1806    // Two dots are merged into one, and both side draw half dot.
   1807    MERGE_HALF,
   1808 
   1809    // Corner between same colors, CCW corner of the side.
   1810    // Two dots are merged into one, and this side draw entire dot.
   1811    //
   1812    // MERGE_ALL               MERGE_NONE
   1813    //   |                       |
   1814    //   v                       v
   1815    // +-----------------------+----+
   1816    // | ##      ##      ##    | ## |
   1817    // |####    ####    ####   |####|
   1818    // |####    ####    ####   |####|
   1819    // | ##      ##      ##    | ## |
   1820    // +----+------------------+    |
   1821    // |    |                  |    |
   1822    // |    |                  |    |
   1823    // |    |                  |    |
   1824    // | ## |                  | ## |
   1825    // |####|                  |####|
   1826    MERGE_ALL,
   1827 
   1828    // Corner between same colors, CW corner of the side.
   1829    // Two dots are merged into one, and this side doesn't draw dot.
   1830    MERGE_NONE
   1831  } mergeStart = NO_MERGE,
   1832    mergeEnd = NO_MERGE;
   1833 
   1834  if (IsCornerMergeable(GetCCWCorner(aSide))) {
   1835    if (borderColor == mBorderColors[PREV_SIDE(aSide)]) {
   1836      mergeStart = MERGE_ALL;
   1837    } else {
   1838      mergeStart = MERGE_HALF;
   1839    }
   1840  }
   1841 
   1842  if (IsCornerMergeable(GetCWCorner(aSide))) {
   1843    if (borderColor == mBorderColors[NEXT_SIDE(aSide)]) {
   1844      mergeEnd = MERGE_NONE;
   1845    } else {
   1846      mergeEnd = MERGE_HALF;
   1847    }
   1848  }
   1849 
   1850  Float borderLength = GetBorderLength(aSide, start, end);
   1851  if (borderLength < 0.0f) {
   1852    if (isStartUnfilled || isEndUnfilled) {
   1853      return;
   1854    }
   1855    borderLength = 0.0f;
   1856    start = end = (start + end) / 2.0f;
   1857  }
   1858 
   1859  Float dotWidth = borderWidth * DOT_LENGTH;
   1860  Float radius = borderWidth / 2.0f;
   1861  if (borderLength < dotWidth) {
   1862    // If dots on start and end may overlap, draw a dot at the middle of them.
   1863    //
   1864    //     ___---+-------+---___
   1865    // __--      | ##### |      --__
   1866    //          #|#######|#
   1867    //         ##|#######|##
   1868    //        ###|#######|###
   1869    //        ###+###+###+###
   1870    //         start ## end #
   1871    //         ##|#######|##
   1872    //          #|#######|#
   1873    //           | ##### |
   1874    //       __--+-------+--__
   1875    //     _-                 -_
   1876    //
   1877    // If that circle overflows from outer rect, do not draw it.
   1878    //
   1879    //           +-------+
   1880    //           | ##### |
   1881    //          #|#######|#
   1882    //         ##|#######|##
   1883    //        ###|#######|###
   1884    //        ###|###+###|###
   1885    //        ###|#######|###
   1886    //         ##|#######|##
   1887    //          #|#######|#
   1888    //           | ##### |
   1889    //           +--+-+--+
   1890    //           |  | |  |
   1891    //           |  | |  |
   1892    if (!mOuterRect.Contains(Rect(start.x - radius, start.y - radius,
   1893                                  borderWidth, borderWidth))) {
   1894      return;
   1895    }
   1896 
   1897    if (isStartUnfilled || isEndUnfilled) {
   1898      return;
   1899    }
   1900 
   1901    Point P = (start + end) / 2;
   1902    RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
   1903    builder->MoveTo(Point(P.x + radius, P.y));
   1904    builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
   1905    RefPtr<Path> path = builder->Finish();
   1906    mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
   1907    return;
   1908  }
   1909 
   1910  if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
   1911    // MERGE_HALF
   1912    //               Eo
   1913    //   -------+----+
   1914    //        ##### /
   1915    //       ######/
   1916    //      ######/
   1917    //      ####+
   1918    //      ##/ end
   1919    //       /
   1920    //      /
   1921    //   --+
   1922    //     Ei
   1923    //
   1924    // other (NO_MERGE, MERGE_ALL, MERGE_NONE)
   1925    //               Eo
   1926    //   ------------+
   1927    //        #####  |
   1928    //       ####### |
   1929    //      #########|
   1930    //      ####+####|
   1931    //      ## end ##|
   1932    //       ####### |
   1933    //        #####  |
   1934    //   ------------+
   1935    //               Ei
   1936 
   1937    Point I(0.0f, 0.0f), J(0.0f, 0.0f);
   1938    if (aSide == eSideTop) {
   1939      I.x = 1.0f;
   1940      J.y = 1.0f;
   1941    } else if (aSide == eSideRight) {
   1942      I.y = 1.0f;
   1943      J.x = -1.0f;
   1944    } else if (aSide == eSideBottom) {
   1945      I.x = -1.0f;
   1946      J.y = -1.0f;
   1947    } else if (aSide == eSideLeft) {
   1948      I.y = -1.0f;
   1949      J.x = 1.0f;
   1950    }
   1951 
   1952    Point So, Si, Eo, Ei;
   1953 
   1954    So = (start + (-I + -J) * borderWidth / 2.0f);
   1955    Si = (mergeStart == MERGE_HALF) ? (start + (I + J) * borderWidth / 2.0f)
   1956                                    : (start + (-I + J) * borderWidth / 2.0f);
   1957    Eo = (end + (I - J) * borderWidth / 2.0f);
   1958    Ei = (mergeEnd == MERGE_HALF) ? (end + (-I + J) * borderWidth / 2.0f)
   1959                                  : (end + (I + J) * borderWidth / 2.0f);
   1960 
   1961    RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
   1962    builder->MoveTo(So);
   1963    builder->LineTo(Eo);
   1964    builder->LineTo(Ei);
   1965    builder->LineTo(Si);
   1966    builder->Close();
   1967    RefPtr<Path> path = builder->Finish();
   1968 
   1969    mDrawTarget->PushClip(path);
   1970  }
   1971 
   1972  size_t count = round(borderLength / dotWidth);
   1973  if (isStartUnfilled == isEndUnfilled) {
   1974    // Split into 2n segments.
   1975    if (count % 2) {
   1976      count++;
   1977    }
   1978  } else {
   1979    // Split into 2n+1 segments.
   1980    if (count % 2 == 0) {
   1981      count++;
   1982    }
   1983  }
   1984 
   1985  // A: radius == borderWidth / 2.0
   1986  // B: borderLength / count == borderWidth * (1 - overlap)
   1987  //
   1988  //   A      B         B        B        B     A
   1989  // |<-->|<------>|<------>|<------>|<------>|<-->|
   1990  // |    |        |        |        |        |    |
   1991  // +----+--------+--------+--------+--------+----+
   1992  // |  ##|##    **|**    ##|##    **|**    ##|##  |
   1993  // | ###|###  ***|***  ###|###  ***|***  ###|### |
   1994  // |####|####****|****####|####****|****####|####|
   1995  // |####+####****+****####+####****+****####+####|
   1996  // |# start #****|****####|####****|****## end ##|
   1997  // | ###|###  ***|***  ###|###  ***|***  ###|### |
   1998  // |  ##|##    **|**    ##|##    **|**    ##|##  |
   1999  // +----+----+---+--------+--------+---+----+----+
   2000  // |         |                         |         |
   2001  // |         |                         |         |
   2002 
   2003  // If isStartUnfilled is true, draw dots on 2j+1 points, if not, draw dots on
   2004  // 2j points.
   2005  size_t from = isStartUnfilled ? 1 : 0;
   2006 
   2007  // If mergeEnd == MERGE_NONE, last dot is drawn by next side.
   2008  size_t to = count;
   2009  if (mergeEnd == MERGE_NONE) {
   2010    if (to > 2) {
   2011      to -= 2;
   2012    } else {
   2013      to = 0;
   2014    }
   2015  }
   2016 
   2017  Point fromP = (start * (count - from) + end * from) / count;
   2018  Point toP = (start * (count - to) + end * to) / count;
   2019  // Extend dirty rect to avoid clipping pixel for anti-aliasing.
   2020  const Float AA_MARGIN = 2.0f;
   2021 
   2022  // The following algorithm assumes the border's rect and the dirty rect
   2023  // intersect.
   2024  MOZ_ASSERT(mDirtyRect.Intersects(mOuterRect));
   2025 
   2026  if (aSide == eSideTop) {
   2027    // Tweak |from| and |to| to fit into |mDirtyRect + radius margin|,
   2028    // to render only paths that may overlap mDirtyRect.
   2029    //
   2030    //                mDirtyRect + radius margin
   2031    //              +--+---------------------+--+
   2032    //              |                           |
   2033    //              |         mDirtyRect        |
   2034    //              +  +---------------------+  +
   2035    // from   ===>  |from                    to |   <===  to
   2036    //    +-----+-----+-----+-----+-----+-----+-----+-----+
   2037    //   ###        |###         ###         ###|        ###
   2038    //  #####       #####       #####       #####       #####
   2039    //  #####       #####       #####       #####       #####
   2040    //  #####       #####       #####       #####       #####
   2041    //   ###        |###         ###         ###|        ###
   2042    //              |  |                     |  |
   2043    //              +  +---------------------+  +
   2044    //              |                           |
   2045    //              |                           |
   2046    //              +--+---------------------+--+
   2047 
   2048    Float left = mDirtyRect.x - radius - AA_MARGIN;
   2049    if (fromP.x < left) {
   2050      size_t tmp = ceil(count * (left - start.x) / (end.x - start.x));
   2051      if (tmp > from) {
   2052        // We increment by 2, so odd/even should match between before/after.
   2053        if ((tmp & 1) != (from & 1)) {
   2054          from = tmp - 1;
   2055        } else {
   2056          from = tmp;
   2057        }
   2058      }
   2059    }
   2060    Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
   2061    if (toP.x > right) {
   2062      size_t tmp = floor(count * (right - start.x) / (end.x - start.x));
   2063      if (tmp < to) {
   2064        if ((tmp & 1) != (to & 1)) {
   2065          to = tmp + 1;
   2066        } else {
   2067          to = tmp;
   2068        }
   2069      }
   2070    }
   2071  } else if (aSide == eSideRight) {
   2072    Float top = mDirtyRect.y - radius - AA_MARGIN;
   2073    if (fromP.y < top) {
   2074      size_t tmp = ceil(count * (top - start.y) / (end.y - start.y));
   2075      if (tmp > from) {
   2076        if ((tmp & 1) != (from & 1)) {
   2077          from = tmp - 1;
   2078        } else {
   2079          from = tmp;
   2080        }
   2081      }
   2082    }
   2083    Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
   2084    if (toP.y > bottom) {
   2085      size_t tmp = floor(count * (bottom - start.y) / (end.y - start.y));
   2086      if (tmp < to) {
   2087        if ((tmp & 1) != (to & 1)) {
   2088          to = tmp + 1;
   2089        } else {
   2090          to = tmp;
   2091        }
   2092      }
   2093    }
   2094  } else if (aSide == eSideBottom) {
   2095    Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
   2096    if (fromP.x > right) {
   2097      size_t tmp = ceil(count * (right - start.x) / (end.x - start.x));
   2098      if (tmp > from) {
   2099        if ((tmp & 1) != (from & 1)) {
   2100          from = tmp - 1;
   2101        } else {
   2102          from = tmp;
   2103        }
   2104      }
   2105    }
   2106    Float left = mDirtyRect.x - radius - AA_MARGIN;
   2107    if (toP.x < left) {
   2108      size_t tmp = floor(count * (left - start.x) / (end.x - start.x));
   2109      if (tmp < to) {
   2110        if ((tmp & 1) != (to & 1)) {
   2111          to = tmp + 1;
   2112        } else {
   2113          to = tmp;
   2114        }
   2115      }
   2116    }
   2117  } else if (aSide == eSideLeft) {
   2118    Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
   2119    if (fromP.y > bottom) {
   2120      size_t tmp = ceil(count * (bottom - start.y) / (end.y - start.y));
   2121      if (tmp > from) {
   2122        if ((tmp & 1) != (from & 1)) {
   2123          from = tmp - 1;
   2124        } else {
   2125          from = tmp;
   2126        }
   2127      }
   2128    }
   2129    Float top = mDirtyRect.y - radius - AA_MARGIN;
   2130    if (toP.y < top) {
   2131      size_t tmp = floor(count * (top - start.y) / (end.y - start.y));
   2132      if (tmp < to) {
   2133        if ((tmp & 1) != (to & 1)) {
   2134          to = tmp + 1;
   2135        } else {
   2136          to = tmp;
   2137        }
   2138      }
   2139    }
   2140  }
   2141 
   2142  RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
   2143  size_t segmentCount = 0;
   2144  for (size_t i = from; i <= to; i += 2) {
   2145    if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
   2146      RefPtr<Path> path = builder->Finish();
   2147      mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
   2148      builder = mDrawTarget->CreatePathBuilder();
   2149      segmentCount = 0;
   2150    }
   2151 
   2152    Point P = (start * (count - i) + end * i) / count;
   2153    builder->MoveTo(Point(P.x + radius, P.y));
   2154    builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
   2155    segmentCount++;
   2156  }
   2157  RefPtr<Path> path = builder->Finish();
   2158  mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
   2159 
   2160  if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
   2161    mDrawTarget->PopClip();
   2162  }
   2163 }
   2164 
   2165 void nsCSSBorderRenderer::DrawDashedOrDottedCorner(mozilla::Side aSide,
   2166                                                   Corner aCorner) {
   2167  // Draw dashed/dotted corner with following approach.
   2168  //
   2169  // dashed corner
   2170  //   If both side has same border-width and border-width <= 2.0, draw dashed
   2171  //   line along the corner, with appropriate dash length and gap to make the
   2172  //   corner symmetric as far as possible.  Dash length equals to the gap, and
   2173  //   the ratio of the dash length to border-width is the maximum value in in
   2174  //   [1, 3] range.
   2175  //   Otherwise, draw dashed segments along the corner, keeping same dash
   2176  //   length ratio to border-width at that point.
   2177  //   (see DashedCornerFinder.h for more detail)
   2178  //   Line ends with half segments, to joint with both side easily.
   2179  //
   2180  // dotted corner
   2181  //   If both side has same border-width and border-width <= 2.0, draw 1:1
   2182  //   dashed line along the corner.
   2183  //   Otherwise Draw circles along the corner, with appropriate gap that makes
   2184  //   the corner symmetric as far as possible.  The size of the circle may
   2185  //   change along the corner, that is tangent to the outer curver and the
   2186  //   inner curve.  The ratio of the gap to circle diameter is the maximum
   2187  //   value in [0.5, 1] range.
   2188  //   (see DottedCornerFinder.h for more detail)
   2189  //   Corner ends with filled dots but those dots are drawn by
   2190  //   DrawDashedOrDottedSide.  So this may draw no circles if there's no space
   2191  //   between 2 dots at both ends.
   2192 
   2193  NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
   2194                   mBorderStyles[aSide] == StyleBorderStyle::Dotted,
   2195               "Style should be dashed or dotted.");
   2196 
   2197  if (IsCornerMergeable(aCorner)) {
   2198    // DrawDashedOrDottedSide will draw corner.
   2199    return;
   2200  }
   2201 
   2202  mozilla::Side sideH(GetHorizontalSide(aCorner));
   2203  mozilla::Side sideV(GetVerticalSide(aCorner));
   2204  Float borderWidthH = mBorderWidths.Side(sideH);
   2205  Float borderWidthV = mBorderWidths.Side(sideV);
   2206  if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
   2207    return;
   2208  }
   2209 
   2210  StyleBorderStyle styleH = mBorderStyles[sideH];
   2211  StyleBorderStyle styleV = mBorderStyles[sideV];
   2212 
   2213  // Corner between dotted and others with radius=0 is drawn by side.
   2214  if (IsZeroSize(mBorderRadii[aCorner]) &&
   2215      (styleV == StyleBorderStyle::Dotted ||
   2216       styleH == StyleBorderStyle::Dotted)) {
   2217    return;
   2218  }
   2219 
   2220  Float maxRadius =
   2221      std::max(mBorderRadii[aCorner].width, mBorderRadii[aCorner].height);
   2222  if (maxRadius > BORDER_DOTTED_CORNER_MAX_RADIUS) {
   2223    DrawFallbackSolidCorner(aSide, aCorner);
   2224    return;
   2225  }
   2226 
   2227  if (borderWidthH != borderWidthV || borderWidthH > 2.0f) {
   2228    StyleBorderStyle style = mBorderStyles[aSide];
   2229    if (style == StyleBorderStyle::Dotted) {
   2230      DrawDottedCornerSlow(aSide, aCorner);
   2231    } else {
   2232      DrawDashedCornerSlow(aSide, aCorner);
   2233    }
   2234    return;
   2235  }
   2236 
   2237  nscolor borderColor = mBorderColors[aSide];
   2238  Point points[4];
   2239  bool ignored;
   2240  // Get the start and end points of the corner arc, ensuring that any dot
   2241  // origins get pushed backwards towards the edges of the corner rect to
   2242  // account for stroking.
   2243  points[0] = GetStraightBorderPoint(sideH, aCorner, &ignored, -0.5f);
   2244  points[3] = GetStraightBorderPoint(sideV, aCorner, &ignored, -0.5f);
   2245  // Round points to draw dot on each pixel.
   2246  if (borderWidthH < 2.0f) {
   2247    points[0].x = round(points[0].x);
   2248  }
   2249  if (borderWidthV < 2.0f) {
   2250    points[3].y = round(points[3].y);
   2251  }
   2252  points[1] = points[0];
   2253  points[1].x += kKappaFactor * (points[3].x - points[0].x);
   2254  points[2] = points[3];
   2255  points[2].y += kKappaFactor * (points[0].y - points[3].y);
   2256 
   2257  Float len = GetQuarterEllipticArcLength(fabs(points[0].x - points[3].x),
   2258                                          fabs(points[0].y - points[3].y));
   2259 
   2260  Float dash[2];
   2261  StrokeOptions strokeOptions(borderWidthH);
   2262  SetupDashedOptions(&strokeOptions, dash, aSide, len, true);
   2263 
   2264  RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
   2265  builder->MoveTo(points[0]);
   2266  builder->BezierTo(points[1], points[2], points[3]);
   2267  RefPtr<Path> path = builder->Finish();
   2268  mDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
   2269                      strokeOptions);
   2270 }
   2271 
   2272 void nsCSSBorderRenderer::DrawDottedCornerSlow(mozilla::Side aSide,
   2273                                               Corner aCorner) {
   2274  NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted,
   2275               "Style should be dotted.");
   2276 
   2277  mozilla::Side sideH(GetHorizontalSide(aCorner));
   2278  mozilla::Side sideV(GetVerticalSide(aCorner));
   2279  Float R0 = mBorderWidths.Side(sideH) / 2.0f;
   2280  Float Rn = mBorderWidths.Side(sideV) / 2.0f;
   2281  if (R0 == 0.0f && Rn == 0.0f) {
   2282    return;
   2283  }
   2284 
   2285  nscolor borderColor = mBorderColors[aSide];
   2286  Bezier outerBezier;
   2287  Bezier innerBezier;
   2288  GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
   2289 
   2290  bool ignored;
   2291  Point C0 = GetStraightBorderPoint(sideH, aCorner, &ignored);
   2292  Point Cn = GetStraightBorderPoint(sideV, aCorner, &ignored);
   2293  DottedCornerFinder finder(outerBezier, innerBezier, aCorner,
   2294                            mBorderRadii[aCorner].width,
   2295                            mBorderRadii[aCorner].height, C0, R0, Cn, Rn,
   2296                            mBorderCornerDimensions[aCorner]);
   2297 
   2298  RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
   2299  size_t segmentCount = 0;
   2300  const Float AA_MARGIN = 2.0f;
   2301  Rect marginedDirtyRect = mDirtyRect;
   2302  marginedDirtyRect.Inflate(std::max(R0, Rn) + AA_MARGIN);
   2303  bool entered = false;
   2304  while (finder.HasMore()) {
   2305    if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
   2306      RefPtr<Path> path = builder->Finish();
   2307      mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
   2308      builder = mDrawTarget->CreatePathBuilder();
   2309      segmentCount = 0;
   2310    }
   2311 
   2312    DottedCornerFinder::Result result = finder.Next();
   2313 
   2314    if (marginedDirtyRect.Contains(result.C) && result.r > 0) {
   2315      entered = true;
   2316      builder->MoveTo(Point(result.C.x + result.r, result.C.y));
   2317      builder->Arc(result.C, result.r, 0, Float(2.0 * M_PI));
   2318      segmentCount++;
   2319    } else if (entered) {
   2320      break;
   2321    }
   2322  }
   2323  RefPtr<Path> path = builder->Finish();
   2324  mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
   2325 }
   2326 
   2327 static inline bool DashedPathOverlapsRect(Rect& pathRect,
   2328                                          const Rect& marginedDirtyRect,
   2329                                          DashedCornerFinder::Result& result) {
   2330  // Calculate a rect that contains all control points of the |result| path,
   2331  // and check if it intersects with |marginedDirtyRect|.
   2332  pathRect.SetRect(result.outerSectionBezier.mPoints[0].x,
   2333                   result.outerSectionBezier.mPoints[0].y, 0, 0);
   2334  pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[1]);
   2335  pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[2]);
   2336  pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[3]);
   2337  pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[0]);
   2338  pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[1]);
   2339  pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[2]);
   2340  pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[3]);
   2341 
   2342  return pathRect.Intersects(marginedDirtyRect);
   2343 }
   2344 
   2345 void nsCSSBorderRenderer::DrawDashedCornerSlow(mozilla::Side aSide,
   2346                                               Corner aCorner) {
   2347  NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed,
   2348               "Style should be dashed.");
   2349 
   2350  mozilla::Side sideH(GetHorizontalSide(aCorner));
   2351  mozilla::Side sideV(GetVerticalSide(aCorner));
   2352  Float borderWidthH = mBorderWidths.Side(sideH);
   2353  Float borderWidthV = mBorderWidths.Side(sideV);
   2354  if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
   2355    return;
   2356  }
   2357 
   2358  nscolor borderColor = mBorderColors[aSide];
   2359  Bezier outerBezier;
   2360  Bezier innerBezier;
   2361  GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
   2362 
   2363  DashedCornerFinder finder(outerBezier, innerBezier, borderWidthH,
   2364                            borderWidthV, mBorderCornerDimensions[aCorner]);
   2365 
   2366  RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
   2367  size_t segmentCount = 0;
   2368  const Float AA_MARGIN = 2.0f;
   2369  Rect marginedDirtyRect = mDirtyRect;
   2370  marginedDirtyRect.Inflate(AA_MARGIN);
   2371  Rect pathRect;
   2372  bool entered = false;
   2373  while (finder.HasMore()) {
   2374    if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
   2375      RefPtr<Path> path = builder->Finish();
   2376      mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
   2377      builder = mDrawTarget->CreatePathBuilder();
   2378      segmentCount = 0;
   2379    }
   2380 
   2381    DashedCornerFinder::Result result = finder.Next();
   2382 
   2383    if (DashedPathOverlapsRect(pathRect, marginedDirtyRect, result)) {
   2384      entered = true;
   2385      builder->MoveTo(result.outerSectionBezier.mPoints[0]);
   2386      builder->BezierTo(result.outerSectionBezier.mPoints[1],
   2387                        result.outerSectionBezier.mPoints[2],
   2388                        result.outerSectionBezier.mPoints[3]);
   2389      builder->LineTo(result.innerSectionBezier.mPoints[3]);
   2390      builder->BezierTo(result.innerSectionBezier.mPoints[2],
   2391                        result.innerSectionBezier.mPoints[1],
   2392                        result.innerSectionBezier.mPoints[0]);
   2393      builder->LineTo(result.outerSectionBezier.mPoints[0]);
   2394      segmentCount++;
   2395    } else if (entered) {
   2396      break;
   2397    }
   2398  }
   2399 
   2400  if (outerBezier.mPoints[0].x != innerBezier.mPoints[0].x) {
   2401    // Fill gap before the first section.
   2402    //
   2403    //     outnerPoint[0]
   2404    //         |
   2405    //         v
   2406    //        _+-----------+--
   2407    //      /   \##########|
   2408    //    /      \#########|
   2409    //   +        \########|
   2410    //   |\         \######|
   2411    //   |  \        \#####|
   2412    //   |    \       \####|
   2413    //   |      \       \##|
   2414    //   |        \      \#|
   2415    //   |          \     \|
   2416    //   |            \  _-+--
   2417    //   +--------------+  ^
   2418    //   |              |  |
   2419    //   |              |  innerPoint[0]
   2420    //   |              |
   2421    builder->MoveTo(outerBezier.mPoints[0]);
   2422    builder->LineTo(innerBezier.mPoints[0]);
   2423    builder->LineTo(Point(innerBezier.mPoints[0].x, outerBezier.mPoints[0].y));
   2424    builder->LineTo(outerBezier.mPoints[0]);
   2425  }
   2426 
   2427  if (outerBezier.mPoints[3].y != innerBezier.mPoints[3].y) {
   2428    // Fill gap after the last section.
   2429    //
   2430    // outnerPoint[3]
   2431    //   |
   2432    //   |
   2433    //   |    _+-----------+--
   2434    //   |  /   \          |
   2435    //   v/      \         |
   2436    //   +        \        |
   2437    //   |\         \      |
   2438    //   |##\        \     |
   2439    //   |####\       \    |
   2440    //   |######\       \  |
   2441    //   |########\      \ |
   2442    //   |##########\     \|
   2443    //   |############\  _-+--
   2444    //   +--------------+<-- innerPoint[3]
   2445    //   |              |
   2446    //   |              |
   2447    //   |              |
   2448    builder->MoveTo(outerBezier.mPoints[3]);
   2449    builder->LineTo(innerBezier.mPoints[3]);
   2450    builder->LineTo(Point(outerBezier.mPoints[3].x, innerBezier.mPoints[3].y));
   2451    builder->LineTo(outerBezier.mPoints[3]);
   2452  }
   2453 
   2454  RefPtr<Path> path = builder->Finish();
   2455  mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
   2456 }
   2457 
   2458 void nsCSSBorderRenderer::DrawFallbackSolidCorner(mozilla::Side aSide,
   2459                                                  Corner aCorner) {
   2460  // Render too large dashed or dotted corner with solid style, to avoid hangup
   2461  // inside DashedCornerFinder and DottedCornerFinder.
   2462 
   2463  NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
   2464                   mBorderStyles[aSide] == StyleBorderStyle::Dotted,
   2465               "Style should be dashed or dotted.");
   2466 
   2467  nscolor borderColor = mBorderColors[aSide];
   2468  Bezier outerBezier;
   2469  Bezier innerBezier;
   2470  GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
   2471 
   2472  RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
   2473 
   2474  builder->MoveTo(outerBezier.mPoints[0]);
   2475  builder->BezierTo(outerBezier.mPoints[1], outerBezier.mPoints[2],
   2476                    outerBezier.mPoints[3]);
   2477  builder->LineTo(innerBezier.mPoints[3]);
   2478  builder->BezierTo(innerBezier.mPoints[2], innerBezier.mPoints[1],
   2479                    innerBezier.mPoints[0]);
   2480  builder->LineTo(outerBezier.mPoints[0]);
   2481 
   2482  RefPtr<Path> path = builder->Finish();
   2483  mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
   2484 
   2485  if (!mPresContext->HasWarnedAboutTooLargeDashedOrDottedRadius()) {
   2486    mPresContext->SetHasWarnedAboutTooLargeDashedOrDottedRadius();
   2487    nsContentUtils::ReportToConsole(
   2488        nsIScriptError::warningFlag, "CSS"_ns, mPresContext->Document(),
   2489        nsContentUtils::eCSS_PROPERTIES,
   2490        mBorderStyles[aSide] == StyleBorderStyle::Dashed
   2491            ? "TooLargeDashedRadius"
   2492            : "TooLargeDottedRadius");
   2493  }
   2494 }
   2495 
   2496 bool nsCSSBorderRenderer::AllBordersSolid() {
   2497  for (const auto i : mozilla::AllPhysicalSides()) {
   2498    if (mBorderStyles[i] == StyleBorderStyle::Solid ||
   2499        mBorderStyles[i] == StyleBorderStyle::None ||
   2500        mBorderStyles[i] == StyleBorderStyle::Hidden) {
   2501      continue;
   2502    }
   2503    return false;
   2504  }
   2505 
   2506  return true;
   2507 }
   2508 
   2509 static bool IsVisible(StyleBorderStyle aStyle) {
   2510  if (aStyle != StyleBorderStyle::None && aStyle != StyleBorderStyle::Hidden) {
   2511    return true;
   2512  }
   2513  return false;
   2514 }
   2515 
   2516 struct twoFloats {
   2517  Float a, b;
   2518 
   2519  twoFloats operator*(const Size& aSize) const {
   2520    return {a * aSize.width, b * aSize.height};
   2521  }
   2522 
   2523  twoFloats operator*(Float aScale) const { return {a * aScale, b * aScale}; }
   2524 
   2525  twoFloats operator+(const Point& aPoint) const {
   2526    return {a + aPoint.x, b + aPoint.y};
   2527  }
   2528 
   2529  operator Point() const { return Point(a, b); }
   2530 };
   2531 
   2532 void nsCSSBorderRenderer::DrawSingleWidthSolidBorder() {
   2533  // Easy enough to deal with.
   2534  Rect rect = mOuterRect;
   2535  rect.Deflate(0.5);
   2536 
   2537  const twoFloats cornerAdjusts[4] = {
   2538      {+0.5, 0}, {0, +0.5}, {-0.5, 0}, {0, -0.5}};
   2539  for (const auto side : mozilla::AllPhysicalSides()) {
   2540    Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
   2541    Point secondCorner = rect.CWCorner(side) + cornerAdjusts[side];
   2542 
   2543    ColorPattern color(ToDeviceColor(mBorderColors[side]));
   2544 
   2545    mDrawTarget->StrokeLine(firstCorner, secondCorner, color);
   2546  }
   2547 }
   2548 
   2549 // Intersect a ray from the inner corner to the outer corner
   2550 // with the border radius, yielding the intersection point.
   2551 static Point IntersectBorderRadius(const Point& aCenter, const Size& aRadius,
   2552                                   const Point& aInnerCorner,
   2553                                   const Point& aCornerDirection) {
   2554  Point toCorner = aCornerDirection;
   2555  // transform to-corner ray to unit-circle space
   2556  toCorner.x /= aRadius.width;
   2557  toCorner.y /= aRadius.height;
   2558  // normalize to-corner ray
   2559  Float cornerDist = toCorner.Length();
   2560  if (cornerDist < 1.0e-6f) {
   2561    return aInnerCorner;
   2562  }
   2563  toCorner = toCorner / cornerDist;
   2564  // ray from inner corner to border radius center
   2565  Point toCenter = aCenter - aInnerCorner;
   2566  // transform to-center ray to unit-circle space
   2567  toCenter.x /= aRadius.width;
   2568  toCenter.y /= aRadius.height;
   2569  // compute offset of intersection with border radius unit circle
   2570  Float offset = toCenter.DotProduct(toCorner);
   2571  // compute discriminant to check for intersections
   2572  Float discrim = 1.0f - toCenter.DotProduct(toCenter) + offset * offset;
   2573  // choose farthest intersection
   2574  offset += sqrtf(std::max(discrim, 0.0f));
   2575  // transform to-corner ray back out of unit-circle space
   2576  toCorner.x *= aRadius.width;
   2577  toCorner.y *= aRadius.height;
   2578  return aInnerCorner + toCorner * offset;
   2579 }
   2580 
   2581 // Calculate the split point and split angle for a border radius with
   2582 // differing sides.
   2583 static inline void SplitBorderRadius(const Point& aCenter, const Size& aRadius,
   2584                                     const Point& aOuterCorner,
   2585                                     const Point& aInnerCorner,
   2586                                     const twoFloats& aCornerMults,
   2587                                     Float aStartAngle, Point& aSplit,
   2588                                     Float& aSplitAngle) {
   2589  Point cornerDir = aOuterCorner - aInnerCorner;
   2590  if (cornerDir.x == cornerDir.y && aRadius.IsSquare()) {
   2591    // optimize 45-degree intersection with circle since we can assume
   2592    // the circle center lies along the intersection edge
   2593    aSplit = aCenter - aCornerMults * (aRadius * Float(1.0f / M_SQRT2));
   2594    aSplitAngle = aStartAngle + 0.5f * M_PI / 2.0f;
   2595  } else {
   2596    aSplit = IntersectBorderRadius(aCenter, aRadius, aInnerCorner, cornerDir);
   2597    aSplitAngle = atan2f((aSplit.y - aCenter.y) / aRadius.height,
   2598                         (aSplit.x - aCenter.x) / aRadius.width);
   2599  }
   2600 }
   2601 
   2602 // Compute the size of the skirt needed, given the color alphas
   2603 // of each corner side and the slope between them.
   2604 static void ComputeCornerSkirtSize(Float aAlpha1, Float aAlpha2, Float aSlopeY,
   2605                                   Float aSlopeX, Float& aSizeResult,
   2606                                   Float& aSlopeResult) {
   2607  // If either side is (almost) invisible or there is no diagonal edge,
   2608  // then don't try to render a skirt.
   2609  if (aAlpha1 < 0.01f || aAlpha2 < 0.01f) {
   2610    return;
   2611  }
   2612  aSlopeX = fabs(aSlopeX);
   2613  aSlopeY = fabs(aSlopeY);
   2614  if (aSlopeX < 1.0e-6f || aSlopeY < 1.0e-6f) {
   2615    return;
   2616  }
   2617 
   2618  // If first and second color don't match, we need to split the corner in
   2619  // half. The diagonal edges created may not have full pixel coverage given
   2620  // anti-aliasing, so we need to compute a small subpixel skirt edge. This
   2621  // assumes each half has half coverage to start with, and that coverage
   2622  // increases as the skirt is pushed over, with the end result that we want
   2623  // to roughly preserve the alpha value along this edge.
   2624  // Given slope m, alphas a and A, use quadratic formula to solve for S in:
   2625  //   a*(1 - 0.5*(1-S)*(1-mS))*(1 - 0.5*A) + 0.5*A = A
   2626  // yielding:
   2627  //   S = ((1+m) - sqrt((1+m)*(1+m) + 4*m*(1 - A/(a*(1-0.5*A))))) / (2*m)
   2628  // and substitute k = (1+m)/(2*m):
   2629  //   S = k - sqrt(k*k + (1 - A/(a*(1-0.5*A)))/m)
   2630  Float slope = aSlopeY / aSlopeX;
   2631  Float slopeScale = (1.0f + slope) / (2.0f * slope);
   2632  Float discrim = slopeScale * slopeScale +
   2633                  (1 - aAlpha2 / (aAlpha1 * (1.0f - 0.49f * aAlpha2))) / slope;
   2634  if (discrim >= 0) {
   2635    aSizeResult = slopeScale - sqrtf(discrim);
   2636    aSlopeResult = slope;
   2637  }
   2638 }
   2639 
   2640 // Draws a border radius with possibly different sides.
   2641 // A skirt is drawn underneath the corner intersection to hide possible
   2642 // seams when anti-aliased drawing is used.
   2643 static void DrawBorderRadius(
   2644    DrawTarget* aDrawTarget, Corner c, const Point& aOuterCorner,
   2645    const Point& aInnerCorner, const twoFloats& aCornerMultPrev,
   2646    const twoFloats& aCornerMultNext, const Size& aCornerDims,
   2647    const Size& aOuterRadius, const Size& aInnerRadius,
   2648    const DeviceColor& aFirstColor, const DeviceColor& aSecondColor,
   2649    Float aSkirtSize, Float aSkirtSlope) {
   2650  // Connect edge to outer arc start point
   2651  Point outerCornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
   2652  // Connect edge to outer arc end point
   2653  Point outerCornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
   2654  // Connect edge to inner arc start point
   2655  Point innerCornerStart =
   2656      outerCornerStart + aCornerMultNext * (aCornerDims - aInnerRadius);
   2657  // Connect edge to inner arc end point
   2658  Point innerCornerEnd =
   2659      outerCornerEnd + aCornerMultPrev * (aCornerDims - aInnerRadius);
   2660 
   2661  // Outer arc start point
   2662  Point outerArcStart = aOuterCorner + aCornerMultPrev * aOuterRadius;
   2663  // Outer arc end point
   2664  Point outerArcEnd = aOuterCorner + aCornerMultNext * aOuterRadius;
   2665  // Inner arc start point
   2666  Point innerArcStart = aInnerCorner + aCornerMultPrev * aInnerRadius;
   2667  // Inner arc end point
   2668  Point innerArcEnd = aInnerCorner + aCornerMultNext * aInnerRadius;
   2669 
   2670  // Outer radius center
   2671  Point outerCenter =
   2672      aOuterCorner + (aCornerMultPrev + aCornerMultNext) * aOuterRadius;
   2673  // Inner radius center
   2674  Point innerCenter =
   2675      aInnerCorner + (aCornerMultPrev + aCornerMultNext) * aInnerRadius;
   2676 
   2677  RefPtr<PathBuilder> builder;
   2678  RefPtr<Path> path;
   2679 
   2680  if (aFirstColor.a > 0) {
   2681    builder = aDrawTarget->CreatePathBuilder();
   2682    builder->MoveTo(outerCornerStart);
   2683  }
   2684 
   2685  if (aFirstColor != aSecondColor) {
   2686    // Start and end angles of corner quadrant
   2687    constexpr float PIf = M_PI;
   2688    Float startAngle = (static_cast<float>(c) * PIf) / 2.0f - PIf;
   2689    Float endAngle = startAngle + PIf / 2.0f;
   2690    Float outerSplitAngle, innerSplitAngle;
   2691    Point outerSplit, innerSplit;
   2692 
   2693    // Outer half-way point
   2694    SplitBorderRadius(outerCenter, aOuterRadius, aOuterCorner, aInnerCorner,
   2695                      aCornerMultPrev + aCornerMultNext, startAngle, outerSplit,
   2696                      outerSplitAngle);
   2697    // Inner half-way point
   2698    if (aInnerRadius.IsEmpty()) {
   2699      innerSplit = aInnerCorner;
   2700      innerSplitAngle = endAngle;
   2701    } else {
   2702      SplitBorderRadius(innerCenter, aInnerRadius, aOuterCorner, aInnerCorner,
   2703                        aCornerMultPrev + aCornerMultNext, startAngle,
   2704                        innerSplit, innerSplitAngle);
   2705    }
   2706 
   2707    // Draw first half with first color
   2708    if (aFirstColor.a > 0) {
   2709      AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart,
   2710                       outerSplit, startAngle, outerSplitAngle);
   2711      // Draw skirt as part of first half
   2712      if (aSkirtSize > 0) {
   2713        builder->LineTo(outerSplit + aCornerMultNext * aSkirtSize);
   2714        builder->LineTo(innerSplit -
   2715                        aCornerMultPrev * (aSkirtSize * aSkirtSlope));
   2716      }
   2717      AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerSplit,
   2718                       innerArcStart, innerSplitAngle, startAngle);
   2719      if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
   2720        builder->LineTo(innerCornerStart);
   2721      }
   2722      builder->Close();
   2723      path = builder->Finish();
   2724      aDrawTarget->Fill(path, ColorPattern(aFirstColor));
   2725    }
   2726 
   2727    // Draw second half with second color
   2728    if (aSecondColor.a > 0) {
   2729      builder = aDrawTarget->CreatePathBuilder();
   2730      builder->MoveTo(outerCornerEnd);
   2731      if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
   2732        builder->LineTo(innerCornerEnd);
   2733      }
   2734      AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd,
   2735                       innerSplit, endAngle, innerSplitAngle);
   2736      AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerSplit,
   2737                       outerArcEnd, outerSplitAngle, endAngle);
   2738      builder->Close();
   2739      path = builder->Finish();
   2740      aDrawTarget->Fill(path, ColorPattern(aSecondColor));
   2741    }
   2742  } else if (aFirstColor.a > 0) {
   2743    // Draw corner with single color
   2744    AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart,
   2745                     outerArcEnd);
   2746    builder->LineTo(outerCornerEnd);
   2747    if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
   2748      builder->LineTo(innerCornerEnd);
   2749    }
   2750    AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd,
   2751                     innerArcStart, -kKappaFactor);
   2752    if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
   2753      builder->LineTo(innerCornerStart);
   2754    }
   2755    builder->Close();
   2756    path = builder->Finish();
   2757    aDrawTarget->Fill(path, ColorPattern(aFirstColor));
   2758  }
   2759 }
   2760 
   2761 // Draw a corner with possibly different sides.
   2762 // A skirt is drawn underneath the corner intersection to hide possible
   2763 // seams when anti-aliased drawing is used.
   2764 static void DrawCorner(DrawTarget* aDrawTarget, const Point& aOuterCorner,
   2765                       const Point& aInnerCorner,
   2766                       const twoFloats& aCornerMultPrev,
   2767                       const twoFloats& aCornerMultNext,
   2768                       const Size& aCornerDims, const DeviceColor& aFirstColor,
   2769                       const DeviceColor& aSecondColor, Float aSkirtSize,
   2770                       Float aSkirtSlope) {
   2771  // Corner box start point
   2772  Point cornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
   2773  // Corner box end point
   2774  Point cornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
   2775 
   2776  RefPtr<PathBuilder> builder;
   2777  RefPtr<Path> path;
   2778 
   2779  if (aFirstColor.a > 0) {
   2780    builder = aDrawTarget->CreatePathBuilder();
   2781    builder->MoveTo(cornerStart);
   2782  }
   2783 
   2784  if (aFirstColor != aSecondColor) {
   2785    // Draw first half with first color
   2786    if (aFirstColor.a > 0) {
   2787      builder->LineTo(aOuterCorner);
   2788      // Draw skirt as part of first half
   2789      if (aSkirtSize > 0) {
   2790        builder->LineTo(aOuterCorner + aCornerMultNext * aSkirtSize);
   2791        builder->LineTo(aInnerCorner -
   2792                        aCornerMultPrev * (aSkirtSize * aSkirtSlope));
   2793      }
   2794      builder->LineTo(aInnerCorner);
   2795      builder->Close();
   2796      path = builder->Finish();
   2797      aDrawTarget->Fill(path, ColorPattern(aFirstColor));
   2798    }
   2799 
   2800    // Draw second half with second color
   2801    if (aSecondColor.a > 0) {
   2802      builder = aDrawTarget->CreatePathBuilder();
   2803      builder->MoveTo(cornerEnd);
   2804      builder->LineTo(aInnerCorner);
   2805      builder->LineTo(aOuterCorner);
   2806      builder->Close();
   2807      path = builder->Finish();
   2808      aDrawTarget->Fill(path, ColorPattern(aSecondColor));
   2809    }
   2810  } else if (aFirstColor.a > 0) {
   2811    // Draw corner with single color
   2812    builder->LineTo(aOuterCorner);
   2813    builder->LineTo(cornerEnd);
   2814    builder->LineTo(aInnerCorner);
   2815    builder->Close();
   2816    path = builder->Finish();
   2817    aDrawTarget->Fill(path, ColorPattern(aFirstColor));
   2818  }
   2819 }
   2820 
   2821 void nsCSSBorderRenderer::DrawSolidBorder() {
   2822  const twoFloats cornerMults[4] = {{-1, 0}, {0, -1}, {+1, 0}, {0, +1}};
   2823 
   2824  const twoFloats centerAdjusts[4] = {
   2825      {0, +0.5}, {-0.5, 0}, {0, -0.5}, {+0.5, 0}};
   2826 
   2827  RectCornerRadii innerRadii;
   2828  ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii);
   2829 
   2830  Rect strokeRect = mOuterRect;
   2831  strokeRect.Deflate(
   2832      Margin(mBorderWidths.top / 2.0f, mBorderWidths.right / 2.0f,
   2833             mBorderWidths.bottom / 2.0f, mBorderWidths.left / 2.0f));
   2834 
   2835  for (const auto i : mozilla::AllPhysicalSides()) {
   2836    // We now draw the current side and the CW corner following it.
   2837    // The CCW corner of this side was already drawn in the previous iteration.
   2838    // The side will be drawn as an explicit stroke, and the CW corner will be
   2839    // filled separately.
   2840    // If the next side does not have a matching color, then we split the
   2841    // corner into two halves, one of each side's color and draw both.
   2842    // Thus, the CCW corner of the next side will end up drawn here.
   2843 
   2844    // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
   2845    Corner c = Corner((i + 1) % 4);
   2846    Corner prevCorner = Corner(i);
   2847 
   2848    // i+2 and i+3 respectively.  These are used to index into the corner
   2849    // multiplier table, and were deduced by calculating out the long form
   2850    // of each corner and finding a pattern in the signs and values.
   2851    auto i1 = Side((i + 1) % 4);
   2852    auto i2 = Side((i + 2) % 4);
   2853    auto i3 = Side((i + 3) % 4);
   2854 
   2855    Float sideWidth = 0.0f;
   2856    DeviceColor firstColor, secondColor;
   2857    if (IsVisible(mBorderStyles[i]) && mBorderWidths.Side(i) != 0.0f) {
   2858      // draw the side since it is visible
   2859      sideWidth = mBorderWidths.Side(i);
   2860      firstColor = ToDeviceColor(mBorderColors[i]);
   2861      // if the next side is visible, use its color for corner
   2862      secondColor =
   2863          IsVisible(mBorderStyles[i1]) && mBorderWidths.Side(i1) != 0.0f
   2864              ? ToDeviceColor(mBorderColors[i1])
   2865              : firstColor;
   2866    } else if (IsVisible(mBorderStyles[i1]) && mBorderWidths.Side(i1) != 0.0f) {
   2867      // assign next side's color to both corner sides
   2868      firstColor = ToDeviceColor(mBorderColors[i1]);
   2869      secondColor = firstColor;
   2870    } else {
   2871      // neither side is visible, so nothing to do
   2872      continue;
   2873    }
   2874 
   2875    Point outerCorner = mOuterRect.AtCorner(c);
   2876    Point innerCorner = mInnerRect.AtCorner(c);
   2877 
   2878    // start and end points of border side stroke between corners
   2879    Point sideStart = mOuterRect.AtCorner(prevCorner) +
   2880                      cornerMults[i2] * mBorderCornerDimensions[prevCorner];
   2881    Point sideEnd = outerCorner + cornerMults[i] * mBorderCornerDimensions[c];
   2882    // check if the side is visible and not inverted
   2883    if (sideWidth > 0 && firstColor.a > 0 &&
   2884        -(sideEnd - sideStart).DotProduct(cornerMults[i]) > 0) {
   2885      mDrawTarget->StrokeLine(sideStart + centerAdjusts[i] * sideWidth,
   2886                              sideEnd + centerAdjusts[i] * sideWidth,
   2887                              ColorPattern(firstColor),
   2888                              StrokeOptions(sideWidth));
   2889    }
   2890 
   2891    Float skirtSize = 0.0f, skirtSlope = 0.0f;
   2892    // the sides don't match, so compute a skirt
   2893    if (firstColor != secondColor &&
   2894        mPresContext->Type() != nsPresContext::eContext_Print) {
   2895      Point cornerDir = outerCorner - innerCorner;
   2896      ComputeCornerSkirtSize(
   2897          firstColor.a, secondColor.a, cornerDir.DotProduct(cornerMults[i]),
   2898          cornerDir.DotProduct(cornerMults[i3]), skirtSize, skirtSlope);
   2899    }
   2900 
   2901    if (!mBorderRadii[c].IsEmpty()) {
   2902      // the corner has a border radius
   2903      DrawBorderRadius(mDrawTarget, c, outerCorner, innerCorner, cornerMults[i],
   2904                       cornerMults[i3], mBorderCornerDimensions[c],
   2905                       mBorderRadii[c], innerRadii[c], firstColor, secondColor,
   2906                       skirtSize, skirtSlope);
   2907    } else if (!mBorderCornerDimensions[c].IsEmpty()) {
   2908      // a corner with no border radius
   2909      DrawCorner(mDrawTarget, outerCorner, innerCorner, cornerMults[i],
   2910                 cornerMults[i3], mBorderCornerDimensions[c], firstColor,
   2911                 secondColor, skirtSize, skirtSlope);
   2912    }
   2913  }
   2914 }
   2915 
   2916 void nsCSSBorderRenderer::DrawBorders() {
   2917  if (MOZ_UNLIKELY(!mDirtyRect.Intersects(mOuterRect))) {
   2918    return;
   2919  }
   2920 
   2921  if (mAllBordersSameStyle && (mBorderStyles[0] == StyleBorderStyle::None ||
   2922                               mBorderStyles[0] == StyleBorderStyle::Hidden ||
   2923                               mBorderColors[0] == NS_RGBA(0, 0, 0, 0))) {
   2924    // All borders are the same style, and the style is either none or hidden,
   2925    // or the color is transparent.
   2926    return;
   2927  }
   2928 
   2929  if (mAllBordersSameWidth && mBorderWidths.top == 0.0) {
   2930    // Some of the mAllBordersSameWidth codepaths depend on the border
   2931    // width being greater than zero.
   2932    return;
   2933  }
   2934 
   2935  AutoRestoreTransform autoRestoreTransform;
   2936  Matrix mat = mDrawTarget->GetTransform();
   2937 
   2938  // Clamp the CTM to be pixel-aligned; we do this only
   2939  // for translation-only matrices now, but we could do it
   2940  // if the matrix has just a scale as well.  We should not
   2941  // do it if there's a rotation.
   2942  if (mat.HasNonTranslation()) {
   2943    if (!mat.HasNonAxisAlignedTransform()) {
   2944      // Scale + transform. Avoid stroke fast-paths so that we have a chance
   2945      // of snapping to pixel boundaries.
   2946      mAvoidStroke = true;
   2947    }
   2948  } else {
   2949    mat._31 = floor(mat._31 + 0.5);
   2950    mat._32 = floor(mat._32 + 0.5);
   2951    autoRestoreTransform.Init(mDrawTarget);
   2952    mDrawTarget->SetTransform(mat);
   2953 
   2954    // round mOuterRect and mInnerRect; they're already an integer
   2955    // number of pixels apart and should stay that way after
   2956    // rounding. We don't do this if there's a scale in the current transform
   2957    // since this loses information that might be relevant when we're scaling.
   2958    mOuterRect.Round();
   2959    mInnerRect.Round();
   2960  }
   2961 
   2962  // Initial values only used when the border colors/widths are all the same:
   2963  ColorPattern color(ToDeviceColor(mBorderColors[eSideTop]));
   2964  StrokeOptions strokeOptions(mBorderWidths.top);
   2965 
   2966  // First there's a couple of 'special cases' that have specifically optimized
   2967  // drawing paths, when none of these can be used we move on to the generalized
   2968  // border drawing code.
   2969  if (mAllBordersSameStyle && mAllBordersSameWidth &&
   2970      mBorderStyles[0] == StyleBorderStyle::Solid && mNoBorderRadius &&
   2971      !mAvoidStroke) {
   2972    // Very simple case.
   2973    Rect rect = mOuterRect;
   2974    rect.Deflate(mBorderWidths.top / 2.0);
   2975    mDrawTarget->StrokeRect(rect, color, strokeOptions);
   2976    return;
   2977  }
   2978 
   2979  if (mAllBordersSameStyle && mBorderStyles[0] == StyleBorderStyle::Solid &&
   2980      !mAvoidStroke && !mNoBorderRadius) {
   2981    // Relatively simple case.
   2982    RoundedRect borderInnerRect(mOuterRect, mBorderRadii);
   2983    borderInnerRect.Deflate(mBorderWidths);
   2984 
   2985    // Instead of stroking we just use two paths: an inner and an outer.
   2986    // This allows us to draw borders that we couldn't when stroking. For
   2987    // example, borders with a border width >= the border radius. (i.e. when
   2988    // there are square corners on the inside)
   2989    //
   2990    // Further, this approach can be more efficient because the backend
   2991    // doesn't need to compute an offset curve to stroke the path. We know that
   2992    // the rounded parts are elipses we can offset exactly and can just compute
   2993    // a new cubic approximation.
   2994    RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
   2995    AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true);
   2996    AppendRoundedRectToPath(builder, borderInnerRect.rect,
   2997                            borderInnerRect.corners, false);
   2998    RefPtr<Path> path = builder->Finish();
   2999    mDrawTarget->Fill(path, color);
   3000    return;
   3001  }
   3002 
   3003  const bool allBordersSolid = AllBordersSolid();
   3004 
   3005  // This leaves the border corners non-interpolated for single width borders.
   3006  // Doing this is slightly faster and shouldn't be a problem visually.
   3007  if (allBordersSolid && mAllBordersSameWidth && mBorderWidths.top == 1 &&
   3008      mNoBorderRadius && !mAvoidStroke) {
   3009    DrawSingleWidthSolidBorder();
   3010    return;
   3011  }
   3012 
   3013  if (allBordersSolid && !mAvoidStroke) {
   3014    DrawSolidBorder();
   3015    return;
   3016  }
   3017 
   3018  PrintAsString(" mOuterRect: ");
   3019  PrintAsString(mOuterRect);
   3020  PrintAsStringNewline();
   3021  PrintAsString(" mInnerRect: ");
   3022  PrintAsString(mInnerRect);
   3023  PrintAsStringNewline();
   3024  PrintAsFormatString(" mBorderColors: 0x%08x 0x%08x 0x%08x 0x%08x\n",
   3025                      mBorderColors[0], mBorderColors[1], mBorderColors[2],
   3026                      mBorderColors[3]);
   3027 
   3028  // if conditioning the outside rect failed, then bail -- the outside
   3029  // rect is supposed to enclose the entire border
   3030  {
   3031    gfxRect outerRect = ThebesRect(mOuterRect);
   3032    gfxUtils::ConditionRect(outerRect);
   3033    if (outerRect.IsEmpty()) {
   3034      return;
   3035    }
   3036    mOuterRect = ToRect(outerRect);
   3037 
   3038    if (MOZ_UNLIKELY(!mDirtyRect.Intersects(mOuterRect))) {
   3039      return;
   3040    }
   3041 
   3042    gfxRect innerRect = ThebesRect(mInnerRect);
   3043    gfxUtils::ConditionRect(innerRect);
   3044    mInnerRect = ToRect(innerRect);
   3045  }
   3046 
   3047  SideBits dashedSides = SideBits::eNone;
   3048  bool forceSeparateCorners = false;
   3049 
   3050  for (const auto i : mozilla::AllPhysicalSides()) {
   3051    StyleBorderStyle style = mBorderStyles[i];
   3052    if (style == StyleBorderStyle::Dashed ||
   3053        style == StyleBorderStyle::Dotted) {
   3054      // we need to draw things separately for dashed/dotting
   3055      forceSeparateCorners = true;
   3056      dashedSides |= static_cast<mozilla::SideBits>(1 << i);
   3057    }
   3058  }
   3059 
   3060  PrintAsFormatString(" mAllBordersSameStyle: %d dashedSides: 0x%02x\n",
   3061                      mAllBordersSameStyle,
   3062                      static_cast<unsigned int>(dashedSides));
   3063 
   3064  if (mAllBordersSameStyle && !forceSeparateCorners) {
   3065    /* Draw everything in one go */
   3066    DrawBorderSides(SideBits::eAll);
   3067    PrintAsStringNewline("---------------- (1)");
   3068  } else {
   3069    AUTO_PROFILER_LABEL("nsCSSBorderRenderer::DrawBorders:multipass", GRAPHICS);
   3070 
   3071    /* We have more than one pass to go.  Draw the corners separately from the
   3072     * sides. */
   3073 
   3074    // The corner is going to have negligible size if its two adjacent border
   3075    // sides are only 1px wide and there is no border radius.  In that case we
   3076    // skip the overhead of painting the corner by setting the width or height
   3077    // of the corner to zero, which effectively extends one of the corner's
   3078    // adjacent border sides.  We extend the longer adjacent side so that
   3079    // opposite sides will be the same length, which is necessary for opposite
   3080    // dashed/dotted sides to be symmetrical.
   3081    //
   3082    //   if width > height
   3083    //     +--+--------------+--+    +--------------------+
   3084    //     |  |              |  |    |                    |
   3085    //     +--+--------------+--+    +--+--------------+--+
   3086    //     |  |              |  |    |  |              |  |
   3087    //     |  |              |  | => |  |              |  |
   3088    //     |  |              |  |    |  |              |  |
   3089    //     +--+--------------+--+    +--+--------------+--+
   3090    //     |  |              |  |    |                    |
   3091    //     +--+--------------+--+    +--------------------+
   3092    //
   3093    //   if width <= height
   3094    //     +--+--------+--+    +--+--------+--+
   3095    //     |  |        |  |    |  |        |  |
   3096    //     +--+--------+--+    |  +--------+  |
   3097    //     |  |        |  |    |  |        |  |
   3098    //     |  |        |  |    |  |        |  |
   3099    //     |  |        |  |    |  |        |  |
   3100    //     |  |        |  | => |  |        |  |
   3101    //     |  |        |  |    |  |        |  |
   3102    //     |  |        |  |    |  |        |  |
   3103    //     |  |        |  |    |  |        |  |
   3104    //     +--+--------+--+    |  +--------+  |
   3105    //     |  |        |  |    |  |        |  |
   3106    //     +--+--------+--+    +--+--------+--+
   3107    //
   3108    // Note that if we have different border widths we could end up with
   3109    // opposite sides of different length.  For example, if the left and
   3110    // bottom borders are 2px wide instead of 1px, we will end up doing
   3111    // something like:
   3112    //
   3113    //     +----+------------+--+    +----+---------------+
   3114    //     |    |            |  |    |    |               |
   3115    //     +----+------------+--+    +----+------------+--+
   3116    //     |    |            |  |    |    |            |  |
   3117    //     |    |            |  | => |    |            |  |
   3118    //     |    |            |  |    |    |            |  |
   3119    //     +----+------------+--+    +----+------------+--+
   3120    //     |    |            |  |    |    |            |  |
   3121    //     |    |            |  |    |    |            |  |
   3122    //     +----+------------+--+    +----+------------+--+
   3123    //
   3124    // XXX Should we only do this optimization if |mAllBordersSameWidth| is
   3125    // true?
   3126    //
   3127    // XXX In fact is this optimization even worth the complexity it adds to
   3128    // the code?  1px wide dashed borders are not overly common, and drawing
   3129    // corners for them is not that expensive.
   3130    for (const auto corner : mozilla::AllPhysicalCorners()) {
   3131      const mozilla::Side sides[2] = {mozilla::Side(corner), PREV_SIDE(corner)};
   3132 
   3133      if (!IsZeroSize(mBorderRadii[corner])) {
   3134        continue;
   3135      }
   3136 
   3137      if (mBorderWidths.Side(sides[0]) == 1.0 &&
   3138          mBorderWidths.Side(sides[1]) == 1.0) {
   3139        if (mOuterRect.Width() > mOuterRect.Height()) {
   3140          mBorderCornerDimensions[corner].width = 0.0;
   3141        } else {
   3142          mBorderCornerDimensions[corner].height = 0.0;
   3143        }
   3144      }
   3145    }
   3146 
   3147    // First, the corners
   3148    for (const auto corner : mozilla::AllPhysicalCorners()) {
   3149      // if there's no corner, don't do all this work for it
   3150      if (IsZeroSize(mBorderCornerDimensions[corner])) {
   3151        continue;
   3152      }
   3153 
   3154      const int sides[2] = {corner, PREV_SIDE(corner)};
   3155      SideBits sideBits =
   3156          static_cast<SideBits>((1 << sides[0]) | (1 << sides[1]));
   3157 
   3158      bool simpleCornerStyle = AreBorderSideFinalStylesSame(sideBits);
   3159 
   3160      // If we don't have anything complex going on in this corner,
   3161      // then we can just fill the corner with a solid color, and avoid
   3162      // the potentially expensive clip.
   3163      if (simpleCornerStyle && IsZeroSize(mBorderRadii[corner]) &&
   3164          IsSolidCornerStyle(mBorderStyles[sides[0]], corner)) {
   3165        sRGBColor color = MakeBorderColor(
   3166            mBorderColors[sides[0]],
   3167            BorderColorStyleForSolidCorner(mBorderStyles[sides[0]], corner));
   3168        mDrawTarget->FillRect(GetCornerRect(corner),
   3169                              ColorPattern(ToDeviceColor(color)));
   3170        continue;
   3171      }
   3172 
   3173      // clip to the corner
   3174      mDrawTarget->PushClipRect(GetCornerRect(corner));
   3175 
   3176      if (simpleCornerStyle) {
   3177        // we don't need a group for this corner, the sides are the same,
   3178        // but we weren't able to render just a solid block for the corner.
   3179        DrawBorderSides(sideBits);
   3180      } else {
   3181        // Sides are different.  We could draw using OP_ADD to
   3182        // get correct color blending behaviour at the seam.  We'd need
   3183        // to do it in an offscreen surface to ensure that we're
   3184        // always compositing on transparent black.  If the colors
   3185        // don't have transparency and the current destination surface
   3186        // has an alpha channel, we could just clear the region and
   3187        // avoid the temporary, but that situation doesn't happen all
   3188        // that often in practice (we double buffer to no-alpha
   3189        // surfaces). We choose just to seam though, as the performance
   3190        // advantages outway the modest easthetic improvement.
   3191 
   3192        for (int cornerSide = 0; cornerSide < 2; cornerSide++) {
   3193          mozilla::Side side = mozilla::Side(sides[cornerSide]);
   3194          StyleBorderStyle style = mBorderStyles[side];
   3195 
   3196          PrintAsFormatString("corner: %d cornerSide: %d side: %d style: %d\n",
   3197                              corner, cornerSide, side,
   3198                              static_cast<int>(style));
   3199 
   3200          RefPtr<Path> path = GetSideClipSubPath(side);
   3201          mDrawTarget->PushClip(path);
   3202 
   3203          DrawBorderSides(static_cast<mozilla::SideBits>(1 << side));
   3204 
   3205          mDrawTarget->PopClip();
   3206        }
   3207      }
   3208 
   3209      mDrawTarget->PopClip();
   3210 
   3211      PrintAsStringNewline();
   3212    }
   3213 
   3214    // in the case of a single-unit border, we already munged the
   3215    // corners up above; so we can just draw the top left and bottom
   3216    // right sides separately, if they're the same.
   3217    //
   3218    // We need to check for mNoBorderRadius, because when there is
   3219    // one, FillSolidBorder always draws the full rounded rectangle
   3220    // and expects there to be a clip in place.
   3221    SideBits alreadyDrawnSides = SideBits::eNone;
   3222    if (mOneUnitBorder && mNoBorderRadius &&
   3223        (dashedSides & (SideBits::eTop | SideBits::eLeft)) == SideBits::eNone) {
   3224      bool tlBordersSameStyle =
   3225          AreBorderSideFinalStylesSame(SideBits::eTop | SideBits::eLeft);
   3226      bool brBordersSameStyle =
   3227          AreBorderSideFinalStylesSame(SideBits::eBottom | SideBits::eRight);
   3228 
   3229      if (tlBordersSameStyle) {
   3230        DrawBorderSides(SideBits::eTop | SideBits::eLeft);
   3231        alreadyDrawnSides |= (SideBits::eTop | SideBits::eLeft);
   3232      }
   3233 
   3234      if (brBordersSameStyle &&
   3235          (dashedSides & (SideBits::eBottom | SideBits::eRight)) ==
   3236              SideBits::eNone) {
   3237        DrawBorderSides(SideBits::eBottom | SideBits::eRight);
   3238        alreadyDrawnSides |= (SideBits::eBottom | SideBits::eRight);
   3239      }
   3240    }
   3241 
   3242    // We're done with the corners, now draw the sides.
   3243    for (const auto side : mozilla::AllPhysicalSides()) {
   3244      // if we drew it above, skip it
   3245      if (alreadyDrawnSides & static_cast<mozilla::SideBits>(1 << side)) {
   3246        continue;
   3247      }
   3248 
   3249      // If there's no border on this side, skip it
   3250      if (mBorderWidths.Side(side) == 0.0 ||
   3251          mBorderStyles[side] == StyleBorderStyle::Hidden ||
   3252          mBorderStyles[side] == StyleBorderStyle::None) {
   3253        continue;
   3254      }
   3255 
   3256      if (dashedSides & static_cast<mozilla::SideBits>(1 << side)) {
   3257        // Dashed sides will always draw just the part ignoring the
   3258        // corners for the side, so no need to clip.
   3259        DrawDashedOrDottedSide(side);
   3260 
   3261        PrintAsStringNewline("---------------- (d)");
   3262        continue;
   3263      }
   3264 
   3265      // Undashed sides will currently draw the entire side,
   3266      // including parts that would normally be covered by a corner,
   3267      // so we need to clip.
   3268      //
   3269      // XXX Optimization -- it would be good to make this work like
   3270      // DrawDashedOrDottedSide, and have a DrawOneSide function that just
   3271      // draws one side and not the corners, because then we can
   3272      // avoid the potentially expensive clip.
   3273      mDrawTarget->PushClipRect(GetSideClipWithoutCornersRect(side));
   3274 
   3275      DrawBorderSides(static_cast<mozilla::SideBits>(1 << side));
   3276 
   3277      mDrawTarget->PopClip();
   3278 
   3279      PrintAsStringNewline("---------------- (*)");
   3280    }
   3281  }
   3282 }
   3283 
   3284 void nsCSSBorderRenderer::CreateWebRenderCommands(
   3285    nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
   3286    wr::IpcResourceUpdateQueue& aResources,
   3287    const layers::StackingContextHelper& aSc) {
   3288  LayoutDeviceRect outerRect = LayoutDeviceRect::FromUnknownRect(mOuterRect);
   3289  wr::LayoutRect roundedRect = wr::ToLayoutRect(outerRect);
   3290  wr::LayoutRect clipRect = roundedRect;
   3291  wr::BorderSide side[4];
   3292  for (const auto i : mozilla::AllPhysicalSides()) {
   3293    side[i] =
   3294        wr::ToBorderSide(ToDeviceColor(mBorderColors[i]), mBorderStyles[i]);
   3295  }
   3296 
   3297  wr::BorderRadius borderRadius = wr::ToBorderRadius(mBorderRadii);
   3298 
   3299  if (mLocalClip) {
   3300    LayoutDeviceRect localClip =
   3301        LayoutDeviceRect::FromUnknownRect(mLocalClip.value());
   3302    clipRect = wr::ToLayoutRect(localClip.Intersect(outerRect));
   3303  }
   3304 
   3305  Range<const wr::BorderSide> wrsides(side, 4);
   3306  aBuilder.PushBorder(roundedRect, clipRect, mBackfaceIsVisible,
   3307                      wr::ToBorderWidths(mBorderWidths), wrsides, borderRadius);
   3308 }
   3309 
   3310 /* static */
   3311 Maybe<nsCSSBorderImageRenderer>
   3312 nsCSSBorderImageRenderer::CreateBorderImageRenderer(
   3313    nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
   3314    const nsStyleBorder& aStyleBorder, const nsRect& aDirtyRect,
   3315    Sides aSkipSides, uint32_t aFlags, ImgDrawResult* aDrawResult) {
   3316  MOZ_ASSERT(aDrawResult);
   3317 
   3318  if (aDirtyRect.IsEmpty()) {
   3319    *aDrawResult = ImgDrawResult::SUCCESS;
   3320    return Nothing();
   3321  }
   3322 
   3323  nsImageRenderer imgRenderer(aForFrame, &aStyleBorder.mBorderImageSource,
   3324                              aFlags);
   3325  if (!imgRenderer.PrepareImage()) {
   3326    *aDrawResult = imgRenderer.PrepareResult();
   3327    return Nothing();
   3328  }
   3329 
   3330  // We should always get here with the frame's border, but we may construct an
   3331  // nsStyleBorder om the stack to deal with :visited and other shenaningans.
   3332  //
   3333  // We always copy the border image and such from the non-visited one, so
   3334  // there's no need to do anything with it.
   3335  MOZ_ASSERT(aStyleBorder.GetBorderImageRequest() ==
   3336             aForFrame->StyleBorder()->GetBorderImageRequest());
   3337 
   3338  nsCSSBorderImageRenderer renderer(aForFrame, aBorderArea, aStyleBorder,
   3339                                    aSkipSides, imgRenderer);
   3340  *aDrawResult = ImgDrawResult::SUCCESS;
   3341  return Some(renderer);
   3342 }
   3343 
   3344 ImgDrawResult nsCSSBorderImageRenderer::DrawBorderImage(
   3345    nsPresContext* aPresContext, gfxContext& aRenderingContext,
   3346    nsIFrame* aForFrame, const nsRect& aDirtyRect) {
   3347  // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
   3348  // in case we need it.
   3349  gfxContextAutoSaveRestore autoSR;
   3350 
   3351  if (!mClip.IsEmpty()) {
   3352    autoSR.EnsureSaved(&aRenderingContext);
   3353    aRenderingContext.Clip(NSRectToSnappedRect(
   3354        mClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
   3355        *aRenderingContext.GetDrawTarget()));
   3356  }
   3357 
   3358  // intrinsicSize.CanComputeConcreteSize() return false means we can not
   3359  // read intrinsic size from aStyleBorder.mBorderImageSource.
   3360  // In this condition, we pass imageSize(a resolved size comes from
   3361  // default sizing algorithm) to renderer as the viewport size.
   3362  CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
   3363  Maybe<nsSize> svgViewportSize =
   3364      intrinsicSize.CanComputeConcreteSize() ? Nothing() : Some(mImageSize);
   3365  bool hasIntrinsicRatio = intrinsicSize.HasRatio();
   3366 
   3367  // These helper tables recharacterize the 'slice' and 'width' margins
   3368  // in a more convenient form: they are the x/y/width/height coords
   3369  // required for various bands of the border, and they have been transformed
   3370  // to be relative to the innerRect (for 'slice') or the page (for 'border').
   3371  enum { LEFT, MIDDLE, RIGHT, TOP = LEFT, BOTTOM = RIGHT };
   3372  const nscoord borderX[3] = {
   3373      mArea.x + 0,
   3374      mArea.x + mWidths.left,
   3375      mArea.x + mArea.width - mWidths.right,
   3376  };
   3377  const nscoord borderY[3] = {
   3378      mArea.y + 0,
   3379      mArea.y + mWidths.top,
   3380      mArea.y + mArea.height - mWidths.bottom,
   3381  };
   3382  const nscoord borderWidth[3] = {
   3383      mWidths.left,
   3384      mArea.width - mWidths.left - mWidths.right,
   3385      mWidths.right,
   3386  };
   3387  const nscoord borderHeight[3] = {
   3388      mWidths.top,
   3389      mArea.height - mWidths.top - mWidths.bottom,
   3390      mWidths.bottom,
   3391  };
   3392  const int32_t sliceX[3] = {
   3393      0,
   3394      mSlice.left,
   3395      mImageSize.width - mSlice.right,
   3396  };
   3397  const int32_t sliceY[3] = {
   3398      0,
   3399      mSlice.top,
   3400      mImageSize.height - mSlice.bottom,
   3401  };
   3402  const int32_t sliceWidth[3] = {
   3403      mSlice.left,
   3404      std::max(mImageSize.width - mSlice.left - mSlice.right, 0),
   3405      mSlice.right,
   3406  };
   3407  const int32_t sliceHeight[3] = {
   3408      mSlice.top,
   3409      std::max(mImageSize.height - mSlice.top - mSlice.bottom, 0),
   3410      mSlice.bottom,
   3411  };
   3412 
   3413  ImgDrawResult result = ImgDrawResult::SUCCESS;
   3414 
   3415  for (int i = LEFT; i <= RIGHT; i++) {
   3416    for (int j = TOP; j <= BOTTOM; j++) {
   3417      StyleBorderImageRepeatKeyword fillStyleH, fillStyleV;
   3418      nsSize unitSize;
   3419 
   3420      if (i == MIDDLE && j == MIDDLE) {
   3421        // Discard the middle portion unless set to fill.
   3422        if (!mFill) {
   3423          continue;
   3424        }
   3425 
   3426        // css-background:
   3427        //     The middle image's width is scaled by the same factor as the
   3428        //     top image unless that factor is zero or infinity, in which
   3429        //     case the scaling factor of the bottom is substituted, and
   3430        //     failing that, the width is not scaled. The height of the
   3431        //     middle image is scaled by the same factor as the left image
   3432        //     unless that factor is zero or infinity, in which case the
   3433        //     scaling factor of the right image is substituted, and failing
   3434        //     that, the height is not scaled.
   3435        gfxFloat hFactor, vFactor;
   3436 
   3437        if (0 < mWidths.left && 0 < mSlice.left) {
   3438          vFactor = gfxFloat(mWidths.left) / mSlice.left;
   3439        } else if (0 < mWidths.right && 0 < mSlice.right) {
   3440          vFactor = gfxFloat(mWidths.right) / mSlice.right;
   3441        } else {
   3442          vFactor = 1;
   3443        }
   3444 
   3445        if (0 < mWidths.top && 0 < mSlice.top) {
   3446          hFactor = gfxFloat(mWidths.top) / mSlice.top;
   3447        } else if (0 < mWidths.bottom && 0 < mSlice.bottom) {
   3448          hFactor = gfxFloat(mWidths.bottom) / mSlice.bottom;
   3449        } else {
   3450          hFactor = 1;
   3451        }
   3452 
   3453        unitSize.width = sliceWidth[i] * hFactor;
   3454        unitSize.height = sliceHeight[j] * vFactor;
   3455        fillStyleH = mRepeatModeHorizontal;
   3456        fillStyleV = mRepeatModeVertical;
   3457 
   3458      } else if (i == MIDDLE) {  // top, bottom
   3459        // Sides are always stretched to the thickness of their border,
   3460        // and stretched proportionately on the other axis.
   3461        gfxFloat factor;
   3462        if (0 < borderHeight[j] && 0 < sliceHeight[j]) {
   3463          factor = gfxFloat(borderHeight[j]) / sliceHeight[j];
   3464        } else {
   3465          factor = 1;
   3466        }
   3467 
   3468        unitSize.width = sliceWidth[i] * factor;
   3469        unitSize.height = borderHeight[j];
   3470        fillStyleH = mRepeatModeHorizontal;
   3471        fillStyleV = StyleBorderImageRepeatKeyword::Stretch;
   3472 
   3473      } else if (j == MIDDLE) {  // left, right
   3474        gfxFloat factor;
   3475        if (0 < borderWidth[i] && 0 < sliceWidth[i]) {
   3476          factor = gfxFloat(borderWidth[i]) / sliceWidth[i];
   3477        } else {
   3478          factor = 1;
   3479        }
   3480 
   3481        unitSize.width = borderWidth[i];
   3482        unitSize.height = sliceHeight[j] * factor;
   3483        fillStyleH = StyleBorderImageRepeatKeyword::Stretch;
   3484        fillStyleV = mRepeatModeVertical;
   3485 
   3486      } else {
   3487        // Corners are always stretched to fit the corner.
   3488        unitSize.width = borderWidth[i];
   3489        unitSize.height = borderHeight[j];
   3490        fillStyleH = StyleBorderImageRepeatKeyword::Stretch;
   3491        fillStyleV = StyleBorderImageRepeatKeyword::Stretch;
   3492      }
   3493 
   3494      nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
   3495      nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
   3496      if (subArea.IsEmpty()) {
   3497        continue;
   3498      }
   3499 
   3500      nsIntRect intSubArea = subArea.ToOutsidePixels(AppUnitsPerCSSPixel());
   3501      result &= mImageRenderer.DrawBorderImageComponent(
   3502          aPresContext, aRenderingContext, aDirtyRect, destArea,
   3503          CSSIntRect(intSubArea.x, intSubArea.y, intSubArea.width,
   3504                     intSubArea.height),
   3505          fillStyleH, fillStyleV, unitSize, j * (RIGHT + 1) + i,
   3506          svgViewportSize, hasIntrinsicRatio);
   3507    }
   3508  }
   3509 
   3510  return result;
   3511 }
   3512 
   3513 ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands(
   3514    nsDisplayItem* aItem, nsIFrame* aForFrame,
   3515    mozilla::wr::DisplayListBuilder& aBuilder,
   3516    mozilla::wr::IpcResourceUpdateQueue& aResources,
   3517    const mozilla::layers::StackingContextHelper& aSc,
   3518    mozilla::layers::RenderRootStateManager* aManager,
   3519    nsDisplayListBuilder* aDisplayListBuilder) {
   3520  if (!mImageRenderer.IsReady()) {
   3521    return ImgDrawResult::NOT_READY;
   3522  }
   3523 
   3524  float widths[4];
   3525  float slice[4];
   3526  const int32_t appUnitsPerDevPixel =
   3527      aForFrame->PresContext()->AppUnitsPerDevPixel();
   3528  for (const auto i : mozilla::AllPhysicalSides()) {
   3529    slice[i] = (float)(mSlice.Side(i)) / appUnitsPerDevPixel;
   3530    widths[i] = (float)(mWidths.Side(i)) / appUnitsPerDevPixel;
   3531  }
   3532 
   3533  LayoutDeviceRect destRect =
   3534      LayoutDeviceRect::FromAppUnits(mArea, appUnitsPerDevPixel);
   3535  destRect.Round();
   3536  wr::LayoutRect dest = wr::ToLayoutRect(destRect);
   3537 
   3538  wr::LayoutRect clip = dest;
   3539  if (!mClip.IsEmpty()) {
   3540    LayoutDeviceRect clipRect =
   3541        LayoutDeviceRect::FromAppUnits(mClip, appUnitsPerDevPixel);
   3542    clip = wr::ToLayoutRect(clipRect);
   3543  }
   3544 
   3545  ImgDrawResult drawResult = ImgDrawResult::SUCCESS;
   3546  switch (mImageRenderer.GetType()) {
   3547    case StyleImage::Tag::Url: {
   3548      RefPtr<imgIContainer> img = mImageRenderer.GetImage();
   3549      if (!img || img->GetType() == imgIContainer::TYPE_VECTOR) {
   3550        // Vector images will redraw each segment of the border up to 8 times.
   3551        // We draw using a restricted region derived from the segment's clip and
   3552        // scale the image accordingly (see ClippedImage::Draw). If we follow
   3553        // this convention as is for WebRender, we will need to rasterize the
   3554        // entire vector image scaled up without the restriction region, which
   3555        // means our main thread CPU and memory footprints will be much higher.
   3556        // Ideally we would be able to provide a raster image for each segment
   3557        // of the border. For now we use fallback.
   3558        return ImgDrawResult::NOT_SUPPORTED;
   3559      }
   3560 
   3561      uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags();
   3562 
   3563      LayoutDeviceRect imageRect = LayoutDeviceRect::FromAppUnits(
   3564          nsRect(nsPoint(), mImageRenderer.GetSize()), appUnitsPerDevPixel);
   3565 
   3566      SVGImageContext svgContext;
   3567      Maybe<ImageIntRegion> region;
   3568      gfx::IntSize decodeSize =
   3569          nsLayoutUtils::ComputeImageContainerDrawingParameters(
   3570              img, aForFrame, imageRect, imageRect, aSc, flags, svgContext,
   3571              region);
   3572 
   3573      RefPtr<WebRenderImageProvider> provider;
   3574      drawResult = img->GetImageProvider(aManager->LayerManager(), decodeSize,
   3575                                         svgContext, region, flags,
   3576                                         getter_AddRefs(provider));
   3577 
   3578      Maybe<wr::ImageKey> key =
   3579          aManager->CommandBuilder().CreateImageProviderKey(
   3580              aItem, provider, drawResult, aResources);
   3581      if (key.isNothing()) {
   3582        break;
   3583      }
   3584 
   3585      auto rendering =
   3586          wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
   3587      if (mFill) {
   3588        float epsilon = 0.0001;
   3589        bool noVerticalBorders = widths[0] <= epsilon && widths[2] < epsilon;
   3590        bool noHorizontalBorders = widths[1] <= epsilon && widths[3] < epsilon;
   3591 
   3592        // Border image with no border. It's a little silly but WebRender
   3593        // currently does not handle this. We could fall back to a blob image
   3594        // but there are reftests that are sensible to the test going through a
   3595        // blob while the reference doesn't.
   3596        if (noVerticalBorders && noHorizontalBorders) {
   3597          aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), false,
   3598                             rendering, key.value());
   3599          break;
   3600        }
   3601 
   3602        // Fall-back if we want to fill the middle area and opposite edges are
   3603        // both empty.
   3604        // TODO(bug 1609893): moving some of the repetition handling code out
   3605        // of the image shader will make it easier to handle these cases
   3606        // properly.
   3607        if (noHorizontalBorders || noVerticalBorders) {
   3608          return ImgDrawResult::NOT_SUPPORTED;
   3609        }
   3610      }
   3611 
   3612      wr::WrBorderImage params{
   3613          wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
   3614          key.value(),
   3615          rendering,
   3616          mImageSize.width / appUnitsPerDevPixel,
   3617          mImageSize.height / appUnitsPerDevPixel,
   3618          mFill,
   3619          wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]),
   3620          wr::ToRepeatMode(mRepeatModeHorizontal),
   3621          wr::ToRepeatMode(mRepeatModeVertical)};
   3622 
   3623      aBuilder.PushBorderImage(dest, clip, !aItem->BackfaceIsHidden(), params);
   3624      break;
   3625    }
   3626    case StyleImage::Tag::Gradient: {
   3627      const StyleGradient& gradient = *mImageRenderer.GetGradientData();
   3628      nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
   3629          aForFrame->PresContext(), aForFrame->Style(), gradient, mImageSize);
   3630 
   3631      wr::ExtendMode extendMode;
   3632      nsTArray<wr::GradientStop> stops;
   3633      LayoutDevicePoint lineStart;
   3634      LayoutDevicePoint lineEnd;
   3635      LayoutDeviceSize gradientRadius;
   3636      LayoutDevicePoint gradientCenter;
   3637      float gradientAngle;
   3638      renderer.BuildWebRenderParameters(1.0, extendMode, stops, lineStart,
   3639                                        lineEnd, gradientRadius, gradientCenter,
   3640                                        gradientAngle);
   3641 
   3642      if (gradient.IsLinear()) {
   3643        LayoutDevicePoint startPoint =
   3644            LayoutDevicePoint(dest.min.x, dest.min.y) + lineStart;
   3645        LayoutDevicePoint endPoint =
   3646            LayoutDevicePoint(dest.min.x, dest.min.y) + lineEnd;
   3647 
   3648        aBuilder.PushBorderGradient(
   3649            dest, clip, !aItem->BackfaceIsHidden(),
   3650            wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
   3651            (float)(mImageSize.width) / appUnitsPerDevPixel,
   3652            (float)(mImageSize.height) / appUnitsPerDevPixel, mFill,
   3653            wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]),
   3654            wr::ToLayoutPoint(startPoint), wr::ToLayoutPoint(endPoint), stops,
   3655            extendMode);
   3656      } else if (gradient.IsRadial()) {
   3657        aBuilder.PushBorderRadialGradient(
   3658            dest, clip, !aItem->BackfaceIsHidden(),
   3659            wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
   3660            mFill, wr::ToLayoutPoint(lineStart),
   3661            wr::ToLayoutSize(gradientRadius), stops, extendMode);
   3662      } else {
   3663        MOZ_ASSERT(gradient.IsConic());
   3664        aBuilder.PushBorderConicGradient(
   3665            dest, clip, !aItem->BackfaceIsHidden(),
   3666            wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
   3667            mFill, wr::ToLayoutPoint(gradientCenter), gradientAngle, stops,
   3668            extendMode);
   3669      }
   3670      break;
   3671    }
   3672    default:
   3673      MOZ_ASSERT_UNREACHABLE("Unsupport border image type");
   3674      drawResult = ImgDrawResult::NOT_SUPPORTED;
   3675  }
   3676 
   3677  return drawResult;
   3678 }
   3679 
   3680 nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(
   3681    const nsCSSBorderImageRenderer& aRhs)
   3682    : mImageRenderer(aRhs.mImageRenderer),
   3683      mImageSize(aRhs.mImageSize),
   3684      mSlice(aRhs.mSlice),
   3685      mWidths(aRhs.mWidths),
   3686      mImageOutset(aRhs.mImageOutset),
   3687      mArea(aRhs.mArea),
   3688      mClip(aRhs.mClip),
   3689      mRepeatModeHorizontal(aRhs.mRepeatModeHorizontal),
   3690      mRepeatModeVertical(aRhs.mRepeatModeVertical),
   3691      mFill(aRhs.mFill) {
   3692  (void)mImageRenderer.PrepareResult();
   3693 }
   3694 
   3695 nsCSSBorderImageRenderer& nsCSSBorderImageRenderer::operator=(
   3696    const nsCSSBorderImageRenderer& aRhs) {
   3697  mImageRenderer = aRhs.mImageRenderer;
   3698  mImageSize = aRhs.mImageSize;
   3699  mSlice = aRhs.mSlice;
   3700  mWidths = aRhs.mWidths;
   3701  mImageOutset = aRhs.mImageOutset;
   3702  mArea = aRhs.mArea;
   3703  mClip = aRhs.mClip;
   3704  mRepeatModeHorizontal = aRhs.mRepeatModeHorizontal;
   3705  mRepeatModeVertical = aRhs.mRepeatModeVertical;
   3706  mFill = aRhs.mFill;
   3707  (void)mImageRenderer.PrepareResult();
   3708 
   3709  return *this;
   3710 }
   3711 
   3712 nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(
   3713    nsIFrame* aForFrame, const nsRect& aBorderArea,
   3714    const nsStyleBorder& aStyleBorder, Sides aSkipSides,
   3715    const nsImageRenderer& aImageRenderer)
   3716    : mImageRenderer(aImageRenderer) {
   3717  // Determine the border image area, which by default corresponds to the
   3718  // border box but can be modified by 'border-image-outset'.
   3719  // Note that 'border-radius' do not apply to 'border-image' borders per
   3720  // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
   3721  nsMargin borderWidths(aStyleBorder.GetComputedBorder());
   3722  mImageOutset = aStyleBorder.GetImageOutset();
   3723  if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder) &&
   3724      !aSkipSides.IsEmpty()) {
   3725    mArea = nsCSSRendering::BoxDecorationRectForBorder(
   3726        aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
   3727    if (mArea.IsEqualEdges(aBorderArea)) {
   3728      // No need for a clip, just skip the sides we don't want.
   3729      borderWidths.ApplySkipSides(aSkipSides);
   3730      mImageOutset.ApplySkipSides(aSkipSides);
   3731      mArea.Inflate(mImageOutset);
   3732    } else {
   3733      // We're drawing borders around the joined continuation boxes so we need
   3734      // to clip that to the slice that we want for this frame.
   3735      mArea.Inflate(mImageOutset);
   3736      mImageOutset.ApplySkipSides(aSkipSides);
   3737      mClip = aBorderArea;
   3738      mClip.Inflate(mImageOutset);
   3739    }
   3740  } else {
   3741    mArea = aBorderArea;
   3742    mArea.Inflate(mImageOutset);
   3743  }
   3744 
   3745  // Calculate the image size used to compute slice points.
   3746  CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
   3747  mImageSize = nsImageRenderer::ComputeConcreteSize(
   3748      CSSSizeOrRatio(), intrinsicSize, mArea.Size());
   3749  mImageRenderer.SetPreferredSize(intrinsicSize, mImageSize);
   3750 
   3751  // Compute the used values of 'border-image-slice' and 'border-image-width';
   3752  // we do them together because the latter can depend on the former.
   3753  for (const auto s : mozilla::AllPhysicalSides()) {
   3754    const auto& slice = aStyleBorder.mBorderImageSlice.offsets.Get(s);
   3755    int32_t imgDimension =
   3756        SideIsVertical(s) ? mImageSize.width : mImageSize.height;
   3757    nscoord borderDimension = SideIsVertical(s) ? mArea.width : mArea.height;
   3758    double value;
   3759    if (slice.IsNumber()) {
   3760      value = nsPresContext::CSSPixelsToAppUnits(NS_lround(slice.AsNumber()));
   3761    } else {
   3762      MOZ_ASSERT(slice.IsPercentage());
   3763      value = slice.AsPercentage()._0 * imgDimension;
   3764    }
   3765    if (value < 0) {
   3766      value = 0;
   3767    }
   3768    if (value > imgDimension && imgDimension > 0) {
   3769      value = imgDimension;
   3770    }
   3771    mSlice.Side(s) = value;
   3772 
   3773    const auto& width = aStyleBorder.mBorderImageWidth.Get(s);
   3774    switch (width.tag) {
   3775      case StyleBorderImageSideWidth::Tag::LengthPercentage:
   3776        value =
   3777            std::max(0, width.AsLengthPercentage().Resolve(borderDimension));
   3778        break;
   3779      case StyleBorderImageSideWidth::Tag::Number:
   3780        value = width.AsNumber() * borderWidths.Side(s);
   3781        break;
   3782      case StyleBorderImageSideWidth::Tag::Auto:
   3783        value = mSlice.Side(s);
   3784        break;
   3785      default:
   3786        MOZ_ASSERT_UNREACHABLE("unexpected CSS unit for border image area");
   3787        value = 0;
   3788        break;
   3789    }
   3790    // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
   3791    // because we expect value to be non-negative.
   3792    MOZ_ASSERT(value >= 0);
   3793    mWidths.Side(s) = NSToCoordRoundWithClamp(value);
   3794    MOZ_ASSERT(mWidths.Side(s) >= 0);
   3795  }
   3796 
   3797  // "If two opposite border-image-width offsets are large enough that they
   3798  // overlap, their used values are proportionately reduced until they no
   3799  // longer overlap."
   3800  uint32_t combinedBorderWidth =
   3801      uint32_t(mWidths.left) + uint32_t(mWidths.right);
   3802  double scaleX = combinedBorderWidth > uint32_t(mArea.width)
   3803                      ? mArea.width / double(combinedBorderWidth)
   3804                      : 1.0;
   3805  uint32_t combinedBorderHeight =
   3806      uint32_t(mWidths.top) + uint32_t(mWidths.bottom);
   3807  double scaleY = combinedBorderHeight > uint32_t(mArea.height)
   3808                      ? mArea.height / double(combinedBorderHeight)
   3809                      : 1.0;
   3810  double scale = std::min(scaleX, scaleY);
   3811  if (scale < 1.0) {
   3812    mWidths.left *= scale;
   3813    mWidths.right *= scale;
   3814    mWidths.top *= scale;
   3815    mWidths.bottom *= scale;
   3816    NS_ASSERTION(mWidths.left + mWidths.right <= mArea.width &&
   3817                     mWidths.top + mWidths.bottom <= mArea.height,
   3818                 "rounding error in width reduction???");
   3819  }
   3820 
   3821  mRepeatModeHorizontal = aStyleBorder.mBorderImageRepeat._0;
   3822  mRepeatModeVertical = aStyleBorder.mBorderImageRepeat._1;
   3823  mFill = aStyleBorder.mBorderImageSlice.fill;
   3824 }