Bug 1285320 - Part 2: Purge border-image cache when hypothetical SVG viewport changes, if using SVG image with no aspect ratio. r=dholbert
--- 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;