Bug 1285320 - (Part 2) Purge border-image cache when hypothetical SVG viewport changes, if using SVG image with no aspect ratio. r=dholbert
☠☠ backed out by b3cd11b98793 ☠ ☠
authorKevin Chen <kechen@mozilla.com>
Fri, 29 Jul 2016 01:05:00 +0200
changeset 347294 a0b872e47ecf4ffda33e2e8dba4e70b874abe94e
parent 347293 acf56d9e08c5e44278e62c28ad1ac8c198718d53
child 347295 3f06b617447c62efd2723fba2d99ec6bacc1e126
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1285320
milestone50.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 1285320 - (Part 2) Purge border-image cache when hypothetical SVG viewport changes, if using SVG image with no aspect ratio. r=dholbert
layout/base/nsCSSRendering.cpp
layout/base/nsCSSRendering.h
layout/reftests/border-image/reftest.list
layout/reftests/border-image/svg-as-border-image-4-ref.html
layout/reftests/border-image/svg-as-border-image-4.html
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -3948,16 +3948,25 @@ DrawBorderImage(nsPresContext*       aPr
   const int32_t sliceHeight[3] = {
     slice.top,
     std::max(imageSize.height - slice.top - slice.bottom, 0),
     slice.bottom,
   };
 
   DrawResult result = DrawResult::SUCCESS;
 
+  // intrinsicSize.CanComputeConcreteSize() return false means we can not
+  // read intrinsic size from aStyleBorder.mBorderImageSource.
+  // In this condition, we pass imageSize(a resolved size comes from
+  // default sizing algorithm) to renderer as the viewport size.
+  Maybe<nsSize> svgViewportSize = intrinsicSize.CanComputeConcreteSize() ?
+    Nothing() : Some(imageSize);
+  bool hasIntrinsicRatio = intrinsicSize.HasRatio();
+  renderer.PurgeCacheForViewportChange(svgViewportSize, hasIntrinsicRatio);
+
   for (int i = LEFT; i <= RIGHT; i++) {
     for (int j = TOP; j <= BOTTOM; j++) {
       uint8_t fillStyleH, fillStyleV;
       nsSize unitSize;
 
       if (i == MIDDLE && j == MIDDLE) {
         // Discard the middle portion unless set to fill.
         if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL ==
@@ -4034,32 +4043,26 @@ DrawBorderImage(nsPresContext*       aPr
       }
 
       nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
       nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
       if (subArea.IsEmpty())
         continue;
 
       nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
-      // intrinsicSize.CanComputeConcreteSize() return false means we can not
-      // read intrinsic size from aStyleBorder.mBorderImageSource.
-      // In this condition, we pass imageSize(a resolved size comes from
-      // default sizing algorithm) to renderer as the viewport size.
-      Maybe<nsSize> svgViewportSize = intrinsicSize.CanComputeConcreteSize() ?
-        Nothing() : Some(imageSize);
       result &=
         renderer.DrawBorderImageComponent(aPresContext,
                                           aRenderingContext, aDirtyRect,
                                           destArea, CSSIntRect(intSubArea.x,
                                                                intSubArea.y,
                                                                intSubArea.width,
                                                                intSubArea.height),
                                           fillStyleH, fillStyleV,
                                           unitSize, j * (RIGHT + 1) + i,
-                                          svgViewportSize);
+                                          svgViewportSize, hasIntrinsicRatio);
     }
   }
 
   return result;
 }
 
 // Begin table border-collapsing section
 // These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
@@ -5676,17 +5679,18 @@ nsImageRenderer::DrawBorderImageComponen
                                           nsRenderingContext&  aRenderingContext,
                                           const nsRect&        aDirtyRect,
                                           const nsRect&        aFill,
                                           const CSSIntRect&    aSrc,
                                           uint8_t              aHFill,
                                           uint8_t              aVFill,
                                           const nsSize&        aUnitSize,
                                           uint8_t              aIndex,
