Bug 1400438 - Use array instead of linked list for -moz-border-*-colors. r?heycam,mattwoodrow draft
authorXidorn Quan <me@upsuper.org>
Mon, 18 Sep 2017 13:38:39 +1000
changeset 667972 12e5a4cd434b7f99942b207a515a7993f9f6f1a8
parent 667971 31fa2f868e284a9866215d36a278508897b21e22
child 732567 164abaaf283bf557bfffe0e632d426e7d7e6c40b
push id80905
push userxquan@mozilla.com
push dateWed, 20 Sep 2017 23:59:29 +0000
reviewersheycam, mattwoodrow
bugs1400438
milestone57.0a1
Bug 1400438 - Use array instead of linked list for -moz-border-*-colors. r?heycam,mattwoodrow MozReview-Commit-ID: LPl1Rlg5gRn
js/src/devtools/rootAnalysis/analyzeHeapWrites.js
layout/base/crashtests/1400438-1.html
layout/base/crashtests/crashtests.list
layout/painting/nsCSSRendering.cpp
layout/painting/nsCSSRenderingBorders.cpp
layout/painting/nsCSSRenderingBorders.h
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/ServoBindings.toml
layout/style/nsComputedDOMStyle.cpp
layout/style/nsRuleNode.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
servo/components/style/properties/gecko.mako.rs
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -232,19 +232,16 @@ function treatAsSafeArgument(entry, varN
         ["Gecko_nsStyleFont_PrefillDefaultForGeneric", "aFont", null],
         ["Gecko_nsStyleSVG_SetContextPropertiesLength", "aSvg", null],
         ["Gecko_ClearAlternateValues", "aFont", null],
         ["Gecko_AppendAlternateValues", "aFont", null],
         ["Gecko_CopyAlternateValuesFrom", "aDest", null],
         ["Gecko_CounterStyle_GetName", "aResult", null],
         ["Gecko_CounterStyle_GetSingleString", "aResult", null],
         ["Gecko_EnsureMozBorderColors", "aBorder", null],
-        ["Gecko_ClearMozBorderColors", "aBorder", null],
-        ["Gecko_AppendMozBorderColors", "aBorder", null],
-        ["Gecko_CopyMozBorderColors", "aDest", null],
     ];
     for (var [entryMatch, varMatch, csuMatch] of whitelist) {
         assert(entryMatch || varMatch || csuMatch);
         if (entryMatch && !nameMatches(entry.name, entryMatch))
             continue;
         if (varMatch && !nameMatches(varName, varMatch))
             continue;
         if (csuMatch && (!csuName || !nameMatches(csuName, csuMatch)))
@@ -465,19 +462,16 @@ function ignoreContents(entry)
 
         // Unable to analyze safety of linked list initialization.
         "Gecko_NewCSSValueSharedList",
         "Gecko_CSSValue_InitSharedList",
 
         // Unable to trace through dataflow, but straightforward if inspected.
         "Gecko_NewNoneTransform",
 
-        // Bug 1400438
-        "Gecko_AppendMozBorderColors",
-
         // Need main thread assertions or other fixes.
         /EffectCompositor::GetServoAnimationRule/,
         /LookAndFeel::GetColor/,
     ];
     if (entry.matches(whitelist))
         return true;
 
     if (entry.isSafeArgument(0)) {
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1400438-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+div {
+  width: 100px; height: 100px;
+  border-left: 10px solid;
+  -moz-border-left-colors: red green;
+}
+</style>
+<div></div>
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -496,9 +496,10 @@ load 1381323.html
 asserts-if(!stylo,1) load 1388625-1.html # bug 1389286
 load 1390389.html
 load 1395591-1.html
 load 1395715-1.html
 load 1397398-1.html
 load 1397398-2.html
 load 1397398-3.html
 load 1398500.html
+load 1400438-1.html
 load 1400599-1.html
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -756,23 +756,21 @@ ConstructBorderRenderer(nsPresContext* a
   Float borderWidths[4] = { Float(border.top) / oneDevPixel,
                                    Float(border.right) / oneDevPixel,
                                    Float(border.bottom) / oneDevPixel,
                                    Float(border.left) / oneDevPixel };
   Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
 
   uint8_t borderStyles[4];
   nscolor borderColors[4];
-  nsBorderColors* compositeColors[4];
-
-  // pull out styles, colors, composite colors
+
+  // pull out styles, colors
   NS_FOR_CSS_SIDES (i) {
     borderStyles[i] = aStyleBorder.GetBorderStyle(i);
     borderColors[i] = ourColor->CalcComplexColor(aStyleBorder.mBorderColor[i]);
-    aStyleBorder.GetCompositeColors(i, &compositeColors[i]);
   }
 
   PrintAsFormatString(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]);
 
   nsIDocument* document = nullptr;
   nsIContent* content = aForFrame->GetContent();
   if (content) {
     document = content->OwnerDoc();
@@ -782,17 +780,17 @@ ConstructBorderRenderer(nsPresContext* a
                              document,
                              aDrawTarget,
                              dirtyRect,
                              joinedBorderAreaPx,
                              borderStyles,
                              borderWidths,
                              bgRadii,
                              borderColors,
-                             compositeColors,
+                             aStyleBorder.mBorderColors.get(),
                              bgColor);
 }
 
 
 DrawResult
 nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext,
                                            gfxContext& aRenderingContext,
                                            nsIFrame* aForFrame,
--- a/layout/painting/nsCSSRenderingBorders.cpp
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -85,19 +85,16 @@ static Color MakeBorderColor(nscolor aCo
 // border going inwards) and an array of line styles, calculate the
 // color that that stripe of the border should be rendered in.
 static Color ComputeColorForLine(uint32_t aLineIndex,
                                    const BorderColorStyle* aBorderColorStyle,
                                    uint32_t aBorderColorStyleCount,
                                    nscolor aBorderColor,
                                    nscolor aBackgroundColor);
 
-static Color ComputeCompositeColorForLine(uint32_t aLineIndex,
-                                          const nsBorderColors* aBorderColors);
-
 // little helper function to check if the array of 4 floats given are
 // equal to the given value
 static bool
 CheckFourFloatsEqual(const Float *vals, Float k)
 {
   return (vals[0] == k &&
           vals[1] == k &&
           vals[2] == k &&
@@ -173,34 +170,35 @@ nsCSSBorderRenderer::nsCSSBorderRenderer
                                          const nsIDocument* aDocument,
                                          DrawTarget* aDrawTarget,
                                          const Rect& aDirtyRect,
                                          Rect& aOuterRect,
                                          const uint8_t* aBorderStyles,
                                          const Float* aBorderWidths,
                                          RectCornerRadii& aBorderRadii,
                                          const nscolor* aBorderColors,
-                                         nsBorderColors* const* aCompositeColors,
+                                         const nsBorderColors* aCompositeColors,
                                          nscolor aBackgroundColor)
   : mPresContext(aPresContext),
     mDocument(aDocument),
     mDrawTarget(aDrawTarget),
     mDirtyRect(aDirtyRect),
     mOuterRect(aOuterRect),
     mBorderRadii(aBorderRadii),
     mBackgroundColor(aBackgroundColor)
 {
   PodCopy(mBorderStyles, aBorderStyles, 4);
   PodCopy(mBorderWidths, aBorderWidths, 4);
   PodCopy(mBorderColors, aBorderColors, 4);
-  if (aCompositeColors) {
-    PodCopy(mCompositeColors, aCompositeColors, 4);
-  } else {
-    static nsBorderColors * const noColors[4] = { nullptr };
-    PodCopy(mCompositeColors, noColors, 4);
+  NS_FOR_CSS_SIDES(side) {
+    if (aCompositeColors && !(*aCompositeColors)[side].IsEmpty()) {
+      mCompositeColors[side] = &(*aCompositeColors)[side];
+    } else {
+      mCompositeColors[side] = nullptr;
+    }
   }
 
   mInnerRect = mOuterRect;
   mInnerRect.Deflate(
       Margin(mBorderStyles[0] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[0] : 0,
              mBorderStyles[1] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[1] : 0,
              mBorderStyles[2] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[2] : 0,
              mBorderStyles[3] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[3] : 0));
@@ -314,19 +312,21 @@ nsCSSBorderRenderer::AreBorderSideFinalS
     }
 
     if (((1 << i) & aSides) == 0) {
       continue;
     }
 
     if (mBorderStyles[firstStyle] != mBorderStyles[i] ||
         mBorderColors[firstStyle] != mBorderColors[i] ||
-        !nsBorderColors::Equal(mCompositeColors[firstStyle],
-                               mCompositeColors[i]))
+        !mCompositeColors[firstStyle] != !mCompositeColors[i] ||
+        (mCompositeColors[firstStyle] &&
+         *mCompositeColors[firstStyle] != *mCompositeColors[i])) {
       return false;
+    }
   }
 
   /* Then if it's one of the two-tone styles and we're not
    * just comparing the TL or BR sides */
   switch (mBorderStyles[firstStyle]) {
     case NS_STYLE_BORDER_STYLE_GROOVE:
     case NS_STYLE_BORDER_STYLE_RIDGE:
     case NS_STYLE_BORDER_STYLE_INSET:
@@ -1260,46 +1260,42 @@ ComputeColorForLine(uint32_t aLineIndex,
                     nscolor aBackgroundColor)
 {
   NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given");
 
   return MakeBorderColor(aBorderColor, aBackgroundColor,
                          aBorderColorStyle[aLineIndex]);
 }
 
-Color
-ComputeCompositeColorForLine(uint32_t aLineIndex,
-                             const nsBorderColors* aBorderColors)
-{
-  while (aLineIndex-- && aBorderColors->mNext)
-    aBorderColors = aBorderColors->mNext;
-
-  return Color::FromABGR(aBorderColors->mColor);
-}
-
 void
-nsCSSBorderRenderer::DrawBorderSidesCompositeColors(int aSides, const nsBorderColors *aCompositeColors)
+nsCSSBorderRenderer::DrawBorderSidesCompositeColors(
+  int aSides, const nsTArray<nscolor>& aCompositeColors)
 {
   RectCornerRadii radii = mBorderRadii;
 
   // the generic composite colors path; each border is 1px in size
   Rect soRect = mOuterRect;
   Float maxBorderWidth = 0;
   NS_FOR_CSS_SIDES (i) {
     maxBorderWidth = std::max(maxBorderWidth, Float(mBorderWidths[i]));
   }
 
   Float fakeBorderSizes[4];
 
   Point itl = mInnerRect.TopLeft();
   Point ibr = mInnerRect.BottomRight();
 
+  MOZ_ASSERT(!aCompositeColors.IsEmpty());
+  Color compositeColor;
   for (uint32_t i = 0; i < uint32_t(maxBorderWidth); i++) {
-    ColorPattern color(ToDeviceColor(
-                         ComputeCompositeColorForLine(i, aCompositeColors)));
+    // advance to next color if exists.
+    if (i < aCompositeColors.Length()) {
+      compositeColor = ToDeviceColor(Color::FromABGR(aCompositeColors[i]));
+    }
+    ColorPattern color(compositeColor);
 
     Rect siRect = soRect;
     siRect.Deflate(1.0);
 
     // now cap the rects to the real mInnerRect
     Point tl = siRect.TopLeft();
     Point br = siRect.BottomRight();
 
@@ -1329,17 +1325,17 @@ nsCSSBorderRenderer::DrawBorderSides(int
 {
   if (aSides == 0 || (aSides & ~eSideBitsAll) != 0) {
     NS_WARNING("DrawBorderSides: invalid sides!");
     return;
   }
 
   uint8_t borderRenderStyle = NS_STYLE_BORDER_STYLE_NONE;
   nscolor borderRenderColor;
-  const nsBorderColors *compositeColors = nullptr;
+  const nsTArray<nscolor>* compositeColors = nullptr;
 
   uint32_t borderColorStyleCount = 0;
   BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3];
   BorderColorStyle *borderColorStyle = nullptr;
 
   NS_FOR_CSS_SIDES (i) {
     if ((aSides & (1 << i)) == 0)
       continue;
@@ -1387,17 +1383,17 @@ nsCSSBorderRenderer::DrawBorderSides(int
   // color is used for the remainder of the border's size.  Just
   // hand off to another function to do all that.
   if (compositeColors) {
     Float maxBorderWidth(0);
     NS_FOR_CSS_SIDES (i) {
       maxBorderWidth = std::max(maxBorderWidth, mBorderWidths[i]);
     }
     if (maxBorderWidth <= MAX_COMPOSITE_BORDER_WIDTH) {
-      DrawBorderSidesCompositeColors(aSides, compositeColors);
+      DrawBorderSidesCompositeColors(aSides, *compositeColors);
       return;
     }
     NS_WARNING("DrawBorderSides: too large border width for composite colors");
  }
 
   // We're not doing compositeColors, so we can calculate the
   // borderColorStyle based on the specified style.  The
   // borderColorStyle array goes from the outer to the inner style.
@@ -3140,80 +3136,73 @@ nsCSSBorderRenderer::DrawNoCompositeColo
                  firstColor, secondColor, skirtSize, skirtSlope);
     }
   }
 }
 
 void
 nsCSSBorderRenderer::DrawRectangularCompositeColors()
 {
-  nsBorderColors *currentColors[4];
-  memcpy(currentColors, mCompositeColors, sizeof(nsBorderColors*) * 4);
+  nscolor currentColors[4];
+  NS_FOR_CSS_SIDES(side) {
+    currentColors[side] = mBorderColors[side];
+  }
   Rect rect = mOuterRect;
   rect.Deflate(0.5);
 
   const twoFloats cornerAdjusts[4] = { { +0.5,  0   },
                                         {    0, +0.5 },
                                         { -0.5,  0   },
                                         {    0, -0.5 } };
 
   for (int i = 0; i < mBorderWidths[0]; i++) {
     NS_FOR_CSS_SIDES(side) {
+      // advance to the next composite color if one exists
+      if (mCompositeColors[side] &&
+          uint32_t(i) < mCompositeColors[side]->Length()) {
+        currentColors[side] = (*mCompositeColors[side])[i];
+      }
+    }
+    NS_FOR_CSS_SIDES(side) {
       int sideNext = (side + 1) % 4;
 
       Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
       Point secondCorner = rect.CWCorner(side) - cornerAdjusts[side];
 
-      Color currentColor = Color::FromABGR(
-        currentColors[side] ? currentColors[side]->mColor
-                            : mBorderColors[side]);
+      Color currentColor = Color::FromABGR(currentColors[side]);
 
       mDrawTarget->StrokeLine(firstCorner, secondCorner,
                               ColorPattern(ToDeviceColor(currentColor)));
 
       Point cornerTopLeft = rect.CWCorner(side) - Point(0.5, 0.5);
-      Color nextColor = Color::FromABGR(
-        currentColors[sideNext] ? currentColors[sideNext]->mColor
-                                : mBorderColors[sideNext]);
+      Color nextColor = Color::FromABGR(currentColors[sideNext]);
 
       Color cornerColor((currentColor.r + nextColor.r) / 2.f,
                         (currentColor.g + nextColor.g) / 2.f,
                         (currentColor.b + nextColor.b) / 2.f,
                         (currentColor.a + nextColor.a) / 2.f);
       mDrawTarget->FillRect(Rect(cornerTopLeft, Size(1, 1)),
                             ColorPattern(ToDeviceColor(cornerColor)));
-
-      if (side != 0) {
-        // We'll have to keep side 0 for the color averaging on side 3.
-        if (currentColors[side] && currentColors[side]->mNext) {
-          currentColors[side] = currentColors[side]->mNext;
-        }
-      }
-    }
-    // Now advance the color for side 0.
-    if (currentColors[0] && currentColors[0]->mNext) {
-      currentColors[0] = currentColors[0]->mNext;
     }
     rect.Deflate(1);
   }
 }
 
 void
 nsCSSBorderRenderer::DrawBorders()
 {
   bool forceSeparateCorners = false;
 
   if (mAllBordersSameStyle &&
       ((mCompositeColors[0] == nullptr &&
        (mBorderStyles[0] == NS_STYLE_BORDER_STYLE_NONE ||
         mBorderStyles[0] == NS_STYLE_BORDER_STYLE_HIDDEN ||
         mBorderColors[0] == NS_RGBA(0,0,0,0))) ||
-       (mCompositeColors[0] &&
-        (mCompositeColors[0]->mColor == NS_RGBA(0,0,0,0) &&
-         !mCompositeColors[0]->mNext))))
+       (mCompositeColors[0] && mCompositeColors[0]->Length() == 1 &&
+        (*mCompositeColors[0])[0] == NS_RGBA(0,0,0,0))))
   {
     // All borders are the same style, and the style is either none or hidden, or the color
     // is transparent.
     // This also checks if the first composite color is transparent, and there are
     // no others. It doesn't check if there are subsequent transparent ones, because
     // that would be very silly.
     return;
   }
--- a/layout/painting/nsCSSRenderingBorders.h
+++ b/layout/painting/nsCSSRenderingBorders.h
@@ -95,17 +95,17 @@ public:
                       const nsIDocument* aDocument,
                       DrawTarget* aDrawTarget,
                       const Rect& aDirtyRect,
                       Rect& aOuterRect,
                       const uint8_t* aBorderStyles,
                       const Float* aBorderWidths,
                       RectCornerRadii& aBorderRadii,
                       const nscolor* aBorderColors,
-                      nsBorderColors* const* aCompositeColors,
+                      const nsBorderColors* aCompositeColors,
                       nscolor aBackgroundColor);
 
   // draw the entire border
   void DrawBorders();
 
   bool CanCreateWebRenderCommands();
   void CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                mozilla::wr::IpcResourceUpdateQueue& aResources,
@@ -146,17 +146,19 @@ private:
   uint8_t mBorderStyles[4];
   Float mBorderWidths[4];
   RectCornerRadii mBorderRadii;
 
   // the colors for 'border-top-color' et. al.
   nscolor mBorderColors[4];
 
   // the lists of colors for '-moz-border-top-colors' et. al.
-  nsBorderColors* mCompositeColors[4];
+  // the pointers here are either nullptr, or referring to a non-empty
+  // nsTArray, so no additional empty check is needed.
+  const nsTArray<nscolor>* mCompositeColors[4];
 
   // the background color
   nscolor mBackgroundColor;
 
   // calculated values
   bool mAllBordersSameStyle;
   bool mAllBordersSameWidth;
   bool mOneUnitBorder;
@@ -230,17 +232,18 @@ private:
   // core rendering
   //
 
   // draw the border for the given sides, using the style of the first side
   // present in the bitmask
   void DrawBorderSides (int aSides);
 
   // function used by the above to handle -moz-border-colors
-  void DrawBorderSidesCompositeColors(int aSides, const nsBorderColors *compositeColors);
+  void DrawBorderSidesCompositeColors(
+    int aSides, const nsTArray<nscolor>& compositeColors);
 
   // Setup the stroke options for the given dashed/dotted side
   void SetupDashedOptions(StrokeOptions* aStrokeOptions,
                           Float aDash[2], mozilla::Side aSide,
                           Float aBorderLength, bool isCorner);
 
   // Draw the given dashed/dotte side
   void DrawDashedOrDottedSide(mozilla::Side aSide);
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1251,44 +1251,16 @@ Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* 
 }
 
 void
 Gecko_EnsureMozBorderColors(nsStyleBorder* aBorder)
 {
   aBorder->EnsureBorderColors();
 }
 
-void Gecko_ClearMozBorderColors(nsStyleBorder* aBorder, mozilla::Side aSide)
-{
-  aBorder->ClearBorderColors(aSide);
-}
-
-void
-Gecko_AppendMozBorderColors(nsStyleBorder* aBorder, mozilla::Side aSide,
-                            nscolor aColor)
-{
-  aBorder->AppendBorderColor(aSide, aColor);
-}
-
-void
-Gecko_CopyMozBorderColors(nsStyleBorder* aDest, const nsStyleBorder* aSrc,
-                          mozilla::Side aSide)
-{
-  if (aSrc->mBorderColors) {
-    aDest->CopyBorderColorsFrom(aSrc->mBorderColors[aSide], aSide);
-  }
-}
-
-const nsBorderColors*
-Gecko_GetMozBorderColors(const nsStyleBorder* aBorder, mozilla::Side aSide)
-{
-  MOZ_ASSERT(aBorder);
-  return aBorder->mBorderColors ? aBorder->mBorderColors[aSide] : nullptr;
-}
-
 void
 Gecko_FontFamilyList_Clear(FontFamilyList* aList) {
   aList->Clear();
 }
 
 void
 Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName, bool aQuoted)
 {
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -277,23 +277,16 @@ nsIAtom* Gecko_Atomize16(const nsAString
 void Gecko_AddRefAtom(nsIAtom* aAtom);
 void Gecko_ReleaseAtom(nsIAtom* aAtom);
 const uint16_t* Gecko_GetAtomAsUTF16(nsIAtom* aAtom, uint32_t* aLength);
 bool Gecko_AtomEqualsUTF8(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 bool Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 
 // Border style
 void Gecko_EnsureMozBorderColors(nsStyleBorder* aBorder);
-void Gecko_ClearMozBorderColors(nsStyleBorder* aBorder, mozilla::Side aSide);
-void Gecko_AppendMozBorderColors(nsStyleBorder* aBorder, mozilla::Side aSide,
-                                 nscolor aColor);
-void Gecko_CopyMozBorderColors(nsStyleBorder* aDest, const nsStyleBorder* aSrc,
-                               mozilla::Side aSide);
-const nsBorderColors* Gecko_GetMozBorderColors(const nsStyleBorder* aBorder,
-                                               mozilla::Side aSide);
 
 // Font style
 void Gecko_FontFamilyList_Clear(FontFamilyList* aList);
 void Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName, bool aQuoted);
 void Gecko_FontFamilyList_AppendGeneric(FontFamilyList* list, FontFamilyType familyType);
 void Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src);
 // will not run destructors on dst, give it uninitialized memory
 // font_id is LookAndFeel::FontID
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -185,17 +185,16 @@ whitelist-types = [
     "gfxFontVariation",
     "GridNamedArea",
     "HalfCorner",
     "Image",
     "ImageURL",
     "Keyframe",
     "nsAttrName",
     "nsAttrValue",
-    "nsBorderColors",
     "nscolor",
     "nsChangeHint",
     "nsCSSCounterDesc",
     "nsCSSCounterStyleRule",
     "nsCSSFontFaceRule",
     "nsCSSKeyword",
     "nsCSSPropertyID",
     "nsCSSPropertyIDSet",
@@ -384,17 +383,16 @@ structs-types = [
     "mozilla::css::GridTemplateAreasValue",
     "mozilla::css::ErrorReporter",
     "mozilla::css::ImageValue",
     "mozilla::css::URLValue",
     "mozilla::css::URLValueData",
     "mozilla::AnonymousCounterStyle",
     "mozilla::MallocSizeOf",
     "mozilla::OriginFlags",
-    "mozilla::Side",
     "mozilla::UniquePtr",
     "ServoRawOffsetArc",
     "nsIContent",
     "nsIDocument",
     "nsIDocument_DocumentTheme",
     "RawGeckoAnimationPropertySegment",
     "RawGeckoComputedTiming",
     "RawGeckoCSSPropertyIDList",
@@ -438,17 +436,16 @@ structs-types = [
     "ServoElementSnapshotTable",
     "ServoStyleSetSizes",
     "SheetParsingMode",
     "StyleBasicShape",
     "StyleBasicShapeType",
     "StyleShapeSource",
     "StyleTransition",
     "gfxFontFeatureValueSet",
-    "nsBorderColors",
     "nsCSSCounterStyleRule",
     "nsCSSFontFaceRule",
     "nsCSSKeyword",
     "nsCSSPropertyID",
     "nsCSSPropertyIDSet",
     "nsCSSShadowArray",
     "nsCSSUnit",
     "nsCSSValue",
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -5485,29 +5485,24 @@ nsComputedDOMStyle::GetLineHeightCoord(n
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::GetBorderColorsFor(mozilla::Side aSide)
 {
   const nsStyleBorder *border = StyleBorder();
 
   if (border->mBorderColors) {
-    nsBorderColors* borderColors = border->mBorderColors[aSide];
-    if (borderColors) {
+    const nsTArray<nscolor>& borderColors = (*border->mBorderColors)[aSide];
+    if (!borderColors.IsEmpty()) {
       RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
-
-      do {
+      for (nscolor color : borderColors) {
         RefPtr<nsROCSSPrimitiveValue> primitive = new nsROCSSPrimitiveValue;
-
-        SetToRGBAColor(primitive, borderColors->mColor);
-
+        SetToRGBAColor(primitive, color);
         valueList->AppendCSSValue(primitive.forget());
-        borderColors = borderColors->mNext;
-      } while (borderColors);
-
+      }
       return valueList.forget();
     }
   }
 
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetIdent(eCSSKeyword_none);
   return val.forget();
 }
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7743,38 +7743,35 @@ nsRuleNode::ComputeBorderData(void* aSta
     case eCSSUnit_Unset:
     case eCSSUnit_None:
       border->ClearBorderColors(side);
       break;
 
     case eCSSUnit_Inherit: {
       conditions.SetUncacheable();
       border->ClearBorderColors(side);
-      if (parentContext) {
-        nsBorderColors *parentColors;
-        parentBorder->GetCompositeColors(side, &parentColors);
-        if (parentColors) {
-          border->EnsureBorderColors();
-          border->mBorderColors[side] = parentColors->Clone();
-        }
+      if (parentBorder->mBorderColors) {
+        border->EnsureBorderColors();
+        border->mBorderColors->mColors[side] =
+          parentBorder->mBorderColors->mColors[side];
       }
       break;
     }
 
     case eCSSUnit_List:
     case eCSSUnit_ListDep: {
       // Some composite border color information has been specified for this
       // border side.
       border->EnsureBorderColors();
       border->ClearBorderColors(side);
       const nsCSSValueList* list = value.GetListValue();
       while (list) {
         if (SetColor(list->mValue, unused, mPresContext,
                      aContext, borderColor, conditions))
-          border->AppendBorderColor(side, borderColor);
+          border->mBorderColors->mColors[side].AppendElement(borderColor);
         else {
           NS_NOTREACHED("unexpected item in -moz-border-*-colors list");
         }
         list = list->mNext;
       }
       break;
     }
 
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -315,18 +315,17 @@ nsStylePadding::CalcDifference(const nsS
   // FIXME: It would be good to return a weaker hint here that doesn't
   // force reflow of all descendants, but the hint would need to force
   // reflow of the frame's children (see how
   // ReflowInput::InitResizeFlags initializes the inline-resize flag).
   return NS_STYLE_HINT_REFLOW & ~nsChangeHint_ClearDescendantIntrinsics;
 }
 
 nsStyleBorder::nsStyleBorder(const nsPresContext* aContext)
-  : mBorderColors(nullptr)
-  , mBorderImageFill(NS_STYLE_BORDER_IMAGE_SLICE_NOFILL)
+  : mBorderImageFill(NS_STYLE_BORDER_IMAGE_SLICE_NOFILL)
   , mBorderImageRepeatH(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH)
   , mBorderImageRepeatV(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH)
   , mFloatEdge(StyleFloatEdge::ContentBox)
   , mBoxDecorationBreak(StyleBoxDecorationBreak::Slice)
   , mComputedBorder(0, 0, 0, 0)
 {
   MOZ_COUNT_CTOR(nsStyleBorder);
 
@@ -344,72 +343,45 @@ nsStyleBorder::nsStyleBorder(const nsPre
     mBorder.Side(side) = medium;
     mBorderStyle[side] = NS_STYLE_BORDER_STYLE_NONE;
     mBorderColor[side] = StyleComplexColor::CurrentColor();
   }
 
   mTwipsPerPixel = aContext->DevPixelsToAppUnits(1);
 }
 
-nsBorderColors::~nsBorderColors()
-{
-  NS_CSS_DELETE_LIST_MEMBER(nsBorderColors, this, mNext);
-}
-
-nsBorderColors*
-nsBorderColors::Clone(bool aDeep) const
-{
-  nsBorderColors* result = new nsBorderColors(mColor);
-  if (MOZ_UNLIKELY(!result)) {
-    return result;
-  }
-  if (aDeep) {
-    NS_CSS_CLONE_LIST_MEMBER(nsBorderColors, this, mNext, result, (false));
-  }
-  return result;
-}
-
 nsStyleBorder::nsStyleBorder(const nsStyleBorder& aSrc)
-  : mBorderColors(nullptr)
-  , mBorderRadius(aSrc.mBorderRadius)
+  : mBorderRadius(aSrc.mBorderRadius)
   , mBorderImageSource(aSrc.mBorderImageSource)
   , mBorderImageSlice(aSrc.mBorderImageSlice)
   , mBorderImageWidth(aSrc.mBorderImageWidth)
   , mBorderImageOutset(aSrc.mBorderImageOutset)
   , mBorderImageFill(aSrc.mBorderImageFill)
   , mBorderImageRepeatH(aSrc.mBorderImageRepeatH)
   , mBorderImageRepeatV(aSrc.mBorderImageRepeatV)
   , mFloatEdge(aSrc.mFloatEdge)
   , mBoxDecorationBreak(aSrc.mBoxDecorationBreak)
   , mComputedBorder(aSrc.mComputedBorder)
   , mBorder(aSrc.mBorder)
   , mTwipsPerPixel(aSrc.mTwipsPerPixel)
 {
   MOZ_COUNT_CTOR(nsStyleBorder);
   if (aSrc.mBorderColors) {
-    NS_FOR_CSS_SIDES(side) {
-      CopyBorderColorsFrom(aSrc.mBorderColors[side], side);
-    }
+    mBorderColors.reset(new nsBorderColors(*aSrc.mBorderColors));
   }
 
   NS_FOR_CSS_SIDES(side) {
     mBorderStyle[side] = aSrc.mBorderStyle[side];
     mBorderColor[side] = aSrc.mBorderColor[side];
   }
 }
 
 nsStyleBorder::~nsStyleBorder()
 {
   MOZ_COUNT_DTOR(nsStyleBorder);
-  if (mBorderColors) {
-    for (int32_t i = 0; i < 4; i++) {
-      delete mBorderColors[i];
-    }
-    delete [] mBorderColors;
-  }
 }
 
 void
 nsStyleBorder::FinishStyle(nsPresContext* aPresContext)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPresContext->StyleSet()->IsServo());
 
@@ -511,19 +483,18 @@ nsStyleBorder::CalcDifference(const nsSt
         mBorderImageWidth   != aNewData.mBorderImageWidth) {
       return nsChangeHint_RepaintFrame;
     }
   }
 
   // Note that at this point if mBorderColors is non-null so is
   // aNewData.mBorderColors
   if (mBorderColors) {
-    NS_FOR_CSS_SIDES(ix) {
-      if (!nsBorderColors::Equal(mBorderColors[ix],
-                                 aNewData.mBorderColors[ix])) {
+    NS_FOR_CSS_SIDES(side) {
+      if ((*mBorderColors)[side] != (*aNewData.mBorderColors)[side]) {
         return nsChangeHint_RepaintFrame;
       }
     }
   }
 
   // mBorder is the specified border value.  Changes to this don't
   // need any change processing, since we operate on the computed
   // border values instead.
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -979,48 +979,16 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
     NS_FOR_CSS_SIDES(side) {
       // Clamp negative calc() to 0.
       aPadding.Side(side) = std::max(mPadding.ToLength(side), 0);
     }
     return true;
   }
 };
 
-struct nsBorderColors
-{
-  nsBorderColors* mNext;
-  nscolor mColor;
-
-  nsBorderColors() : mNext(nullptr), mColor(NS_RGB(0,0,0)) {}
-  explicit nsBorderColors(const nscolor& aColor) : mNext(nullptr), mColor(aColor) {}
-  ~nsBorderColors();
-
-  nsBorderColors* Clone() const { return Clone(true); }
-
-  static bool Equal(const nsBorderColors* c1,
-                      const nsBorderColors* c2) {
-    if (c1 == c2) {
-      return true;
-    }
-    while (c1 && c2) {
-      if (c1->mColor != c2->mColor) {
-        return false;
-      }
-      c1 = c1->mNext;
-      c2 = c2->mNext;
-    }
-    // both should be nullptr if these are equal, otherwise one
-    // has more colors than another
-    return !c1 && !c2;
-  }
-
-private:
-  nsBorderColors* Clone(bool aDeep) const;
-};
-
 struct nsCSSShadowItem
 {
   nscoord mXOffset;
   nscoord mYOffset;
   nscoord mRadius;
   nscoord mSpread;
 
   nscolor      mColor;
@@ -1137,16 +1105,36 @@ private:
 
 // Returns if the given border style type is visible or not
 static bool IsVisibleBorderStyle(uint8_t aStyle)
 {
   return (aStyle != NS_STYLE_BORDER_STYLE_NONE &&
           aStyle != NS_STYLE_BORDER_STYLE_HIDDEN);
 }
 
+struct nsBorderColors
+{
+  nsBorderColors() = default;
+
+  // GCC cannot generate this copy constructor correctly, since nsTArray
+  // has explicit copy constructor, and we use array of nsTArray here.
+  // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82235
+  nsBorderColors(const nsBorderColors& aOther) {
+    NS_FOR_CSS_SIDES(side) {
+      mColors[side] = aOther.mColors[side];
+    }
+  }
+
+  const nsTArray<nscolor>& operator[](mozilla::Side aSide) const {
+    return mColors[aSide];
+  }
+
+  nsTArray<nscolor> mColors[4];
+};
+
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleBorder
 {
   explicit nsStyleBorder(const nsPresContext* aContext);
   nsStyleBorder(const nsStyleBorder& aBorder);
   ~nsStyleBorder();
 
   // Resolves and tracks mBorderImageSource.  Only called with a Servo-backed
   // style system, where those images must be resolved later than the OMT
@@ -1160,37 +1148,23 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
       AllocateByObjectID(mozilla::eArenaObjectID_nsStyleBorder, sz);
   }
   void Destroy(nsPresContext* aContext);
 
   nsChangeHint CalcDifference(const nsStyleBorder& aNewData) const;
 
   void EnsureBorderColors() {
     if (!mBorderColors) {
-      mBorderColors = new nsBorderColors*[4];
-      if (mBorderColors) {
-        for (int32_t i = 0; i < 4; i++) {
-          mBorderColors[i] = nullptr;
-        }
-      }
+      mBorderColors.reset(new nsBorderColors);
     }
   }
 
   void ClearBorderColors(mozilla::Side aSide) {
-    if (mBorderColors && mBorderColors[aSide]) {
-      delete mBorderColors[aSide];
-      mBorderColors[aSide] = nullptr;
-    }
-  }
-
-  void CopyBorderColorsFrom(const nsBorderColors* aSrcBorderColors, mozilla::Side aSide) {
-    if (aSrcBorderColors) {
-      EnsureBorderColors();
-      ClearBorderColors(aSide);
-      mBorderColors[aSide] = aSrcBorderColors->Clone();
+    if (mBorderColors) {
+      mBorderColors->mColors[aSide].Clear();
     }
   }
 
   // Return whether aStyle is a visible style.  Invisible styles cause
   // the relevant computed border width to be 0.
   // Note that this does *not* consider the effects of 'border-image':
   // if border-style is none, but there is a loaded border image,
   // HasVisibleStyle will be false even though there *is* a border.
@@ -1255,50 +1229,27 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   {
     if (mBorderImageSource.GetType() == eStyleImageType_Image) {
       mBorderImageSource.ResolveImage(aContext);
     }
   }
 
   nsMargin GetImageOutset() const;
 
-  void GetCompositeColors(int32_t aIndex, nsBorderColors** aColors) const
-  {
-    if (!mBorderColors) {
-      *aColors = nullptr;
-    } else {
-      *aColors = mBorderColors[aIndex];
-    }
-  }
-
-  void AppendBorderColor(int32_t aIndex, nscolor aColor)
-  {
-    NS_ASSERTION(aIndex >= 0 && aIndex <= 3, "bad side for composite border color");
-    nsBorderColors* colorEntry = new nsBorderColors(aColor);
-    if (!mBorderColors[aIndex]) {
-      mBorderColors[aIndex] = colorEntry;
-    } else {
-      nsBorderColors* last = mBorderColors[aIndex];
-      while (last->mNext) {
-        last = last->mNext;
-      }
-      last->mNext = colorEntry;
-    }
-  }
-
   imgIRequest* GetBorderImageRequest() const
   {
     if (mBorderImageSource.GetType() == eStyleImageType_Image) {
       return mBorderImageSource.GetImageData();
     }
     return nullptr;
   }
 
 public:
-  nsBorderColors** mBorderColors;     // [reset] composite (stripe) colors
+  // [reset] composite (stripe) colors
+  mozilla::UniquePtr<nsBorderColors> mBorderColors;
   nsStyleCorners mBorderRadius;       // [reset] coord, percent
   nsStyleImage   mBorderImageSource;  // [reset]
   nsStyleSides   mBorderImageSlice;   // [reset] factor, percent
   nsStyleSides   mBorderImageWidth;   // [reset] length, factor, percent, auto
   nsStyleSides   mBorderImageOutset;  // [reset] length, factor
 
   uint8_t        mBorderImageFill;    // [reset]
   uint8_t        mBorderImageRepeatH; // [reset] see nsStyleConsts.h
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1267,16 +1267,60 @@ fn static_assert() {
 
 <% skip_moz_border_color_longhands = " ".join("-moz-border-{0}-colors".format(x.ident)
                                               for x in SIDES) %>
 <%self:impl_trait style_struct_name="Border"
                   skip_longhands="${skip_border_longhands} border-image-source border-image-outset
                                   border-image-repeat border-image-width border-image-slice
                                   ${skip_moz_border_color_longhands}">
 
+    fn set_moz_border_colors(&mut self, side: structs::Side, v: Option<Vec<::cssparser::RGBA>>) {
+        match v {
+            None => {
+                let ptr = self.gecko.mBorderColors.mPtr;
+                if let Some(colors) = unsafe { ptr.as_mut() } {
+                    unsafe { colors.mColors[side as usize].clear() };
+                }
+            }
+            Some(ref colors) => {
+                unsafe { bindings::Gecko_EnsureMozBorderColors(&mut self.gecko) };
+                let border_colors = unsafe { self.gecko.mBorderColors.mPtr.as_mut().unwrap() };
+                let dest_colors = &mut border_colors.mColors[side as usize];
+                unsafe { dest_colors.set_len_pod(colors.len() as u32) };
+                for (dst, src) in dest_colors.iter_mut().zip(colors.into_iter()) {
+                    *dst = convert_rgba_to_nscolor(src);
+                }
+            }
+        }
+    }
+
+    fn copy_moz_border_colors_from(&mut self, other: &Self, side: structs::Side) {
+        if let Some(dest) = unsafe { self.gecko.mBorderColors.mPtr.as_mut() } {
+            dest.mColors[side as usize].clear_pod();
+        }
+        if let Some(src) = unsafe { other.gecko.mBorderColors.mPtr.as_ref() } {
+            let src = &src.mColors[side as usize];
+            if !src.is_empty() {
+                unsafe { bindings::Gecko_EnsureMozBorderColors(&mut self.gecko) };
+                let dest = unsafe { self.gecko.mBorderColors.mPtr.as_mut().unwrap() };
+                let dest = &mut dest.mColors[side as usize];
+                unsafe { dest.set_len_pod(src.len() as u32) };
+                dest.copy_from_slice(&src);
+            }
+        }
+    }
+
+    fn clone_moz_border_colors(&self, side: structs::Side) -> Option<Vec<::cssparser::RGBA>> {
+        unsafe { self.gecko.mBorderColors.mPtr.as_ref() }.map(|colors| {
+            colors.mColors[side as usize].iter()
+                .map(|color| convert_nscolor_to_rgba(*color))
+                .collect()
+        })
+    }
+
     % for side in SIDES:
     <% impl_keyword("border_%s_style" % side.ident,
                     "mBorderStyle[%s]" % side.index,
                     border_style_keyword,
                     on_set="update_border_%s" % side.ident) %>
 
     // This is needed because the initial mComputedBorder value is set to zero.
     //
@@ -1317,76 +1361,34 @@ fn static_assert() {
 
     pub fn border_${side.ident}_has_nonzero_width(&self) -> bool {
         self.gecko.mComputedBorder.${side.ident} != 0
     }
 
     #[allow(non_snake_case)]
     pub fn set__moz_border_${side.ident}_colors(&mut self,
                                                 v: longhands::_moz_border_${side.ident}_colors::computed_value::T) {
-        match v.0 {
-            None => {
-                unsafe {
-                    bindings::Gecko_ClearMozBorderColors(&mut self.gecko,
-                                                         structs::Side::eSide${to_camel_case(side.ident)});
-                }
-            },
-            Some(ref colors) => {
-                unsafe {
-                    bindings::Gecko_EnsureMozBorderColors(&mut self.gecko);
-                    bindings::Gecko_ClearMozBorderColors(&mut self.gecko,
-                                                         structs::Side::eSide${to_camel_case(side.ident)});
-                }
-                for color in colors {
-                    let c = convert_rgba_to_nscolor(color);
-                    unsafe {
-                        bindings::Gecko_AppendMozBorderColors(&mut self.gecko,
-                                                              structs::Side::eSide${to_camel_case(side.ident)},
-                                                              c);
-                    }
-                }
-            }
-        }
+        self.set_moz_border_colors(structs::Side::eSide${to_camel_case(side.ident)}, v.0);
     }
 
     #[allow(non_snake_case)]
     pub fn copy__moz_border_${side.ident}_colors_from(&mut self, other: &Self) {
-        unsafe {
-            bindings::Gecko_CopyMozBorderColors(&mut self.gecko, &other.gecko,
-                                                structs::Side::eSide${to_camel_case(side.ident)});
-        }
+        self.copy_moz_border_colors_from(other, structs::Side::eSide${to_camel_case(side.ident)});
     }
 
     #[allow(non_snake_case)]
     pub fn reset__moz_border_${side.ident}_colors(&mut self, other: &Self) {
         self.copy__moz_border_${side.ident}_colors_from(other)
     }
 
     #[allow(non_snake_case)]
     pub fn clone__moz_border_${side.ident}_colors(&self)
                                                   -> longhands::_moz_border_${side.ident}_colors::computed_value::T {
         use self::longhands::_moz_border_${side.ident}_colors::computed_value::T;
-
-        let mut gecko_colors =
-            unsafe { bindings::Gecko_GetMozBorderColors(&self.gecko,
-                                                        structs::Side::eSide${to_camel_case(side.ident)}) };
-
-        if gecko_colors.is_null() {
-            return T(None);
-        }
-
-        let mut colors = Vec::new();
-        loop {
-            unsafe {
-                colors.push(convert_nscolor_to_rgba((*gecko_colors).mColor));
-                if (*gecko_colors).mNext.is_null() { break; }
-                gecko_colors = (*gecko_colors).mNext;
-            }
-        }
-        T(Some(colors))
+        T(self.clone_moz_border_colors(structs::Side::eSide${to_camel_case(side.ident)}))
     }
     % endfor
 
     % for corner in CORNERS:
     <% impl_corner_style_coord("border_%s_radius" % corner.ident,
                                "mBorderRadius",
                                corner.x_index,
                                corner.y_index) %>