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