Bug 1576666 - Use cbindgen for clip / -moz-image-region. r=boris
authorEmilio Cobos Álvarez <emilio@crisal.io>
Mon, 26 Aug 2019 23:09:47 +0000
changeset 553725 2e85fa357d6253a46ad1320780767fdbfc4f35bc
parent 553724 baa507641ae2a35a2a13cd57d1d57dfc27142956
child 553726 2e49430660ae20e1acecff858966effc4d851e24
push id2165
push userffxbld-merge
push dateMon, 14 Oct 2019 16:30:58 +0000
treeherdermozilla-release@0eae18af659f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersboris
bugs1576666
milestone70.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 1576666 - Use cbindgen for clip / -moz-image-region. r=boris This also fixes some of the issues with -moz-image-region, where we just minted an auto out of the blue. Depends on D43472 Differential Revision: https://phabricator.services.mozilla.com/D43474
layout/generic/nsFrame.cpp
layout/style/ServoBindings.toml
layout/style/ServoStyleConstsInlines.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/test_transitions_per_property.html
layout/svg/nsSVGUtils.cpp
layout/xul/nsImageBoxFrame.cpp
layout/xul/tree/nsTreeBodyFrame.cpp
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhands/list.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/generics/mod.rs
servo/components/style/values/specified/mod.rs
servo/ports/geckolib/cbindgen.toml
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2559,37 +2559,38 @@ bool nsIFrame::FormsBackdropRoot(const n
   // TODO(cbrewster): Check will-change attributes
 
   return false;
 }
 
 Maybe<nsRect> nsIFrame::GetClipPropClipRect(const nsStyleDisplay* aDisp,
                                             const nsStyleEffects* aEffects,
                                             const nsSize& aSize) const {
-  if (!(aEffects->mClipFlags & NS_STYLE_CLIP_RECT) ||
+  if (aEffects->mClip.IsAuto() ||
       !(aDisp->IsAbsolutelyPositioned(this) || IsSVGContentWithCSSClip(this))) {
     return Nothing();
   }
 
-  nsRect rect = aEffects->mClip;
+  auto& clipRect = aEffects->mClip.AsRect();
+  nsRect rect = clipRect.ToLayoutRect();
   if (MOZ_LIKELY(StyleBorder()->mBoxDecorationBreak ==
                  StyleBoxDecorationBreak::Slice)) {
     // The clip applies to the joined boxes so it's relative the first
     // continuation.
     nscoord y = 0;
     for (nsIFrame* f = GetPrevContinuation(); f; f = f->GetPrevContinuation()) {
       y += f->GetRect().height;
     }
     rect.MoveBy(nsPoint(0, -y));
   }
 
-  if (NS_STYLE_CLIP_RIGHT_AUTO & aEffects->mClipFlags) {
+  if (clipRect.right.IsAuto()) {
     rect.width = aSize.width - rect.x;
   }
-  if (NS_STYLE_CLIP_BOTTOM_AUTO & aEffects->mClipFlags) {
+  if (clipRect.bottom.IsAuto()) {
     rect.height = aSize.height - rect.y;
   }
   return Some(rect);
 }
 
 /**
  * If the CSS 'overflow' property applies to this frame, and is not
  * handled by constructing a dedicated nsHTML/XULScrollFrame, set up clipping
@@ -4016,18 +4017,17 @@ void nsIFrame::BuildDisplayListForChild(
   const bool isPositioned = disp->IsAbsPosContainingBlock(child);
 
   const bool isStackingContext =
       (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT) ||
       child->IsStackingContext(disp, pos, effects, isPositioned);
 
   if (pseudoStackingContext || isStackingContext || isPositioned ||
       isPlaceholder || (!isSVG && disp->IsFloating(child)) ||
-      (isSVG && (effects->mClipFlags & NS_STYLE_CLIP_RECT) &&
-       IsSVGContentWithCSSClip(child))) {
+      (isSVG && effects->mClip.IsRect() && IsSVGContentWithCSSClip(child))) {
     pseudoStackingContext = true;
     awayFromCommonPath = true;
   }
 
   NS_ASSERTION(!isStackingContext || pseudoStackingContext,
                "Stacking contexts must also be pseudo-stacking-contexts");
 
   nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -518,16 +518,17 @@ cbindgen-types = [
     { gecko = "StyleGenericSVGPaint", servo = "values::generics::svg::SVGPaint" },
     { gecko = "StyleGenericTrackRepeat", servo = "values::generics::grid::TrackRepeat" },
     { gecko = "StyleGenericTrackListValue", servo = "values::generics::grid::TrackListValue" },
     { gecko = "StyleGenericTrackList", servo = "values::generics::grid::TrackList" },
     { gecko = "StyleGenericGridTemplateComponent", servo = "values::generics::grid::GridTemplateComponent" },
     { gecko = "StyleTextEmphasisStyle", servo = "values::computed::text::TextEmphasisStyle" },
     { gecko = "StyleVariantAlternatesList", servo = "values::specified::font::VariantAlternatesList" },
     { gecko = "StyleSVGPaintOrder", servo = "values::specified::svg::SVGPaintOrder" },
+    { gecko = "StyleClipRectOrAuto", servo = "values::computed::ClipRectOrAuto" },
 ]
 
 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
@@ -584,16 +584,21 @@ inline bool LengthOrAuto::IsLength() con
 }
 
 template <>
 inline const Length& LengthOrAuto::AsLength() const {
   return AsLengthPercentage();
 }
 
 template <>
+inline nscoord LengthOrAuto::ToLength() const {
+  return AsLength().ToAppUnits();
+}
+
+template <>
 inline bool StyleFlexBasis::IsAuto() const {
   return IsSize() && AsSize().IsAuto();
 }
 
 template <>
 inline bool StyleSize::BehavesLikeInitialValueOnBlockAxis() const {
   return IsAuto() || IsExtremumLength();
 }
@@ -685,11 +690,20 @@ StyleGridTemplateComponent::GetRepeatAut
     return nullptr;
   }
   return &TrackListValues()[*index].AsTrackRepeat();
 }
 
 constexpr const auto kPaintOrderShift = StylePAINT_ORDER_SHIFT;
 constexpr const auto kPaintOrderMask = StylePAINT_ORDER_MASK;
 
+template <>
+inline nsRect StyleGenericClipRect<LengthOrAuto>::ToLayoutRect(nscoord aAutoSize) const {
+  nscoord x = left.IsLength() ? left.ToLength() : 0;
+  nscoord y = top.IsLength() ? top.ToLength() : 0;
+  nscoord width = right.IsLength() ? right.ToLength() - x : aAutoSize;
+  nscoord height = bottom.IsLength() ? bottom.ToLength() - y : aAutoSize;
+  return nsRect(x, y, width, height);
+}
+
 }  // namespace mozilla
 
 #endif
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -601,19 +601,20 @@ nsChangeHint nsStyleList::CalcDifference
   if (mMozListReversed != aNewData.mMozListReversed) {
     return NS_STYLE_HINT_REFLOW;
   }
   // list-style-image and -moz-image-region may affect some XUL elements
   // regardless of display value, so we still need to check them.
   if (!DefinitelyEqualImages(mListStyleImage, aNewData.mListStyleImage)) {
     return NS_STYLE_HINT_REFLOW;
   }
-  if (!mImageRegion.IsEqualInterior(aNewData.mImageRegion)) {
-    if (mImageRegion.width != aNewData.mImageRegion.width ||
-        mImageRegion.height != aNewData.mImageRegion.height) {
+  if (mImageRegion != aNewData.mImageRegion) {
+    nsRect region = GetImageRegion();
+    nsRect newRegion = aNewData.GetImageRegion();
+    if (region.width != newRegion.width || region.height != newRegion.height) {
       return NS_STYLE_HINT_REFLOW;
     }
     return NS_STYLE_HINT_VISUAL;
   }
   return hint;
 }
 
 already_AddRefed<nsIURI> nsStyleList::GetListStyleImageURI() const {
@@ -3725,54 +3726,66 @@ nsChangeHint nsStyleUIReset::CalcDiffere
   return hint;
 }
 
 //-----------------------
 // nsStyleEffects
 //
 
 nsStyleEffects::nsStyleEffects(const Document&)
-    : mClip(0, 0, 0, 0),
+    : mClip(StyleClipRectOrAuto::Auto()),
       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),
       mBoxShadow(aSource.mBoxShadow),
       mBackdropFilters(aSource.mBackdropFilters),
       mClip(aSource.mClip),
       mOpacity(aSource.mOpacity),
-      mClipFlags(aSource.mClipFlags),
       mMixBlendMode(aSource.mMixBlendMode) {
   MOZ_COUNT_CTOR(nsStyleEffects);
 }
 
 nsStyleEffects::~nsStyleEffects() { MOZ_COUNT_DTOR(nsStyleEffects); }
 
+static bool AnyAutonessChanged(const StyleClipRectOrAuto& aOld,
+                               const StyleClipRectOrAuto& aNew) {
+  if (aOld.IsAuto() != aNew.IsAuto()) {
+    return true;
+  }
+  if (aOld.IsAuto()) {
+    return false;
+  }
+  auto& oldRect = aOld.AsRect();
+  auto& newRect = aNew.AsRect();
+  return oldRect.top.IsAuto() != newRect.top.IsAuto() ||
+         oldRect.right.IsAuto() != newRect.right.IsAuto() ||
+         oldRect.bottom.IsAuto() != newRect.bottom.IsAuto() ||
+         oldRect.left.IsAuto() != newRect.left.IsAuto();
+}
+
 nsChangeHint nsStyleEffects::CalcDifference(
     const nsStyleEffects& aNewData) const {
   nsChangeHint hint = nsChangeHint(0);
 
   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;
   }
 
-  if (mClipFlags != aNewData.mClipFlags) {
+  if (AnyAutonessChanged(mClip, aNewData.mClip)) {
     hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
-  }
-
-  if (!mClip.IsEqualInterior(aNewData.mClip)) {
+  } else if (mClip != aNewData.mClip) {
     // If the clip has changed, we just need to update overflow areas. DLBI
     // will handle the invalidation.
     hint |= nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint;
   }
 
   if (mOpacity != aNewData.mOpacity) {
     // If we're going from the optimized >=0.99 opacity value to 1.0 or back,
     // then repaint the frame because DLBI will not catch the invalidation.
@@ -3807,20 +3820,16 @@ nsChangeHint nsStyleEffects::CalcDiffere
     // A change from/to being a containing block for position:fixed.
     hint |= nsChangeHint_UpdateContainingBlock;
   }
 
   if (mBackdropFilters != aNewData.mBackdropFilters) {
     hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
   }
 
-  if (!hint && !mClip.IsEqualEdges(aNewData.mClip)) {
-    hint |= nsChangeHint_NeutralChange;
-  }
-
   return hint;
 }
 
 static bool TransformOperationHasPercent(const StyleTransformOperation& aOp) {
   switch (aOp.tag) {
     case StyleTransformOperation::Tag::TranslateX:
       return aOp.AsTranslateX().HasPercent();
     case StyleTransformOperation::Tag::TranslateY:
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -929,29 +929,36 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   nsChangeHint CalcDifference(const nsStyleList& aNewData,
                               const nsStyleDisplay& aOldDisplay) const;
 
   imgRequestProxy* GetListStyleImage() const {
     return mListStyleImage ? mListStyleImage->get() : nullptr;
   }
 
+  nsRect GetImageRegion() const {
+    if (!mImageRegion.IsRect()) {
+      return nsRect();
+    }
+    return mImageRegion.AsRect().ToLayoutRect(0);
+  }
+
   already_AddRefed<nsIURI> GetListStyleImageURI() const;
 
   uint8_t mListStylePosition;
   RefPtr<nsStyleImageRequest> mListStyleImage;
 
   mozilla::CounterStylePtr mCounterStyle;
 
  private:
   nsStyleList& operator=(const nsStyleList& aOther) = delete;
 
  public:
   mozilla::StyleQuotes mQuotes;
-  nsRect mImageRegion;  // the rect to use within an image
+  mozilla::StyleClipRectOrAuto mImageRegion;  // the rect to use within an image
   mozilla::StyleMozListReversed
       mMozListReversed;  // true in an <ol reversed> scope
 };
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePosition {
   using LengthPercentageOrAuto = mozilla::LengthPercentageOrAuto;
   using Position = mozilla::Position;
   template <typename T>
@@ -2391,19 +2398,18 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   bool HasMixBlendMode() const {
     return mMixBlendMode != NS_STYLE_BLEND_NORMAL;
   }
 
   mozilla::StyleOwnedSlice<mozilla::StyleFilter> mFilters;
   mozilla::StyleOwnedSlice<mozilla::StyleBoxShadow> mBoxShadow;
   mozilla::StyleOwnedSlice<mozilla::StyleFilter> mBackdropFilters;
-  nsRect mClip;  // offsets from UL border edge
+  mozilla::StyleClipRectOrAuto 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); \
   static_assert(alignof(T1) == alignof(T2),                \
                 "Align mismatch between " #T1 " and " #T2);
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -2493,26 +2493,18 @@ function test_rect_transition(prop) {
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "rect(0px, 4px, 4px, 2px)", "");
   is(cs.getPropertyValue(prop), "rect(3px, 13px, 10px, 5px)",
      "rect-valued property " + prop + ": interpolation of rects");
   check_distance(prop, "rect(4px, 16px, 12px, 6px)",
                        "rect(3px, 13px, 10px, 5px)",
                        "rect(0px, 4px, 4px, 2px)");
   div.style.setProperty(prop, "rect(0px, 6px, 4px, auto)", "");
-  if (prop == "clip") {
-    is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)",
-       "rect-valued property " + prop + ": can't interpolate auto components");
-  } else {
-    is(prop, "-moz-image-region");
-    // Bug 1390067: -moz-image-region should behave just like clip does.
-    todo_is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)",
-            "rect-valued property " + prop +
-            ": can't interpolate auto components");
-  }
+  is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)",
+     "rect-valued property " + prop + ": can't interpolate auto components");
   div.style.setProperty(prop, "rect(0px, 6px, 4px, 2px)", "");
   div.style.setProperty(prop, "auto", "");
   is(cs.getPropertyValue(prop), "auto",
      "rect-valued property " + prop + ": can't interpolate auto components");
 
   div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "rect(-10px, 30px, 0px, 0px)", "");
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -922,48 +922,45 @@ bool nsSVGUtils::HitTestRect(const gfx::
          p.y <= rect.YMost();
 }
 
 gfxRect nsSVGUtils::GetClipRectForFrame(nsIFrame* aFrame, float aX, float aY,
                                         float aWidth, float aHeight) {
   const nsStyleDisplay* disp = aFrame->StyleDisplay();
   const nsStyleEffects* effects = aFrame->StyleEffects();
 
-  if (!(effects->mClipFlags & NS_STYLE_CLIP_RECT)) {
-    NS_ASSERTION(effects->mClipFlags == NS_STYLE_CLIP_AUTO,
-                 "We don't know about this type of clip.");
+  bool clipApplies =
+      disp->mOverflowX == StyleOverflow::Hidden ||
+      disp->mOverflowY == StyleOverflow::Hidden;
+
+  if (!clipApplies || effects->mClip.IsAuto()) {
     return gfxRect(aX, aY, aWidth, aHeight);
   }
 
-  if (disp->mOverflowX == StyleOverflow::Hidden ||
-      disp->mOverflowY == StyleOverflow::Hidden) {
-    nsIntRect clipPxRect = effects->mClip.ToOutsidePixels(
-        aFrame->PresContext()->AppUnitsPerDevPixel());
-    gfxRect clipRect = gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width,
-                               clipPxRect.height);
-
-    if (NS_STYLE_CLIP_RIGHT_AUTO & effects->mClipFlags) {
-      clipRect.width = aWidth - clipRect.X();
-    }
-    if (NS_STYLE_CLIP_BOTTOM_AUTO & effects->mClipFlags) {
-      clipRect.height = aHeight - clipRect.Y();
-    }
-
-    if (disp->mOverflowX != StyleOverflow::Hidden) {
-      clipRect.x = aX;
-      clipRect.width = aWidth;
-    }
-    if (disp->mOverflowY != StyleOverflow::Hidden) {
-      clipRect.y = aY;
-      clipRect.height = aHeight;
-    }
-
-    return clipRect;
+  auto& rect = effects->mClip.AsRect();
+  nsRect coordClipRect = rect.ToLayoutRect();
+  nsIntRect clipPxRect = coordClipRect.ToOutsidePixels(
+      aFrame->PresContext()->AppUnitsPerDevPixel());
+  gfxRect clipRect = gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width,
+                             clipPxRect.height);
+  if (rect.right.IsAuto()) {
+    clipRect.width = aWidth - clipRect.X();
   }
-  return gfxRect(aX, aY, aWidth, aHeight);
+  if (rect.bottom.IsAuto()) {
+    clipRect.height = aHeight - clipRect.Y();
+  }
+  if (disp->mOverflowX != StyleOverflow::Hidden) {
+    clipRect.x = aX;
+    clipRect.width = aWidth;
+  }
+  if (disp->mOverflowY != StyleOverflow::Hidden) {
+    clipRect.y = aY;
+    clipRect.height = aHeight;
+  }
+  return clipRect;
 }
 
 void nsSVGUtils::SetClipRect(gfxContext* aContext, const gfxMatrix& aCTM,
                              const gfxRect& aRect) {
   if (aCTM.IsSingular()) {
     return;
   }
 
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -612,17 +612,17 @@ bool nsImageBoxFrame::CanOptimizeToImage
 // date.
 //
 /* virtual */
 void nsImageBoxFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
   nsLeafBoxFrame::DidSetComputedStyle(aOldComputedStyle);
 
   // Fetch our subrect.
   const nsStyleList* myList = StyleList();
-  mSubRect = myList->mImageRegion;  // before |mSuppressStyleCheck| test!
+  mSubRect = myList->GetImageRegion();  // before |mSuppressStyleCheck| test!
 
   if (mUseSrcAttr || mSuppressStyleCheck)
     return;  // No more work required, since the image isn't specified by style.
 
   // If we're using a native theme implementation, we shouldn't draw anything.
   const nsStyleDisplay* disp = StyleDisplay();
   if (disp->HasAppearance() && nsBox::gTheme &&
       nsBox::gTheme->ThemeSupportsWidget(nullptr, this, disp->mAppearance))
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -1968,35 +1968,36 @@ nsRect nsTreeBodyFrame::GetImageSize(int
   // Don't change this, otherwise things start to go crazy.
   bool useImageRegion = true;
   nsCOMPtr<imgIContainer> image;
   GetImage(aRowIndex, aCol, aUseContext, aComputedStyle, useImageRegion,
            getter_AddRefs(image));
 
   const nsStylePosition* myPosition = aComputedStyle->StylePosition();
   const nsStyleList* myList = aComputedStyle->StyleList();
-
+  nsRect imageRegion = myList->GetImageRegion();
   if (useImageRegion) {
-    r.x += myList->mImageRegion.x;
-    r.y += myList->mImageRegion.y;
+    r.x += imageRegion.x;
+    r.y += imageRegion.y;
   }
 
   if (myPosition->mWidth.ConvertsToLength()) {
     int32_t val = myPosition->mWidth.ToLength();
     r.width += val;
-  } else if (useImageRegion && myList->mImageRegion.width > 0)
-    r.width += myList->mImageRegion.width;
-  else
+  } else if (useImageRegion && imageRegion.width > 0) {
+    r.width += imageRegion.width;
+  } else {
     needWidth = true;
+  }
 
   if (myPosition->mHeight.ConvertsToLength()) {
     int32_t val = myPosition->mHeight.ToLength();
     r.height += val;
-  } else if (useImageRegion && myList->mImageRegion.height > 0)
-    r.height += myList->mImageRegion.height;
+  } else if (useImageRegion && imageRegion.height > 0)
+    r.height += imageRegion.height;
   else
     needHeight = true;
 
   if (image) {
     if (needWidth || needHeight) {
       // Get the natural image size.
 
       if (needWidth) {
@@ -2057,31 +2058,31 @@ nsSize nsTreeBodyFrame::GetImageDestSize
     needHeight = true;
   }
 
   if (needWidth || needHeight) {
     // We need to get the size of the image/region.
     nsSize imageSize(0, 0);
 
     const nsStyleList* myList = aComputedStyle->StyleList();
-
-    if (useImageRegion && myList->mImageRegion.width > 0) {
+    nsRect imageRegion = myList->GetImageRegion();
+    if (useImageRegion && imageRegion.width > 0) {
       // CSS has specified an image region.
       // Use the width of the image region.
-      imageSize.width = myList->mImageRegion.width;
+      imageSize.width = imageRegion.width;
     } else if (image) {
       nscoord width;
       image->GetWidth(&width);
       imageSize.width = nsPresContext::CSSPixelsToAppUnits(width);
     }
 
-    if (useImageRegion && myList->mImageRegion.height > 0) {
+    if (useImageRegion && imageRegion.height > 0) {
       // CSS has specified an image region.
       // Use the height of the image region.
-      imageSize.height = myList->mImageRegion.height;
+      imageSize.height = imageRegion.height;
     } else if (image) {
       nscoord height;
       image->GetHeight(&height);
       imageSize.height = nsPresContext::CSSPixelsToAppUnits(height);
     }
 
     if (needWidth) {
       if (!needHeight && imageSize.height != 0) {
@@ -2114,33 +2115,35 @@ nsSize nsTreeBodyFrame::GetImageDestSize
 // The width and height reflect the image region specified in CSS, or
 // the natural size of the image.
 // The width and height do not include borders and padding.
 // The width and height do not reflect the destination size specified
 // in CSS.
 nsRect nsTreeBodyFrame::GetImageSourceRect(ComputedStyle* aComputedStyle,
                                            bool useImageRegion,
                                            imgIContainer* image) {
-  nsRect r(0, 0, 0, 0);
-
   const nsStyleList* myList = aComputedStyle->StyleList();
-
-  if (useImageRegion &&
-      (myList->mImageRegion.width > 0 || myList->mImageRegion.height > 0)) {
-    // CSS has specified an image region.
-    r = myList->mImageRegion;
-  } else if (image) {
-    // Use the actual image size.
-    nscoord coord;
-    image->GetWidth(&coord);
+  // CSS has specified an image region.
+  if (useImageRegion && myList->mImageRegion.IsRect()) {
+    return myList->GetImageRegion();
+  }
+
+  if (!image) {
+    return nsRect();
+  }
+
+  nsRect r;
+  // Use the actual image size.
+  nscoord coord;
+  if (NS_SUCCEEDED(image->GetWidth(&coord))) {
     r.width = nsPresContext::CSSPixelsToAppUnits(coord);
-    image->GetHeight(&coord);
+  }
+  if (NS_SUCCEEDED(image->GetHeight(&coord))) {
     r.height = nsPresContext::CSSPixelsToAppUnits(coord);
   }
-
   return r;
 }
 
 int32_t nsTreeBodyFrame::GetRowHeight() {
   // Look up the correct height.  It is equal to the specified height
   // + the specified margins.
   mScratchArray.Clear();
   ComputedStyle* rowContext =
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -2157,17 +2157,17 @@ fn static_assert() {
                   skip_longhands="${skip_background_longhands}">
 
     <% impl_common_image_layer_properties("background") %>
     <% impl_simple_image_array_property("attachment", "background", "mImage", "mAttachment", "Background") %>
     <% impl_simple_image_array_property("blend_mode", "background", "mImage", "mBlendMode", "Background") %>
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="List"
-                  skip_longhands="list-style-image list-style-type -moz-image-region">
+                  skip_longhands="list-style-image list-style-type">
 
     pub fn set_list_style_image(&mut self, image: longhands::list_style_image::computed_value::T) {
         match image {
             UrlOrNone::None => {
                 unsafe {
                     Gecko_SetListStyleImageNone(&mut *self.gecko);
                 }
             }
@@ -2227,188 +2227,22 @@ fn static_assert() {
         use crate::values::generics::CounterStyleOrNone;
 
         let result = CounterStyleOrNone::from_gecko_value(&self.gecko.mCounterStyle);
         match result {
             Either::First(counter_style) => T::CounterStyle(counter_style),
             Either::Second(string) => T::String(string),
         }
     }
-
-    #[allow(non_snake_case)]
-    pub fn set__moz_image_region(&mut self, v: longhands::_moz_image_region::computed_value::T) {
-        use crate::values::Either;
-        use crate::values::generics::length::LengthPercentageOrAuto::*;
-
-        match v {
-            Either::Second(_auto) => {
-                self.gecko.mImageRegion.x = 0;
-                self.gecko.mImageRegion.y = 0;
-                self.gecko.mImageRegion.width = 0;
-                self.gecko.mImageRegion.height = 0;
-            }
-            Either::First(rect) => {
-                self.gecko.mImageRegion.x = match rect.left {
-                    LengthPercentage(v) => v.to_i32_au(),
-                    Auto => 0,
-                };
-                self.gecko.mImageRegion.y = match rect.top {
-                    LengthPercentage(v) => v.to_i32_au(),
-                    Auto => 0,
-                };
-                self.gecko.mImageRegion.height = match rect.bottom {
-                    LengthPercentage(value) => (Au::from(value) - Au(self.gecko.mImageRegion.y)).0,
-                    Auto => 0,
-                };
-                self.gecko.mImageRegion.width = match rect.right {
-                    LengthPercentage(value) => (Au::from(value) - Au(self.gecko.mImageRegion.x)).0,
-                    Auto => 0,
-                };
-            }
-        }
-    }
-
-    #[allow(non_snake_case)]
-    pub fn clone__moz_image_region(&self) -> longhands::_moz_image_region::computed_value::T {
-        use crate::values::{Auto, Either};
-        use crate::values::generics::length::LengthPercentageOrAuto::*;
-        use crate::values::computed::ClipRect;
-
-        // There is no ideal way to detect auto type for structs::nsRect and its components, so
-        // if all components are zero, we use Auto.
-        if self.gecko.mImageRegion.x == 0 &&
-           self.gecko.mImageRegion.y == 0 &&
-           self.gecko.mImageRegion.width == 0 &&
-           self.gecko.mImageRegion.height == 0 {
-           return Either::Second(Auto);
-        }
-
-        Either::First(ClipRect {
-            top: LengthPercentage(Au(self.gecko.mImageRegion.y).into()),
-            right: LengthPercentage(Au(self.gecko.mImageRegion.width + self.gecko.mImageRegion.x).into()),
-            bottom: LengthPercentage(Au(self.gecko.mImageRegion.height + self.gecko.mImageRegion.y).into()),
-            left: LengthPercentage(Au(self.gecko.mImageRegion.x).into()),
-        })
-    }
-
-    ${impl_simple_copy('_moz_image_region', 'mImageRegion')}
-
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Table">
 </%self:impl_trait>
 
-<%self:impl_trait style_struct_name="Effects" skip_longhands="clip">
-    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::*;
-        use crate::values::Either;
-
-        match v {
-            Either::First(rect) => {
-                self.gecko.mClipFlags = NS_STYLE_CLIP_RECT as u8;
-                self.gecko.mClip.x = match rect.left {
-                    LengthPercentage(l) => l.to_i32_au(),
-                    Auto => {
-                        self.gecko.mClipFlags |= NS_STYLE_CLIP_LEFT_AUTO as u8;
-                        0
-                    }
-                };
-
-                self.gecko.mClip.y = match rect.top {
-                    LengthPercentage(l) => l.to_i32_au(),
-                    Auto => {
-                        self.gecko.mClipFlags |= NS_STYLE_CLIP_TOP_AUTO as u8;
-                        0
-                    }
-                };
-
-                self.gecko.mClip.height = match rect.bottom {
-                    LengthPercentage(l) => (Au::from(l) - Au(self.gecko.mClip.y)).0,
-                    Auto => {
-                        self.gecko.mClipFlags |= NS_STYLE_CLIP_BOTTOM_AUTO as u8;
-                        1 << 30 // NS_MAXSIZE
-                    }
-                };
-
-                self.gecko.mClip.width = match rect.right {
-                    LengthPercentage(l) => (Au::from(l) - Au(self.gecko.mClip.x)).0,
-                    Auto => {
-                        self.gecko.mClipFlags |= NS_STYLE_CLIP_RIGHT_AUTO as u8;
-                        1 << 30 // NS_MAXSIZE
-                    }
-                };
-            },
-            Either::Second(_auto) => {
-                self.gecko.mClipFlags = NS_STYLE_CLIP_AUTO as u8;
-                self.gecko.mClip.x = 0;
-                self.gecko.mClip.y = 0;
-                self.gecko.mClip.width = 0;
-                self.gecko.mClip.height = 0;
-            }
-        }
-    }
-
-    pub fn copy_clip_from(&mut self, other: &Self) {
-        self.gecko.mClip = other.gecko.mClip;
-        self.gecko.mClipFlags = other.gecko.mClipFlags;
-    }
-
-    pub fn reset_clip(&mut self, other: &Self) {
-        self.copy_clip_from(other)
-    }
-
-    pub fn clone_clip(&self) -> longhands::clip::computed_value::T {
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_BOTTOM_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_LEFT_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_RIGHT_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_TOP_AUTO;
-        use crate::values::generics::length::LengthPercentageOrAuto::*;
-        use crate::values::computed::{ClipRect, ClipRectOrAuto};
-        use crate::values::Either;
-
-        if self.gecko.mClipFlags == NS_STYLE_CLIP_AUTO as u8 {
-            return ClipRectOrAuto::auto()
-        }
-        let left = if self.gecko.mClipFlags & NS_STYLE_CLIP_LEFT_AUTO as u8 != 0 {
-            debug_assert_eq!(self.gecko.mClip.x, 0);
-            Auto
-        } else {
-            LengthPercentage(Au(self.gecko.mClip.x).into())
-        };
-
-        let top = if self.gecko.mClipFlags & NS_STYLE_CLIP_TOP_AUTO as u8 != 0 {
-            debug_assert_eq!(self.gecko.mClip.y, 0);
-            Auto
-        } else {
-            LengthPercentage(Au(self.gecko.mClip.y).into())
-        };
-
-        let bottom = if self.gecko.mClipFlags & NS_STYLE_CLIP_BOTTOM_AUTO as u8 != 0 {
-            debug_assert_eq!(self.gecko.mClip.height, 1 << 30); // NS_MAXSIZE
-            Auto
-        } else {
-            LengthPercentage(Au(self.gecko.mClip.y + self.gecko.mClip.height).into())
-        };
-
-        let right = if self.gecko.mClipFlags & NS_STYLE_CLIP_RIGHT_AUTO as u8 != 0 {
-            debug_assert_eq!(self.gecko.mClip.width, 1 << 30); // NS_MAXSIZE
-            Auto
-        } else {
-            LengthPercentage(Au(self.gecko.mClip.x + self.gecko.mClip.width).into())
-        };
-
-        Either::First(ClipRect { top, right, bottom, left })
-    }
+<%self:impl_trait style_struct_name="Effects">
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedBox">
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedTable"
                   skip_longhands="border-spacing">
 
--- a/servo/components/style/properties/longhands/list.mako.rs
+++ b/servo/components/style/properties/longhands/list.mako.rs
@@ -71,16 +71,17 @@
     servo_restyle_damage="rebuild_and_reflow",
 )}
 
 ${helpers.predefined_type(
     "-moz-image-region",
     "ClipRectOrAuto",
     "computed::ClipRectOrAuto::auto()",
     engines="gecko",
+    gecko_ffi_name="mImageRegion",
     animation_value_type="ComputedValue",
     boxed=True,
     spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-image-region)",
 )}
 
 ${helpers.predefined_type(
     "-moz-list-reversed",
     "MozListReversed",
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -678,21 +678,21 @@ impl From<CSSInteger> for PositiveIntege
     fn from(int: CSSInteger) -> PositiveInteger {
         GreaterThanOrEqualToOne::<CSSInteger>(int)
     }
 }
 
 /// A computed positive `<integer>` value or `none`.
 pub type PositiveIntegerOrNone = Either<PositiveInteger, None_>;
 
-/// rect(...)
-pub type ClipRect = generics::ClipRect<LengthOrAuto>;
+/// rect(...) | auto
+pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
 
 /// rect(...) | auto
-pub type ClipRectOrAuto = Either<ClipRect, Auto>;
+pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
 
 /// The computed value of a grid `<track-breadth>`
 pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
 
 /// The computed value of a grid `<track-size>`
 pub type TrackSize = GenericTrackSize<LengthPercentage>;
 
 /// The computed value of a grid `<track-size>+`
@@ -702,23 +702,8 @@ pub type ImplicitGridTracks = GenericImp
 /// (could also be `<auto-track-list>` or `<explicit-track-list>`)
 pub type TrackList = GenericTrackList<LengthPercentage, Integer>;
 
 /// The computed value of a `<grid-line>`.
 pub type GridLine = GenericGridLine<Integer>;
 
 /// `<grid-template-rows> | <grid-template-columns>`
 pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
-
-impl ClipRectOrAuto {
-    /// Return an auto (default for clip-rect and image-region) value
-    pub fn auto() -> Self {
-        Either::Second(Auto)
-    }
-
-    /// Check if it is auto
-    pub fn is_auto(&self) -> bool {
-        match *self {
-            Either::Second(_) => true,
-            _ => false,
-        }
-    }
-}
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -267,14 +267,52 @@ pub struct ZeroToOne<T>(pub T);
     ToAnimatedValue,
     ToAnimatedZero,
     ToComputedValue,
     ToCss,
     ToResolvedValue,
     ToShmem,
 )]
 #[css(function = "rect", comma)]
-pub struct ClipRect<LengthOrAuto> {
+#[repr(C)]
+pub struct GenericClipRect<LengthOrAuto> {
     pub top: LengthOrAuto,
     pub right: LengthOrAuto,
     pub bottom: LengthOrAuto,
     pub left: LengthOrAuto,
 }
+
+pub use self::GenericClipRect as ClipRect;
+
+/// Either a clip-rect or `auto`.
+#[allow(missing_docs)]
+#[derive(
+    Animate,
+    Clone,
+    ComputeSquaredDistance,
+    Copy,
+    Debug,
+    MallocSizeOf,
+    Parse,
+    PartialEq,
+    SpecifiedValueInfo,
+    ToAnimatedValue,
+    ToAnimatedZero,
+    ToComputedValue,
+    ToCss,
+    ToResolvedValue,
+    ToShmem,
+)]
+#[repr(C, u8)]
+pub enum GenericClipRectOrAuto<R> {
+    Auto,
+    Rect(R),
+}
+
+pub use self::GenericClipRectOrAuto as ClipRectOrAuto;
+
+impl<L> ClipRectOrAuto<L> {
+    /// Returns the `auto` value.
+    #[inline]
+    pub fn auto() -> Self {
+        ClipRectOrAuto::Auto
+    }
+}
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -8,17 +8,17 @@
 
 use super::computed::transform::DirectionVector;
 use super::computed::{Context, ToComputedValue};
 use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks;
 use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
 use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize};
 use super::generics::transform::IsParallelTo;
 use super::generics::{self, GreaterThanOrEqualToOne, NonNegative};
-use super::{Auto, CSSFloat, CSSInteger, Either, None_};
+use super::{CSSFloat, CSSInteger, Either, None_};
 use crate::context::QuirksMode;
 use crate::parser::{Parse, ParserContext};
 use crate::values::serialize_atom_identifier;
 use crate::values::specified::calc::CalcNode;
 use crate::{Atom, Namespace, Prefix, Zero};
 use cssparser::{Parser, Token};
 use num_traits::One;
 use std::f32;
@@ -634,30 +634,30 @@ pub type TrackList = GenericTrackList<Le
 
 /// The specified value of a `<grid-line>`.
 pub type GridLine = GenericGridLine<Integer>;
 
 /// `<grid-template-rows> | <grid-template-columns>`
 pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
 
 /// rect(...)
-pub type ClipRect = generics::ClipRect<LengthOrAuto>;
+pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
 
 impl Parse for ClipRect {
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
         Self::parse_quirky(context, input, AllowQuirks::No)
     }
 }
 
 impl ClipRect {
     /// Parses a rect(<top>, <left>, <bottom>, <right>), allowing quirks.
-    pub fn parse_quirky<'i, 't>(
+    fn parse_quirky<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
         allow_quirks: AllowQuirks,
     ) -> Result<Self, ParseError<'i>> {
         input.expect_function_matching("rect")?;
 
         fn parse_argument<'i, 't>(
             context: &ParserContext,
@@ -691,30 +691,30 @@ impl ClipRect {
                 bottom,
                 left,
             })
         })
     }
 }
 
 /// rect(...) | auto
-pub type ClipRectOrAuto = Either<ClipRect, Auto>;
+pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
 
 impl ClipRectOrAuto {
     /// Parses a ClipRect or Auto, allowing quirks.
     pub fn parse_quirky<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
         allow_quirks: AllowQuirks,
     ) -> Result<Self, ParseError<'i>> {
         if let Ok(v) = input.try(|i| ClipRect::parse_quirky(context, i, allow_quirks)) {
-            Ok(Either::First(v))
-        } else {
-            Auto::parse(context, input).map(Either::Second)
+            return Ok(generics::GenericClipRectOrAuto::Rect(v))
         }
+        input.expect_ident_matching("auto")?;
+        Ok(generics::GenericClipRectOrAuto::Auto)
     }
 }
 
 /// Whether quirks are allowed in this context.
 #[derive(Clone, Copy, PartialEq)]
 pub enum AllowQuirks {
     /// Quirks are not allowed.
     No,
--- a/servo/ports/geckolib/cbindgen.toml
+++ b/servo/ports/geckolib/cbindgen.toml
@@ -166,16 +166,17 @@ include = [
   "ImplicitGridTracks",
   "SVGPaint",
   "SVGPaintKind",
   "GridTemplateComponent",
   "TextEmphasisStyle",
   "VariantAlternatesList",
   "PaintOrder",
   "SVGPaintOrder",
+  "ClipRectOrAuto",
 ]
 item_types = ["enums", "structs", "typedefs", "functions", "constants"]
 renaming_overrides_prefixing = true
 
 # Prevent some renaming for Gecko types that cbindgen doesn't otherwise understand.
 [export.rename]
 "nscolor" = "nscolor"
 "nsAtom" = "nsAtom"
@@ -700,8 +701,13 @@ renaming_overrides_prefixing = true
 "VariantAlternates" = """
  private:
   // Private default constructor without initialization so that the helper
   // constructor functions still work as expected. They take care of
   // initializing the fields properly.
   StyleVariantAlternates() {}
  public:
 """
+
+"GenericClipRect" = """
+  // Get the layout rect, replacing auto right / bottom values for aAutoSize.
+  inline nsRect ToLayoutRect(nscoord aAutoSize = NS_MAXSIZE) const;
+"""