1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SVGAnimatedLength.h"
9 #include "mozAutoDocUpdate.h"
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/Maybe.h"
12 #include "mozilla/SMILValue.h"
13 #include "mozilla/SVGIntegrationUtils.h"
14 #include "mozilla/dom/SVGViewportElement.h"
15 #include "DOMSVGAnimatedLength.h"
16 #include "DOMSVGLength.h"
17 #include "LayoutLogging.h"
18 #include "nsContentUtils.h"
20 #include "nsTextFormatter.h"
21 #include "SMILFloatType.h"
22 #include "SVGAttrTearoffTable.h"
24 using namespace mozilla::dom
;
28 //----------------------------------------------------------------------
29 // Helper class: AutoChangeLengthNotifier
30 // Stack-based helper class to pair calls to WillChangeLength and
32 class MOZ_RAII AutoChangeLengthNotifier
{
34 AutoChangeLengthNotifier(SVGAnimatedLength
* aLength
, SVGElement
* aSVGElement
,
35 bool aDoSetAttr
= true)
36 : mLength(aLength
), mSVGElement(aSVGElement
), mDoSetAttr(aDoSetAttr
) {
37 MOZ_ASSERT(mLength
, "Expecting non-null length");
38 MOZ_ASSERT(mSVGElement
, "Expecting non-null element");
41 mUpdateBatch
.emplace(aSVGElement
->GetComposedDoc(), true);
43 mSVGElement
->WillChangeLength(mLength
->mAttrEnum
, mUpdateBatch
.ref());
47 ~AutoChangeLengthNotifier() {
49 mSVGElement
->DidChangeLength(mLength
->mAttrEnum
, mEmptyOrOldValue
,
52 if (mLength
->mIsAnimated
) {
53 mSVGElement
->AnimationNeedsResample();
58 SVGAnimatedLength
* const mLength
;
59 SVGElement
* const mSVGElement
;
60 Maybe
<mozAutoDocUpdate
> mUpdateBatch
;
61 nsAttrValue mEmptyOrOldValue
;
65 static const nsStaticAtom
* const unitMap
[] = {
66 nullptr, /* SVG_LENGTHTYPE_UNKNOWN */
67 nullptr, /* SVG_LENGTHTYPE_NUMBER */
68 nsGkAtoms::percentage
,
78 static SVGAttrTearoffTable
<SVGAnimatedLength
, DOMSVGAnimatedLength
>
79 sSVGAnimatedLengthTearoffTable
;
81 /* Helper functions */
83 static bool IsValidUnitType(uint16_t unit
) {
84 return unit
> SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN
&&
85 unit
<= SVGLength_Binding::SVG_LENGTHTYPE_PC
;
88 static void GetUnitString(nsAString
& unit
, uint16_t unitType
) {
89 if (IsValidUnitType(unitType
)) {
90 if (unitMap
[unitType
]) {
91 unitMap
[unitType
]->ToString(unit
);
96 MOZ_ASSERT_UNREACHABLE("Unknown unit type");
99 static uint16_t GetUnitTypeForString(const nsAString
& unitStr
) {
100 if (unitStr
.IsEmpty()) return SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
;
102 nsAtom
* unitAtom
= NS_GetStaticAtom(unitStr
);
104 for (uint32_t i
= 0; i
< ArrayLength(unitMap
); i
++) {
105 if (unitMap
[i
] == unitAtom
) {
111 return SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN
;
114 static void GetValueString(nsAString
& aValueAsString
, float aValue
,
115 uint16_t aUnitType
) {
116 nsTextFormatter::ssprintf(aValueAsString
, u
"%g", (double)aValue
);
118 nsAutoString unitString
;
119 GetUnitString(unitString
, aUnitType
);
120 aValueAsString
.Append(unitString
);
123 static bool GetValueFromString(const nsAString
& aString
, float& aValue
,
124 uint16_t* aUnitType
) {
126 auto token
= SVGContentUtils::GetAndEnsureOneToken(aString
, success
);
132 RangedPtr
<const char16_t
> iter
= SVGContentUtils::GetStartRangedPtr(token
);
133 const RangedPtr
<const char16_t
> end
= SVGContentUtils::GetEndRangedPtr(token
);
135 if (!SVGContentUtils::ParseNumber(iter
, end
, aValue
)) {
138 const nsAString
& units
= Substring(iter
.get(), end
.get());
139 *aUnitType
= GetUnitTypeForString(units
);
140 return IsValidUnitType(*aUnitType
);
143 static float FixAxisLength(float aLength
) {
144 if (aLength
== 0.0f
) {
145 LAYOUT_WARNING("zero axis length");
151 SVGElementMetrics::SVGElementMetrics(SVGElement
* aSVGElement
,
152 SVGViewportElement
* aCtx
)
153 : mSVGElement(aSVGElement
), mCtx(aCtx
) {}
155 float SVGElementMetrics::GetEmLength() const {
156 return SVGContentUtils::GetFontSize(mSVGElement
);
159 float SVGElementMetrics::GetExLength() const {
160 return SVGContentUtils::GetFontXHeight(mSVGElement
);
163 float SVGElementMetrics::GetAxisLength(uint8_t aCtxType
) const {
168 return FixAxisLength(mCtx
->GetLength(aCtxType
));
171 bool SVGElementMetrics::EnsureCtx() const {
172 if (!mCtx
&& mSVGElement
) {
173 mCtx
= mSVGElement
->GetCtx();
174 if (!mCtx
&& mSVGElement
->IsSVGElement(nsGkAtoms::svg
)) {
175 auto* e
= static_cast<SVGViewportElement
*>(mSVGElement
);
178 // mSVGElement must be the outer svg element
183 return mCtx
!= nullptr;
186 NonSVGFrameUserSpaceMetrics::NonSVGFrameUserSpaceMetrics(nsIFrame
* aFrame
)
189 float NonSVGFrameUserSpaceMetrics::GetEmLength() const {
190 return SVGContentUtils::GetFontSize(mFrame
);
193 float NonSVGFrameUserSpaceMetrics::GetExLength() const {
194 return SVGContentUtils::GetFontXHeight(mFrame
);
197 gfx::Size
NonSVGFrameUserSpaceMetrics::GetSize() const {
198 return SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(mFrame
);
201 float UserSpaceMetricsWithSize::GetAxisLength(uint8_t aCtxType
) const {
202 gfx::Size size
= GetSize();
205 case SVGContentUtils::X
:
208 case SVGContentUtils::Y
:
209 length
= size
.height
;
211 case SVGContentUtils::XY
:
213 SVGContentUtils::ComputeNormalizedHypotenuse(size
.width
, size
.height
);
216 MOZ_ASSERT_UNREACHABLE("Unknown axis type");
220 return FixAxisLength(length
);
223 float SVGAnimatedLength::GetPixelsPerUnit(SVGElement
* aSVGElement
,
224 uint8_t aUnitType
) const {
225 return GetPixelsPerUnit(SVGElementMetrics(aSVGElement
), aUnitType
);
228 float SVGAnimatedLength::GetPixelsPerUnit(SVGViewportElement
* aCtx
,
229 uint8_t aUnitType
) const {
230 return GetPixelsPerUnit(SVGElementMetrics(aCtx
, aCtx
), aUnitType
);
233 float SVGAnimatedLength::GetPixelsPerUnit(nsIFrame
* aFrame
,
234 uint8_t aUnitType
) const {
235 nsIContent
* content
= aFrame
->GetContent();
236 if (content
->IsSVGElement()) {
237 return GetPixelsPerUnit(
238 SVGElementMetrics(static_cast<SVGElement
*>(content
)), aUnitType
);
240 return GetPixelsPerUnit(NonSVGFrameUserSpaceMetrics(aFrame
), aUnitType
);
243 // See https://www.w3.org/TR/css-values-3/#absolute-lengths
244 static const float DPI
= 96.0f
;
246 bool UserSpaceMetrics::ResolveAbsoluteUnit(uint8_t aUnitType
, float& aRes
) {
248 case SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
:
249 case SVGLength_Binding::SVG_LENGTHTYPE_PX
:
252 case SVGLength_Binding::SVG_LENGTHTYPE_MM
:
253 aRes
= DPI
/ MM_PER_INCH_FLOAT
;
255 case SVGLength_Binding::SVG_LENGTHTYPE_CM
:
256 aRes
= 10.0f
* DPI
/ MM_PER_INCH_FLOAT
;
258 case SVGLength_Binding::SVG_LENGTHTYPE_IN
:
261 case SVGLength_Binding::SVG_LENGTHTYPE_PT
:
262 aRes
= DPI
/ POINTS_PER_INCH_FLOAT
;
264 case SVGLength_Binding::SVG_LENGTHTYPE_PC
:
265 aRes
= 12.0f
* DPI
/ POINTS_PER_INCH_FLOAT
;
273 float SVGAnimatedLength::GetPixelsPerUnit(const UserSpaceMetrics
& aMetrics
,
274 uint8_t aUnitType
) const {
276 if (UserSpaceMetrics::ResolveAbsoluteUnit(aUnitType
, res
)) {
280 case SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE
:
281 return aMetrics
.GetAxisLength(mCtxType
) / 100.0f
;
282 case SVGLength_Binding::SVG_LENGTHTYPE_EMS
:
283 return aMetrics
.GetEmLength();
284 case SVGLength_Binding::SVG_LENGTHTYPE_EXS
:
285 return aMetrics
.GetExLength();
287 MOZ_ASSERT_UNREACHABLE("Unknown unit type");
292 void SVGAnimatedLength::SetBaseValueInSpecifiedUnits(float aValue
,
293 SVGElement
* aSVGElement
,
295 if (mIsBaseSet
&& mBaseVal
== aValue
) {
299 AutoChangeLengthNotifier
notifier(this, aSVGElement
, aDoSetAttr
);
308 nsresult
SVGAnimatedLength::ConvertToSpecifiedUnits(uint16_t unitType
,
309 SVGElement
* aSVGElement
) {
310 if (!IsValidUnitType(unitType
)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
312 if (mIsBaseSet
&& mSpecifiedUnitType
== uint8_t(unitType
)) return NS_OK
;
314 float pixelsPerUnit
= GetPixelsPerUnit(aSVGElement
, unitType
);
315 if (pixelsPerUnit
== 0.0f
) {
316 return NS_ERROR_ILLEGAL_VALUE
;
319 float valueInUserUnits
=
320 mBaseVal
* GetPixelsPerUnit(aSVGElement
, mSpecifiedUnitType
);
321 float valueInSpecifiedUnits
= valueInUserUnits
/ pixelsPerUnit
;
323 if (!IsFinite(valueInSpecifiedUnits
)) {
324 return NS_ERROR_ILLEGAL_VALUE
;
327 // Even though we're not changing the visual effect this length will have
328 // on the document, we still need to send out notifications in case we have
329 // mutation listeners, since the actual string value of the attribute will
331 AutoChangeLengthNotifier
notifier(this, aSVGElement
);
333 mSpecifiedUnitType
= uint8_t(unitType
);
334 // Setting aDoSetAttr to false here will ensure we don't call
335 // Will/DidChangeAngle a second time (and dispatch duplicate notifications).
336 SetBaseValueInSpecifiedUnits(valueInSpecifiedUnits
, aSVGElement
, false);
341 nsresult
SVGAnimatedLength::NewValueSpecifiedUnits(uint16_t aUnitType
,
342 float aValueInSpecifiedUnits
,
343 SVGElement
* aSVGElement
) {
344 NS_ENSURE_FINITE(aValueInSpecifiedUnits
, NS_ERROR_ILLEGAL_VALUE
);
346 if (!IsValidUnitType(aUnitType
)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
348 if (mIsBaseSet
&& mBaseVal
== aValueInSpecifiedUnits
&&
349 mSpecifiedUnitType
== uint8_t(aUnitType
)) {
353 AutoChangeLengthNotifier
notifier(this, aSVGElement
);
355 mBaseVal
= aValueInSpecifiedUnits
;
357 mSpecifiedUnitType
= uint8_t(aUnitType
);
364 already_AddRefed
<DOMSVGLength
> SVGAnimatedLength::ToDOMBaseVal(
365 SVGElement
* aSVGElement
) {
366 return DOMSVGLength::GetTearOff(this, aSVGElement
, false);
369 already_AddRefed
<DOMSVGLength
> SVGAnimatedLength::ToDOMAnimVal(
370 SVGElement
* aSVGElement
) {
371 return DOMSVGLength::GetTearOff(this, aSVGElement
, true);
376 nsresult
SVGAnimatedLength::SetBaseValueString(const nsAString
& aValueAsString
,
377 SVGElement
* aSVGElement
,
382 if (!GetValueFromString(aValueAsString
, value
, &unitType
)) {
383 return NS_ERROR_DOM_SYNTAX_ERR
;
386 if (mIsBaseSet
&& mBaseVal
== float(value
) &&
387 mSpecifiedUnitType
== uint8_t(unitType
)) {
391 AutoChangeLengthNotifier
notifier(this, aSVGElement
, aDoSetAttr
);
395 mSpecifiedUnitType
= uint8_t(unitType
);
403 void SVGAnimatedLength::GetBaseValueString(nsAString
& aValueAsString
) const {
404 GetValueString(aValueAsString
, mBaseVal
, mSpecifiedUnitType
);
407 void SVGAnimatedLength::GetAnimValueString(nsAString
& aValueAsString
) const {
408 GetValueString(aValueAsString
, mAnimVal
, mSpecifiedUnitType
);
411 nsresult
SVGAnimatedLength::SetBaseValue(float aValue
, SVGElement
* aSVGElement
,
413 float pixelsPerUnit
= GetPixelsPerUnit(aSVGElement
, mSpecifiedUnitType
);
414 if (pixelsPerUnit
== 0.0f
) {
415 return NS_ERROR_ILLEGAL_VALUE
;
418 float valueInSpecifiedUnits
= aValue
/ pixelsPerUnit
;
419 if (!IsFinite(valueInSpecifiedUnits
)) {
420 return NS_ERROR_ILLEGAL_VALUE
;
423 SetBaseValueInSpecifiedUnits(valueInSpecifiedUnits
, aSVGElement
, aDoSetAttr
);
427 void SVGAnimatedLength::SetAnimValueInSpecifiedUnits(float aValue
,
428 SVGElement
* aSVGElement
) {
429 if (mAnimVal
== aValue
&& mIsAnimated
) {
434 aSVGElement
->DidAnimateLength(mAttrEnum
);
437 nsresult
SVGAnimatedLength::SetAnimValue(float aValue
,
438 SVGElement
* aSVGElement
) {
439 float valueInSpecifiedUnits
=
440 aValue
/ GetPixelsPerUnit(aSVGElement
, mSpecifiedUnitType
);
442 if (IsFinite(valueInSpecifiedUnits
)) {
443 SetAnimValueInSpecifiedUnits(valueInSpecifiedUnits
, aSVGElement
);
446 return NS_ERROR_ILLEGAL_VALUE
;
449 already_AddRefed
<DOMSVGAnimatedLength
> SVGAnimatedLength::ToDOMAnimatedLength(
450 SVGElement
* aSVGElement
) {
451 RefPtr
<DOMSVGAnimatedLength
> svgAnimatedLength
=
452 sSVGAnimatedLengthTearoffTable
.GetTearoff(this);
453 if (!svgAnimatedLength
) {
454 svgAnimatedLength
= new DOMSVGAnimatedLength(this, aSVGElement
);
455 sSVGAnimatedLengthTearoffTable
.AddTearoff(this, svgAnimatedLength
);
458 return svgAnimatedLength
.forget();
461 DOMSVGAnimatedLength::~DOMSVGAnimatedLength() {
462 sSVGAnimatedLengthTearoffTable
.RemoveTearoff(mVal
);
465 UniquePtr
<SMILAttr
> SVGAnimatedLength::ToSMILAttr(SVGElement
* aSVGElement
) {
466 return MakeUnique
<SMILLength
>(this, aSVGElement
);
469 nsresult
SVGAnimatedLength::SMILLength::ValueFromString(
470 const nsAString
& aStr
, const SVGAnimationElement
* /*aSrcElement*/,
471 SMILValue
& aValue
, bool& aPreventCachingOfSandwich
) const {
475 if (!GetValueFromString(aStr
, value
, &unitType
)) {
476 return NS_ERROR_DOM_SYNTAX_ERR
;
479 SMILValue
val(SMILFloatType::Singleton());
480 val
.mU
.mDouble
= value
* mVal
->GetPixelsPerUnit(mSVGElement
, unitType
);
482 aPreventCachingOfSandwich
=
483 (unitType
== SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE
||
484 unitType
== SVGLength_Binding::SVG_LENGTHTYPE_EMS
||
485 unitType
== SVGLength_Binding::SVG_LENGTHTYPE_EXS
);
490 SMILValue
SVGAnimatedLength::SMILLength::GetBaseValue() const {
491 SMILValue
val(SMILFloatType::Singleton());
492 val
.mU
.mDouble
= mVal
->GetBaseValue(mSVGElement
);
496 void SVGAnimatedLength::SMILLength::ClearAnimValue() {
497 if (mVal
->mIsAnimated
) {
498 mVal
->mIsAnimated
= false;
499 mVal
->mAnimVal
= mVal
->mBaseVal
;
500 mSVGElement
->DidAnimateLength(mVal
->mAttrEnum
);
504 nsresult
SVGAnimatedLength::SMILLength::SetAnimValue(const SMILValue
& aValue
) {
505 NS_ASSERTION(aValue
.mType
== SMILFloatType::Singleton(),
506 "Unexpected type to assign animated value");
507 if (aValue
.mType
== SMILFloatType::Singleton()) {
508 return mVal
->SetAnimValue(float(aValue
.mU
.mDouble
), mSVGElement
);
513 } // namespace mozilla