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 "SVGAnimatedOrient.h"
11 #include "DOMSVGAngle.h"
12 #include "DOMSVGAnimatedAngle.h"
13 #include "SVGAttrTearoffTable.h"
14 #include "SVGOrientSMILType.h"
15 #include "mozilla/ArrayUtils.h"
16 #include "mozilla/Maybe.h"
17 #include "mozilla/SMILValue.h"
18 #include "mozilla/dom/SVGMarkerElement.h"
19 #include "mozAutoDocUpdate.h"
20 #include "nsContentUtils.h"
21 #include "nsTextFormatter.h"
22 #include "nsPrintfCString.h"
24 using namespace mozilla::dom
;
25 using namespace mozilla::dom::SVGAngle_Binding
;
26 using namespace mozilla::dom::SVGMarkerElement_Binding
;
30 static const nsStaticAtom
* const angleUnitMap
[] = {
31 nullptr, /* SVG_ANGLETYPE_UNKNOWN */
32 nullptr, /* SVG_ANGLETYPE_UNSPECIFIED */
33 nsGkAtoms::deg
, nsGkAtoms::rad
, nsGkAtoms::grad
};
35 static SVGAttrTearoffTable
<SVGAnimatedOrient
, DOMSVGAnimatedEnumeration
>
36 sSVGAnimatedEnumTearoffTable
;
37 static SVGAttrTearoffTable
<SVGAnimatedOrient
, DOMSVGAnimatedAngle
>
38 sSVGAnimatedAngleTearoffTable
;
39 static SVGAttrTearoffTable
<SVGAnimatedOrient
, DOMSVGAngle
>
40 sBaseSVGAngleTearoffTable
;
41 static SVGAttrTearoffTable
<SVGAnimatedOrient
, DOMSVGAngle
>
42 sAnimSVGAngleTearoffTable
;
44 /* Helper functions */
46 //----------------------------------------------------------------------
47 // Helper class: AutoChangeOrientNotifier
48 // Stack-based helper class to pair calls to WillChangeOrient and
49 // DidChangeOrient with mozAutoDocUpdate.
50 class MOZ_RAII AutoChangeOrientNotifier
{
52 explicit AutoChangeOrientNotifier(
53 SVGAnimatedOrient
* aOrient
, SVGElement
* aSVGElement
,
54 bool aDoSetAttr
= true MOZ_GUARD_OBJECT_NOTIFIER_PARAM
)
55 : mOrient(aOrient
), mSVGElement(aSVGElement
), mDoSetAttr(aDoSetAttr
) {
56 MOZ_GUARD_OBJECT_NOTIFIER_INIT
;
57 MOZ_ASSERT(mOrient
, "Expecting non-null orient");
58 if (mSVGElement
&& mDoSetAttr
) {
59 mUpdateBatch
.emplace(mSVGElement
->GetComposedDoc(), true);
60 mEmptyOrOldValue
= mSVGElement
->WillChangeOrient(mUpdateBatch
.ref());
64 ~AutoChangeOrientNotifier() {
67 mSVGElement
->DidChangeOrient(mEmptyOrOldValue
, mUpdateBatch
.ref());
69 if (mOrient
->mIsAnimated
) {
70 mSVGElement
->AnimationNeedsResample();
76 Maybe
<mozAutoDocUpdate
> mUpdateBatch
;
77 SVGAnimatedOrient
* const mOrient
;
78 SVGElement
* const mSVGElement
;
79 nsAttrValue mEmptyOrOldValue
;
81 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
84 static bool IsValidAngleUnitType(uint16_t unit
) {
85 return unit
> SVG_ANGLETYPE_UNKNOWN
&& unit
<= SVG_ANGLETYPE_GRAD
;
88 static void GetAngleUnitString(nsAString
& unit
, uint16_t unitType
) {
89 if (IsValidAngleUnitType(unitType
)) {
90 if (angleUnitMap
[unitType
]) {
91 angleUnitMap
[unitType
]->ToString(unit
);
96 MOZ_ASSERT_UNREACHABLE("Unknown unit type");
99 static uint16_t GetAngleUnitTypeForString(const nsAString
& unitStr
) {
100 if (unitStr
.IsEmpty()) return SVG_ANGLETYPE_UNSPECIFIED
;
102 nsStaticAtom
* unitAtom
= NS_GetStaticAtom(unitStr
);
105 for (uint32_t i
= 0; i
< ArrayLength(angleUnitMap
); i
++) {
106 if (angleUnitMap
[i
] == unitAtom
) {
112 return SVG_ANGLETYPE_UNKNOWN
;
115 static void GetAngleValueString(nsAString
& aValueAsString
, float aValue
,
116 uint16_t aUnitType
) {
117 nsTextFormatter::ssprintf(aValueAsString
, u
"%g", (double)aValue
);
119 nsAutoString unitString
;
120 GetAngleUnitString(unitString
, aUnitType
);
121 aValueAsString
.Append(unitString
);
125 bool SVGAnimatedOrient::GetValueFromString(const nsAString
& aString
,
126 float& aValue
, uint16_t* aUnitType
) {
128 auto token
= SVGContentUtils::GetAndEnsureOneToken(aString
, success
);
134 RangedPtr
<const char16_t
> iter
= SVGContentUtils::GetStartRangedPtr(token
);
135 const RangedPtr
<const char16_t
> end
= SVGContentUtils::GetEndRangedPtr(token
);
137 if (!SVGContentUtils::ParseNumber(iter
, end
, aValue
)) {
141 const nsAString
& units
= Substring(iter
.get(), end
.get());
142 *aUnitType
= GetAngleUnitTypeForString(units
);
143 return IsValidAngleUnitType(*aUnitType
);
147 float SVGAnimatedOrient::GetDegreesPerUnit(uint8_t aUnit
) {
149 case SVG_ANGLETYPE_UNSPECIFIED
:
150 case SVG_ANGLETYPE_DEG
:
152 case SVG_ANGLETYPE_RAD
:
153 return static_cast<float>(180.0 / M_PI
);
154 case SVG_ANGLETYPE_GRAD
:
155 return 90.0f
/ 100.0f
;
157 MOZ_ASSERT_UNREACHABLE("Unknown unit type");
162 void SVGAnimatedOrient::SetBaseValueInSpecifiedUnits(float aValue
,
163 SVGElement
* aSVGElement
) {
164 if (mBaseVal
== aValue
&& mBaseType
== SVG_MARKER_ORIENT_ANGLE
) {
168 AutoChangeOrientNotifier
notifier(this, aSVGElement
);
171 mBaseType
= SVG_MARKER_ORIENT_ANGLE
;
174 mAnimType
= mBaseType
;
178 nsresult
SVGAnimatedOrient::ConvertToSpecifiedUnits(uint16_t unitType
,
179 SVGElement
* aSVGElement
) {
180 if (!IsValidAngleUnitType(unitType
)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
182 if (mBaseValUnit
== uint8_t(unitType
) &&
183 mBaseType
== SVG_MARKER_ORIENT_ANGLE
) {
187 float valueInUserUnits
= mBaseVal
* GetDegreesPerUnit(mBaseValUnit
);
188 SetBaseValue(valueInUserUnits
, unitType
, aSVGElement
, true);
193 nsresult
SVGAnimatedOrient::NewValueSpecifiedUnits(uint16_t aUnitType
,
194 float aValueInSpecifiedUnits
,
195 SVGElement
* aSVGElement
) {
196 NS_ENSURE_FINITE(aValueInSpecifiedUnits
, NS_ERROR_ILLEGAL_VALUE
);
198 if (!IsValidAngleUnitType(aUnitType
)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
200 if (mBaseVal
== aValueInSpecifiedUnits
&&
201 mBaseValUnit
== uint8_t(aUnitType
) &&
202 mBaseType
== SVG_MARKER_ORIENT_ANGLE
)
205 AutoChangeOrientNotifier
notifier(this, aSVGElement
);
207 mBaseVal
= aValueInSpecifiedUnits
;
208 mBaseValUnit
= uint8_t(aUnitType
);
209 mBaseType
= SVG_MARKER_ORIENT_ANGLE
;
212 mAnimValUnit
= mBaseValUnit
;
213 mAnimType
= mBaseType
;
218 already_AddRefed
<DOMSVGAngle
> SVGAnimatedOrient::ToDOMBaseVal(
219 SVGElement
* aSVGElement
) {
220 RefPtr
<DOMSVGAngle
> domBaseVal
= sBaseSVGAngleTearoffTable
.GetTearoff(this);
222 domBaseVal
= new DOMSVGAngle(this, aSVGElement
, DOMSVGAngle::BaseValue
);
223 sBaseSVGAngleTearoffTable
.AddTearoff(this, domBaseVal
);
226 return domBaseVal
.forget();
229 already_AddRefed
<DOMSVGAngle
> SVGAnimatedOrient::ToDOMAnimVal(
230 SVGElement
* aSVGElement
) {
231 RefPtr
<DOMSVGAngle
> domAnimVal
= sAnimSVGAngleTearoffTable
.GetTearoff(this);
233 domAnimVal
= new DOMSVGAngle(this, aSVGElement
, DOMSVGAngle::AnimValue
);
234 sAnimSVGAngleTearoffTable
.AddTearoff(this, domAnimVal
);
237 return domAnimVal
.forget();
240 DOMSVGAngle::~DOMSVGAngle() {
241 if (mType
== BaseValue
) {
242 sBaseSVGAngleTearoffTable
.RemoveTearoff(mVal
);
243 } else if (mType
== AnimValue
) {
244 sAnimSVGAngleTearoffTable
.RemoveTearoff(mVal
);
252 nsresult
SVGAnimatedOrient::SetBaseValueString(const nsAString
& aValueAsString
,
253 SVGElement
* aSVGElement
,
258 bool valueChanged
= false;
260 if (aValueAsString
.EqualsLiteral("auto")) {
261 type
= SVG_MARKER_ORIENT_AUTO
;
262 if (type
== mBaseType
) {
265 } else if (aValueAsString
.EqualsLiteral("auto-start-reverse")) {
266 type
= SVG_MARKER_ORIENT_AUTO_START_REVERSE
;
267 if (type
== mBaseType
) {
271 if (!GetValueFromString(aValueAsString
, value
, &unitType
)) {
272 return NS_ERROR_DOM_SYNTAX_ERR
;
274 if (mBaseVal
== value
&& mBaseValUnit
== uint8_t(unitType
) &&
275 mBaseType
== SVG_MARKER_ORIENT_ANGLE
) {
281 AutoChangeOrientNotifier
notifier(this, aSVGElement
, aDoSetAttr
);
285 mBaseValUnit
= uint8_t(unitType
);
286 mBaseType
= SVG_MARKER_ORIENT_ANGLE
;
289 mBaseValUnit
= SVG_ANGLETYPE_UNSPECIFIED
;
295 mAnimValUnit
= mBaseValUnit
;
296 mAnimType
= mBaseType
;
302 void SVGAnimatedOrient::GetBaseValueString(nsAString
& aValueAsString
) const {
304 case SVG_MARKER_ORIENT_AUTO
:
305 aValueAsString
.AssignLiteral("auto");
307 case SVG_MARKER_ORIENT_AUTO_START_REVERSE
:
308 aValueAsString
.AssignLiteral("auto-start-reverse");
311 GetAngleValueString(aValueAsString
, mBaseVal
, mBaseValUnit
);
314 void SVGAnimatedOrient::GetBaseAngleValueString(
315 nsAString
& aValueAsString
) const {
316 GetAngleValueString(aValueAsString
, mBaseVal
, mBaseValUnit
);
319 void SVGAnimatedOrient::GetAnimAngleValueString(
320 nsAString
& aValueAsString
) const {
321 GetAngleValueString(aValueAsString
, mAnimVal
, mAnimValUnit
);
324 void SVGAnimatedOrient::SetBaseValue(float aValue
, uint8_t aUnit
,
325 SVGElement
* aSVGElement
, bool aDoSetAttr
) {
326 float valueInSpecifiedUnits
= aValue
/ GetDegreesPerUnit(aUnit
);
327 if (aUnit
== mBaseValUnit
&& mBaseVal
== valueInSpecifiedUnits
&&
328 mBaseType
== SVG_MARKER_ORIENT_ANGLE
) {
332 AutoChangeOrientNotifier
notifier(this, aSVGElement
, aDoSetAttr
);
334 mBaseValUnit
= aUnit
;
335 mBaseVal
= valueInSpecifiedUnits
;
336 mBaseType
= SVG_MARKER_ORIENT_ANGLE
;
338 mAnimValUnit
= mBaseValUnit
;
340 mAnimType
= mBaseType
;
344 void SVGAnimatedOrient::SetBaseType(SVGEnumValue aValue
,
345 SVGElement
* aSVGElement
, ErrorResult
& aRv
) {
346 if (mBaseType
== aValue
) {
349 if (aValue
== SVG_MARKER_ORIENT_AUTO
|| aValue
== SVG_MARKER_ORIENT_ANGLE
) {
350 AutoChangeOrientNotifier
notifier(this, aSVGElement
);
353 mBaseValUnit
= SVG_ANGLETYPE_UNSPECIFIED
;
357 mAnimValUnit
= mBaseValUnit
;
358 mAnimType
= mBaseType
;
362 nsPrintfCString
err("Invalid base value %u for marker orient", aValue
);
363 aRv
.ThrowTypeError(err
);
366 void SVGAnimatedOrient::SetAnimValue(float aValue
, uint8_t aUnit
,
367 SVGElement
* aSVGElement
) {
368 if (mIsAnimated
&& mAnimVal
== aValue
&& mAnimValUnit
== aUnit
&&
369 mAnimType
== SVG_MARKER_ORIENT_ANGLE
) {
373 mAnimValUnit
= aUnit
;
374 mAnimType
= SVG_MARKER_ORIENT_ANGLE
;
376 aSVGElement
->DidAnimateOrient();
379 void SVGAnimatedOrient::SetAnimType(SVGEnumValue aValue
,
380 SVGElement
* aSVGElement
) {
381 if (mIsAnimated
&& mAnimType
== aValue
) {
385 mAnimValUnit
= SVG_ANGLETYPE_UNSPECIFIED
;
388 aSVGElement
->DidAnimateOrient();
391 already_AddRefed
<DOMSVGAnimatedAngle
> SVGAnimatedOrient::ToDOMAnimatedAngle(
392 SVGElement
* aSVGElement
) {
393 RefPtr
<DOMSVGAnimatedAngle
> domAnimatedAngle
=
394 sSVGAnimatedAngleTearoffTable
.GetTearoff(this);
395 if (!domAnimatedAngle
) {
396 domAnimatedAngle
= new DOMSVGAnimatedAngle(this, aSVGElement
);
397 sSVGAnimatedAngleTearoffTable
.AddTearoff(this, domAnimatedAngle
);
400 return domAnimatedAngle
.forget();
403 already_AddRefed
<DOMSVGAnimatedEnumeration
>
404 SVGAnimatedOrient::ToDOMAnimatedEnum(SVGElement
* aSVGElement
) {
405 RefPtr
<DOMSVGAnimatedEnumeration
> domAnimatedEnum
=
406 sSVGAnimatedEnumTearoffTable
.GetTearoff(this);
407 if (!domAnimatedEnum
) {
408 domAnimatedEnum
= new DOMAnimatedEnum(this, aSVGElement
);
409 sSVGAnimatedEnumTearoffTable
.AddTearoff(this, domAnimatedEnum
);
412 return domAnimatedEnum
.forget();
415 DOMSVGAnimatedAngle::~DOMSVGAnimatedAngle() {
416 sSVGAnimatedAngleTearoffTable
.RemoveTearoff(mVal
);
419 SVGAnimatedOrient::DOMAnimatedEnum::~DOMAnimatedEnum() {
420 sSVGAnimatedEnumTearoffTable
.RemoveTearoff(mVal
);
423 // we want to avoid exposing SVG_MARKER_ORIENT_AUTO_START_REVERSE to
425 uint16_t SVGAnimatedOrient::DOMAnimatedEnum::Sanitize(uint16_t aValue
) {
426 return aValue
== dom::SVG_MARKER_ORIENT_AUTO_START_REVERSE
427 ? dom::SVGMarkerElement_Binding::SVG_MARKER_ORIENT_UNKNOWN
431 UniquePtr
<SMILAttr
> SVGAnimatedOrient::ToSMILAttr(SVGElement
* aSVGElement
) {
432 if (aSVGElement
->NodeInfo()->Equals(nsGkAtoms::marker
, kNameSpaceID_SVG
)) {
433 return MakeUnique
<SMILOrient
>(this, aSVGElement
);
435 // SMILOrient would not be useful for general angle attributes (also,
436 // "orient" is the only animatable <angle>-valued attribute in SVG 1.1).
437 MOZ_ASSERT_UNREACHABLE("Trying to animate unknown angle attribute.");
441 nsresult
SVGAnimatedOrient::SMILOrient::ValueFromString(
442 const nsAString
& aStr
, const SVGAnimationElement
* /*aSrcElement*/,
443 SMILValue
& aValue
, bool& aPreventCachingOfSandwich
) const {
444 SMILValue
val(&SVGOrientSMILType::sSingleton
);
445 if (aStr
.EqualsLiteral("auto")) {
446 val
.mU
.mOrient
.mOrientType
= SVG_MARKER_ORIENT_AUTO
;
447 val
.mU
.mOrient
.mAngle
= .0f
;
448 val
.mU
.mOrient
.mUnit
= SVG_ANGLETYPE_UNSPECIFIED
;
449 } else if (aStr
.EqualsLiteral("auto-start-reverse")) {
450 val
.mU
.mOrient
.mOrientType
= SVG_MARKER_ORIENT_AUTO_START_REVERSE
;
451 val
.mU
.mOrient
.mAngle
= .0f
;
452 val
.mU
.mOrient
.mUnit
= SVG_ANGLETYPE_UNSPECIFIED
;
456 if (!GetValueFromString(aStr
, value
, &unitType
)) {
457 return NS_ERROR_DOM_SYNTAX_ERR
;
459 val
.mU
.mOrient
.mAngle
= value
;
460 val
.mU
.mOrient
.mUnit
= unitType
;
461 val
.mU
.mOrient
.mOrientType
= SVG_MARKER_ORIENT_ANGLE
;
463 aValue
= std::move(val
);
464 aPreventCachingOfSandwich
= false;
469 SMILValue
SVGAnimatedOrient::SMILOrient::GetBaseValue() const {
470 SMILValue
val(&SVGOrientSMILType::sSingleton
);
471 val
.mU
.mOrient
.mAngle
= mOrient
->GetBaseValInSpecifiedUnits();
472 val
.mU
.mOrient
.mUnit
= mOrient
->GetBaseValueUnit();
473 val
.mU
.mOrient
.mOrientType
= mOrient
->mBaseType
;
477 void SVGAnimatedOrient::SMILOrient::ClearAnimValue() {
478 if (mOrient
->mIsAnimated
) {
479 mOrient
->mIsAnimated
= false;
480 mOrient
->mAnimVal
= mOrient
->mBaseVal
;
481 mOrient
->mAnimValUnit
= mOrient
->mBaseValUnit
;
482 mOrient
->mAnimType
= mOrient
->mBaseType
;
483 mSVGElement
->DidAnimateOrient();
487 nsresult
SVGAnimatedOrient::SMILOrient::SetAnimValue(const SMILValue
& aValue
) {
488 NS_ASSERTION(aValue
.mType
== &SVGOrientSMILType::sSingleton
,
489 "Unexpected type to assign animated value");
491 if (aValue
.mType
== &SVGOrientSMILType::sSingleton
) {
492 if (aValue
.mU
.mOrient
.mOrientType
== SVG_MARKER_ORIENT_AUTO
||
493 aValue
.mU
.mOrient
.mOrientType
== SVG_MARKER_ORIENT_AUTO_START_REVERSE
) {
494 mOrient
->SetAnimType(aValue
.mU
.mOrient
.mOrientType
, mSVGElement
);
496 mOrient
->SetAnimValue(aValue
.mU
.mOrient
.mAngle
, aValue
.mU
.mOrient
.mUnit
,
503 } // namespace mozilla