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 "SVGAnimatedViewBox.h"
8 
9 #include "mozAutoDocUpdate.h"
10 #include "mozilla/Maybe.h"
11 #include <utility>
12 
13 #include "SVGViewBoxSMILType.h"
14 #include "mozilla/SMILValue.h"
15 #include "mozilla/SVGContentUtils.h"
16 #include "mozilla/dom/SVGRect.h"
17 #include "nsCharSeparatedTokenizer.h"
18 #include "nsTextFormatter.h"
19 
20 using namespace mozilla::dom;
21 
22 namespace mozilla {
23 
24 #define NUM_VIEWBOX_COMPONENTS 4
25 
26 /* Implementation of SVGViewBox methods */
27 
operator ==(const SVGViewBox & aOther) const28 bool SVGViewBox::operator==(const SVGViewBox& aOther) const {
29   if (&aOther == this) return true;
30 
31   return (none && aOther.none) ||
32          (!none && !aOther.none && x == aOther.x && y == aOther.y &&
33           width == aOther.width && height == aOther.height);
34 }
35 
36 /* static */
FromString(const nsAString & aStr,SVGViewBox * aViewBox)37 nsresult SVGViewBox::FromString(const nsAString& aStr, SVGViewBox* aViewBox) {
38   if (aStr.EqualsLiteral("none")) {
39     aViewBox->none = true;
40     return NS_OK;
41   }
42 
43   nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace,
44                                    nsTokenizerFlags::SeparatorOptional>
45       tokenizer(aStr, ',');
46   float vals[NUM_VIEWBOX_COMPONENTS];
47   uint32_t i;
48   for (i = 0; i < NUM_VIEWBOX_COMPONENTS && tokenizer.hasMoreTokens(); ++i) {
49     if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), vals[i])) {
50       return NS_ERROR_DOM_SYNTAX_ERR;
51     }
52   }
53 
54   if (i != NUM_VIEWBOX_COMPONENTS ||             // Too few values.
55       tokenizer.hasMoreTokens() ||               // Too many values.
56       tokenizer.separatorAfterCurrentToken()) {  // Trailing comma.
57     return NS_ERROR_DOM_SYNTAX_ERR;
58   }
59 
60   aViewBox->x = vals[0];
61   aViewBox->y = vals[1];
62   aViewBox->width = vals[2];
63   aViewBox->height = vals[3];
64   aViewBox->none = false;
65 
66   return NS_OK;
67 }
68 
69 static SVGAttrTearoffTable<SVGAnimatedViewBox, SVGRect>
70     sBaseSVGViewBoxTearoffTable;
71 static SVGAttrTearoffTable<SVGAnimatedViewBox, SVGRect>
72     sAnimSVGViewBoxTearoffTable;
73 SVGAttrTearoffTable<SVGAnimatedViewBox, SVGAnimatedRect>
74     SVGAnimatedViewBox::sSVGAnimatedRectTearoffTable;
75 
76 //----------------------------------------------------------------------
77 // Helper class: AutoChangeViewBoxNotifier
78 // Stack-based helper class to pair calls to WillChangeViewBox and
79 // DidChangeViewBox.
80 class MOZ_RAII AutoChangeViewBoxNotifier {
81  public:
AutoChangeViewBoxNotifier(SVGAnimatedViewBox * aViewBox,SVGElement * aSVGElement,bool aDoSetAttr=true)82   AutoChangeViewBoxNotifier(SVGAnimatedViewBox* aViewBox,
83                             SVGElement* aSVGElement, bool aDoSetAttr = true)
84       : mViewBox(aViewBox), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) {
85     MOZ_ASSERT(mViewBox, "Expecting non-null viewBox");
86     MOZ_ASSERT(mSVGElement, "Expecting non-null element");
87 
88     if (mDoSetAttr) {
89       mUpdateBatch.emplace(aSVGElement->GetComposedDoc(), true);
90       mEmptyOrOldValue = mSVGElement->WillChangeViewBox(mUpdateBatch.ref());
91     }
92   }
93 
~AutoChangeViewBoxNotifier()94   ~AutoChangeViewBoxNotifier() {
95     if (mDoSetAttr) {
96       mSVGElement->DidChangeViewBox(mEmptyOrOldValue, mUpdateBatch.ref());
97     }
98     if (mViewBox->mAnimVal) {
99       mSVGElement->AnimationNeedsResample();
100     }
101   }
102 
103  private:
104   SVGAnimatedViewBox* const mViewBox;
105   SVGElement* const mSVGElement;
106   Maybe<mozAutoDocUpdate> mUpdateBatch;
107   nsAttrValue mEmptyOrOldValue;
108   bool mDoSetAttr;
109 };
110 
111 /* Implementation of SVGAnimatedViewBox methods */
112 
Init()113 void SVGAnimatedViewBox::Init() {
114   mHasBaseVal = false;
115   // We shouldn't use mBaseVal for rendering (its usages should be guarded with
116   // "mHasBaseVal" checks), but just in case we do by accident, this will
117   // ensure that we treat it as "none" and ignore its numeric values:
118   mBaseVal.none = true;
119 
120   mAnimVal = nullptr;
121 }
122 
HasRect() const123 bool SVGAnimatedViewBox::HasRect() const {
124   // Check mAnimVal if we have one; otherwise, check mBaseVal if we have one;
125   // otherwise, just return false (we clearly do not have a rect).
126   const SVGViewBox* rect = mAnimVal.get();
127   if (!rect) {
128     if (!mHasBaseVal) {
129       // no anim val, no base val --> no viewbox rect
130       return false;
131     }
132     rect = &mBaseVal;
133   }
134 
135   return !rect->none && rect->width >= 0 && rect->height >= 0;
136 }
137 
SetAnimValue(const SVGViewBox & aRect,SVGElement * aSVGElement)138 void SVGAnimatedViewBox::SetAnimValue(const SVGViewBox& aRect,
139                                       SVGElement* aSVGElement) {
140   if (!mAnimVal) {
141     // it's okay if allocation fails - and no point in reporting that
142     mAnimVal = MakeUnique<SVGViewBox>(aRect);
143   } else {
144     if (aRect == *mAnimVal) {
145       return;
146     }
147     *mAnimVal = aRect;
148   }
149   aSVGElement->DidAnimateViewBox();
150 }
151 
SetBaseValue(const SVGViewBox & aRect,SVGElement * aSVGElement)152 void SVGAnimatedViewBox::SetBaseValue(const SVGViewBox& aRect,
153                                       SVGElement* aSVGElement) {
154   if (!mHasBaseVal || mBaseVal == aRect) {
155     // This method is used to set a single x, y, width
156     // or height value. It can't create a base value
157     // as the other components may be undefined. We record
158     // the new value though, so as not to lose data.
159     mBaseVal = aRect;
160     return;
161   }
162 
163   AutoChangeViewBoxNotifier notifier(this, aSVGElement);
164 
165   mBaseVal = aRect;
166   mHasBaseVal = true;
167 }
168 
SetBaseValueString(const nsAString & aValue,SVGElement * aSVGElement,bool aDoSetAttr)169 nsresult SVGAnimatedViewBox::SetBaseValueString(const nsAString& aValue,
170                                                 SVGElement* aSVGElement,
171                                                 bool aDoSetAttr) {
172   SVGViewBox viewBox;
173 
174   nsresult rv = SVGViewBox::FromString(aValue, &viewBox);
175   if (NS_FAILED(rv)) {
176     return rv;
177   }
178   // Comparison against mBaseVal is only valid if we currently have a base val.
179   if (mHasBaseVal && viewBox == mBaseVal) {
180     return NS_OK;
181   }
182 
183   AutoChangeViewBoxNotifier notifier(this, aSVGElement, aDoSetAttr);
184   mHasBaseVal = true;
185   mBaseVal = viewBox;
186 
187   return NS_OK;
188 }
189 
GetBaseValueString(nsAString & aValue) const190 void SVGAnimatedViewBox::GetBaseValueString(nsAString& aValue) const {
191   if (mBaseVal.none) {
192     aValue.AssignLiteral("none");
193     return;
194   }
195   nsTextFormatter::ssprintf(aValue, u"%g %g %g %g", (double)mBaseVal.x,
196                             (double)mBaseVal.y, (double)mBaseVal.width,
197                             (double)mBaseVal.height);
198 }
199 
ToSVGAnimatedRect(SVGElement * aSVGElement)200 already_AddRefed<SVGAnimatedRect> SVGAnimatedViewBox::ToSVGAnimatedRect(
201     SVGElement* aSVGElement) {
202   RefPtr<SVGAnimatedRect> domAnimatedRect =
203       sSVGAnimatedRectTearoffTable.GetTearoff(this);
204   if (!domAnimatedRect) {
205     domAnimatedRect = new SVGAnimatedRect(this, aSVGElement);
206     sSVGAnimatedRectTearoffTable.AddTearoff(this, domAnimatedRect);
207   }
208 
209   return domAnimatedRect.forget();
210 }
211 
ToDOMBaseVal(SVGElement * aSVGElement)212 already_AddRefed<SVGRect> SVGAnimatedViewBox::ToDOMBaseVal(
213     SVGElement* aSVGElement) {
214   if (!mHasBaseVal || mBaseVal.none) {
215     return nullptr;
216   }
217 
218   RefPtr<SVGRect> domBaseVal = sBaseSVGViewBoxTearoffTable.GetTearoff(this);
219   if (!domBaseVal) {
220     domBaseVal = new SVGRect(this, aSVGElement, SVGRect::RectType::BaseValue);
221     sBaseSVGViewBoxTearoffTable.AddTearoff(this, domBaseVal);
222   }
223 
224   return domBaseVal.forget();
225 }
226 
~SVGRect()227 SVGRect::~SVGRect() {
228   switch (mType) {
229     case RectType::BaseValue:
230       sBaseSVGViewBoxTearoffTable.RemoveTearoff(mVal);
231       break;
232     case RectType::AnimValue:
233       sAnimSVGViewBoxTearoffTable.RemoveTearoff(mVal);
234       break;
235     default:
236       break;
237   }
238 }
239 
ToDOMAnimVal(SVGElement * aSVGElement)240 already_AddRefed<SVGRect> SVGAnimatedViewBox::ToDOMAnimVal(
241     SVGElement* aSVGElement) {
242   if ((mAnimVal && mAnimVal->none) ||
243       (!mAnimVal && (!mHasBaseVal || mBaseVal.none))) {
244     return nullptr;
245   }
246 
247   RefPtr<SVGRect> domAnimVal = sAnimSVGViewBoxTearoffTable.GetTearoff(this);
248   if (!domAnimVal) {
249     domAnimVal = new SVGRect(this, aSVGElement, SVGRect::RectType::AnimValue);
250     sAnimSVGViewBoxTearoffTable.AddTearoff(this, domAnimVal);
251   }
252 
253   return domAnimVal.forget();
254 }
255 
ToSMILAttr(SVGElement * aSVGElement)256 UniquePtr<SMILAttr> SVGAnimatedViewBox::ToSMILAttr(SVGElement* aSVGElement) {
257   return MakeUnique<SMILViewBox>(this, aSVGElement);
258 }
259 
ValueFromString(const nsAString & aStr,const SVGAnimationElement *,SMILValue & aValue,bool & aPreventCachingOfSandwich) const260 nsresult SVGAnimatedViewBox::SMILViewBox ::ValueFromString(
261     const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/,
262     SMILValue& aValue, bool& aPreventCachingOfSandwich) const {
263   SVGViewBox viewBox;
264   nsresult res = SVGViewBox::FromString(aStr, &viewBox);
265   if (NS_FAILED(res)) {
266     return res;
267   }
268   SMILValue val(&SVGViewBoxSMILType::sSingleton);
269   *static_cast<SVGViewBox*>(val.mU.mPtr) = viewBox;
270   aValue = std::move(val);
271   aPreventCachingOfSandwich = false;
272 
273   return NS_OK;
274 }
275 
GetBaseValue() const276 SMILValue SVGAnimatedViewBox::SMILViewBox::GetBaseValue() const {
277   SMILValue val(&SVGViewBoxSMILType::sSingleton);
278   *static_cast<SVGViewBox*>(val.mU.mPtr) = mVal->mBaseVal;
279   return val;
280 }
281 
ClearAnimValue()282 void SVGAnimatedViewBox::SMILViewBox::ClearAnimValue() {
283   if (mVal->mAnimVal) {
284     mVal->mAnimVal = nullptr;
285     mSVGElement->DidAnimateViewBox();
286   }
287 }
288 
SetAnimValue(const SMILValue & aValue)289 nsresult SVGAnimatedViewBox::SMILViewBox::SetAnimValue(
290     const SMILValue& aValue) {
291   NS_ASSERTION(aValue.mType == &SVGViewBoxSMILType::sSingleton,
292                "Unexpected type to assign animated value");
293   if (aValue.mType == &SVGViewBoxSMILType::sSingleton) {
294     SVGViewBox& vb = *static_cast<SVGViewBox*>(aValue.mU.mPtr);
295     mVal->SetAnimValue(vb, mSVGElement);
296   }
297   return NS_OK;
298 }
299 
300 }  // namespace mozilla
301