Bug 1550554 - Add bindings for box shadows, and remove nsCSSShadowArray and friends. r=heycam
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 16 May 2019 23:04:32 +0000
changeset 474257 9efd6e065be931906e5ef21ba14c01b8ef392cf9
parent 474256 1e32fe8538eddbf6b87bb2dccb716f425d8dd931
child 474258 d0e6717d1cf00e7e33b6eea7021b60d75cc670d0
push id113144
push usershindli@mozilla.com
push dateFri, 17 May 2019 16:44:55 +0000
treeherdermozilla-inbound@f4c4b796f845 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1550554
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1550554 - Add bindings for box shadows, and remove nsCSSShadowArray and friends. r=heycam Differential Revision: https://phabricator.services.mozilla.com/D30547
layout/base/nsLayoutUtils.cpp
layout/forms/nsButtonFrameRenderer.cpp
layout/forms/nsFieldSetFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
layout/painting/nsCSSRendering.cpp
layout/painting/nsCSSRendering.h
layout/painting/nsDisplayList.cpp
layout/style/GeckoBindings.cpp
layout/style/GeckoBindings.h
layout/style/ServoBindings.toml
layout/style/ServoStyleConstsInlines.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/nsStyleStructInlines.h
layout/svg/nsCSSFilterInstance.cpp
layout/tables/nsTableCellFrame.cpp
layout/tables/nsTableFrame.cpp
layout/xul/nsTextBoxFrame.cpp
servo/components/style/gecko_bindings/sugar/mod.rs
servo/components/style/gecko_bindings/sugar/ns_css_shadow_array.rs
servo/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs
servo/components/style/properties/data.py
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/longhands/effects.mako.rs
servo/components/style/properties/longhands/inherited_text.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/values/animated/effects.rs
servo/components/style/values/generics/effects.rs
servo/ports/geckolib/cbindgen.toml
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -4360,29 +4360,33 @@ nsRect nsLayoutUtils::GetAllInFlowRectsU
   GetAllInFlowRects(aFrame, aRelativeTo, &accumulator, aFlags);
   return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
                                            : accumulator.mResultRect;
 }
 
 nsRect nsLayoutUtils::GetTextShadowRectsUnion(
     const nsRect& aTextAndDecorationsRect, nsIFrame* aFrame, uint32_t aFlags) {
   const nsStyleText* textStyle = aFrame->StyleText();
-  if (!textStyle->HasTextShadow()) return aTextAndDecorationsRect;
+  auto shadows = textStyle->mTextShadow.AsSpan();
+  if (shadows.IsEmpty()) {
+    return aTextAndDecorationsRect;
+  }
 
   nsRect resultRect = aTextAndDecorationsRect;
   int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
-  for (uint32_t i = 0; i < textStyle->mTextShadow->Length(); ++i) {
-    nsCSSShadowItem* shadow = textStyle->mTextShadow->ShadowAt(i);
-    nsMargin blur = nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D);
+  for (auto& shadow : shadows) {
+    nsMargin blur =
+        nsContextBoxBlur::GetBlurRadiusMargin(shadow.blur.ToAppUnits(), A2D);
     if ((aFlags & EXCLUDE_BLUR_SHADOWS) && blur != nsMargin(0, 0, 0, 0))
       continue;
 
     nsRect tmpRect(aTextAndDecorationsRect);
 
-    tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset));
+    tmpRect.MoveBy(
+        nsPoint(shadow.horizontal.ToAppUnits(), shadow.vertical.ToAppUnits()));
     tmpRect.Inflate(blur);
 
     resultRect.UnionRect(resultRect, tmpRect);
   }
   return resultRect;
 }
 
 enum ObjectDimensionType { eWidth, eHeight };
@@ -6094,44 +6098,46 @@ void nsLayoutUtils::DrawUniDirString(con
 }
 
 /* static */
 void nsLayoutUtils::PaintTextShadow(
     const nsIFrame* aFrame, gfxContext* aContext, const nsRect& aTextRect,
     const nsRect& aDirtyRect, const nscolor& aForegroundColor,
     TextShadowCallback aCallback, void* aCallbackData) {
   const nsStyleText* textStyle = aFrame->StyleText();
-  if (!textStyle->HasTextShadow()) return;
+  auto shadows = textStyle->mTextShadow.AsSpan();
+  if (shadows.IsEmpty()) {
+    return;
+  }
 
   // Text shadow happens with the last value being painted at the back,
   // ie. it is painted first.
   gfxContext* aDestCtx = aContext;
-  for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) {
-    nsCSSShadowItem* shadowDetails = textStyle->mTextShadow->ShadowAt(i - 1);
-    nsPoint shadowOffset(shadowDetails->mXOffset, shadowDetails->mYOffset);
-    nscoord blurRadius = std::max(shadowDetails->mRadius, 0);
+  for (auto& shadow : Reversed(shadows)) {
+    nsPoint shadowOffset(shadow.horizontal.ToAppUnits(),
+                         shadow.vertical.ToAppUnits());
+    nscoord blurRadius = std::max(shadow.blur.ToAppUnits(), 0);
 
     nsRect shadowRect(aTextRect);
     shadowRect.MoveBy(shadowOffset);
 
     nsPresContext* presCtx = aFrame->PresContext();
     nsContextBoxBlur contextBoxBlur;
 
-    nscolor shadowColor = shadowDetails->mColor.CalcColor(aForegroundColor);
+    nscolor shadowColor = shadow.color.CalcColor(aForegroundColor);
 
     // Webrender just needs the shadow details
     if (auto* textDrawer = aContext->GetTextDrawer()) {
       wr::Shadow wrShadow;
 
       wrShadow.offset = {
-          presCtx->AppUnitsToFloatDevPixels(shadowDetails->mXOffset),
-          presCtx->AppUnitsToFloatDevPixels(shadowDetails->mYOffset)};
-
-      wrShadow.blur_radius =
-          presCtx->AppUnitsToFloatDevPixels(shadowDetails->mRadius);
+          presCtx->AppUnitsToFloatDevPixels(shadow.horizontal.ToAppUnits()),
+          presCtx->AppUnitsToFloatDevPixels(shadow.vertical.ToAppUnits())};
+
+      wrShadow.blur_radius = presCtx->AppUnitsToFloatDevPixels(blurRadius);
       wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
 
       // Gecko already inflates the bounding rect of text shadows,
       // so tell WR not to inflate again.
       bool inflate = false;
       textDrawer->AppendShadow(wrShadow, inflate);
       continue;
     }
@@ -8286,18 +8292,18 @@ bool nsLayoutUtils::FontSizeInflationEna
     return false;
   }
   return presShell->FontSizeInflationEnabled();
 }
 
 /* static */
 nsRect nsLayoutUtils::GetBoxShadowRectForFrame(nsIFrame* aFrame,
                                                const nsSize& aFrameSize) {
-  nsCSSShadowArray* boxShadows = aFrame->StyleEffects()->mBoxShadow;
-  if (!boxShadows) {
+  auto boxShadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
+  if (boxShadows.IsEmpty()) {
     return nsRect();
   }
 
   nsRect inputRect(nsPoint(0, 0), aFrameSize);
 
   // According to the CSS spec, box-shadow should be based on the border box.
   // However, that looks broken when the background extends outside the border
   // box, as can be the case with native theming.  To fix that we expand the
@@ -8312,27 +8318,29 @@ nsRect nsLayoutUtils::GetBoxShadowRectFo
       presContext->GetTheme()->GetWidgetOverflow(
           presContext->DeviceContext(), aFrame, styleDisplay->mAppearance,
           &inputRect);
     }
   }
 
   nsRect shadows;
   int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
-  for (uint32_t i = 0; i < boxShadows->Length(); ++i) {
+  for (auto& shadow : boxShadows) {
     nsRect tmpRect = inputRect;
-    nsCSSShadowItem* shadow = boxShadows->ShadowAt(i);
 
     // inset shadows are never painted outside the frame
-    if (shadow->mInset) continue;
-
-    tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset));
-    tmpRect.Inflate(shadow->mSpread);
-    tmpRect.Inflate(
-        nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D));
+    if (shadow.inset) {
+      continue;
+    }
+
+    tmpRect.MoveBy(nsPoint(shadow.base.horizontal.ToAppUnits(),
+                           shadow.base.vertical.ToAppUnits()));
+    tmpRect.Inflate(shadow.spread.ToAppUnits());
+    tmpRect.Inflate(nsContextBoxBlur::GetBlurRadiusMargin(
+        shadow.base.blur.ToAppUnits(), A2D));
     shadows.UnionRect(shadows, tmpRect);
   }
   return shadows;
 }
 
 /* static */
 bool nsLayoutUtils::GetContentViewerSize(nsPresContext* aPresContext,
                                          LayoutDeviceIntSize& aOutSize) {
--- a/layout/forms/nsButtonFrameRenderer.cpp
+++ b/layout/forms/nsButtonFrameRenderer.cpp
@@ -98,18 +98,18 @@ void nsDisplayButtonBoxShadowOuter::Pain
                                           gfxContext* aCtx) {
   nsRect frameRect = nsRect(ToReferenceFrame(), mFrame->GetSize());
 
   nsCSSRendering::PaintBoxShadowOuter(mFrame->PresContext(), *aCtx, mFrame,
                                       frameRect, GetPaintRect());
 }
 
 bool nsDisplayButtonBoxShadowOuter::CanBuildWebRenderDisplayItems() {
-  nsCSSShadowArray* shadows = mFrame->StyleEffects()->mBoxShadow;
-  if (!shadows) {
+  // FIXME(emilio): Is this right? That doesn't make much sense.
+  if (mFrame->StyleEffects()->mBoxShadow.IsEmpty()) {
     return false;
   }
 
   bool hasBorderRadius;
   bool nativeTheme =
       nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius);
 
   // We don't support native themed things yet like box shadows around
@@ -154,32 +154,36 @@ bool nsDisplayButtonBoxShadowOuter::Crea
       borderRadius = wr::ToBorderRadius(
           LayoutDeviceSize::FromUnknownSize(borderRadii.TopLeft()),
           LayoutDeviceSize::FromUnknownSize(borderRadii.TopRight()),
           LayoutDeviceSize::FromUnknownSize(borderRadii.BottomLeft()),
           LayoutDeviceSize::FromUnknownSize(borderRadii.BottomRight()));
     }
   }
 
-  nsCSSShadowArray* shadows = mFrame->StyleEffects()->mBoxShadow;
-  MOZ_ASSERT(shadows);
+  const Span<const StyleBoxShadow> shadows =
+      mFrame->StyleEffects()->mBoxShadow.AsSpan();
+  MOZ_ASSERT(!shadows.IsEmpty());
 
-  for (uint32_t i = shadows->Length(); i > 0; i--) {
-    nsCSSShadowItem* shadow = shadows->ShadowAt(i - 1);
-    if (shadow->mInset) {
+  for (const StyleBoxShadow& shadow : Reversed(shadows)) {
+    if (shadow.inset) {
       continue;
     }
-    float blurRadius = float(shadow->mRadius) / float(appUnitsPerDevPixel);
+    float blurRadius =
+        float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel);
     gfx::Color shadowColor =
-        nsCSSRendering::GetShadowColor(shadow, mFrame, 1.0);
+        nsCSSRendering::GetShadowColor(shadow.base, mFrame, 1.0);
 
     LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits(
-        nsPoint(shadow->mXOffset, shadow->mYOffset), appUnitsPerDevPixel);
+        nsPoint(shadow.base.horizontal.ToAppUnits(),
+                shadow.base.vertical.ToAppUnits()),
+        appUnitsPerDevPixel);
 
-    float spreadRadius = float(shadow->mSpread) / float(appUnitsPerDevPixel);
+    float spreadRadius =
+        float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel);
 
     aBuilder.PushBoxShadow(deviceBoxRect, deviceClipRect, !BackfaceIsHidden(),
                            deviceBoxRect, wr::ToLayoutVector2D(shadowOffset),
                            wr::ToColorF(shadowColor), blurRadius, spreadRadius,
                            borderRadius, wr::BoxShadowClipMode::Outset);
   }
   return true;
 }
