Bug 1285320 - Part 2: Purge border-image cache when hypothetical SVG viewport changes, if using SVG image with no aspect ratio. r=dholbert, a=ritu
authorKevin Chen <kechen@mozilla.com>
Sun, 31 Jul 2016 20:24:00 -0400
changeset 349824 2065ffb539e10304f2e02007791f8ae045144da5
parent 349823 20dbba05457b32b4350868102fcd3886b796ac3b
child 349825 2d7fe7b749885dce85a312448ab8f30d955d78e7
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert, ritu
bugs1285320
milestone50.0a2
Bug 1285320 - Part 2: Purge border-image cache when hypothetical SVG viewport changes, if using SVG image with no aspect ratio. r=dholbert, a=ritu
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
@@ -5675,17 +5678,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;
   }
@@ -5699,17 +5703,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);
@@ -5798,16 +5802,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
@@ -1910,16 +1910,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)
 {
@@ -2291,16 +2304,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 "nsColor.h"
 #include "nsCoord.h"
 #include "nsMargin.h"
 #include "nsFont.h"
@@ -243,21 +244,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
@@ -363,16 +372,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;