Bug 1273706 - Part 8: Separate nsStyleImage from nsStyleStruct. r?heycam draft
authorJonathan Chan <jyc@eqv.io>
Thu, 18 Aug 2016 13:25:31 -0700
changeset 402897 c3e7cd099dc0483ad1d490796b54a72b823294f9
parent 402896 34404ba89f84f6d2e77ccb079117ee11824202a5
child 402898 ab7c3d32f8deb753ffbde444beb0ecbfce8cde87
push id26775
push userjchan@mozilla.com
push dateThu, 18 Aug 2016 22:38:41 +0000
reviewersheycam
bugs1273706
milestone51.0a1
Bug 1273706 - Part 8: Separate nsStyleImage from nsStyleStruct. r?heycam Also expose EqualURIs as mozilla::EqualURIs, which is necessary to separate nsStyleImage, and which also allows us to remove nsCSSFrameConstructor's own copy of EqualURIs. This is so that CSSComputedValue, implemented by a future patch in this series, can contain nsStyleImages without causing a circular dependency (because CSSComputedValues are stored in a nsStyleStruct added by a different future patch). MozReview-Commit-ID: 5roQChonIs0
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSRendering.h
layout/base/nsDisplayList.cpp
layout/generic/nsIFrame.h
layout/style/moz.build
layout/style/nsRuleNode.cpp
layout/style/nsStyleImage.cpp
layout/style/nsStyleImage.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -9154,23 +9154,16 @@ nsCSSFrameConstructor::CaptureStateForFr
       GetRootFrame();
   }
   for ( ; frame;
         frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) {
     CaptureFrameState(frame, aHistoryState);
   }
 }
 