-                                          const Maybe<nsSize>& aSVGViewportSize)
+                                          const Maybe<nsSize>& aSVGViewportSize,
+                                          const bool           aHasIntrinsicRatio)
 {
   if (!IsReady()) {
     NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
     return DrawResult::BAD_ARGS;
   }
   if (aFill.IsEmpty() || aSrc.IsEmpty()) {
     return DrawResult::SUCCESS;
   }
@@ -5700,17 +5704,17 @@ nsImageRenderer::DrawBorderImageComponen
     // To get correct rendering result, we add
     // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
     // preserveAspectRatio attribute, and always do non-uniform stretch.
     uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
                            imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
     // For those SVG image sources which don't have fixed aspect ratio (i.e.
     // without viewport size and viewBox), we should scale the source uniformly
     // after the viewport size is decided by "Default Sizing Algorithm".
-    if (!ComputeIntrinsicSize().HasRatio()) {
+    if (!aHasIntrinsicRatio) {
       drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
     }
     // Retrieve or create the subimage we'll draw.
     nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
     if (mType == eStyleImageType_Image) {
       if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
         subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
         mImage->SetSubImage(aIndex, subImage);
@@ -5799,16 +5803,28 @@ nsImageRenderer::GetImage()
   if (mType != eStyleImageType_Image || !mImageContainer) {
     return nullptr;
   }
 
   nsCOMPtr<imgIContainer> image = mImageContainer;
   return image.forget();
 }
 
+void
+nsImageRenderer::PurgeCacheForViewportChange(
+  const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio)
+{
+  // Check if we should flush the cached data - only vector images need to do
+  // the check since they might not have fixed ratio.
+  if (mImageContainer &&
+      mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+    mImage->PurgeCacheForViewportChange(aSVGViewportSize, aHasIntrinsicRatio);
+  }
+}
+
 #define MAX_BLUR_RADIUS 300
 #define MAX_SPREAD_RADIUS 50
 
 static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
                                          int32_t aAppUnitsPerDevPixel,
                                          gfxFloat aScaleX,
                                          gfxFloat aScaleY)
 {
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -231,39 +231,44 @@ public:
    * aUnitSize The scaled size of a single source rect (in destination coords)
    * aIndex identifies the component: 0 1 2
    *                                  3 4 5
    *                                  6 7 8
    * aSVGViewportSize The image size evaluated by default sizing algorithm.
    * Pass Nothing() if we can read a valid viewport size or aspect-ratio from
    * the drawing image directly, otherwise, pass Some() with viewport size
    * evaluated from default sizing algorithm.
+   * aHasIntrinsicRatio is used to record if the source image has fixed
+   * intrinsic ratio.
    */
   DrawResult
   DrawBorderImageComponent(nsPresContext*       aPresContext,
                            nsRenderingContext&  aRenderingContext,
                            const nsRect&        aDirtyRect,
                            const nsRect&        aFill,
                            const mozilla::CSSIntRect& aSrc,
                            uint8_t              aHFill,
                            uint8_t              aVFill,
                            const nsSize&        aUnitSize,
                            uint8_t              aIndex,
-                           const mozilla::Maybe<nsSize>& aSVGViewportSize);
+                           const mozilla::Maybe<nsSize>& aSVGViewportSize,
+                           const bool           aHasIntrinsicRatio);
 
   bool IsRasterImage();
   bool IsAnimatedImage();
 
   /// Retrieves the image associated with this nsImageRenderer, if there is one.
   already_AddRefed<imgIContainer> GetImage();
 
   bool IsReady() const { return mPrepareResult == DrawResult::SUCCESS; }
   DrawResult PrepareResult() const { return mPrepareResult; }
   void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; }
   void SetMaskOp(uint8_t aMaskOp) { mMaskOp = aMaskOp; }
