Bug 1555758 - Fallback to GetComputedStyleNoFlush for BuildPath r=longsonr,emilio

It turns out the `BuildPath()` method will still be exposed by DOM
API via `getTotalLength()`. So we need to fallback to `GetComputedStyleNoFlush()`
to handle display:none problem, and if the element doesn't even belong
to a document, we further fallback to 0.

`BuildPath()` being affected also means `GetStrokeWidth()`, etc. will also
be indirectly exposed to DOM API in some obscure cases. Let's add a utility
to handle the fallback.

Differential Revision: https://phabricator.services.mozilla.com/D33247

--HG--
extra : moz-landing-system : lando
This commit is contained in:
violet 2019-05-31 13:17:46 +00:00
parent 62aa7c0ec3
commit a524e7ef15
10 changed files with 160 additions and 107 deletions

View File

@ -127,9 +127,8 @@ bool SVGCircleElement::GetGeometryBounds(
already_AddRefed<Path> SVGCircleElement::BuildPath(PathBuilder* aBuilder) {
float x, y, r;
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::R>(this, &x, &y,
&r);
SVGGeometryProperty::ResolveAllAllowFallback<SVGT::Cx, SVGT::Cy, SVGT::R>(
this, &x, &y, &r);
if (r <= 0.0f) {
return nullptr;

View File

@ -29,6 +29,7 @@
#include "nsWhitespaceTokenizer.h"
#include "SVGAnimationElement.h"
#include "SVGAnimatedPreserveAspectRatio.h"
#include "SVGGeometryProperty.h"
#include "nsContentUtils.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Types.h"
@ -255,93 +256,97 @@ static DashState GetStrokeDashData(
void SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
SVGElement* aElement,
ComputedStyle* aComputedStyle,
const ComputedStyle* aComputedStyle,
SVGContextPaint* aContextPaint,
StrokeOptionFlags aFlags) {
ComputedStyle* computedStyle;
auto doCompute = [&](const ComputedStyle* computedStyle) {
const nsStyleSVG* styleSVG = computedStyle->StyleSVG();
bool checkedDashAndStrokeIsDashed = false;
if (aFlags != eIgnoreStrokeDashing) {
DashState dashState =
GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint);
if (dashState == eNoStroke) {
// Hopefully this will shortcircuit any stroke operations:
aStrokeOptions->mLineWidth = 0;
return;
}
if (dashState == eContinuousStroke && aStrokeOptions->mDashPattern) {
// Prevent our caller from wasting time looking at a pattern without
// gaps:
aStrokeOptions->DiscardDashPattern();
}
checkedDashAndStrokeIsDashed = (dashState == eDashedStroke);
}
aStrokeOptions->mLineWidth =
GetStrokeWidth(aElement, computedStyle, aContextPaint);
aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit);
switch (styleSVG->mStrokeLinejoin) {
case NS_STYLE_STROKE_LINEJOIN_MITER:
aStrokeOptions->mLineJoin = JoinStyle::MITER_OR_BEVEL;
break;
case NS_STYLE_STROKE_LINEJOIN_ROUND:
aStrokeOptions->mLineJoin = JoinStyle::ROUND;
break;
case NS_STYLE_STROKE_LINEJOIN_BEVEL:
aStrokeOptions->mLineJoin = JoinStyle::BEVEL;
break;
}
if (ShapeTypeHasNoCorners(aElement) && !checkedDashAndStrokeIsDashed) {
// Note: if aFlags == eIgnoreStrokeDashing then we may be returning the
// wrong linecap value here, since the actual linecap used on render in
// this case depends on whether the stroke is dashed or not.
aStrokeOptions->mLineCap = CapStyle::BUTT;
} else {
switch (styleSVG->mStrokeLinecap) {
case NS_STYLE_STROKE_LINECAP_BUTT:
aStrokeOptions->mLineCap = CapStyle::BUTT;
break;
case NS_STYLE_STROKE_LINECAP_ROUND:
aStrokeOptions->mLineCap = CapStyle::ROUND;
break;
case NS_STYLE_STROKE_LINECAP_SQUARE:
aStrokeOptions->mLineCap = CapStyle::SQUARE;
break;
}
}
};
if (aComputedStyle) {
computedStyle = aComputedStyle;
} else if (auto* f = aElement->GetPrimaryFrame()) {
computedStyle = f->Style();
doCompute(aComputedStyle);
} else {
return;
}
const nsStyleSVG* styleSVG = computedStyle->StyleSVG();
bool checkedDashAndStrokeIsDashed = false;
if (aFlags != eIgnoreStrokeDashing) {
DashState dashState =
GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint);
if (dashState == eNoStroke) {
// Hopefully this will shortcircuit any stroke operations:
aStrokeOptions->mLineWidth = 0;
return;
}
if (dashState == eContinuousStroke && aStrokeOptions->mDashPattern) {
// Prevent our caller from wasting time looking at a pattern without gaps:
aStrokeOptions->DiscardDashPattern();
}
checkedDashAndStrokeIsDashed = (dashState == eDashedStroke);
}
aStrokeOptions->mLineWidth =
GetStrokeWidth(aElement, computedStyle, aContextPaint);
aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit);
switch (styleSVG->mStrokeLinejoin) {
case NS_STYLE_STROKE_LINEJOIN_MITER:
aStrokeOptions->mLineJoin = JoinStyle::MITER_OR_BEVEL;
break;
case NS_STYLE_STROKE_LINEJOIN_ROUND:
aStrokeOptions->mLineJoin = JoinStyle::ROUND;
break;
case NS_STYLE_STROKE_LINEJOIN_BEVEL:
aStrokeOptions->mLineJoin = JoinStyle::BEVEL;
break;
}
if (ShapeTypeHasNoCorners(aElement) && !checkedDashAndStrokeIsDashed) {
// Note: if aFlags == eIgnoreStrokeDashing then we may be returning the
// wrong linecap value here, since the actual linecap used on render in this
// case depends on whether the stroke is dashed or not.
aStrokeOptions->mLineCap = CapStyle::BUTT;
} else {
switch (styleSVG->mStrokeLinecap) {
case NS_STYLE_STROKE_LINECAP_BUTT:
aStrokeOptions->mLineCap = CapStyle::BUTT;
break;
case NS_STYLE_STROKE_LINECAP_ROUND:
aStrokeOptions->mLineCap = CapStyle::ROUND;
break;
case NS_STYLE_STROKE_LINECAP_SQUARE:
aStrokeOptions->mLineCap = CapStyle::SQUARE;
break;
}
SVGGeometryProperty::DoForComputedStyle(aElement, doCompute);
}
}
Float SVGContentUtils::GetStrokeWidth(SVGElement* aElement,
ComputedStyle* aComputedStyle,
const ComputedStyle* aComputedStyle,
SVGContextPaint* aContextPaint) {
ComputedStyle* computedStyle;
Float res = 0.0;
auto doCompute = [&](ComputedStyle const* computedStyle) {
const nsStyleSVG* styleSVG = computedStyle->StyleSVG();
if (aContextPaint && styleSVG->StrokeWidthFromObject()) {
res = aContextPaint->GetStrokeWidth();
return;
}
res = SVGContentUtils::CoordToFloat(aElement, styleSVG->mStrokeWidth);
};
if (aComputedStyle) {
computedStyle = aComputedStyle;
} else if (auto* f = aElement->GetPrimaryFrame()) {
computedStyle = f->Style();
doCompute(aComputedStyle);
} else {
return 0.0f;
SVGGeometryProperty::DoForComputedStyle(aElement, doCompute);
}
const nsStyleSVG* styleSVG = computedStyle->StyleSVG();
if (aContextPaint && styleSVG->StrokeWidthFromObject()) {
return aContextPaint->GetStrokeWidth();
}
return SVGContentUtils::CoordToFloat(aElement, styleSVG->mStrokeWidth);
return res;
}
float SVGContentUtils::GetFontSize(Element* aElement) {

View File

@ -151,7 +151,7 @@ class SVGContentUtils {
*/
static void GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
dom::SVGElement* aElement,
ComputedStyle* aComputedStyle,
const ComputedStyle* aComputedStyle,
mozilla::SVGContextPaint* aContextPaint,
StrokeOptionFlags aFlags = eAllStrokeOptions);
@ -165,7 +165,7 @@ class SVGContentUtils {
* "0", respectively.
*/
static Float GetStrokeWidth(dom::SVGElement* aElement,
ComputedStyle* aComputedStyle,
const ComputedStyle* aComputedStyle,
mozilla::SVGContextPaint* aContextPaint);
/*

View File

@ -139,9 +139,9 @@ bool SVGEllipseElement::GetGeometryBounds(
already_AddRefed<Path> SVGEllipseElement::BuildPath(PathBuilder* aBuilder) {
float x, y, rx, ry;
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::Rx, SVGT::Ry>(
this, &x, &y, &rx, &ry);
SVGGeometryProperty::ResolveAllAllowFallback<SVGT::Cx, SVGT::Cy, SVGT::Rx,
SVGT::Ry>(this, &x, &y, &rx,
&ry);
if (rx <= 0.0f || ry <= 0.0f) {
return nullptr;

View File

@ -13,6 +13,7 @@
#include "SVGAnimatedLength.h"
#include "SVGCircleElement.h"
#include "SVGEllipseElement.h"
#include "SVGGeometryProperty.h"
#include "SVGRectElement.h"
#include "mozilla/dom/SVGLengthBinding.h"
#include "mozilla/gfx/2D.h"
@ -133,15 +134,19 @@ FillRule SVGGeometryElement::GetFillRule() {
FillRule fillRule =
FillRule::FILL_WINDING; // Equivalent to StyleFillRule::Nonzero
if (auto* f = GetPrimaryFrame()) {
MOZ_ASSERT(f->StyleSVG()->mFillRule == StyleFillRule::Nonzero ||
f->StyleSVG()->mFillRule == StyleFillRule::Evenodd);
bool res = SVGGeometryProperty::DoForComputedStyle(
this, [&](const ComputedStyle* s) {
const auto* styleSVG = s->StyleSVG();
if (f->StyleSVG()->mFillRule == StyleFillRule::Evenodd) {
fillRule = FillRule::FILL_EVEN_ODD;
}
} else {
// ReportToConsole
MOZ_ASSERT(styleSVG->mFillRule == StyleFillRule::Nonzero ||
styleSVG->mFillRule == StyleFillRule::Evenodd);
if (styleSVG->mFillRule == StyleFillRule::Evenodd) {
fillRule = FillRule::FILL_EVEN_ODD;
}
});
if (!res) {
NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule");
}

View File

@ -10,6 +10,7 @@
#include "mozilla/dom/SVGElement.h"
#include "ComputedStyle.h"
#include "SVGAnimatedLength.h"
#include "nsComputedDOMStyle.h"
#include "nsGkAtoms.h"
#include "nsIFrame.h"
#include "nsSVGImageFrame.h"
@ -78,6 +79,7 @@ struct Ry {
namespace details {
template <class T>
using AlwaysFloat = float;
using dummy = int[];
using CtxDirectionType = decltype(SVGContentUtils::X);
@ -187,6 +189,25 @@ float ResolveWith(const ComputedStyle& aStyle, const SVGElement* aElement) {
typename Tag::ResolverType{});
}
template <class Func>
bool DoForComputedStyle(SVGElement* aElement, Func aFunc) {
if (const nsIFrame* f = aElement->GetPrimaryFrame()) {
aFunc(f->Style());
return true;
}
if (RefPtr<ComputedStyle> computedStyle =
nsComputedDOMStyle::GetComputedStyleNoFlush(aElement, nullptr)) {
aFunc(computedStyle.get());
return true;
}
return false;
}
#define SVGGEOMETRYPROPERTY_EVAL_ALL(expr) \
(void)details::dummy { 0, (static_cast<void>(expr), 0)... }
// To add support for new properties, or to handle special cases for
// existing properties, you can add a new tag in |Tags| and |ResolverTypes|
// namespace, then implement the behavior in |details::ResolveImpl|.
@ -194,13 +215,30 @@ template <class... Tags>
bool ResolveAll(const SVGElement* aElement,
details::AlwaysFloat<Tags>*... aRes) {
if (nsIFrame const* f = aElement->GetPrimaryFrame()) {
using dummy = int[];
(void)dummy{0, (*aRes = ResolveWith<Tags>(*f->Style(), aElement), 0)...};
SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes =
ResolveWith<Tags>(*f->Style(), aElement));
return true;
}
return false;
}
template <class... Tags>
bool ResolveAllAllowFallback(SVGElement* aElement,
details::AlwaysFloat<Tags>*... aRes) {
bool res = DoForComputedStyle(aElement, [&](auto const* style) {
SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = ResolveWith<Tags>(*style, aElement));
});
if (res) {
return true;
}
SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = 0);
return false;
}
#undef SVGGEOMETRYPROPERTY_EVAL_ALL
nsCSSUnit SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit);
nsCSSPropertyID AttrEnumToCSSPropId(const SVGElement* aElement,
uint8_t aAttrEnum);

View File

@ -270,9 +270,9 @@ already_AddRefed<Path> SVGImageElement::BuildPath(PathBuilder* aBuilder) {
// hit-testing against it. For that we just pretend to be a rectangle.
float x, y, width, height;
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
this, &x, &y, &width, &height);
SVGGeometryProperty::ResolveAllAllowFallback<SVGT::X, SVGT::Y, SVGT::Width,
SVGT::Height>(this, &x, &y,
&width, &height);
if (width <= 0 || height <= 0) {
return nullptr;

View File

@ -10,6 +10,7 @@
#include "DOMSVGPathSeg.h"
#include "DOMSVGPathSegList.h"
#include "SVGGeometryProperty.h"
#include "gfx2DGlue.h"
#include "gfxPlatform.h"
#include "nsGkAtoms.h"
@ -260,17 +261,17 @@ already_AddRefed<Path> SVGPathElement::BuildPath(PathBuilder* aBuilder) {
uint8_t strokeLineCap = NS_STYLE_STROKE_LINECAP_BUTT;
Float strokeWidth = 0;
if (auto* f = GetPrimaryFrame()) {
const nsStyleSVG* style = f->StyleSVG();
SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) {
const nsStyleSVG* style = s->StyleSVG();
// Note: the path that we return may be used for hit-testing, and SVG
// exposes hit-testing of strokes that are not actually painted. For that
// reason we do not check for eStyleSVGPaintType_None or check the stroke
// opacity here.
if (style->mStrokeLinecap != NS_STYLE_STROKE_LINECAP_BUTT) {
strokeLineCap = style->mStrokeLinecap;
strokeWidth = SVGContentUtils::GetStrokeWidth(this, f->Style(), nullptr);
strokeWidth = SVGContentUtils::GetStrokeWidth(this, s, nullptr);
}
}
});
return mD.GetAnimValue().BuildPath(aBuilder, strokeLineCap, strokeWidth);
}

View File

@ -170,10 +170,9 @@ bool SVGRectElement::GetGeometryBounds(Rect* aBounds,
void SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath) {
float x, y, width, height, rx, ry;
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height,
SVGT::Rx, SVGT::Ry>(this, &x, &y, &width,
&height, &rx, &ry);
SVGGeometryProperty::ResolveAllAllowFallback<
SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height, SVGT::Rx, SVGT::Ry>(
this, &x, &y, &width, &height, &rx, &ry);
if (width <= 0 || height <= 0) {
aSimplePath->Reset();
@ -194,10 +193,9 @@ void SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath) {
already_AddRefed<Path> SVGRectElement::BuildPath(PathBuilder* aBuilder) {
float x, y, width, height, rx, ry;
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height,
SVGT::Rx, SVGT::Ry>(this, &x, &y, &width,
&height, &rx, &ry);
SVGGeometryProperty::ResolveAllAllowFallback<
SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height, SVGT::Rx, SVGT::Ry>(
this, &x, &y, &width, &height, &rx, &ry);
if (width <= 0 || height <= 0) {
return nullptr;

View File

@ -16,6 +16,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1474284
<path id="path1" stroke="#000" fill="none"
d="M 50,40
C 50,40 0,60 30,20"/>
<symbol font-size="10" width="20em" height="20em">
<rect id="r1" x="5em" y="6em" width="20%" height="30%" />
</symbol>
</svg>
<pre id="test">
@ -30,6 +33,10 @@ function expectValue(id, expected) {
function run() {
expectValue("path1", 55.19);
let r1 = document.getElementById("r1");
is(r1.getTotalLength(), 200, "getTotalLength() should work for display:none element");
SimpleTest.finish();
}