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/. */
6
7 #include "DOMSVGLength.h"
8 #include "DOMSVGLengthList.h"
9 #include "DOMSVGAnimatedLengthList.h"
10 #include "SVGLength.h"
11 #include "SVGAnimatedLengthList.h"
12 #include "nsSVGElement.h"
13 #include "nsSVGLength2.h"
14 #include "nsIDOMSVGLength.h"
15 #include "nsError.h"
16 #include "nsMathUtils.h"
17 #include "mozilla/dom/SVGLengthBinding.h"
18 #include "mozilla/FloatingPoint.h"
19 #include "nsSVGAttrTearoffTable.h"
20
21 // See the architecture comment in DOMSVGAnimatedLengthList.h.
22
23 namespace mozilla {
24
25 static nsSVGAttrTearoffTable<nsSVGLength2, DOMSVGLength>
26 sBaseSVGLengthTearOffTable,
27 sAnimSVGLengthTearOffTable;
28
29 // We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to
30 // clear our list's weak ref to us to be safe. (The other option would be to
31 // not unlink and rely on the breaking of the other edges in the cycle, as
32 // NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.)
33 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGLength)
34
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGLength)
36 tmp->CleanupWeakRefs();
37 tmp->mVal = nullptr; // (owned by mSVGElement, which we drop our ref to here)
38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mList)
39 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSVGElement)
40 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
41 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
42
43 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGLength)
44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mList)
45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSVGElement)
46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
48
49 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGLength)
50 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
51 NS_IMPL_CYCLE_COLLECTION_TRACE_END
52
53 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGLength)
54 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGLength)
55
56 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGLength)
57 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
58 NS_INTERFACE_MAP_ENTRY(mozilla::DOMSVGLength) // pseudo-interface
59 NS_INTERFACE_MAP_ENTRY(nsIDOMSVGLength)
60 NS_INTERFACE_MAP_ENTRY(nsISupports)
61 NS_INTERFACE_MAP_END
62
63 //----------------------------------------------------------------------
64 // Helper class: AutoChangeLengthNotifier
65 // Stack-based helper class to pair calls to WillChangeLengthList and
66 // DidChangeLengthList.
67 class MOZ_RAII AutoChangeLengthNotifier
68 {
69 public:
AutoChangeLengthNotifier(DOMSVGLength * aLength MOZ_GUARD_OBJECT_NOTIFIER_PARAM)70 explicit AutoChangeLengthNotifier(DOMSVGLength* aLength MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
71 : mLength(aLength)
72 {
73 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
74 MOZ_ASSERT(mLength, "Expecting non-null length");
75 MOZ_ASSERT(mLength->HasOwner(),
76 "Expecting list to have an owner for notification");
77 mEmptyOrOldValue =
78 mLength->Element()->WillChangeLengthList(mLength->mAttrEnum);
79 }
80
~AutoChangeLengthNotifier()81 ~AutoChangeLengthNotifier()
82 {
83 mLength->Element()->DidChangeLengthList(mLength->mAttrEnum,
84 mEmptyOrOldValue);
85 if (mLength->mList->IsAnimating()) {
86 mLength->Element()->AnimationNeedsResample();
87 }
88 }
89
90 private:
91 DOMSVGLength* const mLength;
92 nsAttrValue mEmptyOrOldValue;
93 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
94 };
95
DOMSVGLength(DOMSVGLengthList * aList,uint8_t aAttrEnum,uint32_t aListIndex,bool aIsAnimValItem)96 DOMSVGLength::DOMSVGLength(DOMSVGLengthList *aList,
97 uint8_t aAttrEnum,
98 uint32_t aListIndex,
99 bool aIsAnimValItem)
100 : mList(aList)
101 , mListIndex(aListIndex)
102 , mAttrEnum(aAttrEnum)
103 , mIsAnimValItem(aIsAnimValItem)
104 , mUnit(nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER)
105 , mValue(0.0f)
106 , mVal(nullptr)
107 {
108 // These shifts are in sync with the members in the header.
109 MOZ_ASSERT(aList &&
110 aAttrEnum < (1 << 4) &&
111 aListIndex <= MaxListIndex(),
112 "bad arg");
113
114 MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGNumber!");
115 }
116
DOMSVGLength()117 DOMSVGLength::DOMSVGLength()
118 : mList(nullptr)
119 , mListIndex(0)
120 , mAttrEnum(0)
121 , mIsAnimValItem(false)
122 , mUnit(nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER)
123 , mValue(0.0f)
124 , mVal(nullptr)
125 {
126 }
127
DOMSVGLength(nsSVGLength2 * aVal,nsSVGElement * aSVGElement,bool aAnimVal)128 DOMSVGLength::DOMSVGLength(nsSVGLength2* aVal, nsSVGElement* aSVGElement,
129 bool aAnimVal)
130 : mList(nullptr)
131 , mListIndex(0)
132 , mAttrEnum(0)
133 , mIsAnimValItem(aAnimVal)
134 , mUnit(nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER)
135 , mValue(0.0f)
136 , mVal(aVal)
137 , mSVGElement(aSVGElement)
138 {
139 }
140
141 void
CleanupWeakRefs()142 DOMSVGLength::CleanupWeakRefs()
143 {
144 // Our mList's weak ref to us must be nulled out when we die (or when we're
145 // cycle collected), so we that don't leave behind a pointer to
146 // free / soon-to-be-free memory.
147 if (mList) {
148 MOZ_ASSERT(mList->mItems[mListIndex] == this,
149 "Clearing out the wrong list index...?");
150 mList->mItems[mListIndex] = nullptr;
151 }
152
153 // Similarly, we must update the tearoff table to remove its (non-owning)
154 // pointer to mVal.
155 if (mVal) {
156 auto& table = mIsAnimValItem ?
157 sAnimSVGLengthTearOffTable : sBaseSVGLengthTearOffTable;
158 table.RemoveTearoff(mVal);
159 }
160 }
161
~DOMSVGLength()162 DOMSVGLength::~DOMSVGLength()
163 {
164 CleanupWeakRefs();
165 }
166
167 already_AddRefed<DOMSVGLength>
GetTearOff(nsSVGLength2 * aVal,nsSVGElement * aSVGElement,bool aAnimVal)168 DOMSVGLength::GetTearOff(nsSVGLength2* aVal, nsSVGElement* aSVGElement,
169 bool aAnimVal)
170 {
171 auto& table = aAnimVal ? sAnimSVGLengthTearOffTable : sBaseSVGLengthTearOffTable;
172 RefPtr<DOMSVGLength> domLength = table.GetTearoff(aVal);
173 if (!domLength) {
174 domLength = new DOMSVGLength(aVal, aSVGElement, aAnimVal);
175 table.AddTearoff(aVal, domLength);
176 }
177
178 return domLength.forget();
179 }
180
181 DOMSVGLength*
Copy()182 DOMSVGLength::Copy()
183 {
184 NS_ASSERTION(HasOwner() || IsReflectingAttribute(), "unexpected caller");
185 DOMSVGLength *copy = new DOMSVGLength();
186 uint16_t unit;
187 float value;
188 if (mVal) {
189 unit = mVal->mSpecifiedUnitType;
190 value = mIsAnimValItem ? mVal->mAnimVal : mVal->mBaseVal;
191 } else {
192 SVGLength &length = InternalItem();
193 unit = length.GetUnit();
194 value = length.GetValueInCurrentUnits();
195 }
196 copy->NewValueSpecifiedUnits(unit, value);
197 return copy;
198 }
199
200 uint16_t
UnitType()201 DOMSVGLength::UnitType()
202 {
203 if (mVal) {
204 if (mIsAnimValItem) {
205 mSVGElement->FlushAnimations();
206 }
207 return mVal->mSpecifiedUnitType;
208 }
209
210 if (mIsAnimValItem && HasOwner()) {
211 Element()->FlushAnimations(); // May make HasOwner() == false
212 }
213 return HasOwner() ? InternalItem().GetUnit() : mUnit;
214 }
215
216 NS_IMETHODIMP
GetUnitType(uint16_t * aUnit)217 DOMSVGLength::GetUnitType(uint16_t* aUnit)
218 {
219 *aUnit = UnitType();
220 return NS_OK;
221 }
222
223 float
GetValue(ErrorResult & aRv)224 DOMSVGLength::GetValue(ErrorResult& aRv)
225 {
226 if (mVal) {
227 if (mIsAnimValItem) {
228 mSVGElement->FlushAnimations();
229 return mVal->GetAnimValue(mSVGElement);
230 }
231 return mVal->GetBaseValue(mSVGElement);
232 }
233
234 if (mIsAnimValItem && HasOwner()) {
235 Element()->FlushAnimations(); // May make HasOwner() == false
236 }
237 if (HasOwner()) {
238 float value = InternalItem().GetValueInUserUnits(Element(), Axis());
239 if (!IsFinite(value)) {
240 aRv.Throw(NS_ERROR_FAILURE);
241 }
242 return value;
243 } else if (mUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER ||
244 mUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_PX) {
245 return mValue;
246 }
247 // else [SVGWG issue] Can't convert this length's value to user units
248 // ReportToConsole
249 aRv.Throw(NS_ERROR_FAILURE);
250 return 0.0f;
251 }
252
253 NS_IMETHODIMP
GetValue(float * aValue)254 DOMSVGLength::GetValue(float* aValue)
255 {
256 ErrorResult rv;
257 *aValue = GetValue(rv);
258 return rv.StealNSResult();
259 }
260
261 void
SetValue(float aUserUnitValue,ErrorResult & aRv)262 DOMSVGLength::SetValue(float aUserUnitValue, ErrorResult& aRv)
263 {
264 if (mIsAnimValItem) {
265 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
266 return;
267 }
268
269 if (mVal) {
270 mVal->SetBaseValue(aUserUnitValue, mSVGElement, true);
271 return;
272 }
273
274 // Although the value passed in is in user units, this method does not turn
275 // this length into a user unit length. Instead it converts the user unit
276 // value to this length's current unit and sets that, leaving this length's
277 // unit as it is.
278
279 if (HasOwner()) {
280 if (InternalItem().GetValueInUserUnits(Element(), Axis()) ==
281 aUserUnitValue) {
282 return;
283 }
284 float uuPerUnit = InternalItem().GetUserUnitsPerUnit(Element(), Axis());
285 if (uuPerUnit > 0) {
286 float newValue = aUserUnitValue / uuPerUnit;
287 if (IsFinite(newValue)) {
288 AutoChangeLengthNotifier notifier(this);
289 InternalItem().SetValueAndUnit(newValue, InternalItem().GetUnit());
290 return;
291 }
292 }
293 } else if (mUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER ||
294 mUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_PX) {
295 mValue = aUserUnitValue;
296 return;
297 }
298 // else [SVGWG issue] Can't convert user unit value to this length's unit
299 // ReportToConsole
300 aRv.Throw(NS_ERROR_FAILURE);
301 }
302
303 NS_IMETHODIMP
SetValue(float aUserUnitValue)304 DOMSVGLength::SetValue(float aUserUnitValue)
305 {
306 if (!IsFinite(aUserUnitValue)) {
307 return NS_ERROR_ILLEGAL_VALUE;
308 }
309
310 ErrorResult rv;
311 SetValue(aUserUnitValue, rv);
312 return rv.StealNSResult();
313 }
314
315 float
ValueInSpecifiedUnits()316 DOMSVGLength::ValueInSpecifiedUnits()
317 {
318 if (mVal) {
319 if (mIsAnimValItem) {
320 mSVGElement->FlushAnimations();
321 return mVal->mAnimVal;
322 }
323 return mVal->mBaseVal;
324 }
325
326 if (mIsAnimValItem && HasOwner()) {
327 Element()->FlushAnimations(); // May make HasOwner() == false
328 }
329 return HasOwner() ? InternalItem().GetValueInCurrentUnits() : mValue;
330 }
331
332 NS_IMETHODIMP
GetValueInSpecifiedUnits(float * aValue)333 DOMSVGLength::GetValueInSpecifiedUnits(float* aValue)
334 {
335 *aValue = ValueInSpecifiedUnits();
336 return NS_OK;
337 }
338
339 void
SetValueInSpecifiedUnits(float aValue,ErrorResult & aRv)340 DOMSVGLength::SetValueInSpecifiedUnits(float aValue, ErrorResult& aRv)
341 {
342 if (mIsAnimValItem) {
343 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
344 return;
345 }
346
347 if (mVal) {
348 mVal->SetBaseValueInSpecifiedUnits(aValue, mSVGElement, true);
349 return;
350 }
351
352 if (HasOwner()) {
353 if (InternalItem().GetValueInCurrentUnits() == aValue) {
354 return;
355 }
356 AutoChangeLengthNotifier notifier(this);
357 InternalItem().SetValueInCurrentUnits(aValue);
358 return;
359 }
360 mValue = aValue;
361 }
362
363 NS_IMETHODIMP
SetValueInSpecifiedUnits(float aValue)364 DOMSVGLength::SetValueInSpecifiedUnits(float aValue)
365 {
366 if (!IsFinite(aValue)) {
367 return NS_ERROR_ILLEGAL_VALUE;
368 }
369
370 ErrorResult rv;
371 SetValueInSpecifiedUnits(aValue, rv);
372 return rv.StealNSResult();
373 }
374
375 void
SetValueAsString(const nsAString & aValue,ErrorResult & aRv)376 DOMSVGLength::SetValueAsString(const nsAString& aValue, ErrorResult& aRv)
377 {
378 if (mIsAnimValItem) {
379 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
380 return;
381 }
382
383 if (mVal) {
384 aRv = mVal->SetBaseValueString(aValue, mSVGElement, true);
385 return;
386 }
387
388 SVGLength value;
389 if (!value.SetValueFromString(aValue)) {
390 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
391 return;
392 }
393 if (HasOwner()) {
394 if (InternalItem() == value) {
395 return;
396 }
397 AutoChangeLengthNotifier notifier(this);
398 InternalItem() = value;
399 return;
400 }
401 mValue = value.GetValueInCurrentUnits();
402 mUnit = value.GetUnit();
403 }
404
405 NS_IMETHODIMP
SetValueAsString(const nsAString & aValue)406 DOMSVGLength::SetValueAsString(const nsAString& aValue)
407 {
408 ErrorResult rv;
409 SetValueAsString(aValue, rv);
410 return rv.StealNSResult();
411 }
412
413 NS_IMETHODIMP
GetValueAsString(nsAString & aValue)414 DOMSVGLength::GetValueAsString(nsAString& aValue)
415 {
416 if (mVal) {
417 if (mIsAnimValItem) {
418 mSVGElement->FlushAnimations();
419 mVal->GetAnimValueString(aValue);
420 } else {
421 mVal->GetBaseValueString(aValue);
422 }
423 return NS_OK;
424 }
425
426 if (mIsAnimValItem && HasOwner()) {
427 Element()->FlushAnimations(); // May make HasOwner() == false
428 }
429 if (HasOwner()) {
430 InternalItem().GetValueAsString(aValue);
431 return NS_OK;
432 }
433 SVGLength(mValue, mUnit).GetValueAsString(aValue);
434 return NS_OK;
435 }
436
437 void
NewValueSpecifiedUnits(uint16_t aUnit,float aValue,ErrorResult & aRv)438 DOMSVGLength::NewValueSpecifiedUnits(uint16_t aUnit, float aValue,
439 ErrorResult& aRv)
440 {
441 if (mIsAnimValItem) {
442 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
443 return;
444 }
445
446 if (mVal) {
447 mVal->NewValueSpecifiedUnits(aUnit, aValue, mSVGElement);
448 return;
449 }
450
451 if (!SVGLength::IsValidUnitType(aUnit)) {
452 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
453 return;
454 }
455 if (HasOwner()) {
456 if (InternalItem().GetUnit() == aUnit &&
457 InternalItem().GetValueInCurrentUnits() == aValue) {
458 return;
459 }
460 AutoChangeLengthNotifier notifier(this);
461 InternalItem().SetValueAndUnit(aValue, uint8_t(aUnit));
462 return;
463 }
464 mUnit = uint8_t(aUnit);
465 mValue = aValue;
466 }
467
468 NS_IMETHODIMP
NewValueSpecifiedUnits(uint16_t aUnit,float aValue)469 DOMSVGLength::NewValueSpecifiedUnits(uint16_t aUnit, float aValue)
470 {
471 if (!IsFinite(aValue)) {
472 return NS_ERROR_ILLEGAL_VALUE;
473 }
474
475 ErrorResult rv;
476 NewValueSpecifiedUnits(aUnit, aValue, rv);
477 return rv.StealNSResult();
478 }
479
480 void
ConvertToSpecifiedUnits(uint16_t aUnit,ErrorResult & aRv)481 DOMSVGLength::ConvertToSpecifiedUnits(uint16_t aUnit, ErrorResult& aRv)
482 {
483 if (mIsAnimValItem) {
484 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
485 return;
486 }
487
488 if (mVal) {
489 mVal->ConvertToSpecifiedUnits(aUnit, mSVGElement);
490 return;
491 }
492
493 if (!SVGLength::IsValidUnitType(aUnit)) {
494 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
495 return;
496 }
497 if (HasOwner()) {
498 if (InternalItem().GetUnit() == aUnit) {
499 return;
500 }
501 float val = InternalItem().GetValueInSpecifiedUnit(
502 aUnit, Element(), Axis());
503 if (IsFinite(val)) {
504 AutoChangeLengthNotifier notifier(this);
505 InternalItem().SetValueAndUnit(val, aUnit);
506 return;
507 }
508 } else {
509 SVGLength len(mValue, mUnit);
510 float val = len.GetValueInSpecifiedUnit(aUnit, nullptr, 0);
511 if (IsFinite(val)) {
512 mValue = val;
513 mUnit = aUnit;
514 return;
515 }
516 }
517 // else [SVGWG issue] Can't convert unit
518 // ReportToConsole
519 aRv.Throw(NS_ERROR_FAILURE);
520 }
521
522 NS_IMETHODIMP
ConvertToSpecifiedUnits(uint16_t aUnit)523 DOMSVGLength::ConvertToSpecifiedUnits(uint16_t aUnit)
524 {
525 ErrorResult rv;
526 ConvertToSpecifiedUnits(aUnit, rv);
527 return rv.StealNSResult();
528 }
529
530 JSObject*
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)531 DOMSVGLength::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
532 {
533 return dom::SVGLengthBinding::Wrap(aCx, this, aGivenProto);
534 }
535
536 void
InsertingIntoList(DOMSVGLengthList * aList,uint8_t aAttrEnum,uint32_t aListIndex,bool aIsAnimValItem)537 DOMSVGLength::InsertingIntoList(DOMSVGLengthList *aList,
538 uint8_t aAttrEnum,
539 uint32_t aListIndex,
540 bool aIsAnimValItem)
541 {
542 NS_ASSERTION(!HasOwner(), "Inserting item that is already in a list");
543
544 mList = aList;
545 mAttrEnum = aAttrEnum;
546 mListIndex = aListIndex;
547 mIsAnimValItem = aIsAnimValItem;
548
549 MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGLength!");
550 }
551
552 void
RemovingFromList()553 DOMSVGLength::RemovingFromList()
554 {
555 mValue = InternalItem().GetValueInCurrentUnits();
556 mUnit = InternalItem().GetUnit();
557 mList = nullptr;
558 mIsAnimValItem = false;
559 }
560
561 SVGLength
ToSVGLength()562 DOMSVGLength::ToSVGLength()
563 {
564 if (HasOwner()) {
565 return SVGLength(InternalItem().GetValueInCurrentUnits(),
566 InternalItem().GetUnit());
567 }
568 return SVGLength(mValue, mUnit);
569 }
570
571 SVGLength&
InternalItem()572 DOMSVGLength::InternalItem()
573 {
574 SVGAnimatedLengthList *alist = Element()->GetAnimatedLengthList(mAttrEnum);
575 return mIsAnimValItem && alist->mAnimVal ?
576 (*alist->mAnimVal)[mListIndex] :
577 alist->mBaseVal[mListIndex];
578 }
579
580 #ifdef DEBUG
581 bool
IndexIsValid()582 DOMSVGLength::IndexIsValid()
583 {
584 SVGAnimatedLengthList *alist = Element()->GetAnimatedLengthList(mAttrEnum);
585 return (mIsAnimValItem &&
586 mListIndex < alist->GetAnimValue().Length()) ||
587 (!mIsAnimValItem &&
588 mListIndex < alist->GetBaseValue().Length());
589 }
590 #endif
591
592 } // namespace mozilla
593