-static bool EqualURIs(mozilla::css::URLValue *aURI1,
-                      mozilla::css::URLValue *aURI2)
-{
-  return aURI1 == aURI2 ||    // handle null==null, and optimize
-         (aURI1 && aURI2 && aURI1->URIEquals(*aURI2));
-}
-
 nsStyleContext*
 nsCSSFrameConstructor::MaybeRecreateFramesForElement(Element* aElement)
 {
   RefPtr<nsStyleContext> oldContext = GetUndisplayedContent(aElement);
   uint8_t oldDisplay = NS_STYLE_DISPLAY_NONE;
   if (!oldContext) {
     oldContext = GetDisplayContentsStyleFor(aElement);
     if (!oldContext) {
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -10,16 +10,17 @@
 
 #include "gfxBlur.h"
 #include "gfxContext.h"
 #include "imgIContainer.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/TypedEnumBits.h"
 #include "nsLayoutUtils.h"
+#include "nsStyleImage.h"
 #include "nsStyleStruct.h"
 #include "nsIFrame.h"
 
 class gfxDrawable;
 class nsStyleContext;
 class nsPresContext;
 class nsRenderingContext;
 
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -70,16 +70,17 @@
 #include "mozilla/EventStateManager.h"
 #include "mozilla/RestyleManager.h"
 #include "nsCaret.h"
 #include "nsISelection.h"
 #include "nsDOMTokenList.h"
 #include "mozilla/RuleNodeCacheConditions.h"
 #include "nsCSSProps.h"
 #include "nsPluginFrame.h"
+#include "nsStyleImage.h"
 #include "DisplayItemScrollClip.h"
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount().
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -32,16 +32,17 @@
 #include "nsFrameList.h"
 #include "nsFrameState.h"
 #include "mozilla/ReflowOutput.h"
 #include "nsITheme.h"
 #include "nsLayoutUtils.h"
 #include "nsQueryFrame.h"
 #include "nsStringGlue.h"
 #include "nsStyleContext.h"
+#include "nsStyleImage.h"
 #include "nsStyleStruct.h"
 #include "Visibility.h"
 
 #ifdef ACCESSIBILITY
 #include "mozilla/a11y/AccTypes.h"
 #endif
 
 /**
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -66,16 +66,17 @@ EXPORTS += [
     'nsLayoutStylesheetCache.h',
     'nsRuleData.h',
     'nsRuleNode.h',
     'nsRuleProcessorData.h',
     'nsRuleWalker.h',
     'nsStyleConsts.h',
     'nsStyleContext.h',
     'nsStyleCoord.h',
+    'nsStyleImage.h',
     'nsStyleSet.h',
     'nsStyleStruct.h',
     'nsStyleStructFwd.h',
     'nsStyleStructInlines.h',
     'nsStyleTransformMatrix.h',
     'nsStyleUtil.h',
 ]
 
@@ -179,16 +180,17 @@ UNIFIED_SOURCES += [
     'nsHTMLStyleSheet.cpp',
     'nsMediaFeatures.cpp',
     'nsNthIndexCache.cpp',
     'nsROCSSPrimitiveValue.cpp',
     'nsRuleData.cpp',
     'nsRuleNode.cpp',
     'nsStyleContext.cpp',
     'nsStyleCoord.cpp',
+    'nsStyleImage.cpp',
     'nsStyleSet.cpp',
     'nsStyleStruct.cpp',
     'nsStyleTransformMatrix.cpp',
     'nsStyleUtil.cpp',
     'nsTransitionManager.cpp',
     'RuleNodeCacheConditions.cpp',
     'RuleProcessorCache.cpp',
     'ServoBindings.cpp',
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -29,16 +29,17 @@
 #include "nsIPresShell.h"
 #include "nsFontMetrics.h"
 #include "gfxFont.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsCSSPseudoElements.h"
 #include "nsThemeConstants.h"
 #include "PLDHashTable.h"
 #include "nsStyleContext.h"
+#include "nsStyleImage.h"
 #include "nsStyleSet.h"
 #include "nsStyleStruct.h"
 #include "nsSize.h"
 #include "nsRuleData.h"
 #include "nsIStyleRule.h"
 #include "nsBidiUtils.h"
 #include "nsStyleStructInlines.h"
 #include "nsCSSProps.h"
new file mode 100644
--- /dev/null
+++ b/layout/style/nsStyleImage.cpp
@@ -0,0 +1,385 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsStyleImage.h"
+#include "nsStyleStruct.h"
+
+nsStyleImage::nsStyleImage()
+  : mType(eStyleImageType_Null)
+  , mCropRect(nullptr)
+#ifdef DEBUG
+  , mImageTracked(false)
+#endif
+{
+  MOZ_COUNT_CTOR(nsStyleImage);
+}
+
+nsStyleImage::~nsStyleImage()
+{
+  MOZ_COUNT_DTOR(nsStyleImage);
+  if (mType != eStyleImageType_Null) {
+    SetNull();
+  }
+}
+
+nsStyleImage::nsStyleImage(const nsStyleImage& aOther)
+  : mType(eStyleImageType_Null)
+  , mCropRect(nullptr)
+#ifdef DEBUG
+  , mImageTracked(false)
+#endif
+{
+  // We need our own copy constructor because we don't want
+  // to copy the reference count
+  MOZ_COUNT_CTOR(nsStyleImage);
+  DoCopy(aOther);
+}
+
+nsStyleImage&
+nsStyleImage::operator=(const nsStyleImage& aOther)
+{
+  if (this != &aOther) {
+    DoCopy(aOther);
+  }
+
+  return *this;
+}
+
+void
+nsStyleImage::DoCopy(const nsStyleImage& aOther)
+{
+  SetNull();
+
+  if (aOther.mType == eStyleImageType_Image) {
+    SetImageData(aOther.mImage);
+  } else if (aOther.mType == eStyleImageType_Gradient) {
+    SetGradientData(aOther.mGradient);
+  } else if (aOther.mType == eStyleImageType_Element) {
+    SetElementId(aOther.mElementId);
+  }
+
+  UniquePtr<nsStyleSides> cropRectCopy;
+  if (aOther.mCropRect) {
+    cropRectCopy = MakeUnique<nsStyleSides>(*aOther.mCropRect.get());
+  }
+  SetCropRect(Move(cropRectCopy));
+}
+
+void
+nsStyleImage::SetNull()
+{
+  MOZ_ASSERT(!mImageTracked,
+             "Calling SetNull() with image tracked!");
+
+  if (mType == eStyleImageType_Gradient) {
+    mGradient->Release();
+  } else if (mType == eStyleImageType_Image) {
+    NS_RELEASE(mImage);
+  } else if (mType == eStyleImageType_Element) {
+    free(mElementId);
+  }
+
+  mType = eStyleImageType_Null;
+  mCropRect = nullptr;
+}
+
+void
+nsStyleImage::SetImageData(imgRequestProxy* aImage)
+{
+  MOZ_ASSERT(!mImageTracked,
+             "Setting a new image without untracking the old one!");
+
+  NS_IF_ADDREF(aImage);
+
+  if (mType != eStyleImageType_Null) {
+    SetNull();
+  }
+
+  if (aImage) {
+    mImage = aImage;
+    mType = eStyleImageType_Image;
+  }
+  if (mCachedBIData) {
+    mCachedBIData->PurgeCachedImages();
+  }
+}
+
+void
+nsStyleImage::TrackImage(nsPresContext* aContext)
+{
+  // Sanity
+  MOZ_ASSERT(!mImageTracked, "Already tracking image!");
+  MOZ_ASSERT(mType == eStyleImageType_Image,
+             "Can't track image when there isn't one!");
+
+  // Register the image with the document
+  nsIDocument* doc = aContext->Document();
+  if (doc) {
+    doc->AddImage(mImage);
+  }
+
+  // Mark state
+#ifdef DEBUG
+  mImageTracked = true;
+#endif
+}
+
+void
+nsStyleImage::UntrackImage(nsPresContext* aContext)
+{
+  // Sanity
+  MOZ_ASSERT(mImageTracked, "Image not tracked!");
+  MOZ_ASSERT(mType == eStyleImageType_Image,
+             "Can't untrack image when there isn't one!");
+
+  // Unregister the image with the document
+  nsIDocument* doc = aContext->Document();
+  if (doc) {
+    doc->RemoveImage(mImage);
+  }
+
+  // Mark state
+#ifdef DEBUG
+  mImageTracked = false;
+#endif
+}
+
+void
+nsStyleImage::SetGradientData(nsStyleGradient* aGradient)
+{
+  if (aGradient) {
+    aGradient->AddRef();
+  }
+
+  if (mType != eStyleImageType_Null) {
+    SetNull();
+  }
+
+  if (aGradient) {
+    mGradient = aGradient;
+    mType = eStyleImageType_Gradient;
+  }
+}
+
+void
+nsStyleImage::SetElementId(const char16_t* aElementId)
+{
+  if (mType != eStyleImageType_Null) {
+    SetNull();
+  }
+
+  if (aElementId) {
+    mElementId = NS_strdup(aElementId);
+    mType = eStyleImageType_Element;
+  }
+}
+
+void
+nsStyleImage::SetCropRect(UniquePtr<nsStyleSides> aCropRect)
+{
+    mCropRect = Move(aCropRect);
+}
+
+static int32_t
+ConvertToPixelCoord(const nsStyleCoord& aCoord, int32_t aPercentScale)
+{
+  double pixelValue;
+  switch (aCoord.GetUnit()) {
+    case eStyleUnit_Percent:
+      pixelValue = aCoord.GetPercentValue() * aPercentScale;
+      break;
+    case eStyleUnit_Factor:
+      pixelValue = aCoord.GetFactorValue();
+      break;
+    default:
+      NS_NOTREACHED("unexpected unit for image crop rect");
+      return 0;
+  }
+  MOZ_ASSERT(pixelValue >= 0, "we ensured non-negative while parsing");
+  pixelValue = std::min(pixelValue, double(INT32_MAX)); // avoid overflow
+  return NS_lround(pixelValue);
+}
+
+bool
+nsStyleImage::ComputeActualCropRect(nsIntRect& aActualCropRect,
+                                    bool* aIsEntireImage) const
+{
+  if (mType != eStyleImageType_Image) {
+    return false;
+  }
+
+  nsCOMPtr<imgIContainer> imageContainer;
+  mImage->GetImage(getter_AddRefs(imageContainer));
+  if (!imageContainer) {
+    return false;
+  }
+
+  nsIntSize imageSize;
+  imageContainer->GetWidth(&imageSize.width);
+  imageContainer->GetHeight(&imageSize.height);
+  if (imageSize.width <= 0 || imageSize.height <= 0) {
+    return false;
+  }
+
+  int32_t left   = ConvertToPixelCoord(mCropRect->GetLeft(),   imageSize.width);
+  int32_t top    = ConvertToPixelCoord(mCropRect->GetTop(),    imageSize.height);
+  int32_t right  = ConvertToPixelCoord(mCropRect->GetRight(),  imageSize.width);
+  int32_t bottom = ConvertToPixelCoord(mCropRect->GetBottom(), imageSize.height);
+
+  // IntersectRect() returns an empty rect if we get negative width or height
+  nsIntRect cropRect(left, top, right - left, bottom - top);
+  nsIntRect imageRect(nsIntPoint(0, 0), imageSize);
+  aActualCropRect.IntersectRect(imageRect, cropRect);
+
+  if (aIsEntireImage) {
+    *aIsEntireImage = aActualCropRect.IsEqualInterior(imageRect);
+  }
+  return true;
+}
+
+nsresult
+nsStyleImage::StartDecoding() const
+{
+  if ((mType == eStyleImageType_Image) && mImage) {
+    return mImage->StartDecoding();
+  }
+  return NS_OK;
+}
+
+bool
+nsStyleImage::IsOpaque() const
+{
+  if (!IsComplete()) {
+    return false;
+  }
+
+  if (mType == eStyleImageType_Gradient) {
+    return mGradient->IsOpaque();
+  }
+
+  if (mType == eStyleImageType_Element) {
+    return false;
+  }
+
+  MOZ_ASSERT(mType == eStyleImageType_Image, "unexpected image type");
+
+  nsCOMPtr<imgIContainer> imageContainer;
+  mImage->GetImage(getter_AddRefs(imageContainer));
+  MOZ_ASSERT(imageContainer, "IsComplete() said image container is ready");
+
+  // Check if the crop region of the image is opaque.
+  if (imageContainer->IsOpaque()) {
+    if (!mCropRect) {
+      return true;
+    }
+
+    // Must make sure if mCropRect contains at least a pixel.
+    // XXX Is this optimization worth it? Maybe I should just return false.
+    nsIntRect actualCropRect;
+    bool rv = ComputeActualCropRect(actualCropRect);
+    NS_ASSERTION(rv, "ComputeActualCropRect() can not fail here");
+    return rv && !actualCropRect.IsEmpty();
+  }
+
+  return false;
+}
+
+bool
+nsStyleImage::IsComplete() const
+{
+  switch (mType) {
+    case eStyleImageType_Null:
+      return false;
+    case eStyleImageType_Gradient:
+    case eStyleImageType_Element:
+      return true;
+    case eStyleImageType_Image:
+    {
+      uint32_t status = imgIRequest::STATUS_ERROR;
+      return NS_SUCCEEDED(mImage->GetImageStatus(&status)) &&
+             (status & imgIRequest::STATUS_SIZE_AVAILABLE) &&
+             (status & imgIRequest::STATUS_FRAME_COMPLETE);
+    }
+    default:
+      NS_NOTREACHED("unexpected image type");
+      return false;
+  }
+}
+
+bool
+nsStyleImage::IsLoaded() const
+{
+  switch (mType) {
+    case eStyleImageType_Null:
+      return false;
+    case eStyleImageType_Gradient:
+    case eStyleImageType_Element:
+      return true;
+    case eStyleImageType_Image:
+    {
+      uint32_t status = imgIRequest::STATUS_ERROR;
+      return NS_SUCCEEDED(mImage->GetImageStatus(&status)) &&
+             !(status & imgIRequest::STATUS_ERROR) &&
+             (status & imgIRequest::STATUS_LOAD_COMPLETE);
+    }
+    default:
+      NS_NOTREACHED("unexpected image type");
+      return false;
+  }
+}
+
+static inline bool
+EqualRects(const UniquePtr<nsStyleSides>& aRect1, const UniquePtr<nsStyleSides>& aRect2)
+{
+  return aRect1 == aRect2 || /* handles null== null, and optimize */
+         (aRect1 && aRect2 && *aRect1 == *aRect2);
+}
+
+bool
+nsStyleImage::operator==(const nsStyleImage& aOther) const
+{
+  if (mType != aOther.mType) {
+    return false;
+  }
+
+  if (!EqualRects(mCropRect, aOther.mCropRect)) {
+    return false;
+  }
+
+  if (mType == eStyleImageType_Image) {
+    return mozilla::EqualImages(mImage, aOther.mImage);
+  }
+
+  if (mType == eStyleImageType_Gradient) {
+    return *mGradient == *aOther.mGradient;
+  }
+
+  if (mType == eStyleImageType_Element) {
+    return NS_strcmp(mElementId, aOther.mElementId) == 0;
+  }
+
+  return true;
+}
+
+void
+nsStyleImage::PurgeCacheForViewportChange(
+  const mozilla::Maybe<nsSize>& aSVGViewportSize,
+  const bool aHasIntrinsicRatio) const
+{
+  EnsureCachedBIData();
+
+  // If we're redrawing with a different viewport-size than we used for our
+  // cached subimages, then we can't trust that our subimages are valid;
+  // any percent sizes/positions in our SVG doc may be different now. Purge!
+  // (We don't have to purge if the SVG document has an intrinsic ratio,
+  // though, because the actual size of elements in SVG documant's coordinate
+  // axis are fixed in this case.)
+  if (aSVGViewportSize != mCachedBIData->GetCachedSVGViewportSize() &&
+      !aHasIntrinsicRatio) {
+    mCachedBIData->PurgeCachedImages();
+    mCachedBIData->SetCachedSVGViewportSize(aSVGViewportSize);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/nsStyleImage.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsStyleImage and support classes.
+ */
+
+#ifndef nsStyleImage_h
+#define nsStyleImage_h
+
+#include <stdint.h>
+
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMArray.h"
+#include "nsSize.h"
+#include "imgIContainer.h"
+#include "nsRect.h"
+
+class nsPresContext;
+class nsStyleSides;
+class nsStyleGradient;
+class imgRequestProxy;
+
+enum nsStyleImageType {
+  eStyleImageType_Null,
+  eStyleImageType_Image,
+  eStyleImageType_Gradient,
+  eStyleImageType_Element
+};
+
+struct CachedBorderImageData
+{
+  // Caller are expected to ensure that the value of aSVGViewportSize is
+  // different from the cached one since the method won't do the check.
+  void SetCachedSVGViewportSize(const mozilla::Maybe<nsSize>& aSVGViewportSize);
+  const mozilla::Maybe<nsSize>& GetCachedSVGViewportSize();
+  void PurgeCachedImages();
+  void SetSubImage(uint8_t aIndex, imgIContainer* aSubImage);
+  imgIContainer* GetSubImage(uint8_t aIndex);
+
+private:
+  // If this is a SVG border-image, we save the size of the SVG viewport that
+  // we used when rasterizing any cached border-image subimages. (The viewport
+  // size matters for percent-valued sizes & positions in inner SVG doc).
+  mozilla::Maybe<nsSize> mCachedSVGViewportSize;
+  nsCOMArray<imgIContainer> mSubImages;
+};
+
+/**
+ * Represents a paintable image of one of the following types.
+ * (1) A real image loaded from an external source.
+ * (2) A CSS linear or radial gradient.
+ * (3) An element within a document, or an <img>, <video>, or <canvas> element
+ *     not in a document.
+ * (*) Optionally a crop rect can be set to paint a partial (rectangular)
+ * region of an image. (Currently, this feature is only supported with an
+ * image of type (1)).
+ */
+struct nsStyleImage
+{
+  nsStyleImage();
+  ~nsStyleImage();
+  nsStyleImage(const nsStyleImage& aOther);
+  nsStyleImage& operator=(const nsStyleImage& aOther);
+
+  void SetNull();
+  void SetImageData(imgRequestProxy* aImage);
+  void TrackImage(nsPresContext* aContext);
+  void UntrackImage(nsPresContext* aContext);
+  void SetGradientData(nsStyleGradient* aGradient);
+  void SetElementId(const char16_t* aElementId);
+  void SetCropRect(mozilla::UniquePtr<nsStyleSides> aCropRect);
+
+  nsStyleImageType GetType() const {
+    return mType;
+  }
+  imgRequestProxy* GetImageData() const {
+    MOZ_ASSERT(mType == eStyleImageType_Image, "Data is not an image!");
+    MOZ_ASSERT(mImageTracked,
+               "Should be tracking any image we're going to use!");
+    return mImage;
+  }
+  nsStyleGradient* GetGradientData() const {
+    NS_ASSERTION(mType == eStyleImageType_Gradient, "Data is not a gradient!");
+    return mGradient;
+  }
+  const char16_t* GetElementId() const {
+    NS_ASSERTION(mType == eStyleImageType_Element, "Data is not an element!");
+    return mElementId;
+  }
+  const mozilla::UniquePtr<nsStyleSides>& GetCropRect() const {
+    NS_ASSERTION(mType == eStyleImageType_Image,
+                 "Only image data can have a crop rect");
+    return mCropRect;
+  }
+
+  /**
+   * Compute the actual crop rect in pixels, using the source image bounds.
+   * The computation involves converting percentage unit to pixel unit and
+   * clamping each side value to fit in the source image bounds.
+   * @param aActualCropRect the computed actual crop rect.
+   * @param aIsEntireImage true iff |aActualCropRect| is identical to the
+   * source image bounds.
+   * @return true iff |aActualCropRect| holds a meaningful value.
+   */
+  bool ComputeActualCropRect(nsIntRect& aActualCropRect,
+                               bool* aIsEntireImage = nullptr) const;
+
+  /**
+   * Starts the decoding of a image.
+   */
+  nsresult StartDecoding() const;
+  /**
+   * @return true if the item is definitely opaque --- i.e., paints every
+   * pixel within its bounds opaquely, and the bounds contains at least a pixel.
+   */
+  bool IsOpaque() const;
+  /**
+   * @return true if this image is fully loaded, and its size is calculated;
+   * always returns true if |mType| is |eStyleImageType_Gradient| or
+   * |eStyleImageType_Element|.
+   */
+  bool IsComplete() const;
+  /**
+   * @return true if this image is loaded without error;
+   * always returns true if |mType| is |eStyleImageType_Gradient| or
+   * |eStyleImageType_Element|.
+   */
+  bool IsLoaded() const;
+  /**
+   * @return true if it is 100% confident that this image contains no pixel
+   * to draw.
+   */
+  bool IsEmpty() const {
+    // There are some other cases when the image will be empty, for example
+    // when the crop rect is empty. However, checking the emptiness of crop
+    // rect is non-trivial since each side value can be specified with
+    // percentage unit, which can not be evaluated until the source image size
+    // is available. Therefore, we currently postpone the evaluation of crop
+    // rect until the actual rendering time --- alternatively until GetOpaqueRegion()
+    // is called.
+    return mType == eStyleImageType_Null;
+  }
+
+  bool operator==(const nsStyleImage& aOther) const;
+  bool operator!=(const nsStyleImage& aOther) const {
+    return !(*this == aOther);
+  }
+
+  bool ImageDataEquals(const nsStyleImage& aOther) const
+  {
+    return GetType() == eStyleImageType_Image &&
+           aOther.GetType() == eStyleImageType_Image &&
+           GetImageData() == aOther.GetImageData();
+  }
+
+  // These methods are used for the caller to caches the sub images created
+  // during a border-image paint operation
+  inline void SetSubImage(uint8_t aIndex, imgIContainer* aSubImage) const;
+  inline imgIContainer* GetSubImage(uint8_t aIndex) const;
+  void PurgeCacheForViewportChange(
+    const mozilla::Maybe<nsSize>& aSVGViewportSize,
+    const bool aHasIntrinsicRatio) const;
+
+private:
+  void DoCopy(const nsStyleImage& aOther);
+  void EnsureCachedBIData() const;
+
+  // This variable keeps some cache data for border image and is lazily
+  // allocated since it is only used in border image case.
+  mozilla::UniquePtr<CachedBorderImageData> mCachedBIData;
+
+  nsStyleImageType mType;
+  union {
+    imgRequestProxy* mImage;
+    nsStyleGradient* mGradient;
+    char16_t* mElementId;
+  };
+
+  // This is _currently_ used only in conjunction with eStyleImageType_Image.
+  mozilla::UniquePtr<nsStyleSides> mCropRect;
+#ifdef DEBUG
+  bool mImageTracked;
+#endif
+};
+
+#endif // nsStyleImage_h
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -39,62 +39,65 @@
 #include <algorithm>
 
 using namespace mozilla;
 
 static_assert((((1 << nsStyleStructID_Length) - 1) &
                ~(NS_STYLE_INHERIT_MASK)) == 0,
               "Not enough bits in NS_STYLE_INHERIT_MASK");
 
-// These are the limits that we choose to clamp grid line numbers to.
-// http://dev.w3.org/csswg/css-grid/#overlarge-grids
-const int32_t nsStyleGridLine::kMinLine = -10000;
-const int32_t nsStyleGridLine::kMaxLine = 10000;
-
-static bool
+namespace mozilla {
+
+bool
 EqualURIs(nsIURI *aURI1, nsIURI *aURI2)
 {
   bool eq;
   return aURI1 == aURI2 ||    // handle null==null, and optimize
          (aURI1 && aURI2 &&
           NS_SUCCEEDED(aURI1->Equals(aURI2, &eq)) && // not equal on fail
           eq);
 }
 
-static bool
+bool
 EqualURIs(mozilla::css::URLValue *aURI1, mozilla::css::URLValue *aURI2)
 {
   return aURI1 == aURI2 ||    // handle null==null, and optimize
          (aURI1 && aURI2 && aURI1->URIEquals(*aURI2));
 }
 
-static
 bool EqualURIs(const FragmentOrURL* aURI1, const FragmentOrURL* aURI2)
 {
   return aURI1 == aURI2 ||    // handle null==null, and optimize
          (aURI1 && aURI2 && *aURI1 == *aURI2);
 }
 
-static bool
+bool
 EqualImages(imgIRequest *aImage1, imgIRequest* aImage2)
 {
   if (aImage1 == aImage2) {
     return true;
   }
 
   if (!aImage1 || !aImage2) {
     return false;
   }
 
   nsCOMPtr<nsIURI> uri1, uri2;
   aImage1->GetURI(getter_AddRefs(uri1));
   aImage2->GetURI(getter_AddRefs(uri2));
   return EqualURIs(uri1, uri2);
 }
 
+} // namespace mozilla
+
+// These are the limits that we choose to clamp grid line numbers to.
+// http://dev.w3.org/csswg/css-grid/#overlarge-grids
+const int32_t nsStyleGridLine::kMinLine = -10000;
+const int32_t nsStyleGridLine::kMaxLine = 10000;
+
 // A nullsafe wrapper for strcmp. We depend on null-safety.
 static int
 safe_strcmp(const char16_t* a, const char16_t* b)
 {
   if (!a || !b) {
     return (int)(a - b);
   }
   return NS_strcmp(a, b);
@@ -157,17 +160,17 @@ FragmentOrURL::operator=(const FragmentO
 
 bool
 FragmentOrURL::operator==(const FragmentOrURL& aOther) const
 {
   if (aOther.mIsLocalRef != mIsLocalRef) {
     return false;
   }
 
-  return EqualURIs(aOther.mURL, mURL);
+  return mozilla::EqualURIs(aOther.mURL, mURL);
 }
 
 bool
 FragmentOrURL::EqualsExceptRef(nsIURI* aURI) const
 {
   bool ret = false;
   mURL->EqualsExceptRef(aURI, &ret);
   return ret;
@@ -795,17 +798,17 @@ nsStyleList::CalcDifference(const nsStyl
   if (mQuotes != aNewData.mQuotes &&
       (mQuotes || aNewData.mQuotes) &&
       GetQuotePairs() != aNewData.GetQuotePairs()) {
     return nsChangeHint_ReconstructFrame;
   }
   if (mListStylePosition != aNewData.mListStylePosition) {
     return nsChangeHint_ReconstructFrame;
   }
-  if (EqualImages(mListStyleImage, aNewData.mListStyleImage) &&
+  if (mozilla::EqualImages(mListStyleImage, aNewData.mListStyleImage) &&
       mCounterStyle == aNewData.mCounterStyle) {
     if (mImageRegion.IsEqualInterior(aNewData.mImageRegion)) {
       return nsChangeHint(0);
     }
     if (mImageRegion.width == aNewData.mImageRegion.width &&
         mImageRegion.height == aNewData.mImageRegion.height) {
       return NS_STYLE_HINT_VISUAL;
     }
@@ -1004,17 +1007,17 @@ nsStyleSVG::nsStyleSVG(const nsStyleSVG&
 static bool
 PaintURIChanged(const nsStyleSVGPaint& aPaint1, const nsStyleSVGPaint& aPaint2)
 {
   if (aPaint1.mType != aPaint2.mType) {
     return aPaint1.mType == eStyleSVGPaintType_Server ||
            aPaint2.mType == eStyleSVGPaintType_Server;
   }
   return aPaint1.mType == eStyleSVGPaintType_Server &&
-    !EqualURIs(aPaint1.mPaint.mPaintServer, aPaint2.mPaint.mPaintServer);
+    !mozilla::EqualURIs(aPaint1.mPaint.mPaintServer, aPaint2.mPaint.mPaintServer);
 }
 
 nsChangeHint
 nsStyleSVG::CalcDifference(const nsStyleSVG& aNewData) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   if (mMarkerEnd != aNewData.mMarkerEnd || mMarkerMid != aNewData.mMarkerMid ||
@@ -1159,17 +1162,17 @@ nsStyleFilter::operator=(const nsStyleFi
 bool
 nsStyleFilter::operator==(const nsStyleFilter& aOther) const
 {
   if (mType != aOther.mType) {
       return false;
   }
 
   if (mType == NS_STYLE_FILTER_URL) {
-    return EqualURIs(mURL, aOther.mURL);
+    return mozilla::EqualURIs(mURL, aOther.mURL);
   } else if (mType == NS_STYLE_FILTER_DROP_SHADOW) {
     return *mDropShadow == *aOther.mDropShadow;
   } else if (mType != NS_STYLE_FILTER_NONE) {
     return mFilterParameter == aOther.mFilterParameter;
   }
 
   return true;
 }
@@ -1380,17 +1383,17 @@ nsStyleSVGPaint::operator=(const nsStyle
 }
 
 bool nsStyleSVGPaint::operator==(const nsStyleSVGPaint& aOther) const
 {
   if (mType != aOther.mType) {
     return false;
   }
   if (mType == eStyleSVGPaintType_Server) {
-    return EqualURIs(mPaint.mPaintServer, aOther.mPaint.mPaintServer) &&
+    return mozilla::EqualURIs(mPaint.mPaintServer, aOther.mPaint.mPaintServer) &&
            mFallbackColor == aOther.mFallbackColor;
   }
   if (mType == eStyleSVGPaintType_Color) {
     return mPaint.mColor == aOther.mPaint.mColor;
   }
   return true;
 }
 
@@ -1944,397 +1947,16 @@ CachedBorderImageData::GetSubImage(uint8
 {
   imgIContainer* subImage = nullptr;
   if (aIndex < mSubImages.Count())
     subImage = mSubImages[aIndex];
   return subImage;
 }
 
 // --------------------
-// nsStyleImage
-//
-
-nsStyleImage::nsStyleImage()
-  : mType(eStyleImageType_Null)
-  , mCropRect(nullptr)
-#ifdef DEBUG
-  , mImageTracked(false)
-#endif
-{
-  MOZ_COUNT_CTOR(nsStyleImage);
-}
-
-nsStyleImage::~nsStyleImage()
-{
-  MOZ_COUNT_DTOR(nsStyleImage);
-  if (mType != eStyleImageType_Null) {
-    SetNull();
-  }
-}
-
-nsStyleImage::nsStyleImage(const nsStyleImage& aOther)
-  : mType(eStyleImageType_Null)
-  , mCropRect(nullptr)
-#ifdef DEBUG
-  , mImageTracked(false)
-#endif
-{
-  // We need our own copy constructor because we don't want
-  // to copy the reference count
-  MOZ_COUNT_CTOR(nsStyleImage);
-  DoCopy(aOther);
-}
-
-nsStyleImage&
-nsStyleImage::operator=(const nsStyleImage& aOther)
-{
-  if (this != &aOther) {
-    DoCopy(aOther);
-  }
-
-  return *this;
-}
-
-void
-nsStyleImage::DoCopy(const nsStyleImage& aOther)
-{
-  SetNull();
-
-  if (aOther.mType == eStyleImageType_Image) {
-    SetImageData(aOther.mImage);
-  } else if (aOther.mType == eStyleImageType_Gradient) {
-    SetGradientData(aOther.mGradient);
-  } else if (aOther.mType == eStyleImageType_Element) {
-    SetElementId(aOther.mElementId);
-  }
-
-  UniquePtr<nsStyleSides> cropRectCopy;
-  if (aOther.mCropRect) {
-    cropRectCopy = MakeUnique<nsStyleSides>(*aOther.mCropRect.get());
-  }
-  SetCropRect(Move(cropRectCopy));
-}
-
-void
-nsStyleImage::SetNull()
-{
-  MOZ_ASSERT(!mImageTracked,
-             "Calling SetNull() with image tracked!");
-
-  if (mType == eStyleImageType_Gradient) {
-    mGradient->Release();
-  } else if (mType == eStyleImageType_Image) {
-    NS_RELEASE(mImage);
-  } else if (mType == eStyleImageType_Element) {
-    free(mElementId);
-  }
-
-  mType = eStyleImageType_Null;
-  mCropRect = nullptr;
-}
-
-void
-nsStyleImage::SetImageData(imgRequestProxy* aImage)
-{
-  MOZ_ASSERT(!mImageTracked,
-             "Setting a new image without untracking the old one!");
-
-  NS_IF_ADDREF(aImage);
-
-  if (mType != eStyleImageType_Null) {
-    SetNull();
-  }
-
-  if (aImage) {
-    mImage = aImage;
-    mType = eStyleImageType_Image;
-  }
-  if (mCachedBIData) {
-    mCachedBIData->PurgeCachedImages();
-  }
-}
-
-void
-nsStyleImage::TrackImage(nsPresContext* aContext)
-{
-  // Sanity
-  MOZ_ASSERT(!mImageTracked, "Already tracking image!");
-  MOZ_ASSERT(mType == eStyleImageType_Image,
-             "Can't track image when there isn't one!");
-
-  // Register the image with the document
-  nsIDocument* doc = aContext->Document();
-  if (doc) {
-    doc->AddImage(mImage);
-  }
-
-  // Mark state
-#ifdef DEBUG
-  mImageTracked = true;
-#endif
-}
-
-void
-nsStyleImage::UntrackImage(nsPresContext* aContext)
-{
-  // Sanity
-  MOZ_ASSERT(mImageTracked, "Image not tracked!");
-  MOZ_ASSERT(mType == eStyleImageType_Image,
-             "Can't untrack image when there isn't one!");
-
-  // Unregister the image with the document
-  nsIDocument* doc = aContext->Document();
-  if (doc) {
-    doc->RemoveImage(mImage);
-  }
-
-  // Mark state
-#ifdef DEBUG
-  mImageTracked = false;
-#endif
-}
-
-void
-nsStyleImage::SetGradientData(nsStyleGradient* aGradient)
-{
-  if (aGradient) {
-    aGradient->AddRef();
-  }
-
-  if (mType != eStyleImageType_Null) {
-    SetNull();
-  }
-
-  if (aGradient) {
-    mGradient = aGradient;
-    mType = eStyleImageType_Gradient;
-  }
-}
-
-void
-nsStyleImage::SetElementId(const char16_t* aElementId)
-{
-  if (mType != eStyleImageType_Null) {
-    SetNull();
-  }
-
-  if (aElementId) {
-    mElementId = NS_strdup(aElementId);
-    mType = eStyleImageType_Element;
-  }
-}
-
-void
-nsStyleImage::SetCropRect(UniquePtr<nsStyleSides> aCropRect)
-{
-    mCropRect = Move(aCropRect);
-}
-
-static int32_t
-ConvertToPixelCoord(const nsStyleCoord& aCoord, int32_t aPercentScale)
-{
-  double pixelValue;
-  switch (aCoord.GetUnit()) {
-    case eStyleUnit_Percent:
-      pixelValue = aCoord.GetPercentValue() * aPercentScale;
-      break;
-    case eStyleUnit_Factor:
-      pixelValue = aCoord.GetFactorValue();
-      break;
-    default:
-      NS_NOTREACHED("unexpected unit for image crop rect");
-      return 0;
-  }
-  MOZ_ASSERT(pixelValue >= 0, "we ensured non-negative while parsing");
-  pixelValue = std::min(pixelValue, double(INT32_MAX)); // avoid overflow
-  return NS_lround(pixelValue);
-}
-
-bool
-nsStyleImage::ComputeActualCropRect(nsIntRect& aActualCropRect,
-                                    bool* aIsEntireImage) const
-{
-  if (mType != eStyleImageType_Image) {
-    return false;
-  }
-
-  nsCOMPtr<imgIContainer> imageContainer;
-  mImage->GetImage(getter_AddRefs(imageContainer));
-  if (!imageContainer) {
-    return false;
-  }
-
-  nsIntSize imageSize;
-  imageContainer->GetWidth(&imageSize.width);
-  imageContainer->GetHeight(&imageSize.height);
-  if (imageSize.width <= 0 || imageSize.height <= 0) {
-    return false;
-  }
-
-  int32_t left   = ConvertToPixelCoord(mCropRect->GetLeft(),   imageSize.width);
-  int32_t top    = ConvertToPixelCoord(mCropRect->GetTop(),    imageSize.height);
-  int32_t right  = ConvertToPixelCoord(mCropRect->GetRight(),  imageSize.width);
-  int32_t bottom = ConvertToPixelCoord(mCropRect->GetBottom(), imageSize.height);
-
-  // IntersectRect() returns an empty rect if we get negative width or height
-  nsIntRect cropRect(left, top, right - left, bottom - top);
-  nsIntRect imageRect(nsIntPoint(0, 0), imageSize);
-  aActualCropRect.IntersectRect(imageRect, cropRect);
-
-  if (aIsEntireImage) {
-    *aIsEntireImage = aActualCropRect.IsEqualInterior(imageRect);
-  }
-  return true;
-}
-
-nsresult
-nsStyleImage::StartDecoding() const
-{
-  if ((mType == eStyleImageType_Image) && mImage) {
-    return mImage->StartDecoding();
-  }
-  return NS_OK;
-}
-
-bool
-nsStyleImage::IsOpaque() const
-{
-  if (!IsComplete()) {
-    return false;
-  }
-
-  if (mType == eStyleImageType_Gradient) {
-    return mGradient->IsOpaque();
-  }
-
-  if (mType == eStyleImageType_Element) {
-    return false;
-  }
-
-  MOZ_ASSERT(mType == eStyleImageType_Image, "unexpected image type");
-
-  nsCOMPtr<imgIContainer> imageContainer;
-  mImage->GetImage(getter_AddRefs(imageContainer));
-  MOZ_ASSERT(imageContainer, "IsComplete() said image container is ready");
-
-  // Check if the crop region of the image is opaque.
-  if (imageContainer->IsOpaque()) {
-    if (!mCropRect) {
-      return true;
-    }
-
-    // Must make sure if mCropRect contains at least a pixel.
-    // XXX Is this optimization worth it? Maybe I should just return false.
-    nsIntRect actualCropRect;
-    bool rv = ComputeActualCropRect(actualCropRect);
-    NS_ASSERTION(rv, "ComputeActualCropRect() can not fail here");
-    return rv && !actualCropRect.IsEmpty();
-  }
-
-  return false;
-}
-
-bool
-nsStyleImage::IsComplete() const
-{
-  switch (mType) {
-    case eStyleImageType_Null:
-      return false;
-    case eStyleImageType_Gradient:
-    case eStyleImageType_Element:
-      return true;
-    case eStyleImageType_Image:
-    {
-      uint32_t status = imgIRequest::STATUS_ERROR;
-      return NS_SUCCEEDED(mImage->GetImageStatus(&status)) &&
-             (status & imgIRequest::STATUS_SIZE_AVAILABLE) &&
-             (status & imgIRequest::STATUS_FRAME_COMPLETE);
-    }
-    default:
-      NS_NOTREACHED("unexpected image type");
-      return false;
-  }
-}
-
-bool
-nsStyleImage::IsLoaded() const
-{
-  switch (mType) {
-    case eStyleImageType_Null:
-      return false;
-    case eStyleImageType_Gradient:
-    case eStyleImageType_Element:
-      return true;
-    case eStyleImageType_Image:
-    {
-      uint32_t status = imgIRequest::STATUS_ERROR;
-      return NS_SUCCEEDED(mImage->GetImageStatus(&status)) &&
-             !(status & imgIRequest::STATUS_ERROR) &&
-             (status & imgIRequest::STATUS_LOAD_COMPLETE);
-    }
-    default:
-      NS_NOTREACHED("unexpected image type");
-      return false;
-  }
-}
-
-static inline bool
-EqualRects(const UniquePtr<nsStyleSides>& aRect1, const UniquePtr<nsStyleSides>& aRect2)
-{
-  return aRect1 == aRect2 || /* handles null== null, and optimize */
-         (aRect1 && aRect2 && *aRect1 == *aRect2);
-}
-
-bool
-nsStyleImage::operator==(const nsStyleImage& aOther) const
-{
-  if (mType != aOther.mType) {
-    return false;
-  }
-
-  if (!EqualRects(mCropRect, aOther.mCropRect)) {
-    return false;
-  }
-
-  if (mType == eStyleImageType_Image) {
-    return EqualImages(mImage, aOther.mImage);
-  }
-
-  if (mType == eStyleImageType_Gradient) {
-    return *mGradient == *aOther.mGradient;
-  }
-
-  if (mType == eStyleImageType_Element) {
-    return NS_strcmp(mElementId, aOther.mElementId) == 0;
-  }
-
-  return true;
-}
-
-void
-nsStyleImage::PurgeCacheForViewportChange(
-  const mozilla::Maybe<nsSize>& aSVGViewportSize,
-  const bool aHasIntrinsicRatio) const
-{
-  EnsureCachedBIData();
-
-  // If we're redrawing with a different viewport-size than we used for our
-  // cached subimages, then we can't trust that our subimages are valid;
-  // any percent sizes/positions in our SVG doc may be different now. Purge!
-  // (We don't have to purge if the SVG document has an intrinsic ratio,
-  // though, because the actual size of elements in SVG documant's coordinate
-  // axis are fixed in this case.)
-  if (aSVGViewportSize != mCachedBIData->GetCachedSVGViewportSize() &&
-      !aHasIntrinsicRatio) {
-    mCachedBIData->PurgeCachedImages();
-    mCachedBIData->SetCachedSVGViewportSize(aSVGViewportSize);
-  }
-}
-
-// --------------------
 // nsStyleImageLayers
 //
 
 const nsCSSPropertyID nsStyleImageLayers::kBackgroundLayerTable[] = {
   eCSSProperty_background,                // shorthand
   eCSSProperty_background_color,          // color
   eCSSProperty_background_image,          // image
   eCSSProperty_background_repeat,         // repeat
@@ -3094,17 +2716,17 @@ nsStyleDisplay::nsStyleDisplay(const nsS
   mPerspectiveOrigin[1] = aSource.mPerspectiveOrigin[1];
 }
 
 nsChangeHint
 nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
-  if (!EqualURIs(mBinding, aNewData.mBinding)
+  if (!mozilla::EqualURIs(mBinding, aNewData.mBinding)
       || mPosition != aNewData.mPosition
       || mDisplay != aNewData.mDisplay
       || mContain != aNewData.mContain
       || (mFloat == NS_STYLE_FLOAT_NONE) != (aNewData.mFloat == NS_STYLE_FLOAT_NONE)
       || mOverflowX != aNewData.mOverflowX
       || mOverflowY != aNewData.mOverflowY
       || mScrollBehavior != aNewData.mScrollBehavior
       || mScrollSnapTypeX != aNewData.mScrollSnapTypeX
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -21,16 +21,17 @@
 #include "mozilla/StyleStructContext.h"
 #include "mozilla/UniquePtr.h"
 #include "nsColor.h"
 #include "nsCoord.h"
 #include "nsMargin.h"
 #include "nsFont.h"
 #include "nsStyleCoord.h"
 #include "nsStyleConsts.h"
+#include "nsStyleImage.h"
 #include "nsChangeHint.h"
 #include "nsPresContext.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsTArray.h"
 #include "nsCSSValue.h"
 #include "imgRequestProxy.h"
 #include "Orientation.h"
@@ -105,16 +106,28 @@ static_assert(int(mozilla::SheetType::Co
               "NS_RULE_NODE_LEVEL_MASK cannot fit SheetType");
 
 static_assert(NS_STYLE_INHERIT_MASK == (1 << nsStyleStructID_Length) - 1,
               "NS_STYLE_INHERIT_MASK is not correct");
 
 static_assert((NS_RULE_NODE_IS_ANIMATION_RULE & NS_STYLE_INHERIT_MASK) == 0,
   "NS_RULE_NODE_IS_ANIMATION_RULE must not overlap the style struct bits.");
 
+struct FragmentOrURL;
+
+namespace mozilla {
+
+bool EqualURIs(nsIURI *aURI1, nsIURI *aURI2);
+bool EqualURIs(mozilla::css::URLValue *aURI1, mozilla::css::URLValue *aURI2);
+bool EqualURIs(const FragmentOrURL* aURI1, const FragmentOrURL* aURI2);
+
+bool EqualImages(imgIRequest *aImage1, imgIRequest* aImage2);
+
+} // namespace mozilla
+
 struct FragmentOrURL
 {
   FragmentOrURL() : mIsLocalRef(false) {}
   FragmentOrURL(const FragmentOrURL& aSource)
     : mIsLocalRef(false)
   { *this = aSource; }
 
   void SetValue(const nsCSSValue* aValue);
@@ -268,179 +281,16 @@ public:
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~nsStyleGradient() {}
 
   nsStyleGradient(const nsStyleGradient& aOther) = delete;
   nsStyleGradient& operator=(const nsStyleGradient& aOther) = delete;
 };
 
-enum nsStyleImageType {
-  eStyleImageType_Null,
-  eStyleImageType_Image,
-  eStyleImageType_Gradient,
-  eStyleImageType_Element
-};
-
-struct CachedBorderImageData
-{
-  // Caller are expected to ensure that the value of aSVGViewportSize is
-  // different from the cached one since the method won't do the check.
-  void SetCachedSVGViewportSize(const mozilla::Maybe<nsSize>& aSVGViewportSize);
-  const mozilla::Maybe<nsSize>& GetCachedSVGViewportSize();
-  void PurgeCachedImages();
-  void SetSubImage(uint8_t aIndex, imgIContainer* aSubImage);
-  imgIContainer* GetSubImage(uint8_t aIndex);
-
-private:
-  // If this is a SVG border-image, we save the size of the SVG viewport that
-  // we used when rasterizing any cached border-image subimages. (The viewport
-  // size matters for percent-valued sizes & positions in inner SVG doc).
-  mozilla::Maybe<nsSize> mCachedSVGViewportSize;
-  nsCOMArray<imgIContainer> mSubImages;
-};
-
-/**
- * Represents a paintable image of one of the following types.
- * (1) A real image loaded from an external source.
- * (2) A CSS linear or radial gradient.
- * (3) An element within a document, or an <img>, <video>, or <canvas> element
- *     not in a document.
- * (*) Optionally a crop rect can be set to paint a partial (rectangular)
- * region of an image. (Currently, this feature is only supported with an
- * image of type (1)).
- */
-struct nsStyleImage
-{
-  nsStyleImage();
-  ~nsStyleImage();
-  nsStyleImage(const nsStyleImage& aOther);
-  nsStyleImage& operator=(const nsStyleImage& aOther);
-
-  void SetNull();
-  void SetImageData(imgRequestProxy* aImage);
-  void TrackImage(nsPresContext* aContext);
-  void UntrackImage(nsPresContext* aContext);
-  void SetGradientData(nsStyleGradient* aGradient);
-  void SetElementId(const char16_t* aElementId);
-  void SetCropRect(mozilla::UniquePtr<nsStyleSides> aCropRect);
-
-  nsStyleImageType GetType() const {
-    return mType;
-  }
-  imgRequestProxy* GetImageData() const {
-    MOZ_ASSERT(mType == eStyleImageType_Image, "Data is not an image!");
-    MOZ_ASSERT(mImageTracked,
-               "Should be tracking any image we're going to use!");
-    return mImage;
-  }
-  nsStyleGradient* GetGradientData() const {
-    NS_ASSERTION(mType == eStyleImageType_Gradient, "Data is not a gradient!");
-    return mGradient;
-  }
-  const char16_t* GetElementId() const {
-    NS_ASSERTION(mType == eStyleImageType_Element, "Data is not an element!");
-    return mElementId;
-  }
-  const mozilla::UniquePtr<nsStyleSides>& GetCropRect() const {
-    NS_ASSERTION(mType == eStyleImageType_Image,
-                 "Only image data can have a crop rect");
-    return mCropRect;
-  }
-
-  /**
-   * Compute the actual crop rect in pixels, using the source image bounds.
-   * The computation involves converting percentage unit to pixel unit and
-   * clamping each side value to fit in the source image bounds.
-   * @param aActualCropRect the computed actual crop rect.
-   * @param aIsEntireImage true iff |aActualCropRect| is identical to the
-   * source image bounds.
-   * @return true iff |aActualCropRect| holds a meaningful value.
-   */
-  bool ComputeActualCropRect(nsIntRect& aActualCropRect,
-                               bool* aIsEntireImage = nullptr) const;
-
-  /**
-   * Starts the decoding of a image.
-   */
-  nsresult StartDecoding() const;
-  /**
-   * @return true if the item is definitely opaque --- i.e., paints every
-   * pixel within its bounds opaquely, and the bounds contains at least a pixel.
-   */
-  bool IsOpaque() const;
-  /**
-   * @return true if this image is fully loaded, and its size is calculated;
-   * always returns true if |mType| is |eStyleImageType_Gradient| or
-   * |eStyleImageType_Element|.
-   */
-  bool IsComplete() const;
-  /**
-   * @return true if this image is loaded without error;
-   * always returns true if |mType| is |eStyleImageType_Gradient| or
-   * |eStyleImageType_Element|.
-   */
-  bool IsLoaded() const;
-  /**
-   * @return true if it is 100% confident that this image contains no pixel
-   * to draw.
-   */
-  bool IsEmpty() const {
-    // There are some other cases when the image will be empty, for example
-    // when the crop rect is empty. However, checking the emptiness of crop
-    // rect is non-trivial since each side value can be specified with
-    // percentage unit, which can not be evaluated until the source image size
-    // is available. Therefore, we currently postpone the evaluation of crop
-    // rect until the actual rendering time --- alternatively until GetOpaqueRegion()
-    // is called.
-    return mType == eStyleImageType_Null;
-  }
-
-  bool operator==(const nsStyleImage& aOther) const;
-  bool operator!=(const nsStyleImage& aOther) const {
-    return !(*this == aOther);
-  }
-
-  bool ImageDataEquals(const nsStyleImage& aOther) const
-  {
-    return GetType() == eStyleImageType_Image &&
-           aOther.GetType() == eStyleImageType_Image &&
-           GetImageData() == aOther.GetImageData();
-  }
-
-  // These methods are used for the caller to caches the sub images created
-  // during a border-image paint operation
-  inline void SetSubImage(uint8_t aIndex, imgIContainer* aSubImage) const;
-  inline imgIContainer* GetSubImage(uint8_t aIndex) const;
-  void PurgeCacheForViewportChange(
-    const mozilla::Maybe<nsSize>& aSVGViewportSize,
-    const bool aHasIntrinsicRatio) const;
-
-private:
-  void DoCopy(const nsStyleImage& aOther);
-  void EnsureCachedBIData() const;
-
-  // This variable keeps some cache data for border image and is lazily
-  // allocated since it is only used in border image case.
-  mozilla::UniquePtr<CachedBorderImageData> mCachedBIData;
-
-  nsStyleImageType mType;
-  union {
-    imgRequestProxy* mImage;
-    nsStyleGradient* mGradient;
-    char16_t* mElementId;
-  };
-
-  // This is _currently_ used only in conjunction with eStyleImageType_Image.
-  mozilla::UniquePtr<nsStyleSides> mCropRect;
-#ifdef DEBUG
-  bool mImageTracked;
-#endif
-};
-
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleColor
 {
   explicit nsStyleColor(StyleStructContext aContext);
   nsStyleColor(const nsStyleColor& aOther);
   ~nsStyleColor() {
     MOZ_COUNT_DTOR(nsStyleColor);
   }