@@ -380,17 +384,17 @@ bool nsDisplayButtonForeground::CreateWe
 
   br->CreateWebRenderCommands(this, aBuilder, aResources, aSc);
   return true;
 }
 
 nsresult nsButtonFrameRenderer::DisplayButton(nsDisplayListBuilder* aBuilder,
                                               nsDisplayList* aBackground,
                                               nsDisplayList* aForeground) {
-  if (mFrame->StyleEffects()->mBoxShadow) {
+  if (!mFrame->StyleEffects()->mBoxShadow.IsEmpty()) {
     aBackground->AppendNewToTop<nsDisplayButtonBoxShadowOuter>(aBuilder,
                                                                GetFrame());
   }
 
   nsRect buttonRect = mFrame->GetRectRelativeToSelf();
 
   nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, mFrame,
                                                        buttonRect, aBackground);
--- a/layout/forms/nsFieldSetFrame.cpp
+++ b/layout/forms/nsFieldSetFrame.cpp
@@ -217,17 +217,17 @@ bool nsDisplayFieldSetBorder::CreateWebR
 void nsFieldSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                        const nsDisplayListSet& aLists) {
   // Paint our background and border in a special way.
   // REVIEW: We don't really need to check frame emptiness here; if it's empty,
   // the background/border display item won't do anything, and if it isn't
   // empty, we need to paint the outline
   if (!(GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) &&
       IsVisibleForPainting()) {
-    if (StyleEffects()->mBoxShadow) {
+    if (!StyleEffects()->mBoxShadow.IsEmpty()) {
       aLists.BorderBackground()->AppendNewToTop<nsDisplayBoxShadowOuter>(
           aBuilder, this);
     }
 
     nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
         aBuilder, this, VisualBorderRectRelativeToSelf(),
         aLists.BorderBackground(),
         /* aAllowWillPaintBorderOptimization = */ false);
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2410,26 +2410,26 @@ void nsFrame::DisplayBorderBackgroundOut
                                              bool aForceBackground) {
   // The visibility check belongs here since child elements have the
   // opportunity to override the visibility property and display even if
   // their parent is hidden.
   if (!IsVisibleForPainting()) {
     return;
   }
 
-  nsCSSShadowArray* shadows = StyleEffects()->mBoxShadow;
-  if (shadows && shadows->HasShadowWithInset(false)) {
+  const auto* effects = StyleEffects();
+  if (effects->HasBoxShadowWithInset(false)) {
     aLists.BorderBackground()->AppendNewToTop<nsDisplayBoxShadowOuter>(aBuilder,
                                                                        this);
   }
 
   bool bgIsThemed =
       DisplayBackgroundUnconditional(aBuilder, aLists, aForceBackground);
 
-  if (shadows && shadows->HasShadowWithInset(true)) {
+  if (effects->HasBoxShadowWithInset(true)) {
     aLists.BorderBackground()->AppendNewToTop<nsDisplayBoxShadowInner>(aBuilder,
                                                                        this);
   }
 
   // If there's a themed background, we should not create a border item.
   // It won't be rendered.
   if (!bgIsThemed && StyleBorder()->HasBorder()) {
     aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder, this);
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -348,17 +348,17 @@ class nsTextPaintStyle {
 
   // if this returns false, we don't need to draw underline.
   static bool GetSelectionUnderline(nsPresContext* aPresContext, int32_t aIndex,
                                     nscolor* aLineColor, float* aRelativeSize,
                                     uint8_t* aStyle);
 
   // if this returns false, no text-shadow was specified for the selection
   // and the *aShadow parameter was not modified.
-  bool GetSelectionShadow(nsCSSShadowArray** aShadow);
+  bool GetSelectionShadow(Span<const StyleSimpleShadow>* aShadows);
 
   nsPresContext* PresContext() const { return mPresContext; }
 
   enum {
     eIndexRawInput = 0,
     eIndexSelRawText,
     eIndexConvText,
     eIndexSelConvText,
@@ -393,18 +393,17 @@ class nsTextPaintStyle {
   bool mInitCommonColors;
   bool mInitSelectionColorsAndShadow;
   bool mResolveColors;
 
   // Selection data
 
   nscolor mSelectionTextColor;
   nscolor mSelectionBGColor;
-  RefPtr<nsCSSShadowArray> mSelectionShadow;
-  bool mHasSelectionShadow;
+  RefPtr<ComputedStyle> mSelectionPseudoStyle;
 
   // Common data
 
   int32_t mSufficientContrast;
   nscolor mFrameBackgroundColor;
   nscolor mSystemFieldForegroundColor;
   nscolor mSystemFieldBackgroundColor;
 
@@ -1817,62 +1816,62 @@ bool BuildTextRunsScanner::ContinueTextR
       HasTerminalNewline(aFrame1)) {
     return false;
   }
 
   if (aFrame1->GetParent()->GetContent() !=
       aFrame2->GetParent()->GetContent()) {
     // Does aFrame, or any ancestor between it and aAncestor, have a property
     // that should inhibit cross-element-boundary shaping on aSide?
-    auto PreventCrossBoundaryShaping =
-        [](const nsIFrame* aFrame, const nsIFrame* aAncestor, Side aSide) {
-          while (aFrame != aAncestor) {
-            ComputedStyle* ctx = aFrame->Style();
-            // According to https://drafts.csswg.org/css-text/#boundary-shaping:
-            //
-            // Text shaping must be broken at inline box boundaries when any of
-            // the following are true for any box whose boundary separates the
-            // two typographic character units:
-            //
-            // 1. Any of margin/border/padding separating the two typographic
-            //    character units in the inline axis is non-zero.
-            const auto& margin = ctx->StyleMargin()->mMargin.Get(aSide);
-            if (!margin.ConvertsToLength() ||
-                margin.AsLengthPercentage().ToLength() != 0) {
-              return true;
-            }
-            const auto& padding = ctx->StylePadding()->mPadding.Get(aSide);
-            if (!padding.ConvertsToLength() || padding.ToLength() != 0) {
-              return true;
-            }
-            if (ctx->StyleBorder()->GetComputedBorderWidth(aSide) != 0) {
-              return true;
-            }
-
-            // 2. vertical-align is not baseline.
-            //
-            // FIXME: Should this use VerticalAlignEnum()?
-            const auto& verticalAlign = ctx->StyleDisplay()->mVerticalAlign;
-            if (!verticalAlign.IsKeyword() ||
-                verticalAlign.AsKeyword() !=
-                    StyleVerticalAlignKeyword::Baseline) {
-              return true;
-            }
-
-            // 3. The boundary is a bidi isolation boundary.
-            const uint8_t unicodeBidi = ctx->StyleTextReset()->mUnicodeBidi;
-            if (unicodeBidi == NS_STYLE_UNICODE_BIDI_ISOLATE ||
-                unicodeBidi == NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE) {
-              return true;
-            }
-
-            aFrame = aFrame->GetParent();
-          }
-          return false;
-        };
+    auto PreventCrossBoundaryShaping = [](const nsIFrame* aFrame,
+                                          const nsIFrame* aAncestor,
+                                          Side aSide) {
+      while (aFrame != aAncestor) {
+        ComputedStyle* ctx = aFrame->Style();
+        // According to https://drafts.csswg.org/css-text/#boundary-shaping:
+        //
+        // Text shaping must be broken at inline box boundaries when any of
+        // the following are true for any box whose boundary separates the
+        // two typographic character units:
+        //
+        // 1. Any of margin/border/padding separating the two typographic
+        //    character units in the inline axis is non-zero.
+        const auto& margin = ctx->StyleMargin()->mMargin.Get(aSide);
+        if (!margin.ConvertsToLength() ||
+            margin.AsLengthPercentage().ToLength() != 0) {
+          return true;
+        }
+        const auto& padding = ctx->StylePadding()->mPadding.Get(aSide);
+        if (!padding.ConvertsToLength() || padding.ToLength() != 0) {
+          return true;
+        }
+        if (ctx->StyleBorder()->GetComputedBorderWidth(aSide) != 0) {
+          return true;
+        }
+
+        // 2. vertical-align is not baseline.
+        //
+        // FIXME: Should this use VerticalAlignEnum()?
+        const auto& verticalAlign = ctx->StyleDisplay()->mVerticalAlign;
+        if (!verticalAlign.IsKeyword() ||
+            verticalAlign.AsKeyword() != StyleVerticalAlignKeyword::Baseline) {
+          return true;
+        }
+
+        // 3. The boundary is a bidi isolation boundary.
+        const uint8_t unicodeBidi = ctx->StyleTextReset()->mUnicodeBidi;
+        if (unicodeBidi == NS_STYLE_UNICODE_BIDI_ISOLATE ||
+            unicodeBidi == NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE) {
+          return true;
+        }
+
+        aFrame = aFrame->GetParent();
+      }
+      return false;
+    };
 
     const nsIFrame* ancestor =
         nsLayoutUtils::FindNearestCommonAncestorFrame(aFrame1, aFrame2);
     MOZ_ASSERT(ancestor);
 
     // Map inline-end and inline-start to physical sides for checking presence
     // of non-zero margin/border/padding.
     Side side1 = wm.PhysicalSide(eLogicalSideIEnd);
@@ -2583,18 +2582,17 @@ void BuildTextRunsScanner::SetupBreakSin
   // whitespace...
   gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
 
   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
     MappedFlow* mappedFlow = &mMappedFlows[i];
     // The CSS word-break value may change within a word, so we reset it for
     // each MappedFlow. The line-breaker will flush its text if the property
     // actually changes.
-    auto wordBreak =
-      mappedFlow->mStartFrame->StyleText()->EffectiveWordBreak();
+    auto wordBreak = mappedFlow->mStartFrame->StyleText()->EffectiveWordBreak();
     switch (wordBreak) {
       case StyleWordBreak::BreakAll:
         mLineBreaker.SetWordBreak(LineBreaker::kWordBreak_BreakAll);
         break;
       case StyleWordBreak::KeepAll:
         mLineBreaker.SetWordBreak(LineBreaker::kWordBreak_KeepAll);
         break;
       case StyleWordBreak::Normal:
@@ -3783,17 +3781,16 @@ static nscolor EnsureDifferentColors(nsc
 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
     : mFrame(aFrame),
       mPresContext(aFrame->PresContext()),
       mInitCommonColors(false),
       mInitSelectionColorsAndShadow(false),
       mResolveColors(true),
       mSelectionTextColor(NS_RGBA(0, 0, 0, 0)),
       mSelectionBGColor(NS_RGBA(0, 0, 0, 0)),
-      mHasSelectionShadow(false),
       mSufficientContrast(0),
       mFrameBackgroundColor(NS_RGBA(0, 0, 0, 0)),
       mSystemFieldForegroundColor(NS_RGBA(0, 0, 0, 0)),
       mSystemFieldBackgroundColor(NS_RGBA(0, 0, 0, 0)) {
   for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++)
     mSelectionStyle[i].mInit = false;
 }
 
@@ -4057,18 +4054,17 @@ bool nsTextPaintStyle::InitSelectionColo
 
   // Use ::selection pseudo class if applicable.
   if (RefPtr<ComputedStyle> style =
           mFrame->ComputeSelectionStyle(selectionStatus)) {
     mSelectionBGColor =
         style->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
     mSelectionTextColor =
         style->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
-    mHasSelectionShadow = true;
-    mSelectionShadow = style->StyleText()->mTextShadow;
+    mSelectionPseudoStyle = style.forget();
     return true;
   }
 
   nscolor selectionBGColor =
       LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
 
   if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
     mSelectionBGColor = LookAndFeel::GetColor(
@@ -4235,23 +4231,24 @@ bool nsTextPaintStyle::GetSelectionUnder
   }
   *aRelativeSize = size;
   *aStyle = style;
 
   return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE &&
          color != NS_TRANSPARENT && size > 0.0f;
 }
 
-bool nsTextPaintStyle::GetSelectionShadow(nsCSSShadowArray** aShadow) {
+bool nsTextPaintStyle::GetSelectionShadow(
+    Span<const StyleSimpleShadow>* aShadows) {
   if (!InitSelectionColorsAndShadow()) {
     return false;
   }
 
-  if (mHasSelectionShadow) {
-    *aShadow = mSelectionShadow;
+  if (mSelectionPseudoStyle) {
+    *aShadows = mSelectionPseudoStyle->StyleText()->mTextShadow.AsSpan();
     return true;
   }
 
   return false;
 }
 
 inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor) {
   nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor), NS_GET_G(aForeColor),
@@ -5696,31 +5693,28 @@ bool nsTextFrame::GetSelectionTextColors
     default:
       *aForeground = aTextPaintStyle.GetTextColor();
       *aBackground = NS_RGBA(0, 0, 0, 0);
       return false;
   }
 }
 
 /**
- * This sets *aShadow to the appropriate shadow, if any, for the given
- * type of selection. Returns true if *aShadow was set.
- * If text-shadow was not specified, *aShadow is left untouched
- * (NOT reset to null), and the function returns false.
+ * This sets *aShadows to the appropriate shadows, if any, for the given
+ * type of selection.
+ * If text-shadow was not specified, *aShadows is left untouched.
  */
-static bool GetSelectionTextShadow(nsIFrame* aFrame,
+static void GetSelectionTextShadow(nsIFrame* aFrame,
                                    SelectionType aSelectionType,
                                    nsTextPaintStyle& aTextPaintStyle,
-                                   nsCSSShadowArray** aShadow) {
-  switch (aSelectionType) {
-    case SelectionType::eNormal:
-      return aTextPaintStyle.GetSelectionShadow(aShadow);
-    default:
-      return false;
-  }
+                                   Span<const StyleSimpleShadow>* aShadows) {
+  if (aSelectionType != SelectionType::eNormal) {
+    return;
+  }
+  aTextPaintStyle.GetSelectionShadow(aShadows);
 }
 
 /**
  * This class lets us iterate over chunks of text in a uniform selection state,
  * observing cluster boundaries, in content order, maintaining the current
  * x-offset as we go, and telling whether the text chunk has a hyphen after
  * it or not. The caller is responsible for actually computing the advance
  * width of each chunk.
@@ -5841,32 +5835,31 @@ static void AddHyphenToMetrics(nsTextFra
       hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget);
   if (aTextFrame->GetWritingMode().IsLineInverted()) {
     hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost();
   }
   aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft());
 }
 
 void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams,
-                                 nsCSSShadowItem* aShadowDetails,
+                                 const StyleSimpleShadow& aShadowDetails,
                                  gfxRect& aBoundingBox, uint32_t aBlurFlags) {
   AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS);
 
-  gfx::Point shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset);
-  nscoord blurRadius = std::max(aShadowDetails->mRadius, 0);
-
-  nscolor shadowColor =
-      aShadowDetails->mColor.CalcColor(aParams.foregroundColor);
+  nsPoint shadowOffset(aShadowDetails.horizontal.ToAppUnits(),
+                       aShadowDetails.vertical.ToAppUnits());
+  nscoord blurRadius = std::max(aShadowDetails.blur.ToAppUnits(), 0);
+
+  nscolor shadowColor = aShadowDetails.color.CalcColor(aParams.foregroundColor);
 
   if (auto* textDrawer = aParams.context->GetTextDrawer()) {
     wr::Shadow wrShadow;
 
-    wrShadow.offset = {
-        PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mXOffset),
-        PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mYOffset)};
+    wrShadow.offset = {PresContext()->AppUnitsToFloatDevPixels(shadowOffset.x),
+                       PresContext()->AppUnitsToFloatDevPixels(shadowOffset.y)};
 
     wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius);
     wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
 
     bool inflate = true;
     textDrawer->AppendShadow(wrShadow, inflate);
     return;
   }
@@ -5886,17 +5879,18 @@ void nsTextFrame::PaintOneShadow(const P
     }
     shadowGfxRect += gfxPoint(aParams.textBaselinePt.x,
                               aParams.framePt.y + aParams.leftSideOffset);
   } else {
     shadowGfxRect =
         aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset,
                                 aParams.textBaselinePt.y);
   }
-  shadowGfxRect += gfxPoint(shadowOffset.x, shadowOffset.y);
+  Point shadowGfxOffset(shadowOffset.x, shadowOffset.y);
+  shadowGfxRect += gfxPoint(shadowGfxOffset.x, shadowOffset.y);
 
   nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
                     NSToCoordRound(shadowGfxRect.Y()),
                     NSToCoordRound(shadowGfxRect.Width()),
                     NSToCoordRound(shadowGfxRect.Height()));
 
   nsContextBoxBlur contextBoxBlur;
   const auto A2D = PresContext()->AppUnitsPerDevPixel();
@@ -5912,27 +5906,27 @@ void nsTextFrame::PaintOneShadow(const P
   // Draw the text onto our alpha-only surface to capture the alpha values.
   // Remember that the box blur context has a device offset on it, so we don't
   // need to translate any coordinates to fit on the surface.
   gfxFloat advanceWidth;
   nsTextPaintStyle textPaintStyle(this);
   DrawTextParams params(shadowContext);
   params.advanceWidth = &advanceWidth;
   params.dirtyRect = aParams.dirtyRect;
-  params.framePt = aParams.framePt + shadowOffset;
+  params.framePt = aParams.framePt + shadowGfxOffset;
   params.provider = aParams.provider;
   params.textStyle = &textPaintStyle;
   params.textColor =
       aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0);
   params.clipEdges = aParams.clipEdges;
   params.drawSoftHyphen = (GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
   // Multi-color shadow is not allowed, so we use the same color of the text
   // color.
   params.decorationOverrideColor = &params.textColor;
-  DrawText(aParams.range, aParams.textBaselinePt + shadowOffset, params);
+  DrawText(aParams.range, aParams.textBaselinePt + shadowGfxOffset, params);
 
   contextBoxBlur.DoPaint();
   aParams.context->Restore();
 }
 
 // Paints selection backgrounds and text in the correct colors. Also computes
 // aAllTypes, the union of all selection types that are applying to this text.
 bool nsTextFrame::PaintTextWithSelectionColors(
@@ -6076,30 +6070,30 @@ bool nsTextFrame::PaintTextWithSelection
 
     gfx::Point textBaselinePt =
         vertical
             ? gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset)
             : gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y);
 
     // Determine what shadow, if any, to draw - either from textStyle
     // or from the ::-moz-selection pseudo-class if specified there
-    nsCSSShadowArray* shadow = textStyle->GetTextShadow();
+    Span<const StyleSimpleShadow> shadows = textStyle->mTextShadow.AsSpan();
     GetSelectionTextShadow(this, selectionType, *aParams.textPaintStyle,
-                           &shadow);
-    if (shadow) {
+                           &shadows);
+    if (!shadows.IsEmpty()) {
       nscoord startEdge = iOffset;
       if (mTextRun->IsInlineReversed()) {
         startEdge -=
             hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
       }
       shadowParams.range = range;
       shadowParams.textBaselinePt = textBaselinePt;
       shadowParams.foregroundColor = foreground;
       shadowParams.leftSideOffset = startEdge;
-      PaintShadows(shadow, shadowParams);
+      PaintShadows(shadows, shadowParams);
     }
 
     // Draw text segment
     params.textColor = foreground;
     params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor();
     params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth();
     params.drawSoftHyphen = hyphenWidth > 0;
     DrawText(range, textBaselinePt, params);
@@ -6449,19 +6443,19 @@ bool nsTextFrame::MeasureCharClippedText
     maxLength = offset - *aStartOffset;
     nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge;
     *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth);
   }
   *aMaxLength = maxLength;
   return maxLength != 0;
 }
 
-void nsTextFrame::PaintShadows(nsCSSShadowArray* aShadow,
+void nsTextFrame::PaintShadows(Span<const StyleSimpleShadow> aShadows,
                                const PaintShadowParams& aParams) {
-  if (!aShadow) {
+  if (aShadows.IsEmpty()) {
     return;
   }
 
   gfxTextRun::Metrics shadowMetrics = mTextRun->MeasureText(
       aParams.range, gfxFont::LOOSE_INK_EXTENTS, nullptr, aParams.provider);
   if (GetWritingMode().IsLineInverted()) {
     Swap(shadowMetrics.mAscent, shadowMetrics.mDescent);
     shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost();
@@ -6492,19 +6486,18 @@ void nsTextFrame::PaintShadows(nsCSSShad
     run++;
   }
 
   if (mTextRun->IsVertical()) {
     Swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
     Swap(shadowMetrics.mBoundingBox.width, shadowMetrics.mBoundingBox.height);
   }
 
-  for (uint32_t i = aShadow->Length(); i > 0; --i) {
-    PaintOneShadow(aParams, aShadow->ShadowAt(i - 1),
-                   shadowMetrics.mBoundingBox, blurFlags);
+  for (const auto& shadow : Reversed(aShadows)) {
+    PaintOneShadow(aParams, shadow, shadowMetrics.mBoundingBox, blurFlags);
   }
 }
 
 void nsTextFrame::PaintText(const PaintTextParams& aParams,
                             const nscoord aVisIStartEdge,
                             const nscoord aVisIEndEdge,
                             const nsPoint& aToReferenceFrame,
                             const bool aIsSelected,
@@ -6604,17 +6597,17 @@ void nsTextFrame::PaintText(const PaintT
     const nsStyleText* textStyle = StyleText();
     PaintShadowParams shadowParams(aParams);
     shadowParams.range = range;
     shadowParams.textBaselinePt = textBaselinePt;
     shadowParams.leftSideOffset = snappedStartEdge;
     shadowParams.provider = &provider;
     shadowParams.foregroundColor = foregroundColor;
     shadowParams.clipEdges = &clipEdges;
-    PaintShadows(textStyle->mTextShadow, shadowParams);
+    PaintShadows(textStyle->mTextShadow.AsSpan(), shadowParams);
   }
 
   gfxFloat advanceWidth;
   DrawTextParams params(aParams.context);
   params.dirtyRect = aParams.dirtyRect;
   params.framePt = aParams.framePt;
   params.provider = &provider;
   params.advanceWidth = &advanceWidth;
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -671,20 +671,20 @@ class nsTextFrame : public nsFrame {
     nscoord leftSideOffset = 0;
     explicit PaintShadowParams(const PaintTextParams& aParams)
         : dirtyRect(aParams.dirtyRect),
           framePt(aParams.framePt),
           context(aParams.context) {}
   };
 
   void PaintOneShadow(const PaintShadowParams& aParams,
-                      nsCSSShadowItem* aShadowDetails, gfxRect& aBoundingBox,
-                      uint32_t aBlurFlags);
+                      const mozilla::StyleSimpleShadow& aShadowDetails,
+                      gfxRect& aBoundingBox, uint32_t aBlurFlags);
 
-  void PaintShadows(nsCSSShadowArray* aShadow,
+  void PaintShadows(mozilla::Span<const mozilla::StyleSimpleShadow>,
                     const PaintShadowParams& aParams);
 
   struct LineDecoration {
     nsIFrame* mFrame;
 
     // This is represents the offset from our baseline to mFrame's baseline;
     // positive offsets are *above* the baseline and negative offsets below
     nscoord mBaselineOffset;
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -1349,20 +1349,20 @@ bool nsCSSRendering::HasBoxShadowNativeT
     // border-box path with border-radius disabled.
     return transparency != nsITheme::eOpaque;
   }
 
   aMaybeHasBorderRadius = true;
   return false;
 }
 
-gfx::Color nsCSSRendering::GetShadowColor(nsCSSShadowItem* aShadow,
+gfx::Color nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow,
                                           nsIFrame* aFrame, float aOpacity) {
   // Get the shadow color; if not specified, use the foreground color
-  nscolor shadowColor = aShadow->mColor.CalcColor(aFrame);
+  nscolor shadowColor = aShadow.color.CalcColor(aFrame);
   Color color = Color::FromABGR(shadowColor);
   color.a *= aOpacity;
   return color;
 }
 
 nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea,
                                      bool aNativeTheme, nsIFrame* aForFrame) {
   nsRect frameRect = aNativeTheme
@@ -1396,18 +1396,20 @@ bool nsCSSRendering::GetBorderRadii(cons
 
 void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
                                          gfxContext& aRenderingContext,
                                          nsIFrame* aForFrame,
                                          const nsRect& aFrameArea,
                                          const nsRect& aDirtyRect,
                                          float aOpacity) {
   DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
-  nsCSSShadowArray* shadows = aForFrame->StyleEffects()->mBoxShadow;
-  if (!shadows) return;
+  auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan();
+  if (shadows.IsEmpty()) {
+    return;
+  }
 
   bool hasBorderRadius;
   // mutually exclusive with hasBorderRadius
   bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius);
   const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
 
   nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame);
 
@@ -1444,51 +1446,54 @@ void nsCSSRendering::PaintBoxShadowOuter
         aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft();
     skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel);
   } else if (hasBorderRadius) {
     skipGfxRect.Deflate(gfxMargin(
         std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
         std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
   }
 
-  for (uint32_t i = shadows->Length(); i > 0; --i) {
-    nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
-    if (shadowItem->mInset) continue;
+  for (const StyleBoxShadow& shadow : Reversed(shadows)) {
+    if (shadow.inset) {
+      continue;
+    }
 
     nsRect shadowRect = frameRect;
-    shadowRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
+    nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(),
+                         shadow.base.vertical.ToAppUnits());
+    shadowRect.MoveBy(shadowOffset);
+    nscoord shadowSpread = shadow.spread.ToAppUnits();
     if (!nativeTheme) {
-      shadowRect.Inflate(shadowItem->mSpread);
+      shadowRect.Inflate(shadowSpread);
     }
 
     // shadowRect won't include the blur, so make an extra rect here that
     // includes the blur for use in the even-odd rule below.
     nsRect shadowRectPlusBlur = shadowRect;
-    nscoord blurRadius = shadowItem->mRadius;
+    nscoord blurRadius = shadow.base.blur.ToAppUnits();
     shadowRectPlusBlur.Inflate(
         nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel));
 
     Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel);
     shadowGfxRectPlusBlur.RoundOut();
     MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
 
-    Color gfxShadowColor = GetShadowColor(shadowItem, aForFrame, aOpacity);
+    Color gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity);
 
     if (nativeTheme) {
       nsContextBoxBlur blurringArea;
 
       // When getting the widget shape from the native theme, we're going
       // to draw the widget into the shadow surface to create a mask.
       // We need to ensure that there actually *is* a shadow surface
       // and that we're not going to draw directly into aRenderingContext.
-      gfxContext* shadowContext =
-          blurringArea.Init(shadowRect, shadowItem->mSpread, blurRadius,
-                            oneDevPixel, &aRenderingContext, aDirtyRect,
-                            useSkipGfxRect ? &skipGfxRect : nullptr,
-                            nsContextBoxBlur::FORCE_MASK);
+      gfxContext* shadowContext = blurringArea.Init(
+          shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext,
+          aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr,
+          nsContextBoxBlur::FORCE_MASK);
       if (!shadowContext) continue;
 
       MOZ_ASSERT(shadowContext == blurringArea.GetContext());
 
       aRenderingContext.Save();
       aRenderingContext.SetColor(gfxShadowColor);
 
       // Draw the shape of the frame so it can be blurred. Recall how
@@ -1499,23 +1504,22 @@ void nsCSSRendering::PaintBoxShadowOuter
       // this.
 
       // We don't clip the border-box from the shadow, nor any other box.
       // We assume that the native theme is going to paint over the shadow.
 
       // Draw the widget shape
       gfxContextMatrixAutoSaveRestore save(shadowContext);
       gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
-          nsPoint(shadowItem->mXOffset, shadowItem->mYOffset),
-          aPresContext->AppUnitsPerDevPixel());
+          shadowOffset, aPresContext->AppUnitsPerDevPixel());
       shadowContext->SetMatrixDouble(
           shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset));
 
       nsRect nativeRect = aDirtyRect;
-      nativeRect.MoveBy(-nsPoint(shadowItem->mXOffset, shadowItem->mYOffset));
+      nativeRect.MoveBy(-shadowOffset);
       nativeRect.IntersectRect(frameRect, nativeRect);
       aPresContext->GetTheme()->DrawWidgetBackground(shadowContext, aForFrame,
                                                      styleDisplay->mAppearance,
                                                      aFrameArea, nativeRect);
 
       blurringArea.DoPaint();
       aRenderingContext.Restore();
     } else {
@@ -1572,17 +1576,17 @@ void nsCSSRendering::PaintBoxShadowOuter
       }
       fragmentClip = fragmentClip.Intersect(aDirtyRect);
       aRenderingContext.Clip(NSRectToSnappedRect(
           fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
           aDrawTarget));
 
       RectCornerRadii clipRectRadii;
       if (hasBorderRadius) {
-        Float spreadDistance = Float(shadowItem->mSpread) / oneDevPixel;
+        Float spreadDistance = Float(shadowSpread / oneDevPixel);
 
         Float borderSizes[4];
 
         borderSizes[eSideLeft] = spreadDistance;
         borderSizes[eSideTop] = spreadDistance;
         borderSizes[eSideRight] = spreadDistance;
         borderSizes[eSideBottom] = spreadDistance;
 
@@ -1605,18 +1609,21 @@ nsRect nsCSSRendering::GetBoxShadowInner
 
   nsRect paddingRect = frameRect;
   nsMargin border = aFrame->GetUsedBorder();
   paddingRect.Deflate(border);
   return paddingRect;
 }
 
 bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame* aFrame) {
-  nsCSSShadowArray* shadows = aFrame->StyleEffects()->mBoxShadow;
-  if (!shadows) return false;
+  const Span<const StyleBoxShadow> shadows =
+      aFrame->StyleEffects()->mBoxShadow.AsSpan();
+  if (shadows.IsEmpty()) {
+    return false;
+  }
 
   if (aFrame->IsThemed() && aFrame->GetContent() &&
       !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) {
     // There's no way of getting hold of a shape corresponding to a
     // "padding-box" for native-themed widgets, so just don't draw
     // inner box-shadows for them. But we allow chrome to paint inner
     // box shadows since chrome can be aware of the platform theme.
     return false;
@@ -1659,52 +1666,55 @@ bool nsCSSRendering::GetShadowInnerRadii
 void nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
                                          gfxContext& aRenderingContext,
                                          nsIFrame* aForFrame,
                                          const nsRect& aFrameArea) {
   if (!ShouldPaintBoxShadowInner(aForFrame)) {
     return;
   }
 
-  nsCSSShadowArray* shadows = aForFrame->StyleEffects()->mBoxShadow;
+  const Span<const StyleBoxShadow> shadows =
+      aForFrame->StyleEffects()->mBoxShadow.AsSpan();
   NS_ASSERTION(
       aForFrame->IsFieldSetFrame() || aFrameArea.Size() == aForFrame->GetSize(),
       "unexpected size");
 
   nsRect paddingRect = GetBoxShadowInnerPaddingRect(aForFrame, aFrameArea);
 
   RectCornerRadii innerRadii;
   bool hasBorderRadius = GetShadowInnerRadii(aForFrame, aFrameArea, innerRadii);
 
   const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
 
-  for (uint32_t i = shadows->Length(); i > 0; --i) {
-    nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
-    if (!shadowItem->mInset) continue;
+  for (const StyleBoxShadow& shadow : Reversed(shadows)) {
+    if (!shadow.inset) {
+      continue;
+    }
 
     // shadowPaintRect: the area to paint on the temp surface
     // shadowClipRect: the area on the temporary surface within shadowPaintRect
     //                 that we will NOT paint in
-    nscoord blurRadius = shadowItem->mRadius;
+    nscoord blurRadius = shadow.base.blur.ToAppUnits();
     nsMargin blurMargin =
         nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel);
     nsRect shadowPaintRect = paddingRect;
     shadowPaintRect.Inflate(blurMargin);
 
     // Round the spread radius to device pixels (by truncation).
     // This mostly matches what we do for borders, except that we don't round
     // up values between zero and one device pixels to one device pixel.
     // This way of rounding is symmetric around zero, which makes sense for
     // the spread radius.
-    int32_t spreadDistance = shadowItem->mSpread / oneDevPixel;
+    int32_t spreadDistance = shadow.spread.ToAppUnits() / oneDevPixel;
     nscoord spreadDistanceAppUnits =
         aPresContext->DevPixelsToAppUnits(spreadDistance);
 
     nsRect shadowClipRect = paddingRect;
-    shadowClipRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
+    shadowClipRect.MoveBy(shadow.base.horizontal.ToAppUnits(),
+                          shadow.base.vertical.ToAppUnits());
     shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits);
 
     Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, oneDevPixel);
     shadowClipGfxRect.Round();
 
     RectCornerRadii clipRectRadii;
     if (hasBorderRadius) {
       // Calculate the radii the inner clipping rect will have
@@ -1749,34 +1759,34 @@ void nsCSSRendering::PaintBoxShadowInner
     DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
 
     // Clip the context to the area of the frame's padding rect, so no part of
     // the shadow is painted outside. Also cut out anything beyond where the
     // inset shadow will be.
     Rect shadowGfxRect = NSRectToRect(paddingRect, oneDevPixel);
     shadowGfxRect.Round();
 
-    Color shadowColor = GetShadowColor(shadowItem, aForFrame, 1.0);
+    Color shadowColor = GetShadowColor(shadow.base, aForFrame, 1.0);
     aRenderingContext.Save();
 
     // This clips the outside border radius.
     // clipRectRadii is the border radius inside the inset shadow.
     if (hasBorderRadius) {
       RefPtr<Path> roundedRect =
           MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
       aRenderingContext.Clip(roundedRect);
     } else {
       aRenderingContext.Clip(shadowGfxRect);
     }
 
     nsContextBoxBlur insetBoxBlur;
     gfxRect destRect =
         nsLayoutUtils::RectToGfxRect(shadowPaintRect, oneDevPixel);
-    Point shadowOffset(shadowItem->mXOffset / oneDevPixel,
-                       shadowItem->mYOffset / oneDevPixel);
+    Point shadowOffset(shadow.base.horizontal.ToAppUnits() / oneDevPixel,
+                       shadow.base.vertical.ToAppUnits() / oneDevPixel);
 
     insetBoxBlur.InsetBoxBlur(
         &aRenderingContext, ToRect(destRect), shadowClipGfxRect, shadowColor,
         blurRadius, spreadDistanceAppUnits, oneDevPixel, hasBorderRadius,
         clipRectRadii, ToRect(skipGfxRect), shadowOffset);
     aRenderingContext.Restore();
   }
 }
--- a/layout/painting/nsCSSRendering.h
+++ b/layout/painting/nsCSSRendering.h
@@ -136,17 +136,17 @@ struct nsCSSRendering {
                                   nsIFrame* aForFrame,
                                   const nsRect& aFrameArea);
 
   static bool GetBorderRadii(const nsRect& aFrameRect,
                              const nsRect& aBorderRect, nsIFrame* aFrame,
                              RectCornerRadii& aOutRadii);
   static nsRect GetShadowRect(const nsRect& aFrameArea, bool aNativeTheme,
                               nsIFrame* aForFrame);
-  static mozilla::gfx::Color GetShadowColor(nsCSSShadowItem* aShadow,
+  static mozilla::gfx::Color GetShadowColor(const mozilla::StyleSimpleShadow&,
                                             nsIFrame* aFrame, float aOpacity);
   // Returns if the frame has a themed frame.
   // aMaybeHasBorderRadius will return false if we can early detect
   // that we don't have a border radius.
   static bool HasBoxShadowNativeTheme(nsIFrame* aFrame,
                                       bool& aMaybeHasBorderRadius);
   static void PaintBoxShadowOuter(nsPresContext* aPresContext,
                                   gfxContext& aRenderingContext,
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -3740,18 +3740,17 @@ bool nsDisplayBackgroundImage::AppendBac
 
   if (SpecialCutoutRegionCase(aBuilder, aFrame, aBackgroundRect, aList,
                               color)) {
     return false;
   }
 
   const nsStyleBorder* borderStyle = aFrame->StyleBorder();
   const nsStyleEffects* effectsStyle = aFrame->StyleEffects();
-  bool hasInsetShadow = effectsStyle->mBoxShadow &&
-                        effectsStyle->mBoxShadow->HasShadowWithInset(true);
+  bool hasInsetShadow = effectsStyle->HasBoxShadowWithInset(true);
   bool willPaintBorder = aAllowWillPaintBorderOptimization && !isThemed &&
                          !hasInsetShadow && borderStyle->HasBorder();
 
   nsPoint toRef = aBuilder->ToReferenceFrame(aFrame);
 
   // An auxiliary list is necessary in case we have background blending; if that
   // is the case, background items need to be wrapped by a blend container to
   // isolate blending to the background
@@ -5379,18 +5378,18 @@ bool nsDisplayBoxShadowOuter::ComputeVis
     return false;
   }
 
   mVisibleRegion.And(*aVisibleRegion, GetPaintRect());
   return true;
 }
 
 bool nsDisplayBoxShadowOuter::CanBuildWebRenderDisplayItems() {
-  nsCSSShadowArray* shadows = mFrame->StyleEffects()->mBoxShadow;
-  if (!shadows) {
+  auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan();
+  if (shadows.IsEmpty()) {
     return false;
   }
 
   bool hasBorderRadius;
   bool nativeTheme =
       nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius);
 
   // We don't support native themed things yet like box shadows around
@@ -5435,34 +5434,36 @@ bool nsDisplayBoxShadowOuter::CreateWebR
     hasBorderRadius = nsCSSRendering::GetBorderRadii(frameRect, borderRect,
                                                      mFrame, borderRadii);
   }
 
   // Everything here is in app units, change to device units.
   for (uint32_t i = 0; i < rects.Length(); ++i) {
     LayoutDeviceRect clipRect =
         LayoutDeviceRect::FromAppUnits(rects[i], appUnitsPerDevPixel);
-    nsCSSShadowArray* shadows = mFrame->StyleEffects()->mBoxShadow;
-    MOZ_ASSERT(shadows);
-
-    for (uint32_t j = shadows->Length(); j > 0; j--) {
-      nsCSSShadowItem* shadow = shadows->ShadowAt(j - 1);
-      if (shadow->mInset) {
+    auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan();
+    MOZ_ASSERT(!shadows.IsEmpty());
+
+    for (auto& shadow : Reversed(shadows)) {
+      if (shadow.inset) {
         continue;
       }
 
-      float blurRadius = float(shadow->mRadius) / float(appUnitsPerDevPixel);
+      float blurRadius =
+          float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel);
       gfx::Color shadowColor =
-          nsCSSRendering::GetShadowColor(shadow, mFrame, mOpacity);
+          nsCSSRendering::GetShadowColor(shadow.base, mFrame, mOpacity);
 
       // We don't move the shadow rect here since WR does it for us
       // Now translate everything to device pixels.
       const nsRect& shadowRect = frameRect;
       LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits(
-          nsPoint(shadow->mXOffset, shadow->mYOffset), appUnitsPerDevPixel);
+          nsPoint(shadow.base.horizontal.ToAppUnits(),
+                  shadow.base.vertical.ToAppUnits()),
+          appUnitsPerDevPixel);
 
       LayoutDeviceRect deviceBox =
           LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel);
       wr::LayoutRect deviceBoxRect = wr::ToRoundedLayoutRect(deviceBox);
       wr::LayoutRect deviceClipRect = wr::ToRoundedLayoutRect(clipRect);
 
       LayoutDeviceSize zeroSize;
       wr::BorderRadius borderRadius =
@@ -5470,17 +5471,18 @@ bool nsDisplayBoxShadowOuter::CreateWebR
       if (hasBorderRadius) {
         borderRadius = wr::ToBorderRadius(
             LayoutDeviceSize::FromUnknownSize(borderRadii.TopLeft()),
             LayoutDeviceSize::FromUnknownSize(borderRadii.TopRight()),
             LayoutDeviceSize::FromUnknownSize(borderRadii.BottomLeft()),
             LayoutDeviceSize::FromUnknownSize(borderRadii.BottomRight()));
       }
 
-      float spreadRadius = float(shadow->mSpread) / float(appUnitsPerDevPixel);
+      float spreadRadius =
+          float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel);
 
       aBuilder.PushBoxShadow(deviceBoxRect, deviceClipRect, !BackfaceIsHidden(),
                              deviceBoxRect, wr::ToLayoutVector2D(shadowOffset),
                              wr::ToColorF(shadowColor), blurRadius,
                              spreadRadius, borderRadius,
                              wr::BoxShadowClipMode::Outset);
     }
   }
@@ -5533,18 +5535,18 @@ void nsDisplayBoxShadowInner::Paint(nsDi
     nsCSSRendering::PaintBoxShadowInner(presContext, *aCtx, mFrame, borderRect);
     gfx->Restore();
   }
 }
 
 bool nsDisplayBoxShadowInner::CanCreateWebRenderCommands(
     nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
     const nsPoint& aReferenceOffset) {
-  nsCSSShadowArray* shadows = aFrame->StyleEffects()->mBoxShadow;
-  if (!shadows) {
+  auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
+  if (shadows.IsEmpty()) {
     // Means we don't have to paint anything
     return true;
   }
 
   bool hasBorderRadius;
   bool nativeTheme =
       nsCSSRendering::HasBoxShadowNativeTheme(aFrame, hasBorderRadius);
 
@@ -5565,55 +5567,55 @@ void nsDisplayBoxShadowInner::CreateInse
     return;
   }
 
   int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
 
   AutoTArray<nsRect, 10> rects;
   ComputeDisjointRectangles(aVisibleRegion, &rects);
 
-  nsCSSShadowArray* shadows = aFrame->StyleEffects()->mBoxShadow;
+  auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
 
   for (uint32_t i = 0; i < rects.Length(); ++i) {
     LayoutDeviceRect clipRect =
         LayoutDeviceRect::FromAppUnits(rects[i], appUnitsPerDevPixel);
 
-    for (uint32_t i = shadows->Length(); i > 0; --i) {
-      nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
-      if (!shadowItem->mInset) {
+    for (auto& shadow : Reversed(shadows)) {
+      if (!shadow.inset) {
         continue;
       }
 
       nsRect shadowRect =
           nsCSSRendering::GetBoxShadowInnerPaddingRect(aFrame, aBorderRect);
       RectCornerRadii innerRadii;
       nsCSSRendering::GetShadowInnerRadii(aFrame, aBorderRect, innerRadii);
 
       // Now translate everything to device pixels.
       LayoutDeviceRect deviceBoxRect =
           LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel);
       wr::LayoutRect deviceClipRect = wr::ToRoundedLayoutRect(clipRect);
       Color shadowColor =
-          nsCSSRendering::GetShadowColor(shadowItem, aFrame, 1.0);
+          nsCSSRendering::GetShadowColor(shadow.base, aFrame, 1.0);
 
       LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits(
-          nsPoint(shadowItem->mXOffset, shadowItem->mYOffset),
+          nsPoint(shadow.base.horizontal.ToAppUnits(),
+                  shadow.base.vertical.ToAppUnits()),
           appUnitsPerDevPixel);
 
       float blurRadius =
-          float(shadowItem->mRadius) / float(appUnitsPerDevPixel);
+          float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel);
 
       wr::BorderRadius borderRadius = wr::ToBorderRadius(
           LayoutDeviceSize::FromUnknownSize(innerRadii.TopLeft()),
           LayoutDeviceSize::FromUnknownSize(innerRadii.TopRight()),
           LayoutDeviceSize::FromUnknownSize(innerRadii.BottomLeft()),
           LayoutDeviceSize::FromUnknownSize(innerRadii.BottomRight()));
       // NOTE: Any spread radius > 0 will render nothing. WR Bug.
       float spreadRadius =
-          float(shadowItem->mSpread) / float(appUnitsPerDevPixel);
+          float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel);
 
       aBuilder.PushBoxShadow(
           wr::ToLayoutRect(deviceBoxRect), deviceClipRect,
           !aFrame->BackfaceIsHidden(), wr::ToLayoutRect(deviceBoxRect),
           wr::ToLayoutVector2D(shadowOffset), wr::ToColorF(shadowColor),
           blurRadius, spreadRadius, borderRadius, wr::BoxShadowClipMode::Inset);
     }
   }
@@ -8964,17 +8966,17 @@ nsDisplayText::nsDisplayText(nsDisplayLi
 
 bool nsDisplayText::CanApplyOpacity() const {
   if (IsSelected()) {
     return false;
   }
 
   nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
   const nsStyleText* textStyle = f->StyleText();
-  if (textStyle->mTextShadow) {
+  if (textStyle->HasTextShadow()) {
     return false;
   }
 
   nsTextFrame::TextDecorations decorations;
   f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
                         decorations);
   if (decorations.HasDecorationLines()) {
     return false;
@@ -9012,17 +9014,17 @@ bool nsDisplayText::CreateWebRenderComma
           .ToUnknownPoint();
 
   // Clipping the bounds to the PaintRect (factoring in what's covered by parent
   // frames) let's us early reject a bunch of things, but it can produce
   // incorrect results for shadows, because they can translate things back into
   // view. Also if we're selected we might have some shadows from the
   // ::selected and ::inctive-selected pseudo-selectors. So don't do this
   // optimization if we have shadows or a selection.
-  if (!(IsSelected() || f->StyleText()->GetTextShadow())) {
+  if (!(IsSelected() || f->StyleText()->HasTextShadow())) {
     nsRect visible = GetPaintRect();
     visible.Inflate(3 * appUnitsPerDevPixel);
     bounds = bounds.Intersect(visible);
   }
 
   RefPtr<gfxContext> textDrawer = aBuilder.GetTextContext(
       aResources, aSc, aManager, this, bounds, deviceOffset);
 
@@ -9943,33 +9945,27 @@ bool nsDisplayFilters::CreateWebRenderCS
             ClampStdDeviation(NSAppUnitsToFloatPixels(
                 filter.GetFilterParameter().GetCoordValue(),
                 appUnitsPerDevPixel))));
         break;
       }
       case NS_STYLE_FILTER_DROP_SHADOW: {
         float appUnitsPerDevPixel =
             mFrame->PresContext()->AppUnitsPerDevPixel();
-        nsCSSShadowArray* shadows = filter.GetDropShadow();
-        if (!shadows || shadows->Length() != 1) {
-          MOZ_ASSERT_UNREACHABLE(
-              "Exactly one drop shadow should have been "
-              "parsed.");
-          return false;
-        }
-
-        nsCSSShadowItem* shadow = shadows->ShadowAt(0);
-        nscolor color = shadow->mColor.CalcColor(mFrame);
+        const StyleSimpleShadow& shadow = filter.GetDropShadow();
+        nscolor color = shadow.color.CalcColor(mFrame);
 
         wr::Shadow wrShadow;
         wrShadow.offset = {
-            NSAppUnitsToFloatPixels(shadow->mXOffset, appUnitsPerDevPixel),
-            NSAppUnitsToFloatPixels(shadow->mYOffset, appUnitsPerDevPixel)};
-        wrShadow.blur_radius =
-            NSAppUnitsToFloatPixels(shadow->mRadius, appUnitsPerDevPixel);
+            NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(),
+                                    appUnitsPerDevPixel),
+            NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(),
+                                    appUnitsPerDevPixel)};
+        wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(),
+                                                       appUnitsPerDevPixel);
         wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
                           NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
         auto filterOp = wr::FilterOp::DropShadow(wrShadow);
 
         wrFilters.filters.AppendElement(filterOp);
         break;
       }
       default:
--- a/layout/style/GeckoBindings.cpp
+++ b/layout/style/GeckoBindings.cpp
@@ -1700,23 +1700,16 @@ void Gecko_Snapshot_DebugListAttributes(
 }
 
 NS_IMPL_THREADSAFE_FFI_REFCOUNTING(URLValue, CSSURLValue);
 
 NS_IMPL_THREADSAFE_FFI_REFCOUNTING(URLExtraData, URLExtraData);
 
 NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsStyleCoord::Calc, Calc);
 
-nsCSSShadowArray* Gecko_NewCSSShadowArray(uint32_t aLen) {
-  RefPtr<nsCSSShadowArray> arr = new (aLen) nsCSSShadowArray(aLen);
-  return arr.forget().take();
-}
-
-NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsCSSShadowArray, CSSShadowArray);
-
 nsCSSValueSharedList* Gecko_NewCSSValueSharedList(uint32_t aLen) {
   RefPtr<nsCSSValueSharedList> list = new nsCSSValueSharedList;
   if (aLen == 0) {
     return list.forget().take();
   }
 
   list->mHead = new nsCSSValueList;
   nsCSSValueList* cur = list->mHead;
--- a/layout/style/GeckoBindings.h
+++ b/layout/style/GeckoBindings.h
@@ -584,20 +584,16 @@ void Gecko_nsIURI_Debug(nsIURI*, nsCStri
 
 NS_DECL_THREADSAFE_FFI_REFCOUNTING(mozilla::css::URLValue, CSSURLValue);
 NS_DECL_THREADSAFE_FFI_REFCOUNTING(mozilla::URLExtraData, URLExtraData);
 
 void Gecko_FillAllImageLayers(nsStyleImageLayers* layers, uint32_t max_len);
 
 NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsStyleCoord::Calc, Calc);
 
-nsCSSShadowArray* Gecko_NewCSSShadowArray(uint32_t len);
-
-NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsCSSShadowArray, CSSShadowArray);
-
 nsCSSValueSharedList* Gecko_NewCSSValueSharedList(uint32_t len);
 nsCSSValueSharedList* Gecko_NewNoneTransform();
 void Gecko_StyleDisplay_GenerateCombinedTransform(nsStyleDisplay*);
 
 // Getter for nsCSSValue
 nsCSSValue* Gecko_CSSValue_GetArrayItem(nsCSSValue*, int32_t index);
 
 // const version of the above function.
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -475,19 +475,22 @@ cbindgen-types = [
     { gecko = "StyleRGBA", servo = "cssparser::RGBA" },
     { gecko = "StyleOrigin", servo = "stylesheets::Origin" },
     { gecko = "StyleGenericGradientItem", servo = "values::generics::image::GradientItem" },
     { gecko = "StyleGenericVerticalAlign", servo = "values::generics::box_::VerticalAlign" },
     { gecko = "StyleVerticalAlignKeyword", servo = "values::generics::box_::VerticalAlignKeyword" },
     { gecko = "StyleGenericBasicShape", servo = "values::generics::basic_shape::BasicShape" },
     { gecko = "StyleArcSlice", servo = "style_traits::arc_slice::ArcSlice" },
     { gecko = "StyleForgottenArcSlicePtr", servo = "style_traits::arc_slice::ForgottenArcSlicePtr" },
+    { gecko = "StyleOwnedSlice", servo = "style_traits::owned_slice::OwnedSlice" },
     { gecko = "StyleMozContextProperties", servo = "values::specified::svg::MozContextProperties" },
     { gecko = "StyleQuotes", servo = "values::specified::list::Quotes" },
     { gecko = "StyleOwnedStr", servo = "style_traits::owned_str::OwnedStr" },
+    { gecko = "StyleGenericBoxShadow", servo = "values::generics::effects::BoxShadow" },
+    { gecko = "StyleGenericSimpleShadow", servo = "values::generics::effects::SimpleShadow" },
 ]
 
 mapped-generic-types = [
     { generic = true, gecko = "mozilla::RustCell", servo = "::std::cell::Cell" },
     { generic = false, gecko = "ServoNodeData", servo = "AtomicRefCell<ElementData>" },
     { generic = false, gecko = "mozilla::ServoWritingMode", servo = "::logical_geometry::WritingMode" },
     { generic = false, gecko = "mozilla::ServoCustomPropertiesMap", servo = "Option<::servo_arc::Arc<::custom_properties::CustomPropertiesMap>>" },
     { generic = false, gecko = "mozilla::ServoRuleNode", servo = "Option<::rule_tree::StrongRuleNode>" },
--- a/layout/style/ServoStyleConstsInlines.h
+++ b/layout/style/ServoStyleConstsInlines.h
@@ -7,22 +7,50 @@
 /* Some inline functions declared in cbindgen.toml */
 
 #ifndef mozilla_ServoStyleConstsInlines_h
 #define mozilla_ServoStyleConstsInlines_h
 
 #include "mozilla/ServoStyleConsts.h"
 #include "nsGkAtoms.h"
 #include <type_traits>
+#include <new>
 
 // TODO(emilio): there are quite a few other implementations scattered around
 // that should move here.
 
 namespace mozilla {
 
+template <typename T>
+inline StyleOwnedSlice<T>::StyleOwnedSlice(const StyleOwnedSlice& aOther) {
+  len = aOther.len;
+  if (!len) {
+    ptr = (T*)alignof(T);
+  } else {
+    ptr = (T*)malloc(len * sizeof(T));
+    size_t i = 0;
+    for (const T& elem : aOther.AsSpan()) {
+      new (ptr + i++) T(elem);
+    }
+  }
+}
+
+template <typename T>
+inline StyleOwnedSlice<T>::~StyleOwnedSlice() {
+  if (!len) {
+    return;
+  }
+  for (size_t i : IntegerRange(len)) {
+    ptr[i].~T();
+  }
+  free(ptr);
+  ptr = (T*)alignof(T);
+  len = 0;
+}
+
 // This code is basically a C++ port of the Arc::clone() implementation in
 // servo/components/servo_arc/lib.rs.
 static constexpr const size_t kStaticRefcount =
     std::numeric_limits<size_t>::max();
 static constexpr const size_t kMaxRefcount =
     std::numeric_limits<intptr_t>::max();
 static constexpr const uint64_t kArcSliceCanary = 0xf3f3f3f3f3f3f3f3;
 
@@ -57,16 +85,22 @@ inline StyleArcSlice<T>::StyleArcSlice(
 
 template <typename T>
 inline size_t StyleArcSlice<T>::Length() const {
   ASSERT_CANARY
   return _0.ptr->data.header.length;
 }
 
 template <typename T>
+inline bool StyleArcSlice<T>::IsEmpty() const {
+  ASSERT_CANARY
+  return Length() == 0;
+}
+
+template <typename T>
 inline Span<const T> StyleArcSlice<T>::AsSpan() const {
   ASSERT_CANARY
   return MakeSpan(_0.ptr->data.slice, Length());
 }
 
 template <typename T>
 inline bool StyleArcSlice<T>::operator==(const StyleArcSlice& aOther) const {
   ASSERT_CANARY
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -80,18 +80,16 @@ static bool DefinitelyEqualImages(const 
 
   if (!aRequest1 || !aRequest2) {
     return false;
   }
 
   return aRequest1->DefinitelyEquals(*aRequest2);
 }
 
-static bool AreShadowArraysEqual(nsCSSShadowArray* lhs, nsCSSShadowArray* rhs);
-
 // --------------------
 // nsStyleFont
 //
 nsStyleFont::nsStyleFont(const nsStyleFont& aSrc)
     : mFont(aSrc.mFont),
       mSize(aSrc.mSize),
       mFontSizeFactor(aSrc.mFontSizeFactor),
       mFontSizeOffset(aSrc.mFontSizeOffset),
@@ -967,23 +965,22 @@ void StyleShapeSource::DoDestroy() {
       break;
   }
   mType = StyleShapeSourceType::None;
 }
 
 // --------------------
 // nsStyleFilter
 //
-nsStyleFilter::nsStyleFilter()
-    : mType(NS_STYLE_FILTER_NONE), mDropShadow(nullptr) {
+nsStyleFilter::nsStyleFilter() : mType(NS_STYLE_FILTER_NONE), mURL(nullptr) {
   MOZ_COUNT_CTOR(nsStyleFilter);
 }
 
 nsStyleFilter::nsStyleFilter(const nsStyleFilter& aSource)
-    : mType(NS_STYLE_FILTER_NONE), mDropShadow(nullptr) {
+    : mType(NS_STYLE_FILTER_NONE), mURL(nullptr) {
   MOZ_COUNT_CTOR(nsStyleFilter);
   if (aSource.mType == NS_STYLE_FILTER_URL) {
     SetURL(aSource.mURL);
   } else if (aSource.mType == NS_STYLE_FILTER_DROP_SHADOW) {
     SetDropShadow(aSource.mDropShadow);
   } else if (aSource.mType != NS_STYLE_FILTER_NONE) {
     SetFilterParameter(aSource.mFilterParameter, aSource.mType);
   }
@@ -1016,28 +1013,27 @@ nsStyleFilter& nsStyleFilter::operator=(
 bool nsStyleFilter::operator==(const nsStyleFilter& aOther) const {
   if (mType != aOther.mType) {
     return false;
   }
 
   if (mType == NS_STYLE_FILTER_URL) {
     return DefinitelyEqualURIs(mURL, aOther.mURL);
   } else if (mType == NS_STYLE_FILTER_DROP_SHADOW) {
-    return *mDropShadow == *aOther.mDropShadow;
+    return mDropShadow == aOther.mDropShadow;
   } else if (mType != NS_STYLE_FILTER_NONE) {
     return mFilterParameter == aOther.mFilterParameter;
   }
 
   return true;
 }
 
 void nsStyleFilter::ReleaseRef() {
   if (mType == NS_STYLE_FILTER_DROP_SHADOW) {
-    NS_ASSERTION(mDropShadow, "expected pointer");
-    mDropShadow->Release();
+    mDropShadow.~StyleSimpleShadow();
   } else if (mType == NS_STYLE_FILTER_URL) {
     NS_ASSERTION(mURL, "expected pointer");
     mURL->Release();
   }
   mURL = nullptr;
 }
 
 void nsStyleFilter::SetFilterParameter(const nsStyleCoord& aFilterParameter,
@@ -1050,21 +1046,19 @@ void nsStyleFilter::SetFilterParameter(c
 bool nsStyleFilter::SetURL(css::URLValue* aURL) {
   ReleaseRef();
   mURL = aURL;
   mURL->AddRef();
   mType = NS_STYLE_FILTER_URL;
   return true;
 }
 
-void nsStyleFilter::SetDropShadow(nsCSSShadowArray* aDropShadow) {
-  NS_ASSERTION(aDropShadow, "expected pointer");
+void nsStyleFilter::SetDropShadow(const StyleSimpleShadow& aSrc) {
   ReleaseRef();
-  mDropShadow = aDropShadow;
-  mDropShadow->AddRef();
+  new (&mDropShadow) StyleSimpleShadow(aSrc);
   mType = NS_STYLE_FILTER_DROP_SHADOW;
 }
 
 // --------------------
 // nsStyleSVGReset
 //
 nsStyleSVGReset::nsStyleSVGReset(const Document& aDocument)
     : mMask(nsStyleImageLayers::LayerType::Mask),
@@ -3688,34 +3682,16 @@ nsChangeHint nsStyleTextReset::CalcDiffe
 
   if (mTextOverflow != aNewData.mTextOverflow) {
     return nsChangeHint_RepaintFrame;
   }
 
   return nsChangeHint(0);
 }
 
-// Returns true if the given shadow-arrays are equal.
-static bool AreShadowArraysEqual(nsCSSShadowArray* lhs, nsCSSShadowArray* rhs) {
-  if (lhs == rhs) {
-    return true;
-  }
-
-  if (!lhs || !rhs || lhs->Length() != rhs->Length()) {
-    return false;
-  }
-
-  for (uint32_t i = 0; i < lhs->Length(); ++i) {
-    if (*lhs->ShadowAt(i) != *rhs->ShadowAt(i)) {
-      return false;
-    }
-  }
-  return true;
-}
-
 // --------------------
 // nsStyleText
 //
 
 nsStyleText::nsStyleText(const Document& aDocument)
     : mTextTransform(StyleTextTransform::None()),
       mTextAlign(NS_STYLE_TEXT_ALIGN_START),
       mTextAlignLast(NS_STYLE_TEXT_ALIGN_AUTO),
@@ -3734,18 +3710,17 @@ nsStyleText::nsStyleText(const Document&
       mWebkitTextFillColor(StyleColor::CurrentColor()),
       mWebkitTextStrokeColor(StyleColor::CurrentColor()),
       mMozTabSize(
           StyleNonNegativeLengthOrNumber::Number(NS_STYLE_TABSIZE_INITIAL)),
       mWordSpacing(LengthPercentage::Zero()),
       mLetterSpacing({0.}),
       mLineHeight(StyleLineHeight::Normal()),
       mTextIndent(LengthPercentage::Zero()),
-      mWebkitTextStrokeWidth(0),
-      mTextShadow(nullptr) {
+      mWebkitTextStrokeWidth(0) {
   MOZ_COUNT_CTOR(nsStyleText);
   RefPtr<nsAtom> language = aDocument.GetContentLanguageAsAtomForStyle();
   mTextEmphasisPosition =
       language && nsStyleUtil::MatchesLanguagePrefix(language, u"zh")
           ? NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH
           : NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT;
 }
 
@@ -3824,17 +3799,17 @@ nsChangeHint nsStyleText::CalcDifference
   // text-rendering changes require a reflow since they change SVG
   // frames' rects.
   if (mTextRendering != aNewData.mTextRendering) {
     hint |= nsChangeHint_NeedReflow |
             nsChangeHint_NeedDirtyReflow |  // XXX remove me: bug 876085
             nsChangeHint_RepaintFrame;
   }
 
-  if (!AreShadowArraysEqual(mTextShadow, aNewData.mTextShadow) ||
+  if (mTextShadow != aNewData.mTextShadow ||
       mTextEmphasisStyle != aNewData.mTextEmphasisStyle ||
       mTextEmphasisStyleString != aNewData.mTextEmphasisStyleString ||
       mWebkitTextStrokeWidth != aNewData.mWebkitTextStrokeWidth) {
     hint |= nsChangeHint_UpdateSubtreeOverflow | nsChangeHint_SchedulePaint |
             nsChangeHint_RepaintFrame;
 
     // We don't add any other hints below.
     return hint;
@@ -4080,18 +4055,17 @@ nsChangeHint nsStyleUIReset::CalcDiffere
   return hint;
 }
 
 //-----------------------
 // nsStyleEffects
 //
 
 nsStyleEffects::nsStyleEffects(const Document&)
-    : mBoxShadow(nullptr),
-      mClip(0, 0, 0, 0),
+    : mClip(0, 0, 0, 0),
       mOpacity(1.0f),
       mClipFlags(NS_STYLE_CLIP_AUTO),
       mMixBlendMode(NS_STYLE_BLEND_NORMAL) {
   MOZ_COUNT_CTOR(nsStyleEffects);
 }
 
 nsStyleEffects::nsStyleEffects(const nsStyleEffects& aSource)
     : mFilters(aSource.mFilters),
@@ -4104,17 +4078,17 @@ nsStyleEffects::nsStyleEffects(const nsS
 }
 
 nsStyleEffects::~nsStyleEffects() { MOZ_COUNT_DTOR(nsStyleEffects); }
 
 nsChangeHint nsStyleEffects::CalcDifference(
     const nsStyleEffects& aNewData) const {
   nsChangeHint hint = nsChangeHint(0);
 
-  if (!AreShadowArraysEqual(mBoxShadow, aNewData.mBoxShadow)) {
+  if (mBoxShadow != aNewData.mBoxShadow) {
     // Update overflow regions & trigger DLBI to be sure it's noticed.
     // Also request a repaint, since it's possible that only the color
     // of the shadow is changing (and UpdateOverflow/SchedulePaint won't
     // repaint for that, since they won't know what needs invalidating.)
     hint |= nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint |
             nsChangeHint_RepaintFrame;
   }
 
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -742,119 +742,16 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
     NS_FOR_CSS_SIDES(side) {
       // Clamp negative calc() to 0.
       aPadding.Side(side) = std::max(mPadding.Get(side).ToLength(), 0);
     }
     return true;
   }
 };
 
-struct nsCSSShadowItem {
-  nscoord mXOffset;
-  nscoord mYOffset;
-  nscoord mRadius;
-  nscoord mSpread;
-
-  mozilla::StyleColor mColor;
-  bool mInset;
-
-  nsCSSShadowItem()
-      : mXOffset(0),
-        mYOffset(0),
-        mRadius(0),
-        mSpread(0),
-        mColor(mozilla::StyleColor::CurrentColor()),
-        mInset(false) {
-    MOZ_COUNT_CTOR(nsCSSShadowItem);
-  }
-  ~nsCSSShadowItem() { MOZ_COUNT_DTOR(nsCSSShadowItem); }
-
-  bool operator==(const nsCSSShadowItem& aOther) const {
-    return (mXOffset == aOther.mXOffset && mYOffset == aOther.mYOffset &&
-            mRadius == aOther.mRadius && mSpread == aOther.mSpread &&
-            mInset == aOther.mInset && mColor == aOther.mColor);
-  }
-  bool operator!=(const nsCSSShadowItem& aOther) const {
-    return !(*this == aOther);
-  }
-};
-
-class nsCSSShadowArray final {
- public:
-  void* operator new(size_t aBaseSize, uint32_t aArrayLen) {
-    // We can allocate both this nsCSSShadowArray and the
-    // actual array in one allocation. The amount of memory to
-    // allocate is equal to the class's size + the number of bytes for all
-    // but the first array item (because aBaseSize includes one
-    // item, see the private declarations)
-    return ::operator new(aBaseSize +
-                          (aArrayLen - 1) * sizeof(nsCSSShadowItem));
-  }
-
-  void operator delete(void* aPtr) { ::operator delete(aPtr); }
-
-  explicit nsCSSShadowArray(uint32_t aArrayLen) : mLength(aArrayLen) {
-    for (uint32_t i = 1; i < mLength; ++i) {
-      // Make sure we call the constructors of each nsCSSShadowItem
-      // (the first one is called for us because we declared it under private)
-      new (&mArray[i]) nsCSSShadowItem();
-    }
-  }
-
- private:
-  // Private destructor, to discourage deletion outside of Release():
-  ~nsCSSShadowArray() {
-    for (uint32_t i = 1; i < mLength; ++i) {
-      mArray[i].~nsCSSShadowItem();
-    }
-  }
-
- public:
-  uint32_t Length() const { return mLength; }
-  nsCSSShadowItem* ShadowAt(uint32_t i) {
-    MOZ_ASSERT(i < mLength,
-               "Accessing too high an index in the text shadow array!");
-    return &mArray[i];
-  }
-  const nsCSSShadowItem* ShadowAt(uint32_t i) const {
-    MOZ_ASSERT(i < mLength,
-               "Accessing too high an index in the text shadow array!");
-    return &mArray[i];
-  }
-
-  bool HasShadowWithInset(bool aInset) {
-    for (uint32_t i = 0; i < mLength; ++i) {
-      if (mArray[i].mInset == aInset) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  bool operator==(const nsCSSShadowArray& aOther) const {
-    if (mLength != aOther.Length()) {
-      return false;
-    }
-
-    for (uint32_t i = 0; i < mLength; ++i) {
-      if (ShadowAt(i) != aOther.ShadowAt(i)) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCSSShadowArray)
-
- private:
-  uint32_t mLength;
-  nsCSSShadowItem mArray[1];  // This MUST be the last item
-};
-
 // Border widths are rounded to the nearest-below integer number of pixels,
 // but values between zero and one device pixels are always rounded up to
 // one device pixel.
 #define NS_ROUND_BORDER_TO_PIXELS(l, tpp) \
   ((l) == 0) ? 0 : std::max((tpp), (l) / (tpp) * (tpp))
 
 // Returns if the given border style type is visible or not
 static bool IsVisibleBorderStyle(mozilla::StyleBorderStyle aStyle) {
@@ -1481,17 +1378,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   mozilla::StyleNonNegativeLengthOrNumber mMozTabSize;
   mozilla::LengthPercentage mWordSpacing;
   mozilla::StyleLetterSpacing mLetterSpacing;
   mozilla::StyleLineHeight mLineHeight;
   mozilla::LengthPercentage mTextIndent;
   nscoord mWebkitTextStrokeWidth;  // coord
 
-  RefPtr<nsCSSShadowArray> mTextShadow;  // nullptr in case of a zero-length
+  mozilla::StyleArcSlice<mozilla::StyleSimpleShadow> mTextShadow;
 
   nsString mTextEmphasisStyleString;
 
   mozilla::StyleWordBreak EffectiveWordBreak() const {
     if (mWordBreak == mozilla::StyleWordBreak::BreakWord) {
       return mozilla::StyleWordBreak::Normal;
     }
     return mWordBreak;
@@ -1542,19 +1439,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
     return owrap == mozilla::StyleOverflowWrap::BreakWord ||
            owrap == mozilla::StyleOverflowWrap::Anywhere;
   }
 
   bool HasTextEmphasis() const { return !mTextEmphasisStyleString.IsEmpty(); }
 
   bool HasWebkitTextStroke() const { return mWebkitTextStrokeWidth > 0; }
 
-  // These are defined in nsStyleStructInlines.h.
-  inline bool HasTextShadow() const;
-  inline nsCSSShadowArray* GetTextShadow() const;
+  bool HasTextShadow() const { return !mTextShadow.IsEmpty(); }
 
   // The aContextFrame argument on each of these is the frame this
   // style struct is for.  If the frame is for SVG text or inside ruby,
   // the return value will be massaged to be something that makes sense
   // for those cases.
   inline bool NewlineIsSignificant(const nsTextFrame* aContextFrame) const;
   inline bool WhiteSpaceCanWrap(const nsIFrame* aContextFrame) const;
   inline bool WordCanWrap(const nsIFrame* aContextFrame) const;
@@ -2806,30 +2701,30 @@ struct nsStyleFilter {
 
   mozilla::css::URLValue* GetURL() const {
     MOZ_ASSERT(mType == NS_STYLE_FILTER_URL, "wrong filter type");
     return mURL;
   }
 
   bool SetURL(mozilla::css::URLValue* aValue);
 
-  nsCSSShadowArray* GetDropShadow() const {
+  const mozilla::StyleSimpleShadow& GetDropShadow() const {
     NS_ASSERTION(mType == NS_STYLE_FILTER_DROP_SHADOW, "wrong filter type");
     return mDropShadow;
   }
-  void SetDropShadow(nsCSSShadowArray* aDropShadow);
+  void SetDropShadow(const mozilla::StyleSimpleShadow&);
 
  private:
   void ReleaseRef();
 
   uint32_t mType;                 // NS_STYLE_FILTER_*
   nsStyleCoord mFilterParameter;  // coord, percent, factor, angle
   union {
     mozilla::css::URLValue* mURL;
-    nsCSSShadowArray* mDropShadow;
+    mozilla::StyleSimpleShadow mDropShadow;
   };
 };
 
 template <>
 struct nsTArray_CopyChooser<nsStyleFilter> {
   typedef nsTArray_CopyWithConstructors<nsStyleFilter> Type;
 };
 
@@ -2876,19 +2771,28 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   ~nsStyleEffects();
   void TriggerImageLoads(mozilla::dom::Document&, const nsStyleEffects*) {}
   const static bool kHasTriggerImageLoads = false;
 
   nsChangeHint CalcDifference(const nsStyleEffects& aNewData) const;
 
   bool HasFilters() const { return !mFilters.IsEmpty(); }
 
+  bool HasBoxShadowWithInset(bool aInset) const {
+    for (auto& shadow : mBoxShadow.AsSpan()) {
+      if (shadow.inset == aInset) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   nsTArray<nsStyleFilter> mFilters;
-  RefPtr<nsCSSShadowArray> mBoxShadow;  // nullptr for 'none'
-  nsRect mClip;                         // offsets from UL border edge
+  mozilla::StyleOwnedSlice<mozilla::StyleBoxShadow> mBoxShadow;
+  nsRect mClip;  // offsets from UL border edge
   float mOpacity;
   uint8_t mClipFlags;     // bitfield of NS_STYLE_CLIP_* values
   uint8_t mMixBlendMode;  // NS_STYLE_BLEND_*
 };
 
 #define STATIC_ASSERT_TYPE_LAYOUTS_MATCH(T1, T2)           \
   static_assert(sizeof(T1) == sizeof(T2),                  \
                 "Size mismatch between " #T1 " and " #T2); \
--- a/layout/style/nsStyleStructInlines.h
+++ b/layout/style/nsStyleStructInlines.h
@@ -30,20 +30,16 @@ inline void nsStyleImage::SetSubImage(ui
   EnsureCachedBIData();
   mCachedBIData->SetSubImage(aIndex, aSubImage);
 }
 
 inline imgIContainer* nsStyleImage::GetSubImage(uint8_t aIndex) const {
   return (mCachedBIData) ? mCachedBIData->GetSubImage(aIndex) : nullptr;
 }
 
-bool nsStyleText::HasTextShadow() const { return mTextShadow; }
-
-nsCSSShadowArray* nsStyleText::GetTextShadow() const { return mTextShadow; }
-
 bool nsStyleText::NewlineIsSignificant(const nsTextFrame* aContextFrame) const {
   NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
   return NewlineIsSignificantStyle() &&
          !aContextFrame->ShouldSuppressLineBreak() &&
          !aContextFrame->Style()->IsTextCombined();
 }
 
 bool nsStyleText::WhiteSpaceCanWrap(const nsIFrame* aContextFrame) const {
--- a/layout/svg/nsCSSFilterInstance.cpp
+++ b/layout/svg/nsCSSFilterInstance.cpp
@@ -164,36 +164,31 @@ nsresult nsCSSFilterInstance::SetAttribu
   atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY;
 
   aDescr.Attributes() = AsVariant(std::move(atts));
   return NS_OK;
 }
 
 nsresult nsCSSFilterInstance::SetAttributesForDropShadow(
     FilterPrimitiveDescription& aDescr) {
-  nsCSSShadowArray* shadows = mFilter.GetDropShadow();
-  if (!shadows || shadows->Length() != 1) {
-    MOZ_ASSERT_UNREACHABLE("Exactly one drop shadow should have been parsed.");
-    return NS_ERROR_FAILURE;
-  }
+  const auto& shadow = mFilter.GetDropShadow();
 
   DropShadowAttributes atts;
-  nsCSSShadowItem* shadow = shadows->ShadowAt(0);
 
   // Set drop shadow blur radius.
-  Size radiusInFilterSpace = BlurRadiusToFilterSpace(shadow->mRadius);
+  Size radiusInFilterSpace = BlurRadiusToFilterSpace(shadow.blur.ToAppUnits());
   atts.mStdDeviation = radiusInFilterSpace;
 
   // Set offset.
-  IntPoint offsetInFilterSpace =
-      OffsetToFilterSpace(shadow->mXOffset, shadow->mYOffset);
+  IntPoint offsetInFilterSpace = OffsetToFilterSpace(
+      shadow.horizontal.ToAppUnits(), shadow.vertical.ToAppUnits());
   atts.mOffset = offsetInFilterSpace;
 
   // Set color. If unspecified, use the CSS color property.
-  nscolor shadowColor = shadow->mColor.CalcColor(mShadowFallbackColor);
+  nscolor shadowColor = shadow.color.CalcColor(mShadowFallbackColor);
   atts.mColor = ToAttributeColor(shadowColor);
 
   aDescr.Attributes() = AsVariant(std::move(atts));
   return NS_OK;
 }
 
 nsresult nsCSSFilterInstance::SetAttributesForGrayscale(
     FilterPrimitiveDescription& aDescr) {
--- a/layout/tables/nsTableCellFrame.cpp
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -439,17 +439,17 @@ bool nsTableCellFrame::ShouldPaintBackgr
   return ShouldPaintBordersAndBackgrounds();
 }
 
 void nsTableCellFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                         const nsDisplayListSet& aLists) {
   DO_GLOBAL_REFLOW_COUNT_DSP("nsTableCellFrame");
   if (ShouldPaintBordersAndBackgrounds()) {
     // display outset box-shadows if we need to.
-    bool hasBoxShadow = !!StyleEffects()->mBoxShadow;
+    bool hasBoxShadow = !StyleEffects()->mBoxShadow.IsEmpty();
     if (hasBoxShadow) {
       aLists.BorderBackground()->AppendNewToTop<nsDisplayBoxShadowOuter>(
           aBuilder, this);
     }
 
     // display background if we need to.
     if (aBuilder->IsForEventDelivery() ||
         !StyleBackground()->IsTransparent(this) ||
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -1405,17 +1405,17 @@ void nsTableFrame::DisplayGenericTablePa
   }
 
   if (isVisible) {
     // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
     // just because we're visible?  Or should it depend on the cell visibility
     // when we're not the whole table?
 
     // Paint the outset box-shadows for the table frames
-    if (aFrame->StyleEffects()->mBoxShadow) {
+    if (!aFrame->StyleEffects()->mBoxShadow.IsEmpty()) {
       aLists.BorderBackground()->AppendNewToTop<nsDisplayBoxShadowOuter>(
           aBuilder, aFrame);
     }
   }
 
   // Background visibility for rows, rowgroups, columns, colgroups depends on
   // the visibility of the _cell_, not of the row/col(group).
   if (aFrame->IsTableRowGroupFrame()) {
@@ -1478,17 +1478,17 @@ void nsTableFrame::DisplayGenericTablePa
   }
 
   if (isVisible) {
     // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
     // just because we're visible?  Or should it depend on the cell visibility
     // when we're not the whole table?
 
     // Paint the inset box-shadows for the table frames
-    if (aFrame->StyleEffects()->mBoxShadow) {
+    if (!aFrame->StyleEffects()->mBoxShadow.IsEmpty()) {
       aLists.BorderBackground()->AppendNewToTop<nsDisplayBoxShadowInner>(
           aBuilder, aFrame);
     }
   }
 
   aFrame->DisplayOutline(aBuilder, aLists);
 
   aTraversal(aBuilder, aFrame, aLists);
--- a/layout/xul/nsTextBoxFrame.cpp
+++ b/layout/xul/nsTextBoxFrame.cpp
@@ -943,29 +943,29 @@ nsTextBoxFrame::DoXULLayout(nsBoxLayoutS
   textRect = tr.GetPhysicalRect(wm, GetSize());
 
   // Our scrollable overflow is our bounds; our visual overflow may
   // extend beyond that.
   nsRect visualBounds;
   visualBounds.UnionRect(scrollBounds, textRect);
   nsOverflowAreas overflow(visualBounds, scrollBounds);
 
-  if (textStyle->mTextShadow) {
+  if (textStyle->HasTextShadow()) {
     // text-shadow extends our visual but not scrollable bounds
     nsRect& vis = overflow.VisualOverflow();
     vis.UnionRect(vis,
                   nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this));
   }
   FinishAndStoreOverflow(overflow, GetSize());
 
   return rv;
 }
 
 nsRect nsTextBoxFrame::GetComponentAlphaBounds() const {
-  if (StyleText()->mTextShadow) {
+  if (StyleText()->HasTextShadow()) {
     return GetVisualOverflowRectRelativeToSelf();
   }
   return mTextDrawRect;
 }
 
 bool nsTextBoxFrame::ComputesOwnOverflowArea() { return true; }
 
 /* virtual */
--- a/servo/components/style/gecko_bindings/sugar/mod.rs
+++ b/servo/components/style/gecko_bindings/sugar/mod.rs
@@ -1,17 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 //! Rust sugar and convenience methods for Gecko types.
 
 mod ns_com_ptr;
 mod ns_compatibility;
-mod ns_css_shadow_array;
-mod ns_css_shadow_item;
 pub mod ns_css_value;
 mod ns_style_auto_array;
 pub mod ns_style_coord;
 mod ns_t_array;
 pub mod origin_flags;
 pub mod ownership;
 pub mod refptr;
deleted file mode 100644
--- a/servo/components/style/gecko_bindings/sugar/ns_css_shadow_array.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-
-//! Rust helpers for Gecko's `nsCSSShadowArray`.
-
-use crate::gecko_bindings::bindings::Gecko_AddRefCSSShadowArrayArbitraryThread;
-use crate::gecko_bindings::bindings::Gecko_NewCSSShadowArray;
-use crate::gecko_bindings::bindings::Gecko_ReleaseCSSShadowArrayArbitraryThread;
-use crate::gecko_bindings::structs::{nsCSSShadowArray, nsCSSShadowItem, RefPtr};
-use std::ops::{Deref, DerefMut};
-use std::{ptr, slice};
-
-impl RefPtr<nsCSSShadowArray> {
-    /// Replaces the current `nsCSSShadowArray` with a new one of len `len`.
-    pub fn replace_with_new(&mut self, len: u32) {
-        unsafe {
-            if !self.mRawPtr.is_null() {
-                Gecko_ReleaseCSSShadowArrayArbitraryThread(self.mRawPtr);
-            }
-
-            self.mRawPtr = if len == 0 {
-                ptr::null_mut()
-            } else {
-                Gecko_NewCSSShadowArray(len)
-            }
-        }
-    }
-
-    /// Sets the value to other `nsCSSShadowArray`, bumping and decreasing
-    /// refcounts as needed.
-    ///
-    /// TODO(emilio): Seems like this could move to `refptr.rs`, and be more
-    /// generic.
-    pub fn copy_from(&mut self, other: &Self) {
-        unsafe {
-            if !self.mRawPtr.is_null() {
-                Gecko_ReleaseCSSShadowArrayArbitraryThread(self.mRawPtr);
-            }
-            if !other.mRawPtr.is_null() {
-                Gecko_AddRefCSSShadowArrayArbitraryThread(other.mRawPtr);
-            }
-
-            self.mRawPtr = other.mRawPtr;
-        }
-    }
-}
-
-impl Deref for RefPtr<nsCSSShadowArray> {
-    type Target = [nsCSSShadowItem];
-    fn deref(&self) -> &[nsCSSShadowItem] {
-        if self.mRawPtr.is_null() {
-            &[]
-        } else {
-            unsafe {
-                slice::from_raw_parts(
-                    (*self.mRawPtr).mArray.as_ptr(),
-                    (*self.mRawPtr).mLength as usize,
-                )
-            }
-        }
-    }
-}
-
-impl DerefMut for RefPtr<nsCSSShadowArray> {
-    fn deref_mut(&mut self) -> &mut [nsCSSShadowItem] {
-        if self.mRawPtr.is_null() {
-            &mut []
-        } else {
-            unsafe {
-                slice::from_raw_parts_mut(
-                    (*self.mRawPtr).mArray.as_mut_ptr(),
-                    (*self.mRawPtr).mLength as usize,
-                )
-            }
-        }
-    }
-}
deleted file mode 100644
--- a/servo/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-
-//! Rust helpers for Gecko's `nsCSSShadowItem`.
-
-use crate::gecko_bindings::structs::nsCSSShadowItem;
-use crate::values::computed::effects::{BoxShadow, SimpleShadow};
-use app_units::Au;
-
-impl nsCSSShadowItem {
-    /// Sets this item from the given box shadow.
-    #[inline]
-    pub fn set_from_box_shadow(&mut self, shadow: BoxShadow) {
-        self.set_from_simple_shadow(shadow.base);
-        self.mSpread = shadow.spread.to_i32_au();
-        self.mInset = shadow.inset;
-    }
-
-    /// Returns this item as a box shadow.
-    #[inline]
-    pub fn to_box_shadow(&self) -> BoxShadow {
-        BoxShadow {
-            base: self.extract_simple_shadow(),
-            spread: Au(self.mSpread).into(),
-            inset: self.mInset,
-        }
-    }
-
-    /// Sets this item from the given simple shadow.
-    #[inline]
-    pub fn set_from_simple_shadow(&mut self, shadow: SimpleShadow) {
-        self.mXOffset = shadow.horizontal.to_i32_au();
-        self.mYOffset = shadow.vertical.to_i32_au();
-        self.mRadius = shadow.blur.0.to_i32_au();
-        self.mSpread = 0;
-        self.mInset = false;
-        self.mColor = shadow.color.into();
-    }
-
-    /// Gets a simple shadow from this item.
-    #[inline]
-    fn extract_simple_shadow(&self) -> SimpleShadow {
-        SimpleShadow {
-            color: self.mColor.into(),
-            horizontal: Au(self.mXOffset).into(),
-            vertical: Au(self.mYOffset).into(),
-            blur: Au(self.mRadius).into(),
-        }
-    }
-
-    /// Returns this item as a simple shadow.
-    #[inline]
-    pub fn to_simple_shadow(&self) -> SimpleShadow {
-        debug_assert_eq!(self.mSpread, 0);
-        debug_assert_eq!(self.mInset, false);
-        self.extract_simple_shadow()
-    }
-}
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -169,16 +169,17 @@ class Longhand(object):
     def __init__(self, style_struct, name, spec=None, animation_value_type=None, keyword=None,
                  predefined_type=None, servo_pref=None, gecko_pref=None,
                  enabled_in="content", need_index=False,
                  gecko_ffi_name=None,
                  allowed_in_keyframe_block=True, cast_type='u8',
                  logical=False, logical_group=None, alias=None, extra_prefixes=None, boxed=False,
                  flags=None, allowed_in_page_rule=False, allow_quirks=False,
                  ignored_when_colors_disabled=False,
+                 simple_vector_bindings=False,
                  vector=False, servo_restyle_damage="repaint"):
         self.name = name
         if not spec:
             raise TypeError("Spec should be specified for %s" % name)
         self.spec = spec
         self.keyword = keyword
         self.predefined_type = predefined_type
         self.ident = to_rust_ident(name)
@@ -205,16 +206,17 @@ class Longhand(object):
         self.alias = parse_property_aliases(alias)
         self.extra_prefixes = parse_property_aliases(extra_prefixes)
         self.boxed = arg_to_bool(boxed)
         self.flags = flags.split() if flags else []
         self.allowed_in_page_rule = arg_to_bool(allowed_in_page_rule)
         self.allow_quirks = allow_quirks
         self.ignored_when_colors_disabled = ignored_when_colors_disabled
         self.is_vector = vector
+        self.simple_vector_bindings = simple_vector_bindings
 
         # https://drafts.csswg.org/css-animations/#keyframes
         # > The <declaration-list> inside of <keyframe-block> accepts any CSS property
         # > except those defined in this specification,
         # > but does accept the `animation-play-state` property and interprets it specially.
         self.allowed_in_keyframe_block = allowed_in_keyframe_block \
             and allowed_in_keyframe_block != "False"
 
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -23,17 +23,16 @@ use crate::gecko_bindings::bindings::Gec
 use crate::gecko_bindings::bindings::Gecko_CopyCounterStyle;
 use crate::gecko_bindings::bindings::Gecko_CopyCursorArrayFrom;
 use crate::gecko_bindings::bindings::Gecko_CopyFontFamilyFrom;
 use crate::gecko_bindings::bindings::Gecko_CopyImageValueFrom;
 use crate::gecko_bindings::bindings::Gecko_CopyListStyleImageFrom;
 use crate::gecko_bindings::bindings::Gecko_EnsureImageLayersLength;
 use crate::gecko_bindings::bindings::Gecko_SetCursorArrayLength;
 use crate::gecko_bindings::bindings::Gecko_SetCursorImageValue;
-use crate::gecko_bindings::bindings::Gecko_NewCSSShadowArray;
 use crate::gecko_bindings::bindings::Gecko_nsStyleFont_SetLang;
 use crate::gecko_bindings::bindings::Gecko_nsStyleFont_CopyLangFrom;
 use crate::gecko_bindings::bindings::Gecko_SetListStyleImageNone;
 use crate::gecko_bindings::bindings::Gecko_SetListStyleImageImageValue;
 use crate::gecko_bindings::bindings::Gecko_SetNullImageValue;
 use crate::gecko_bindings::bindings::{Gecko_ResetFilters, Gecko_CopyFiltersFrom};
 use crate::gecko_bindings::structs;
 use crate::gecko_bindings::structs::nsCSSPropertyID;
@@ -51,17 +50,17 @@ use crate::selector_parser::PseudoElemen
 use servo_arc::{Arc, RawOffsetArc};
 use std::marker::PhantomData;
 use std::mem::{forget, uninitialized, zeroed, ManuallyDrop};
 use std::{cmp, ops, ptr};
 use crate::values::{self, CustomIdent, Either, KeyframesName, None_};
 use crate::values::computed::{NonNegativeLength, Percentage, TransitionProperty};
 use crate::values::computed::BorderStyle;
 use crate::values::computed::font::FontSize;
-use crate::values::computed::effects::{BoxShadow, Filter, SimpleShadow};
+use crate::values::computed::effects::Filter;
 use crate::values::generics::column::ColumnCount;
 use crate::values::generics::transform::TransformStyle;
 use crate::values::generics::url::UrlOrNone;
 
 pub mod style_structs {
     % for style_struct in data.style_structs:
     pub use super::${style_struct.gecko_struct_name} as ${style_struct.name};
 
@@ -3450,41 +3449,17 @@ fn static_assert() {
             self.gecko.mSpan
         )
     }
 
     ${impl_simple_copy('_x_span', 'mSpan')}
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Effects"
-                  skip_longhands="box-shadow clip filter">
-    pub fn set_box_shadow<I>(&mut self, v: I)
-        where I: IntoIterator<Item = BoxShadow>,
-              I::IntoIter: ExactSizeIterator
-    {
-        let v = v.into_iter();
-        self.gecko.mBoxShadow.replace_with_new(v.len() as u32);
-        for (servo, gecko_shadow) in v.zip(self.gecko.mBoxShadow.iter_mut()) {
-            gecko_shadow.set_from_box_shadow(servo);
-        }
-    }
-
-    pub fn copy_box_shadow_from(&mut self, other: &Self) {
-        self.gecko.mBoxShadow.copy_from(&other.gecko.mBoxShadow);
-    }
-
-    pub fn reset_box_shadow(&mut self, other: &Self) {
-        self.copy_box_shadow_from(other)
-    }
-
-    pub fn clone_box_shadow(&self) -> longhands::box_shadow::computed_value::T {
-        let buf = self.gecko.mBoxShadow.iter().map(|v| v.to_box_shadow()).collect();
-        longhands::box_shadow::computed_value::List(buf)
-    }
-
+                  skip_longhands="clip filter">
     pub fn set_clip(&mut self, v: longhands::clip::computed_value::T) {
         use crate::gecko_bindings::structs::NS_STYLE_CLIP_AUTO;
         use crate::gecko_bindings::structs::NS_STYLE_CLIP_RECT;
         use crate::gecko_bindings::structs::NS_STYLE_CLIP_LEFT_AUTO;
         use crate::gecko_bindings::structs::NS_STYLE_CLIP_TOP_AUTO;
         use crate::gecko_bindings::structs::NS_STYLE_CLIP_RIGHT_AUTO;
         use crate::gecko_bindings::structs::NS_STYLE_CLIP_BOTTOM_AUTO;
         use crate::values::generics::length::LengthPercentageOrAuto::*;
@@ -3598,17 +3573,16 @@ fn static_assert() {
      %>
 
     pub fn set_filter<I>(&mut self, v: I)
     where
         I: IntoIterator<Item = Filter>,
         I::IntoIter: ExactSizeIterator,
     {
         use crate::values::generics::effects::Filter::*;
-        use crate::gecko_bindings::structs::nsCSSShadowArray;
         use crate::gecko_bindings::structs::nsStyleFilter;
         use crate::gecko_bindings::structs::NS_STYLE_FILTER_BLUR;
         use crate::gecko_bindings::structs::NS_STYLE_FILTER_BRIGHTNESS;
         use crate::gecko_bindings::structs::NS_STYLE_FILTER_CONTRAST;
         use crate::gecko_bindings::structs::NS_STYLE_FILTER_GRAYSCALE;
         use crate::gecko_bindings::structs::NS_STYLE_FILTER_INVERT;
         use crate::gecko_bindings::structs::NS_STYLE_FILTER_OPACITY;
         use crate::gecko_bindings::structs::NS_STYLE_FILTER_SATURATE;
@@ -3639,29 +3613,20 @@ fn static_assert() {
                                             gecko_filter),
 
                 HueRotate(angle) => fill_filter(NS_STYLE_FILTER_HUE_ROTATE,
                                                 CoordDataValue::from(angle),
                                                 gecko_filter),
 
                 DropShadow(shadow) => {
                     gecko_filter.mType = NS_STYLE_FILTER_DROP_SHADOW;
-
-                    fn init_shadow(filter: &mut nsStyleFilter) -> &mut nsCSSShadowArray {
-                        unsafe {
-                            let ref mut union = filter.__bindgen_anon_1;
-                            let shadow_array: &mut *mut nsCSSShadowArray = union.mDropShadow.as_mut();
-                            *shadow_array = Gecko_NewCSSShadowArray(1);
-
-                            &mut **shadow_array
-                        }
+                    unsafe {
+                        let ref mut union = gecko_filter.__bindgen_anon_1;
+                        ptr::write(union.mDropShadow.as_mut(), shadow);
                     }
-
-                    let gecko_shadow = init_shadow(gecko_filter);
-                    gecko_shadow.mArray[0].set_from_simple_shadow(shadow);
                 },
                 Url(ref url) => {
                     unsafe {
                         bindings::Gecko_nsStyleFilter_SetURLValue(gecko_filter, url.url_value_ptr());
                     }
                 },
             }
         }
@@ -3710,17 +3675,17 @@ fn static_assert() {
                 },
                 NS_STYLE_FILTER_HUE_ROTATE => {
                     Filter::HueRotate(GeckoStyleCoordConvertible::from_gecko_style_coord(
                         &filter.mFilterParameter,
                     ).unwrap())
                 },
                 NS_STYLE_FILTER_DROP_SHADOW => {
                     Filter::DropShadow(unsafe {
-                        (**filter.__bindgen_anon_1.mDropShadow.as_ref()).mArray[0].to_simple_shadow()
+                        (*filter.__bindgen_anon_1.mDropShadow.as_ref()).clone()
                     })
                 },
                 NS_STYLE_FILTER_URL => {
                     Filter::Url(unsafe {
                         let url = RefPtr::new(*filter.__bindgen_anon_1.mURL.as_ref());
                         ComputedUrl::from_url_value(url)
                     })
                 }
@@ -3756,50 +3721,24 @@ fn static_assert() {
             Au(self.gecko.mBorderSpacingCol).into(),
             Au(self.gecko.mBorderSpacingRow).into()
         )
     }
 </%self:impl_trait>
 
 
 <%self:impl_trait style_struct_name="InheritedText"
-                  skip_longhands="text-align text-emphasis-style text-shadow
+                  skip_longhands="text-align text-emphasis-style
                                   -webkit-text-stroke-width text-emphasis-position">
 
     <% text_align_keyword = Keyword("text-align",
                                     "start end left right center justify -moz-center -moz-left -moz-right char",
                                     gecko_strip_moz_prefix=False) %>
     ${impl_keyword('text_align', 'mTextAlign', text_align_keyword)}
 
-    pub fn set_text_shadow<I>(&mut self, v: I)
-    where
-        I: IntoIterator<Item = SimpleShadow>,
-        I::IntoIter: ExactSizeIterator
-    {
-        let v = v.into_iter();
-        self.gecko.mTextShadow.replace_with_new(v.len() as u32);
-        for (servo, gecko_shadow) in v.zip(self.gecko.mTextShadow.iter_mut()) {
-            gecko_shadow.set_from_simple_shadow(servo);
-        }
-    }
-
-    pub fn copy_text_shadow_from(&mut self, other: &Self) {
-        self.gecko.mTextShadow.copy_from(&other.gecko.mTextShadow);
-    }
-
-    pub fn reset_text_shadow(&mut self, other: &Self) {
-        self.copy_text_shadow_from(other)
-    }
-
-    // FIXME(emilio): Remove by sharing representation.
-    pub fn clone_text_shadow(&self) -> longhands::text_shadow::computed_value::T {
-        let iter = self.gecko.mTextShadow.iter().map(|v| v.to_simple_shadow());
-        longhands::text_shadow::computed_value::List(crate::ArcSlice::from_iter(iter))
-    }
-
     fn clear_text_emphasis_style_if_string(&mut self) {
         if self.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING as u8 {
             self.gecko.mTextEmphasisStyleString.truncate();
             self.gecko.mTextEmphasisStyle = structs::NS_STYLE_TEXT_EMPHASIS_STYLE_NONE as u8;
         }
     }
 
     ${impl_simple_type_with_conversion("text_emphasis_position")}
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -85,22 +85,26 @@
 //
 //  * UnderlyingList is the list that is stored in the computed value. This may
 //    be a shared ArcSlice if the property is inherited.
 //  * UnderlyingOwnedList is the list that is used for animation.
 //  * Specified values always use OwnedSlice, since it's more compact.
 //  * computed_value::List is just a convenient alias that you can use for the
 //    computed value list, since this is in the computed_value module.
 //
+// If simple_vector_bindings is true, then we don't use the complex iterator
+// machinery and set_foo_from, and just compute the value like any other
+// longhand.
 <%def name="vector_longhand(name, animation_value_type=None,
                             vector_animation_type=None, allow_empty=False,
+                            simple_vector_bindings=False,
                             separator='Comma',
                             **kwargs)">
     <%call expr="longhand(name, animation_value_type=animation_value_type, vector=True,
-                          **kwargs)">
+                          simple_vector_bindings=simple_vector_bindings, **kwargs)">
         #[allow(unused_imports)]
         use smallvec::SmallVec;
 
         pub mod single_value {
             #[allow(unused_imports)]
             use cssparser::{Parser, BasicParseError};
             #[allow(unused_imports)]
             use crate::parser::{Parse, ParserContext};
@@ -232,16 +236,30 @@
                     % endif
                     let iter =
                         resolved.0.into_iter().map(ToResolvedValue::from_resolved_value);
                     ComputedList(UnderlyingList::from_iter(iter))
                 }
             }
             % endif
 
+            % if simple_vector_bindings:
+            impl From<ComputedList> for UnderlyingList<single_value::T> {
+                #[inline]
+                fn from(l: ComputedList) -> Self {
+                    l.0
+                }
+            }
+            impl From<UnderlyingList<single_value::T>> for ComputedList {
+                #[inline]
+                fn from(l: UnderlyingList<single_value::T>) -> Self {
+                    List(l)
+                }
+            }
+            % endif
 
             % if vector_animation_type:
             % if not animation_value_type:
                 Sorry, this is stupid but needed for now.
             % endif
 
             use crate::properties::animated_properties::ListAnimation;
             use crate::values::animated::{Animate, ToAnimatedZero, Procedure};
@@ -340,24 +358,26 @@
             let v = style_traits::${separator}::parse(input, |parser| {
                 single_value::parse(context, parser)
             })?;
             Ok(SpecifiedValue(v.into()))
         }
 
         pub use self::single_value::SpecifiedValue as SingleSpecifiedValue;
 
+        % if not simple_vector_bindings:
         impl SpecifiedValue {
             fn compute_iter<'a, 'cx, 'cx_a>(
                 &'a self,
                 context: &'cx Context<'cx_a>,
             ) -> computed_value::Iter<'a, 'cx, 'cx_a> {
                 computed_value::Iter::new(context, &self.0)
             }
         }
+        % endif
 
         impl ToComputedValue for SpecifiedValue {
             type ComputedValue = computed_value::T;
 
             #[inline]
             fn to_computed_value(&self, context: &Context) -> computed_value::T {
                 % if not is_shared_list:
                 use std::iter::FromIterator;
@@ -468,17 +488,17 @@
                 }
             % endif
 
             % if not property.style_struct.inherited and property.logical:
                 context.rule_cache_conditions.borrow_mut()
                     .set_writing_mode_dependency(context.builder.writing_mode);
             % endif
 
-            % if property.is_vector:
+            % if property.is_vector and not property.simple_vector_bindings:
                 // In the case of a vector property we want to pass down an
                 // iterator so that this can be computed without allocation.
                 //
                 // However, computing requires a context, but the style struct
                 // being mutated is on the context. We temporarily remove it,
                 // mutate it, and then put it back. Vector longhands cannot
                 // touch their own style struct whilst computing, else this will
                 // panic.
--- a/servo/components/style/properties/longhands/effects.mako.rs
+++ b/servo/components/style/properties/longhands/effects.mako.rs
@@ -18,16 +18,17 @@
     servo_restyle_damage = "reflow_out_of_flow",
 )}
 
 ${helpers.predefined_type(
     "box-shadow",
     "BoxShadow",
     None,
     vector=True,
+    simple_vector_bindings=True,
     animation_value_type="AnimatedBoxShadowList",
     vector_animation_type="with_zero",
     extra_prefixes="webkit",
     ignored_when_colors_disabled=True,
     flags="APPLIES_TO_FIRST_LETTER",
     spec="https://drafts.csswg.org/css-backgrounds/#box-shadow",
 )}
 
--- a/servo/components/style/properties/longhands/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhands/inherited_text.mako.rs
@@ -213,16 +213,17 @@
 ${helpers.predefined_type(
     "text-shadow",
     "SimpleShadow",
     None,
     vector=True,
     vector_animation_type="with_zero",
     animation_value_type="AnimatedTextShadowList",
     ignored_when_colors_disabled=True,
+    simple_vector_bindings=True,
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
     spec="https://drafts.csswg.org/css-text-decor-3/#text-shadow-property",
 )}
 
 ${helpers.predefined_type(
     "text-emphasis-style",
     "TextEmphasisStyle",
     None,
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -3439,17 +3439,17 @@ impl<'a> StyleBuilder<'a> {
                 reset_struct,
                 % if property.logical:
                 self.writing_mode,
                 % endif
             );
     }
     % endif
 
-    % if not property.is_vector:
+    % if not property.is_vector or property.simple_vector_bindings:
     /// Set the `${property.ident}` to the computed value `value`.
     #[allow(non_snake_case)]
     pub fn set_${property.ident}(
         &mut self,
         value: longhands::${property.ident}::computed_value::T
     ) {
         % if not property.style_struct.inherited:
         self.modified_reset = true;
--- a/servo/components/style/values/animated/effects.rs
+++ b/servo/components/style/values/animated/effects.rs
@@ -4,27 +4,23 @@
 
 //! Animated types for CSS values related to effects.
 
 use crate::values::animated::color::Color;
 use crate::values::computed::length::Length;
 #[cfg(feature = "gecko")]
 use crate::values::computed::url::ComputedUrl;
 use crate::values::computed::{Angle, Number};
-use crate::values::generics::effects::BoxShadow as GenericBoxShadow;
 use crate::values::generics::effects::Filter as GenericFilter;
 use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow;
 #[cfg(not(feature = "gecko"))]
 use crate::values::Impossible;
 
-/// An animated value for a single `box-shadow`.
-pub type BoxShadow = GenericBoxShadow<Color, Length, Length, Length>;
+/// An animated value for the `drop-shadow()` filter.
+type AnimatedSimpleShadow = GenericSimpleShadow<Color, Length, Length>;
 
 /// An animated value for a single `filter`.
 #[cfg(feature = "gecko")]
-pub type Filter = GenericFilter<Angle, Number, Length, SimpleShadow, ComputedUrl>;
+pub type Filter = GenericFilter<Angle, Number, Length, AnimatedSimpleShadow, ComputedUrl>;
 
 /// An animated value for a single `filter`.
 #[cfg(not(feature = "gecko"))]
 pub type Filter = GenericFilter<Angle, Number, Length, Impossible, Impossible>;
-
-/// An animated value for the `drop-shadow()` filter.
-pub type SimpleShadow = GenericSimpleShadow<Color, Length, Length>;
--- a/servo/components/style/values/generics/effects.rs
+++ b/servo/components/style/values/generics/effects.rs
@@ -14,27 +14,30 @@
     PartialEq,
     SpecifiedValueInfo,
     ToAnimatedValue,
     ToAnimatedZero,
     ToCss,
     ToResolvedValue,
     ToShmem,
 )]
-pub struct BoxShadow<Color, SizeLength, BlurShapeLength, ShapeLength> {
+#[repr(C)]
+pub struct GenericBoxShadow<Color, SizeLength, BlurShapeLength, ShapeLength> {
     /// The base shadow.
-    pub base: SimpleShadow<Color, SizeLength, BlurShapeLength>,
+    pub base: GenericSimpleShadow<Color, SizeLength, BlurShapeLength>,
     /// The spread radius.
     pub spread: ShapeLength,
     /// Whether this is an inset box shadow.
     #[animation(constant)]
     #[css(represents_keyword)]
     pub inset: bool,
 }
 
+pub use self::GenericBoxShadow as BoxShadow;
+
 /// A generic value for a single `filter`.
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[animation(no_bound(Url))]
 #[derive(
     Clone,
     ComputeSquaredDistance,
     Debug,
     MallocSizeOf,
@@ -95,18 +98,21 @@ pub enum Filter<Angle, Factor, Length, D
     PartialEq,
     SpecifiedValueInfo,
     ToAnimatedValue,
     ToAnimatedZero,
     ToCss,
     ToResolvedValue,
     ToShmem,
 )]
-pub struct SimpleShadow<Color, SizeLength, ShapeLength> {
+#[repr(C)]
+pub struct GenericSimpleShadow<Color, SizeLength, ShapeLength> {
     /// Color.
     pub color: Color,
     /// Horizontal radius.
     pub horizontal: SizeLength,
     /// Vertical radius.
     pub vertical: SizeLength,
     /// Blur radius.
     pub blur: ShapeLength,
 }
+
+pub use self::GenericSimpleShadow as SimpleShadow;
--- a/servo/ports/geckolib/cbindgen.toml
+++ b/servo/ports/geckolib/cbindgen.toml
@@ -124,16 +124,18 @@ include = [
   "VerticalAlign",
   "BasicShape",
   "ShapeRadius",
   "ArcSlice",
   "ForgottenArcSlicePtr",
   "HeaderWithLength",
   "MozContextProperties",
   "Quotes",
+  "BoxShadow",
+  "SimpleShadow",
 ]
 item_types = ["enums", "structs", "typedefs", "functions"]
 renaming_overrides_prefixing = true
 
 # Prevent some renaming for Gecko types that cbindgen doesn't otherwise understand.
 [export.rename]
 "nscolor" = "nscolor"
 "nsAtom" = "nsAtom"
@@ -343,55 +345,47 @@ renaming_overrides_prefixing = true
   inline nscolor ToColor() const;
 """
 
 "OwnedSlice" = """
   StyleOwnedSlice() :
     ptr((T*)alignof(T)),
     len(0) {}
 
-  // NOTE(emilio): Could be implemented with some effort, but for now no copy.
-  StyleOwnedSlice(const StyleOwnedSlice& aOther) = delete;
+  inline StyleOwnedSlice(const StyleOwnedSlice& aOther);
 
-  ~StyleOwnedSlice() {
-    if (!len) {
-      return;
-    }
-    for (size_t i : IntegerRange(len)) {
-      ptr[i].~T();
-    }
-    free(ptr);
-    ptr = (T*)alignof(T);
-    len = 0;
-  }
+  inline ~StyleOwnedSlice();
 
   Span<const T> AsSpan() const {
     return MakeSpan(ptr, len);
   }
 
   size_t Length() const {
     return AsSpan().Length();
   }
 
+  bool IsEmpty() const { return Length() == 0; }
+
   bool operator==(const StyleOwnedSlice& other) const {
     return AsSpan() == other.AsSpan();
   }
 
   bool operator!=(const StyleOwnedSlice& other) const {
     return !(*this == other);
   }
 """
 
 "ArcSlice" = """
   inline StyleArcSlice();
   inline StyleArcSlice(const StyleArcSlice& aOther);
   inline explicit StyleArcSlice(const StyleForgottenArcSlicePtr<T>& aPtr);
   inline ~StyleArcSlice();
   inline Span<const T> AsSpan() const;
   inline size_t Length() const;
+  inline bool IsEmpty() const;
   inline bool operator==(const StyleArcSlice& other) const;
   inline bool operator!=(const StyleArcSlice& other) const;
 """
 
 "Atom" = """
   StyleAtom(size_t) = delete;
   StyleAtom() = delete;