|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /* representation of a value for a SMIL-animated CSS property */ |
|
7 |
|
8 #include "nsSMILCSSValueType.h" |
|
9 #include "nsString.h" |
|
10 #include "nsStyleAnimation.h" |
|
11 #include "nsSMILParserUtils.h" |
|
12 #include "nsSMILValue.h" |
|
13 #include "nsCSSValue.h" |
|
14 #include "nsColor.h" |
|
15 #include "nsPresContext.h" |
|
16 #include "mozilla/dom/Element.h" |
|
17 #include "nsDebug.h" |
|
18 #include "nsStyleUtil.h" |
|
19 #include "nsIDocument.h" |
|
20 |
|
21 using namespace mozilla::dom; |
|
22 |
|
23 /*static*/ nsSMILCSSValueType nsSMILCSSValueType::sSingleton; |
|
24 |
|
25 struct ValueWrapper { |
|
26 ValueWrapper(nsCSSProperty aPropID, const nsStyleAnimation::Value& aValue) : |
|
27 mPropID(aPropID), mCSSValue(aValue) {} |
|
28 |
|
29 nsCSSProperty mPropID; |
|
30 nsStyleAnimation::Value mCSSValue; |
|
31 }; |
|
32 |
|
33 // Helper Methods |
|
34 // -------------- |
|
35 static const nsStyleAnimation::Value* |
|
36 GetZeroValueForUnit(nsStyleAnimation::Unit aUnit) |
|
37 { |
|
38 static const nsStyleAnimation::Value |
|
39 sZeroCoord(0, nsStyleAnimation::Value::CoordConstructor); |
|
40 static const nsStyleAnimation::Value |
|
41 sZeroPercent(0.0f, nsStyleAnimation::Value::PercentConstructor); |
|
42 static const nsStyleAnimation::Value |
|
43 sZeroFloat(0.0f, nsStyleAnimation::Value::FloatConstructor); |
|
44 static const nsStyleAnimation::Value |
|
45 sZeroColor(NS_RGB(0,0,0), nsStyleAnimation::Value::ColorConstructor); |
|
46 |
|
47 NS_ABORT_IF_FALSE(aUnit != nsStyleAnimation::eUnit_Null, |
|
48 "Need non-null unit for a zero value"); |
|
49 switch (aUnit) { |
|
50 case nsStyleAnimation::eUnit_Coord: |
|
51 return &sZeroCoord; |
|
52 case nsStyleAnimation::eUnit_Percent: |
|
53 return &sZeroPercent; |
|
54 case nsStyleAnimation::eUnit_Float: |
|
55 return &sZeroFloat; |
|
56 case nsStyleAnimation::eUnit_Color: |
|
57 return &sZeroColor; |
|
58 default: |
|
59 return nullptr; |
|
60 } |
|
61 } |
|
62 |
|
63 // This method requires at least one of its arguments to be non-null. |
|
64 // |
|
65 // If one argument is null, this method updates it to point to "zero" |
|
66 // for the other argument's Unit (if applicable; otherwise, we return false). |
|
67 // |
|
68 // If neither argument is null, this method generally does nothing, though it |
|
69 // may apply a workaround for the special case where a 0 length-value is mixed |
|
70 // with a eUnit_Float value. (See comment below.) |
|
71 // |
|
72 // Returns true on success, or false. |
|
73 static const bool |
|
74 FinalizeStyleAnimationValues(const nsStyleAnimation::Value*& aValue1, |
|
75 const nsStyleAnimation::Value*& aValue2) |
|
76 { |
|
77 NS_ABORT_IF_FALSE(aValue1 || aValue2, |
|
78 "expecting at least one non-null value"); |
|
79 |
|
80 // Are we missing either val? (If so, it's an implied 0 in other val's units) |
|
81 if (!aValue1) { |
|
82 aValue1 = GetZeroValueForUnit(aValue2->GetUnit()); |
|
83 return !!aValue1; // Fail if we have no zero value for this unit. |
|
84 } |
|
85 if (!aValue2) { |
|
86 aValue2 = GetZeroValueForUnit(aValue1->GetUnit()); |
|
87 return !!aValue2; // Fail if we have no zero value for this unit. |
|
88 } |
|
89 |
|
90 // Ok, both values were specified. |
|
91 // Need to handle a special-case, though: unitless nonzero length (parsed as |
|
92 // eUnit_Float) mixed with unitless 0 length (parsed as eUnit_Coord). These |
|
93 // won't interoperate in nsStyleAnimation, since their Units don't match. |
|
94 // In this case, we replace the eUnit_Coord 0 value with eUnit_Float 0 value. |
|
95 const nsStyleAnimation::Value& zeroCoord = |
|
96 *GetZeroValueForUnit(nsStyleAnimation::eUnit_Coord); |
|
97 if (*aValue1 == zeroCoord && |
|
98 aValue2->GetUnit() == nsStyleAnimation::eUnit_Float) { |
|
99 aValue1 = GetZeroValueForUnit(nsStyleAnimation::eUnit_Float); |
|
100 } else if (*aValue2 == zeroCoord && |
|
101 aValue1->GetUnit() == nsStyleAnimation::eUnit_Float) { |
|
102 aValue2 = GetZeroValueForUnit(nsStyleAnimation::eUnit_Float); |
|
103 } |
|
104 |
|
105 return true; |
|
106 } |
|
107 |
|
108 static void |
|
109 InvertSign(nsStyleAnimation::Value& aValue) |
|
110 { |
|
111 switch (aValue.GetUnit()) { |
|
112 case nsStyleAnimation::eUnit_Coord: |
|
113 aValue.SetCoordValue(-aValue.GetCoordValue()); |
|
114 break; |
|
115 case nsStyleAnimation::eUnit_Percent: |
|
116 aValue.SetPercentValue(-aValue.GetPercentValue()); |
|
117 break; |
|
118 case nsStyleAnimation::eUnit_Float: |
|
119 aValue.SetFloatValue(-aValue.GetFloatValue()); |
|
120 break; |
|
121 default: |
|
122 NS_NOTREACHED("Calling InvertSign with an unsupported unit"); |
|
123 break; |
|
124 } |
|
125 } |
|
126 |
|
127 static ValueWrapper* |
|
128 ExtractValueWrapper(nsSMILValue& aValue) |
|
129 { |
|
130 return static_cast<ValueWrapper*>(aValue.mU.mPtr); |
|
131 } |
|
132 |
|
133 static const ValueWrapper* |
|
134 ExtractValueWrapper(const nsSMILValue& aValue) |
|
135 { |
|
136 return static_cast<const ValueWrapper*>(aValue.mU.mPtr); |
|
137 } |
|
138 |
|
139 // Class methods |
|
140 // ------------- |
|
141 void |
|
142 nsSMILCSSValueType::Init(nsSMILValue& aValue) const |
|
143 { |
|
144 NS_ABORT_IF_FALSE(aValue.IsNull(), "Unexpected SMIL value type"); |
|
145 |
|
146 aValue.mU.mPtr = nullptr; |
|
147 aValue.mType = this; |
|
148 } |
|
149 |
|
150 void |
|
151 nsSMILCSSValueType::Destroy(nsSMILValue& aValue) const |
|
152 { |
|
153 NS_ABORT_IF_FALSE(aValue.mType == this, "Unexpected SMIL value type"); |
|
154 delete static_cast<ValueWrapper*>(aValue.mU.mPtr); |
|
155 aValue.mType = nsSMILNullType::Singleton(); |
|
156 } |
|
157 |
|
158 nsresult |
|
159 nsSMILCSSValueType::Assign(nsSMILValue& aDest, const nsSMILValue& aSrc) const |
|
160 { |
|
161 NS_ABORT_IF_FALSE(aDest.mType == aSrc.mType, "Incompatible SMIL types"); |
|
162 NS_ABORT_IF_FALSE(aDest.mType == this, "Unexpected SMIL value type"); |
|
163 const ValueWrapper* srcWrapper = ExtractValueWrapper(aSrc); |
|
164 ValueWrapper* destWrapper = ExtractValueWrapper(aDest); |
|
165 |
|
166 if (srcWrapper) { |
|
167 if (!destWrapper) { |
|
168 // barely-initialized dest -- need to alloc & copy |
|
169 aDest.mU.mPtr = new ValueWrapper(*srcWrapper); |
|
170 } else { |
|
171 // both already fully-initialized -- just copy straight across |
|
172 *destWrapper = *srcWrapper; |
|
173 } |
|
174 } else if (destWrapper) { |
|
175 // fully-initialized dest, barely-initialized src -- clear dest |
|
176 delete destWrapper; |
|
177 aDest.mU.mPtr = destWrapper = nullptr; |
|
178 } // else, both are barely-initialized -- nothing to do. |
|
179 |
|
180 return NS_OK; |
|
181 } |
|
182 |
|
183 bool |
|
184 nsSMILCSSValueType::IsEqual(const nsSMILValue& aLeft, |
|
185 const nsSMILValue& aRight) const |
|
186 { |
|
187 NS_ABORT_IF_FALSE(aLeft.mType == aRight.mType, "Incompatible SMIL types"); |
|
188 NS_ABORT_IF_FALSE(aLeft.mType == this, "Unexpected SMIL value"); |
|
189 const ValueWrapper* leftWrapper = ExtractValueWrapper(aLeft); |
|
190 const ValueWrapper* rightWrapper = ExtractValueWrapper(aRight); |
|
191 |
|
192 if (leftWrapper) { |
|
193 if (rightWrapper) { |
|
194 // Both non-null |
|
195 NS_WARN_IF_FALSE(leftWrapper != rightWrapper, |
|
196 "Two nsSMILValues with matching ValueWrapper ptr"); |
|
197 return (leftWrapper->mPropID == rightWrapper->mPropID && |
|
198 leftWrapper->mCSSValue == rightWrapper->mCSSValue); |
|
199 } |
|
200 // Left non-null, right null |
|
201 return false; |
|
202 } |
|
203 if (rightWrapper) { |
|
204 // Left null, right non-null |
|
205 return false; |
|
206 } |
|
207 // Both null |
|
208 return true; |
|
209 } |
|
210 |
|
211 nsresult |
|
212 nsSMILCSSValueType::Add(nsSMILValue& aDest, const nsSMILValue& aValueToAdd, |
|
213 uint32_t aCount) const |
|
214 { |
|
215 NS_ABORT_IF_FALSE(aValueToAdd.mType == aDest.mType, |
|
216 "Trying to add invalid types"); |
|
217 NS_ABORT_IF_FALSE(aValueToAdd.mType == this, "Unexpected source type"); |
|
218 |
|
219 ValueWrapper* destWrapper = ExtractValueWrapper(aDest); |
|
220 const ValueWrapper* valueToAddWrapper = ExtractValueWrapper(aValueToAdd); |
|
221 NS_ABORT_IF_FALSE(destWrapper || valueToAddWrapper, |
|
222 "need at least one fully-initialized value"); |
|
223 |
|
224 nsCSSProperty property = (valueToAddWrapper ? valueToAddWrapper->mPropID : |
|
225 destWrapper->mPropID); |
|
226 // Special case: font-size-adjust and stroke-dasharray are explicitly |
|
227 // non-additive (even though nsStyleAnimation *could* support adding them) |
|
228 if (property == eCSSProperty_font_size_adjust || |
|
229 property == eCSSProperty_stroke_dasharray) { |
|
230 return NS_ERROR_FAILURE; |
|
231 } |
|
232 |
|
233 const nsStyleAnimation::Value* valueToAdd = valueToAddWrapper ? |
|
234 &valueToAddWrapper->mCSSValue : nullptr; |
|
235 const nsStyleAnimation::Value* destValue = destWrapper ? |
|
236 &destWrapper->mCSSValue : nullptr; |
|
237 if (!FinalizeStyleAnimationValues(valueToAdd, destValue)) { |
|
238 return NS_ERROR_FAILURE; |
|
239 } |
|
240 // Did FinalizeStyleAnimationValues change destValue? |
|
241 // If so, update outparam to use the new value. |
|
242 if (destWrapper && &destWrapper->mCSSValue != destValue) { |
|
243 destWrapper->mCSSValue = *destValue; |
|
244 } |
|
245 |
|
246 // Handle barely-initialized "zero" destination. |
|
247 if (!destWrapper) { |
|
248 aDest.mU.mPtr = destWrapper = |
|
249 new ValueWrapper(property, *destValue); |
|
250 } |
|
251 |
|
252 return nsStyleAnimation::Add(property, |
|
253 destWrapper->mCSSValue, *valueToAdd, aCount) ? |
|
254 NS_OK : NS_ERROR_FAILURE; |
|
255 } |
|
256 |
|
257 nsresult |
|
258 nsSMILCSSValueType::ComputeDistance(const nsSMILValue& aFrom, |
|
259 const nsSMILValue& aTo, |
|
260 double& aDistance) const |
|
261 { |
|
262 NS_ABORT_IF_FALSE(aFrom.mType == aTo.mType, |
|
263 "Trying to compare different types"); |
|
264 NS_ABORT_IF_FALSE(aFrom.mType == this, "Unexpected source type"); |
|
265 |
|
266 const ValueWrapper* fromWrapper = ExtractValueWrapper(aFrom); |
|
267 const ValueWrapper* toWrapper = ExtractValueWrapper(aTo); |
|
268 NS_ABORT_IF_FALSE(toWrapper, "expecting non-null endpoint"); |
|
269 |
|
270 const nsStyleAnimation::Value* fromCSSValue = fromWrapper ? |
|
271 &fromWrapper->mCSSValue : nullptr; |
|
272 const nsStyleAnimation::Value* toCSSValue = &toWrapper->mCSSValue; |
|
273 if (!FinalizeStyleAnimationValues(fromCSSValue, toCSSValue)) { |
|
274 return NS_ERROR_FAILURE; |
|
275 } |
|
276 |
|
277 return nsStyleAnimation::ComputeDistance(toWrapper->mPropID, |
|
278 *fromCSSValue, *toCSSValue, |
|
279 aDistance) ? |
|
280 NS_OK : NS_ERROR_FAILURE; |
|
281 } |
|
282 |
|
283 nsresult |
|
284 nsSMILCSSValueType::Interpolate(const nsSMILValue& aStartVal, |
|
285 const nsSMILValue& aEndVal, |
|
286 double aUnitDistance, |
|
287 nsSMILValue& aResult) const |
|
288 { |
|
289 NS_ABORT_IF_FALSE(aStartVal.mType == aEndVal.mType, |
|
290 "Trying to interpolate different types"); |
|
291 NS_ABORT_IF_FALSE(aStartVal.mType == this, |
|
292 "Unexpected types for interpolation"); |
|
293 NS_ABORT_IF_FALSE(aResult.mType == this, "Unexpected result type"); |
|
294 NS_ABORT_IF_FALSE(aUnitDistance >= 0.0 && aUnitDistance <= 1.0, |
|
295 "unit distance value out of bounds"); |
|
296 NS_ABORT_IF_FALSE(!aResult.mU.mPtr, "expecting barely-initialized outparam"); |
|
297 |
|
298 const ValueWrapper* startWrapper = ExtractValueWrapper(aStartVal); |
|
299 const ValueWrapper* endWrapper = ExtractValueWrapper(aEndVal); |
|
300 NS_ABORT_IF_FALSE(endWrapper, "expecting non-null endpoint"); |
|
301 |
|
302 const nsStyleAnimation::Value* startCSSValue = startWrapper ? |
|
303 &startWrapper->mCSSValue : nullptr; |
|
304 const nsStyleAnimation::Value* endCSSValue = &endWrapper->mCSSValue; |
|
305 if (!FinalizeStyleAnimationValues(startCSSValue, endCSSValue)) { |
|
306 return NS_ERROR_FAILURE; |
|
307 } |
|
308 |
|
309 nsStyleAnimation::Value resultValue; |
|
310 if (nsStyleAnimation::Interpolate(endWrapper->mPropID, |
|
311 *startCSSValue, *endCSSValue, |
|
312 aUnitDistance, resultValue)) { |
|
313 aResult.mU.mPtr = new ValueWrapper(endWrapper->mPropID, resultValue); |
|
314 return NS_OK; |
|
315 } |
|
316 return NS_ERROR_FAILURE; |
|
317 } |
|
318 |
|
319 // Helper function to extract presContext |
|
320 static nsPresContext* |
|
321 GetPresContextForElement(Element* aElem) |
|
322 { |
|
323 nsIDocument* doc = aElem->GetCurrentDoc(); |
|
324 if (!doc) { |
|
325 // This can happen if we process certain types of restyles mid-sample |
|
326 // and remove anonymous animated content from the document as a result. |
|
327 // See bug 534975. |
|
328 return nullptr; |
|
329 } |
|
330 nsIPresShell* shell = doc->GetShell(); |
|
331 return shell ? shell->GetPresContext() : nullptr; |
|
332 } |
|
333 |
|
334 // Helper function to parse a string into a nsStyleAnimation::Value |
|
335 static bool |
|
336 ValueFromStringHelper(nsCSSProperty aPropID, |
|
337 Element* aTargetElement, |
|
338 nsPresContext* aPresContext, |
|
339 const nsAString& aString, |
|
340 nsStyleAnimation::Value& aStyleAnimValue, |
|
341 bool* aIsContextSensitive) |
|
342 { |
|
343 // If value is negative, we'll strip off the "-" so the CSS parser won't |
|
344 // barf, and then manually make the parsed value negative. |
|
345 // (This is a partial solution to let us accept some otherwise out-of-bounds |
|
346 // CSS values. Bug 501188 will provide a more complete fix.) |
|
347 bool isNegative = false; |
|
348 uint32_t subStringBegin = 0; |
|
349 |
|
350 // NOTE: We need to opt-out 'stroke-dasharray' from the negative-number |
|
351 // check. Its values might look negative (e.g. by starting with "-1"), but |
|
352 // they're more complicated than our simple negation logic here can handle. |
|
353 if (aPropID != eCSSProperty_stroke_dasharray) { |
|
354 int32_t absValuePos = nsSMILParserUtils::CheckForNegativeNumber(aString); |
|
355 if (absValuePos > 0) { |
|
356 isNegative = true; |
|
357 subStringBegin = (uint32_t)absValuePos; // Start parsing after '-' sign |
|
358 } |
|
359 } |
|
360 nsDependentSubstring subString(aString, subStringBegin); |
|
361 if (!nsStyleAnimation::ComputeValue(aPropID, aTargetElement, subString, |
|
362 true, aStyleAnimValue, |
|
363 aIsContextSensitive)) { |
|
364 return false; |
|
365 } |
|
366 if (isNegative) { |
|
367 InvertSign(aStyleAnimValue); |
|
368 } |
|
369 |
|
370 if (aPropID == eCSSProperty_font_size) { |
|
371 // Divide out text-zoom, since SVG is supposed to ignore it |
|
372 NS_ABORT_IF_FALSE(aStyleAnimValue.GetUnit() == |
|
373 nsStyleAnimation::eUnit_Coord, |
|
374 "'font-size' value with unexpected style unit"); |
|
375 aStyleAnimValue.SetCoordValue(aStyleAnimValue.GetCoordValue() / |
|
376 aPresContext->TextZoom()); |
|
377 } |
|
378 return true; |
|
379 } |
|
380 |
|
381 // static |
|
382 void |
|
383 nsSMILCSSValueType::ValueFromString(nsCSSProperty aPropID, |
|
384 Element* aTargetElement, |
|
385 const nsAString& aString, |
|
386 nsSMILValue& aValue, |
|
387 bool* aIsContextSensitive) |
|
388 { |
|
389 NS_ABORT_IF_FALSE(aValue.IsNull(), "Outparam should be null-typed"); |
|
390 nsPresContext* presContext = GetPresContextForElement(aTargetElement); |
|
391 if (!presContext) { |
|
392 NS_WARNING("Not parsing animation value; unable to get PresContext"); |
|
393 return; |
|
394 } |
|
395 |
|
396 nsIDocument* doc = aTargetElement->GetCurrentDoc(); |
|
397 if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr, |
|
398 doc->NodePrincipal(), |
|
399 doc->GetDocumentURI(), |
|
400 0, aString, nullptr)) { |
|
401 return; |
|
402 } |
|
403 |
|
404 nsStyleAnimation::Value parsedValue; |
|
405 if (ValueFromStringHelper(aPropID, aTargetElement, presContext, |
|
406 aString, parsedValue, aIsContextSensitive)) { |
|
407 sSingleton.Init(aValue); |
|
408 aValue.mU.mPtr = new ValueWrapper(aPropID, parsedValue); |
|
409 } |
|
410 } |
|
411 |
|
412 // static |
|
413 bool |
|
414 nsSMILCSSValueType::ValueToString(const nsSMILValue& aValue, |
|
415 nsAString& aString) |
|
416 { |
|
417 NS_ABORT_IF_FALSE(aValue.mType == &nsSMILCSSValueType::sSingleton, |
|
418 "Unexpected SMIL value type"); |
|
419 const ValueWrapper* wrapper = ExtractValueWrapper(aValue); |
|
420 return !wrapper || |
|
421 nsStyleAnimation::UncomputeValue(wrapper->mPropID, |
|
422 wrapper->mCSSValue, aString); |
|
423 } |