--- a/content/svg/content/src/DOMSVGPathSeg.cpp
+++ b/content/svg/content/src/DOMSVGPathSeg.cpp
@@ -175,17 +175,16 @@ DOMSVGPathSeg::IndexIsValid()
return; \
} \
if (HasOwner()) { \
if (InternalItem()[1+index] == float(a##propName)) { \
return; \
} \
AutoChangePathSegNotifier notifier(this); \
InternalItem()[1+index] = float(a##propName); \
- InvalidateCachedList(); \
} else { \
mArgs[index] = float(a##propName); \
} \
}
// For float, the normal type of arguments
#define IMPL_FLOAT_PROP(segName, propName, index) \
IMPL_PROP_WITH_TYPE(segName, propName, index, float)
--- a/content/svg/content/src/DOMSVGPathSeg.h
+++ b/content/svg/content/src/DOMSVGPathSeg.h
@@ -204,20 +204,16 @@ protected:
*
* To simplify the code we just have this one method for obtaining both
* baseVal and animVal internal items. This means that animVal items don't
* get const protection, but then our setter methods guard against changing
* animVal items.
*/
float* InternalItem();
- void InvalidateCachedList() {
- mList->InternalList().mCachedPath = nullptr;
- }
-
virtual float* PtrToMemberArgs() = 0;
#ifdef DEBUG
bool IndexIsValid();
#endif
nsRefPtr<DOMSVGPathSegList> mList;
--- a/content/svg/content/src/DOMSVGPathSegList.cpp
+++ b/content/svg/content/src/DOMSVGPathSegList.cpp
@@ -378,17 +378,16 @@ DOMSVGPathSegList::InsertItemBefore(DOMS
AutoChangePathSegListNotifier notifier(this);
// Now that we know we're inserting, keep animVal list in sync as necessary.
MaybeInsertNullInAnimValListAt(aIndex, internalIndex, argCount);
float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
domItem->ToSVGPathSegEncodedData(segAsRaw);
InternalList().mData.InsertElementsAt(internalIndex, segAsRaw, 1 + argCount);
- InternalList().mCachedPath = nullptr;
mItems.InsertElementAt(aIndex, ItemProxy(domItem.get(), internalIndex));
// This MUST come after the insertion into InternalList(), or else under the
// insertion into InternalList() the values read from domItem would be bad
// data from InternalList() itself!:
domItem->InsertingIntoList(this, aIndex, IsAnimValList());
UpdateListIndicesFromIndex(aIndex + 1, argCount + 1);
@@ -435,17 +434,16 @@ DOMSVGPathSegList::ReplaceItem(DOMSVGPat
int32_t newArgCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
domItem->ToSVGPathSegEncodedData(segAsRaw);
bool ok = !!InternalList().mData.ReplaceElementsAt(
internalIndex, 1 + oldArgCount,
segAsRaw, 1 + newArgCount);
- InternalList().mCachedPath = nullptr;
if (!ok) {
aError.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
ItemAt(aIndex) = domItem;
// This MUST come after the ToSVGPathSegEncodedData call, otherwise that call
// would end up reading bad data from InternalList()!
@@ -490,17 +488,16 @@ DOMSVGPathSegList::RemoveItem(uint32_t a
int32_t argCount = SVGPathSegUtils::ArgCountForType(segType);
// Now that we know we're removing, keep animVal list in sync as necessary.
// Do this *before* touching InternalList() so the removed item can get its
// internal value.
MaybeRemoveItemFromAnimValListAt(aIndex, argCount);
InternalList().mData.RemoveElementsAt(internalIndex, 1 + argCount);
- InternalList().mCachedPath = nullptr;
mItems.RemoveElementAt(aIndex);
UpdateListIndicesFromIndex(aIndex, -(argCount + 1));
return result.forget();
}
already_AddRefed<DOMSVGPathSeg>
--- a/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp
+++ b/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp
@@ -222,17 +222,17 @@ SVGMotionSMILAnimationFunction::
if (pathElem) {
const SVGPathData &path = pathElem->GetAnimPathSegList()->GetAnimValue();
// Path data must contain of at least one path segment (if the path data
// doesn't begin with a valid "M", then it's invalid).
if (path.Length()) {
bool ok =
path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
if (ok && mPathVertices.Length()) {
- mPath = pathElem->GetPathForLengthOrPositionMeasuring();
+ mPath = pathElem->GetOrBuildPathForMeasuring();
}
}
}
}
void
SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr()
{
@@ -247,17 +247,17 @@ SVGMotionSMILAnimationFunction::RebuildP
// accept all segments up to the first invalid token. Instead we must
// explicitly check that the parse produces at least one path segment (if
// the path data doesn't begin with a valid "M", then it's invalid).
pathParser.Parse();
if (!path.Length()) {
return;
}
- mPath = path.ToPathForLengthOrPositionMeasuring();
+ mPath = path.BuildPathForMeasuring();
bool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
if (!ok || !mPathVertices.Length()) {
mPath = nullptr;
}
}
// Helper to regenerate our path representation & its list of vertices
void
--- a/content/svg/content/src/SVGPathData.cpp
+++ b/content/svg/content/src/SVGPathData.cpp
@@ -32,17 +32,16 @@ static bool IsMoveto(uint16_t aSegType)
nsresult
SVGPathData::CopyFrom(const SVGPathData& rhs)
{
if (!mData.SetCapacity(rhs.mData.Length())) {
// Yes, we do want fallible alloc here
return NS_ERROR_OUT_OF_MEMORY;
}
- mCachedPath = nullptr;
mData = rhs.mData;
return NS_OK;
}
void
SVGPathData::GetValueAsString(nsAString& aValue) const
{
// we need this function in DidChangePathSegList
@@ -67,30 +66,28 @@ SVGPathData::GetValueAsString(nsAString&
nsresult
SVGPathData::SetValueFromString(const nsAString& aValue)
{
// We don't use a temp variable since the spec says to parse everything up to
// the first error. We still return any error though so that callers know if
// there's a problem.
- mCachedPath = nullptr;
nsSVGPathDataParser pathParser(aValue, this);
return pathParser.Parse() ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR;
}
nsresult
SVGPathData::AppendSeg(uint32_t aType, ...)
{
uint32_t oldLength = mData.Length();
uint32_t newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType);
if (!mData.SetLength(newLength)) {
return NS_ERROR_OUT_OF_MEMORY;
}
- mCachedPath = nullptr;
mData[oldLength] = SVGPathSegUtils::EncodeType(aType);
va_list args;
va_start(args, aType);
for (uint32_t i = oldLength + 1; i < newLength; ++i) {
// NOTE! 'float' is promoted to 'double' when passed through '...'!
mData[i] = float(va_arg(args, double));
}
@@ -505,35 +502,31 @@ SVGPathData::BuildPath(PathBuilder* buil
"prevSegType should be left at the final segType");
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
return builder->Finish();
}
TemporaryRef<Path>
-SVGPathData::ToPathForLengthOrPositionMeasuring() const
+SVGPathData::BuildPathForMeasuring() const
{
// Since the path that we return will not be used for painting it doesn't
// matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want
// to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as
// aStrokeLineCap to avoid the insertion of extra little lines (by
// ApproximateZeroLengthSubpathSquareCaps), in which case the value that we
// pass as aStrokeWidth doesn't matter (since it's only used to determine the
// length of those extra little lines).
- if (!mCachedPath) {
- RefPtr<DrawTarget> drawTarget =
- gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
- RefPtr<PathBuilder> builder =
- drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
- mCachedPath = BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 0);
- }
-
- return mCachedPath;
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<PathBuilder> builder =
+ drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
+ return BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 0);
}
static double
AngleOfVector(const Point& aVector)
{
// C99 says about atan2 "A domain error may occur if both arguments are
// zero" and "On a domain error, the function returns an implementation-
// defined value". In the case of atan2 the implementation-defined value
--- a/content/svg/content/src/SVGPathData.h
+++ b/content/svg/content/src/SVGPathData.h
@@ -158,17 +158,17 @@ public:
*/
bool GetDistancesFromOriginToEndsOfVisibleSegments(FallibleTArray<double> *aArray) const;
/**
* This returns a path without the extra little line segments that
* ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps.
* See the comment for that function for more info on that.
*/
- TemporaryRef<Path> ToPathForLengthOrPositionMeasuring() const;
+ TemporaryRef<Path> BuildPathForMeasuring() const;
TemporaryRef<Path> BuildPath(PathBuilder* aBuilder,
uint8_t aCapStyle,
Float aStrokeWidth) const;
const_iterator begin() const { return mData.Elements(); }
const_iterator end() const { return mData.Elements() + mData.Length(); }
@@ -188,51 +188,47 @@ protected:
/**
* This may fail on OOM if the internal capacity needs to be increased, in
* which case the list will be left unmodified.
*/
nsresult CopyFrom(const SVGPathData& rhs);
float& operator[](uint32_t aIndex) {
- mCachedPath = nullptr;
return mData[aIndex];
}
/**
* This may fail (return false) on OOM if the internal capacity is being
* increased, in which case the list will be left unmodified.
*/
bool SetLength(uint32_t aLength) {
- mCachedPath = nullptr;
return mData.SetLength(aLength);
}
nsresult SetValueFromString(const nsAString& aValue);
void Clear() {
- mCachedPath = nullptr;
mData.Clear();
}
// Our DOM wrappers have direct access to our mData, so they directly
// manipulate it rather than us implementing:
//
// * InsertItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs);
// * ReplaceItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs);
// * RemoveItem(uint32_t aDataIndex);
// * bool AppendItem(uint32_t aType, const float *aArgs);
nsresult AppendSeg(uint32_t aType, ...); // variable number of float args
- iterator begin() { mCachedPath = nullptr; return mData.Elements(); }
- iterator end() { mCachedPath = nullptr; return mData.Elements() + mData.Length(); }
+ iterator begin() { return mData.Elements(); }
+ iterator end() { return mData.Elements() + mData.Length(); }
FallibleTArray<float> mData;
- mutable RefPtr<gfx::Path> mCachedPath;
};
/**
* This SVGPathData subclass is for SVGPathSegListSMILType which needs to
* have write access to the lists it works with.
*
* Instances of this class do not have DOM wrappers that need to be kept in
--- a/content/svg/content/src/SVGPathElement.cpp
+++ b/content/svg/content/src/SVGPathElement.cpp
@@ -65,24 +65,24 @@ already_AddRefed<SVGAnimatedNumber>
SVGPathElement::PathLength()
{
return mPathLength.ToDOMAnimatedNumber(this);
}
float
SVGPathElement::GetTotalLength()
{
- RefPtr<Path> flat = GetPathForLengthOrPositionMeasuring();
+ RefPtr<Path> flat = GetOrBuildPathForMeasuring();
return flat ? flat->ComputeLength() : 0.f;
}
already_AddRefed<nsISVGPoint>
SVGPathElement::GetPointAtLength(float distance, ErrorResult& rv)
{
- RefPtr<Path> path = GetPathForLengthOrPositionMeasuring();
+ RefPtr<Path> path = GetOrBuildPathForMeasuring();
if (!path) {
rv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
float totalLength = path->ComputeLength();
if (mPathLength.IsExplicitlySet()) {
float pathLength = mPathLength.GetAnimValue();
@@ -300,19 +300,19 @@ SVGPathElement::IsAttributeMapped(const
sMarkersMap
};
return FindAttributeDependence(name, map) ||
SVGPathElementBase::IsAttributeMapped(name);
}
TemporaryRef<Path>
-SVGPathElement::GetPathForLengthOrPositionMeasuring()
+SVGPathElement::GetOrBuildPathForMeasuring()
{
- return mD.GetAnimValue().ToPathForLengthOrPositionMeasuring();
+ return mD.GetAnimValue().BuildPathForMeasuring();
}
//----------------------------------------------------------------------
// nsSVGPathGeometryElement methods
bool
SVGPathElement::AttributeDefinesGeometry(const nsIAtom *aName)
{
@@ -335,17 +335,17 @@ SVGPathElement::GetMarkPoints(nsTArray<n
float
SVGPathElement::GetPathLengthScale(PathLengthScaleForType aFor)
{
NS_ABORT_IF_FALSE(aFor == eForTextPath || aFor == eForStroking,
"Unknown enum");
if (mPathLength.IsExplicitlySet()) {
float authorsPathLengthEstimate = mPathLength.GetAnimValue();
if (authorsPathLengthEstimate > 0) {
- RefPtr<Path> path = GetPathForLengthOrPositionMeasuring();
+ RefPtr<Path> path = GetOrBuildPathForMeasuring();
if (!path) {
// The path is empty or invalid so its length must be zero and
// we know that 0 / authorsPathLengthEstimate = 0.
return 0.0;
}
if (aFor == eForTextPath) {
// For textPath, a transform on the referenced path affects the
// textPath layout, so when calculating the actual path length
--- a/content/svg/content/src/SVGPathElement.h
+++ b/content/svg/content/src/SVGPathElement.h
@@ -52,18 +52,17 @@ public:
virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks) MOZ_OVERRIDE;
virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder) MOZ_OVERRIDE;
/**
* This returns a path without the extra little line segments that
* ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps.
* See the comment for that function for more info on that.
*/
- virtual TemporaryRef<Path>
- GetPathForLengthOrPositionMeasuring() MOZ_OVERRIDE;
+ virtual TemporaryRef<Path> GetOrBuildPathForMeasuring() MOZ_OVERRIDE;
// nsIContent interface
virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
virtual SVGAnimatedPathSegList* GetAnimPathSegList() MOZ_OVERRIDE {
return &mD;
}
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -10,16 +10,17 @@
#include "nsSVGElement.h"
#include "mozilla/dom/SVGSVGElement.h"
#include "mozilla/dom/SVGTests.h"
#include "nsContentUtils.h"
#include "nsICSSDeclaration.h"
#include "nsIDocument.h"
#include "nsIDOMMutationEvent.h"
+#include "nsSVGPathGeometryElement.h"
#include "mozilla/InternalMutationEvent.h"
#include "nsError.h"
#include "nsIPresShell.h"
#include "nsGkAtoms.h"
#include "mozilla/css/StyleRule.h"
#include "nsRuleWalker.h"
#include "mozilla/css/Declaration.h"
#include "nsCSSProps.h"
@@ -1600,16 +1601,18 @@ nsSVGElement::DidChangeLength(uint8_t aA
DidChangeValue(*info.mLengthInfo[aAttrEnum].mName, aEmptyOrOldValue,
newValue);
}
void
nsSVGElement::DidAnimateLength(uint8_t aAttrEnum)
{
+ ClearAnyCachedPath();
+
nsIFrame* frame = GetPrimaryFrame();
if (frame) {
LengthAttributesInfo info = GetLengthInfo();
frame->AttributeChanged(kNameSpaceID_None,
*info.mLengthInfo[aAttrEnum].mName,
nsIDOMMutationEvent::MODIFICATION);
}
@@ -1845,16 +1848,18 @@ nsSVGElement::DidChangePointList(const n
}
void
nsSVGElement::DidAnimatePointList()
{
NS_ABORT_IF_FALSE(GetPointListAttrName(),
"Animating non-existent path data?");
+ ClearAnyCachedPath();
+
nsIFrame* frame = GetPrimaryFrame();
if (frame) {
frame->AttributeChanged(kNameSpaceID_None,
GetPointListAttrName(),
nsIDOMMutationEvent::MODIFICATION);
}
}
@@ -1880,16 +1885,18 @@ nsSVGElement::DidChangePathSegList(const
}
void
nsSVGElement::DidAnimatePathSegList()
{
NS_ABORT_IF_FALSE(GetPathDataAttrName(),
"Animating non-existent path data?");
+ ClearAnyCachedPath();
+
nsIFrame* frame = GetPrimaryFrame();
if (frame) {
frame->AttributeChanged(kNameSpaceID_None,
GetPathDataAttrName(),
nsIDOMMutationEvent::MODIFICATION);
}
}
--- a/content/svg/content/src/nsSVGElement.h
+++ b/content/svg/content/src/nsSVGElement.h
@@ -28,16 +28,17 @@
class nsSVGAngle;
class nsSVGBoolean;
class nsSVGEnum;
class nsSVGInteger;
class nsSVGIntegerPair;
class nsSVGLength2;
class nsSVGNumber2;
class nsSVGNumberPair;
+class nsSVGPathGeometryElement;
class nsSVGString;
class nsSVGViewBox;
namespace mozilla {
namespace dom {
class CSSValue;
class SVGSVGElement;
@@ -308,16 +309,17 @@ public:
const nsAttrValue* GetAnimatedClassName() const
{
if (!mClassAttribute.IsAnimated()) {
return nullptr;
}
return mClassAnimAttr;
}
+ virtual void ClearAnyCachedPath() {}
virtual nsIDOMNode* AsDOMNode() MOZ_FINAL MOZ_OVERRIDE { return this; }
virtual bool IsTransformable() { return false; }
// WebIDL
mozilla::dom::SVGSVGElement* GetOwnerSVGElement();
nsSVGElement* GetViewportElement();
already_AddRefed<mozilla::dom::SVGAnimatedString> ClassName();
protected:
--- a/content/svg/content/src/nsSVGPathGeometryElement.cpp
+++ b/content/svg/content/src/nsSVGPathGeometryElement.cpp
@@ -3,30 +3,44 @@
* 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 "nsSVGPathGeometryElement.h"
#include "gfxPlatform.h"
#include "mozilla/gfx/2D.h"
#include "nsComputedDOMStyle.h"
+#include "nsSVGUtils.h"
#include "nsSVGLength2.h"
#include "SVGContentUtils.h"
using namespace mozilla;
using namespace mozilla::gfx;
//----------------------------------------------------------------------
// Implementation
nsSVGPathGeometryElement::nsSVGPathGeometryElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
: nsSVGPathGeometryElementBase(aNodeInfo)
{
}
+nsresult
+nsSVGPathGeometryElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (mCachedPath &&
+ aNamespaceID == kNameSpaceID_None &&
+ AttributeDefinesGeometry(aName)) {
+ mCachedPath = nullptr;
+ }
+ return nsSVGPathGeometryElementBase::AfterSetAttr(aNamespaceID, aName,
+ aValue, aNotify);
+}
+
bool
nsSVGPathGeometryElement::AttributeDefinesGeometry(const nsIAtom *aName)
{
// Check for nsSVGLength2 attribute
LengthAttributesInfo info = GetLengthInfo();
for (uint32_t i = 0; i < info.mLengthCount; i++) {
if (aName == *info.mLengthInfo[i].mName) {
return true;
@@ -56,17 +70,40 @@ nsSVGPathGeometryElement::IsMarkable()
}
void
nsSVGPathGeometryElement::GetMarkPoints(nsTArray<nsSVGMark> *aMarks)
{
}
TemporaryRef<Path>
-nsSVGPathGeometryElement::GetPathForLengthOrPositionMeasuring()
+nsSVGPathGeometryElement::GetOrBuildPath(const DrawTarget& aDrawTarget,
+ FillRule aFillRule)
+{
+ // We only cache the path if it matches the backend used for screen painting:
+ bool cacheable = aDrawTarget.GetBackendType() ==
+ gfxPlatform::GetPlatform()->GetContentBackend();
+
+ // Checking for and returning mCachedPath before checking the pref means
+ // that the pref is only live on page reload (or app restart for SVG in
+ // chrome). The benefit is that we avoid causing a CPU memory cache miss by
+ // looking at the global variable that the pref's stored in.
+ if (cacheable && mCachedPath) {
+ return mCachedPath;
+ }
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(aFillRule);
+ RefPtr<Path> path = BuildPath(builder);
+ if (cacheable && NS_SVGPathCachingEnabled()) {
+ mCachedPath = path;
+ }
+ return path.forget();
+}
+
+TemporaryRef<Path>
+nsSVGPathGeometryElement::GetOrBuildPathForMeasuring()
{
return nullptr;
}
FillRule
nsSVGPathGeometryElement::GetFillRule()
{
FillRule fillRule = FillRule::FILL_WINDING; // Equivalent to NS_STYLE_FILL_RULE_NONZERO
--- a/content/svg/content/src/nsSVGPathGeometryElement.h
+++ b/content/svg/content/src/nsSVGPathGeometryElement.h
@@ -26,24 +26,36 @@ struct nsSVGMark {
x(aX), y(aY), angle(aAngle), type(aType) {}
};
typedef mozilla::dom::SVGGraphicsElement nsSVGPathGeometryElementBase;
class nsSVGPathGeometryElement : public nsSVGPathGeometryElementBase
{
protected:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
typedef mozilla::gfx::FillRule FillRule;
typedef mozilla::gfx::Float Float;
typedef mozilla::gfx::Path Path;
typedef mozilla::gfx::PathBuilder PathBuilder;
public:
explicit nsSVGPathGeometryElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+ virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) MOZ_OVERRIDE;
+
+ /**
+ * Causes this element to discard any Path object that GetOrBuildPath may
+ * have cached.
+ */
+ virtual void ClearAnyCachedPath() MOZ_OVERRIDE MOZ_FINAL {
+ mCachedPath = nullptr;
+ }
+
virtual bool AttributeDefinesGeometry(const nsIAtom *aName);
/**
* Returns true if this element's geometry depends on the width or height of its
* coordinate context (typically the viewport established by its nearest <svg>
* ancestor). In other words, returns true if one of the attributes for which
* AttributeDefinesGeometry returns true has a percentage value.
*
@@ -52,22 +64,49 @@ public:
*/
bool GeometryDependsOnCoordCtx();
virtual bool IsMarkable();
virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks);
/**
* Returns a Path that can be used to paint, hit-test or calculate bounds for
+ * this element. May return nullptr if there is no [valid] path. The path
+ * that is created may be cached and returned on subsequent calls.
+ */
+ virtual mozilla::TemporaryRef<Path> GetOrBuildPath(const DrawTarget& aDrawTarget,
+ FillRule fillRule);
+
+ /**
+ * The same as GetOrBuildPath, but bypasses the cache (neither returns any
+ * previously cached Path, nor caches the Path that in does return).
* this element. May return nullptr if there is no [valid] path.
*/
- virtual mozilla::TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) = 0;
+ virtual mozilla::TemporaryRef<Path> BuildPath(PathBuilder* aBuilder) = 0;
- virtual mozilla::TemporaryRef<Path> GetPathForLengthOrPositionMeasuring();
+ /**
+ * Returns a Path that can be used to measure the length of this elements
+ * path, or to find the position at a given distance along it.
+ *
+ * This is currently equivalent to calling GetOrBuildPath, but it may not be
+ * in the future. The reason for this function to be separate from
+ * GetOrBuildPath is because SVGPathData::BuildPath inserts small lines into
+ * the path if zero length subpaths are encountered, in order to implement
+ * the SVG specifications requirements that zero length subpaths should
+ * render circles/squares if stroke-linecap is round/square, respectively.
+ * In principle these inserted lines could interfere with path measurement,
+ * so we keep callers that are looking to do measurement separate in case we
+ * run into problems with the inserted lines negatively affecting measuring
+ * for content.
+ */
+ virtual mozilla::TemporaryRef<Path> GetOrBuildPathForMeasuring();
/**
* Returns the current computed value of the CSS property 'fill-rule' for
* this element.
*/
FillRule GetFillRule();
+
+protected:
+ mutable mozilla::RefPtr<Path> mCachedPath;
};
#endif
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/dynamic-fill-rule-01-ref.svg
@@ -0,0 +1,20 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <title>Reference for dynamic changes to fill-rule</title>
+ <!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=932762 -->
+
+ <!-- for p1 -->
+ <rect x="50" y="100" width="150" height="50"/>
+ <rect x="100" y="50" width="50" height="150"/>
+
+ <!-- for p2 -->
+ <rect x="250" y="100" width="50" height="50"/>
+ <rect x="350" y="100" width="50" height="50"/>
+ <rect x="300" y="50" width="50" height="50"/>
+ <rect x="300" y="150" width="50" height="50"/>
+
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/dynamic-fill-rule-01.svg
@@ -0,0 +1,25 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
+ <title>Testcase for dynamic changes to fill-rule</title>
+ <!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=932762 -->
+ <script>
+
+function doTest() {
+ document.getElementById("p1").removeAttribute("style");
+ document.getElementById("p2").setAttribute("style", "fill-rule: evenodd;");
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("MozReftestInvalidate", doTest, false);
+window.setTimeout(doTest, 4000); // fallback for running outside reftest
+
+ </script>
+ <path id="p1" style="fill-rule: evenodd;"
+ d="M100,50 l0,150 50,0 0,-100 -100,0 0,50 150,0 0,-50 -50,0 0,-50 z"/>
+ <path id="p2"
+ d="M300,50 l0,150 50,0 0,-100 -100,0 0,50 150,0 0,-50 -50,0 0,-50 z"/>
+</svg>
+
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -73,16 +73,17 @@ fuzzy-if(/^Windows\x20NT\x206\.[12]/.tes
== dynamic-conditions-11.svg pass.svg
== dynamic-conditions-12.svg pass.svg
== dynamic-conditions-13.svg about:blank
fuzzy-if(Android,4,87) == dynamic-clipPath-01.svg pass.svg
== dynamic-clipPath-02.svg pass.svg
== dynamic-feFlood-01.svg pass.svg
asserts(0-1) == dynamic-feImage-01.svg pass.svg # intermittent assertions (bug 886080)
== dynamic-fill-01.svg dynamic-fill-01-ref.svg
+== dynamic-fill-rule-01.svg dynamic-fill-rule-01-ref.svg
fuzzy-if(d2d,1,10000) == dynamic-filter-contents-01a.svg dynamic-filter-contents-01-ref.svg
fuzzy-if(d2d,1,10000) == dynamic-filter-contents-01b.svg dynamic-filter-contents-01-ref.svg
== dynamic-gradient-contents-01.svg pass.svg
== dynamic-gradient-contents-02.svg pass.svg
== dynamic-inner-svg-01.svg pass.svg
== dynamic-link-style-01.svg pass.svg
== dynamic-marker-01.svg pass.svg
== dynamic-marker-02.svg dynamic-marker-02-ref.svg
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -4800,17 +4800,17 @@ SVGTextFrame::GetTextPath(nsIFrame* aTex
if (!pathFrame) {
return nullptr;
}
nsSVGPathGeometryElement *element =
static_cast<nsSVGPathGeometryElement*>(pathFrame->GetContent());
- RefPtr<Path> path = element->GetPathForLengthOrPositionMeasuring();
+ RefPtr<Path> path = element->GetOrBuildPathForMeasuring();
if (!path) {
return nullptr;
}
gfxMatrix matrix = element->PrependLocalTransformsTo(gfxMatrix());
if (!matrix.IsIdentity()) {
RefPtr<PathBuilder> builder =
path->TransformedCopyToBuilder(ToMatrix(matrix));
--- a/layout/svg/nsSVGClipPathFrame.cpp
+++ b/layout/svg/nsSVGClipPathFrame.cpp
@@ -61,20 +61,18 @@ nsSVGClipPathFrame::ApplyClipOrPaintClip
static_cast<nsSVGPathGeometryElement*>(pathFrame->GetContent());
gfxMatrix toChildsUserSpace = pathElement->
PrependLocalTransformsTo(mMatrixForChildren,
nsSVGElement::eUserSpaceToParent);
gfxMatrix newMatrix =
gfx->CurrentMatrix().PreMultiply(toChildsUserSpace).NudgeToIntegers();
if (!newMatrix.IsSingular()) {
gfx->SetMatrix(newMatrix);
- RefPtr<PathBuilder> builder =
- gfx->GetDrawTarget()->CreatePathBuilder(
- nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule));
- clipPath = pathElement->BuildPath(builder);
+ clipPath = pathElement->GetOrBuildPath(*gfx->GetDrawTarget(),
+ nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule));
}
}
}
gfx->NewPath();
if (clipPath) {
gfx->SetPath(clipPath);
// gfxContext::Clip() resets the FillRule on any Path set by
// gfxContext::SetPath() call to the contexts own current FillRule.
--- a/layout/svg/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/nsSVGPathGeometryFrame.cpp
@@ -153,16 +153,35 @@ nsSVGPathGeometryFrame::DidSetStyleConte
float oldOpacity = aOldStyleContext->PeekStyleDisplay()->mOpacity;
float newOpacity = StyleDisplay()->mOpacity;
if (newOpacity != oldOpacity &&
nsSVGUtils::CanOptimizeOpacity(this)) {
// nsIFrame::BuildDisplayListForStackingContext() is not going to create an
// nsDisplayOpacity display list item, so DLBI won't invalidate for us.
InvalidateFrame();
}
+
+ nsSVGPathGeometryElement* element =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
+
+ if (aOldStyleContext->PeekStyleSVG()) {
+ if ((StyleSVG()->mStrokeLinecap !=
+ aOldStyleContext->PeekStyleSVG()->mStrokeLinecap) &&
+ element->Tag() == nsGkAtoms::path) {
+ // If the stroke-linecap changes to or from "butt" then our element
+ // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
+ // decides whether or not to insert little lines into the path for zero
+ // length subpaths base on that property.
+ element->ClearAnyCachedPath();
+ } else if (StyleSVG()->mFillRule !=
+ aOldStyleContext->PeekStyleSVG()->mFillRule) {
+ // Moz2D Path objects are fill-rule specific.
+ element->ClearAnyCachedPath();
+ }
+ }
}
}
nsIAtom *
nsSVGPathGeometryFrame::GetType() const
{
return nsGkAtoms::svgPathGeometryFrame;
}
@@ -283,19 +302,17 @@ nsSVGPathGeometryFrame::GetFrameForPoint
nsSVGPathGeometryElement* content =
static_cast<nsSVGPathGeometryElement*>(mContent);
// Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
// testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
// so that we get more consistent/backwards compatible results?
RefPtr<DrawTarget> drawTarget =
gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
- RefPtr<PathBuilder> builder =
- drawTarget->CreatePathBuilder(fillRule);
- RefPtr<Path> path = content->BuildPath(builder);
+ RefPtr<Path> path = content->GetOrBuildPath(*drawTarget, fillRule);
if (!path) {
return nullptr; // no path, so we don't paint anything that can be hit
}
if (hitTestFlags & SVG_HIT_TEST_FILL) {
isHit = path->ContainsPoint(ToPoint(aPoint), Matrix());
}
if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
@@ -405,16 +422,17 @@ nsSVGPathGeometryFrame::NotifySVGChanged
if (aFlags & COORD_CONTEXT_CHANGED) {
// Stroke currently contributes to our mRect, which is why we have to take
// account of stroke-width here. Note that we do not need to take account
// of stroke-dashoffset since, although that can have a percentage value
// that is resolved against our coordinate context, it does not affect our
// mRect.
if (static_cast<nsSVGPathGeometryElement*>(mContent)->GeometryDependsOnCoordCtx() ||
StyleSVG()->mStrokeWidth.HasPercent()) {
+ static_cast<nsSVGPathGeometryElement*>(mContent)->ClearAnyCachedPath();
nsSVGUtils::ScheduleReflowSVG(this);
}
}
if ((aFlags & TRANSFORM_CHANGED) &&
StyleSVGReset()->mVectorEffect ==
NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) {
// Stroke currently contributes to our mRect, and our stroke depends on
@@ -449,26 +467,25 @@ nsSVGPathGeometryFrame::GetBBoxContribut
tmpDT = gfxPlatform::GetPlatform()->
CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
#else
tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
#endif
FillRule fillRule = StyleSVG()->mFillRule == NS_STYLE_FILL_RULE_NONZERO
? FillRule::FILL_WINDING : FillRule::FILL_EVEN_ODD;
- RefPtr<PathBuilder> builder = tmpDT->CreatePathBuilder(fillRule);
- RefPtr<Path> pathInUserSpace = element->BuildPath(builder);
+ RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
if (!pathInUserSpace) {
return bbox;
}
RefPtr<Path> pathInBBoxSpace;
if (aToBBoxUserspace.IsIdentity()) {
pathInBBoxSpace = pathInUserSpace;
} else {
- builder =
+ RefPtr<PathBuilder> builder =
pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
pathInBBoxSpace = builder->Finish();
if (!pathInBBoxSpace) {
return bbox;
}
}
// Be careful when replacing the following logic to get the fill and stroke
@@ -658,23 +675,20 @@ nsSVGPathGeometryFrame::Render(gfxContex
MOZ_ASSERT(renderMode == SVGAutoRenderState::NORMAL ||
renderMode == SVGAutoRenderState::CLIP_MASK,
"Unknown render mode");
FillRule fillRule =
nsSVGUtils::ToFillRule(renderMode == SVGAutoRenderState::NORMAL ?
StyleSVG()->mFillRule : StyleSVG()->mClipRule);
- RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder(fillRule);
- if (!builder) {
- return;
- }
+ nsSVGPathGeometryElement* element =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
- RefPtr<Path> path =
- static_cast<nsSVGPathGeometryElement*>(mContent)->BuildPath(builder);
+ RefPtr<Path> path = element->GetOrBuildPath(*drawTarget, fillRule);
if (!path) {
return;
}
AntialiasMode aaMode =
(StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED ||
StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_CRISPEDGES) ?
AntialiasMode::NONE : AntialiasMode::SUBPIXEL;
@@ -710,17 +724,18 @@ nsSVGPathGeometryFrame::Render(gfxContex
gfxMatrix userToOuterSVG;
if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
// We need to transform the path back into the appropriate ancestor
// coordinate system, and paint it it that coordinate system, in order
// for non-scaled stroke to paint correctly.
gfxMatrix outerSVGToUser = userToOuterSVG;
outerSVGToUser.Invert();
aContext->Multiply(outerSVGToUser);
- builder = path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
+ RefPtr<PathBuilder> builder =
+ path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
path = builder->Finish();
}
GeneralPattern strokePattern;
nsSVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, contextPaint);
if (strokePattern.GetPattern()) {
SVGContentUtils::AutoStrokeOptions strokeOptions;
SVGContentUtils::GetStrokeOptions(&strokeOptions,
static_cast<nsSVGElement*>(mContent),
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -52,21 +52,28 @@
#include "nsTextFrame.h"
#include "SVGContentUtils.h"
#include "mozilla/unused.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
+static bool sSVGPathCachingEnabled;
static bool sSVGDisplayListHitTestingEnabled;
static bool sSVGDisplayListPaintingEnabled;
static bool sSVGNewGetBBoxEnabled;
bool
+NS_SVGPathCachingEnabled()
+{
+ return sSVGPathCachingEnabled;
+}
+
+bool
NS_SVGDisplayListHitTestingEnabled()
{
return sSVGDisplayListHitTestingEnabled;
}
bool
NS_SVGDisplayListPaintingEnabled()
{
@@ -132,16 +139,19 @@ SVGAutoRenderState::IsPaintingToWindow(D
return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow;
}
return false;
}
void
nsSVGUtils::Init()
{
+ Preferences::AddBoolVarCache(&sSVGPathCachingEnabled,
+ "svg.path-caching.enabled");
+
Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled,
"svg.display-lists.hit-testing.enabled");
Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled,
"svg.display-lists.painting.enabled");
Preferences::AddBoolVarCache(&sSVGNewGetBBoxEnabled,
"svg.new-getBBox.enabled");
--- a/layout/svg/nsSVGUtils.h
+++ b/layout/svg/nsSVGUtils.h
@@ -73,16 +73,17 @@ class SourceSurface;
// In fact Macs can't even manage that
#define NS_SVG_OFFSCREEN_MAX_DIMENSION 4096
#define SVG_HIT_TEST_FILL 0x01
#define SVG_HIT_TEST_STROKE 0x02
#define SVG_HIT_TEST_CHECK_MRECT 0x04
+bool NS_SVGPathCachingEnabled();
bool NS_SVGDisplayListHitTestingEnabled();
bool NS_SVGDisplayListPaintingEnabled();
bool NS_SVGNewGetBBoxEnabled();
/**
* Sometimes we need to distinguish between an empty box and a box
* that contains an element that has no size e.g. a point at the origin.
*/
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2286,16 +2286,19 @@ pref("dom.ipc.plugins.flash.subprocess.c
pref("dom.ipc.plugins.reportCrashURL", true);
// How long we wait before unloading an idle plugin process.
// Defaults to 30 seconds.
pref("dom.ipc.plugins.unloadTimeoutSecs", 30);
pref("dom.ipc.processCount", 1);
+// Enable caching of Moz2D Path objects for SVG geometry elements
+pref("svg.path-caching.enabled", true);
+
// Enable the use of display-lists for SVG hit-testing and painting.
pref("svg.display-lists.hit-testing.enabled", true);
pref("svg.display-lists.painting.enabled", true);
// Is support for the SVG 2 paint-order property enabled?
pref("svg.paint-order.enabled", true);
// Is support for the new marker features from SVG 2 enabled? Currently