+  void PurgeCacheForViewportChange(const mozilla::Maybe<nsSize>& aSVGViewportSize,
+                                   const bool aHasRatio);
 
 private:
   /**
    * Draws the image to the target rendering context.
    * aSrc is a rect on the source image which will be mapped to aDest; it's
    * currently only used for gradients.
    *
    * @see nsLayoutUtils::DrawImage() for other parameters.
--- a/layout/reftests/border-image/reftest.list
+++ b/layout/reftests/border-image/reftest.list
@@ -84,8 +84,9 @@ fuzzy(1,1054) fails-if(OSX) fuzzy-if(ski
 fuzzy(125,5808) fuzzy-if(B2G,151,5809) == border-image-element.html border-image-element-ref.html
 
 # svg-as-border-image
 == svg-as-border-image-1a.html svg-as-border-image-1-ref.html
 == svg-as-border-image-1b.html svg-as-border-image-1-ref.html
 == svg-as-border-image-1c.html svg-as-border-image-1-ref.html
 == svg-as-border-image-2.html svg-as-border-image-2-ref.html
 == svg-as-border-image-3.html svg-as-border-image-3-ref.html
+== svg-as-border-image-4.html svg-as-border-image-4-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/border-image/svg-as-border-image-4-ref.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<title>reference of svg-as-border-image</title>
+<style type="text/css">
+div {
+  width: 100px;
+  height: 100px;
+  margin: 30px;
+  border-width: 30px;
+  border-style: solid;
+  border-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><circle cx="50%" cy="50%" r="50%" fill="red"/></svg>') 5% stretch;
+}
+</style>
+</head>
+<body>
+<div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/border-image/svg-as-border-image-4.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait">
+<head>
+<title>test of svg-as-border-image</title>
+<style type="text/css">
+div {
+  width: 400px;
+  height: 100px;
+  margin: 30px;
+  border-width: 30px;
+  border-style: solid;
+  border-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><circle cx="50%" cy="50%" r="50%" fill="red"/></svg>') 5% stretch;
+}
+</style>
+<script>
+function resizeDiv() {
+  document.getElementById('borderdiv').style.width = '100px';
+  document.documentElement.removeAttribute('class');
+}
+
+document.addEventListener('MozReftestInvalidate', resizeDiv, false);
+</script>
+</head>
+<body>
+<div id="borderdiv"></div>
+</body>
+</html>
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1908,16 +1908,29 @@ nsStyleGradient::HasCalc()
   return mBgPosX.IsCalcUnit() || mBgPosY.IsCalcUnit() || mAngle.IsCalcUnit() ||
          mRadiusX.IsCalcUnit() || mRadiusY.IsCalcUnit();
 }
 
 // --------------------
 // CachedBorderImageData
 //
 void
+CachedBorderImageData::SetCachedSVGViewportSize(
+  const mozilla::Maybe<nsSize>& aSVGViewportSize)
+{
+  mCachedSVGViewportSize = aSVGViewportSize;
+}
+
+const mozilla::Maybe<nsSize>&
+CachedBorderImageData::GetCachedSVGViewportSize()
+{
+  return mCachedSVGViewportSize;
+}
+
+void
 CachedBorderImageData::PurgeCachedImages()
 {
   mSubImages.Clear();
 }
 
 void
 CachedBorderImageData::SetSubImage(uint8_t aIndex, imgIContainer* aSubImage)
 {
@@ -2290,16 +2303,36 @@ nsStyleImage::operator==(const nsStyleIm
 
   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 nsCSSProperty nsStyleImageLayers::kBackgroundLayerTable[] = {
   eCSSProperty_background,                // shorthand
   eCSSProperty_background_color,          // color
   eCSSProperty_background_image,          // image
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -10,16 +10,17 @@
  */
 
 #ifndef nsStyleStruct_h___
 #define nsStyleStruct_h___
 
 #include "mozilla/ArenaObjectID.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/CSSVariableValues.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/SheetType.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/StyleStructContext.h"
 #include "mozilla/UniquePtr.h"
 #include "nsAutoPtr.h"
 #include "nsColor.h"
 #include "nsCoord.h"
 #include "nsMargin.h"
@@ -244,21 +245,29 @@ 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
@@ -364,16 +373,19 @@ struct nsStyleImage
            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;