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