|
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 #include "nsSMILParserUtils.h" |
|
7 #include "nsSMILKeySpline.h" |
|
8 #include "nsISMILAttr.h" |
|
9 #include "nsSMILValue.h" |
|
10 #include "nsSMILTimeValue.h" |
|
11 #include "nsSMILTimeValueSpecParams.h" |
|
12 #include "nsSMILTypes.h" |
|
13 #include "nsSMILRepeatCount.h" |
|
14 #include "nsContentUtils.h" |
|
15 #include "nsCharSeparatedTokenizer.h" |
|
16 #include "SVGContentUtils.h" |
|
17 |
|
18 using namespace mozilla; |
|
19 using namespace mozilla::dom; |
|
20 //------------------------------------------------------------------------------ |
|
21 // Helper functions and Constants |
|
22 |
|
23 namespace { |
|
24 |
|
25 const uint32_t MSEC_PER_SEC = 1000; |
|
26 const uint32_t MSEC_PER_MIN = 1000 * 60; |
|
27 const uint32_t MSEC_PER_HOUR = 1000 * 60 * 60; |
|
28 |
|
29 #define ACCESSKEY_PREFIX_LC NS_LITERAL_STRING("accesskey(") // SMIL2+ |
|
30 #define ACCESSKEY_PREFIX_CC NS_LITERAL_STRING("accessKey(") // SVG/SMIL ANIM |
|
31 #define REPEAT_PREFIX NS_LITERAL_STRING("repeat(") |
|
32 #define WALLCLOCK_PREFIX NS_LITERAL_STRING("wallclock(") |
|
33 |
|
34 inline bool |
|
35 SkipWhitespace(RangedPtr<const char16_t>& aIter, |
|
36 const RangedPtr<const char16_t>& aEnd) |
|
37 { |
|
38 while (aIter != aEnd) { |
|
39 if (!IsSVGWhitespace(*aIter)) { |
|
40 return true; |
|
41 } |
|
42 ++aIter; |
|
43 } |
|
44 return false; |
|
45 } |
|
46 |
|
47 inline bool |
|
48 ParseColon(RangedPtr<const char16_t>& aIter, |
|
49 const RangedPtr<const char16_t>& aEnd) |
|
50 { |
|
51 if (aIter == aEnd || *aIter != ':') { |
|
52 return false; |
|
53 } |
|
54 ++aIter; |
|
55 return true; |
|
56 } |
|
57 |
|
58 /* |
|
59 * Exactly two digits in the range 00 - 59 are expected. |
|
60 */ |
|
61 bool |
|
62 ParseSecondsOrMinutes(RangedPtr<const char16_t>& aIter, |
|
63 const RangedPtr<const char16_t>& aEnd, |
|
64 uint32_t& aValue) |
|
65 { |
|
66 if (aIter == aEnd || !SVGContentUtils::IsDigit(*aIter)) { |
|
67 return false; |
|
68 } |
|
69 |
|
70 RangedPtr<const char16_t> iter(aIter); |
|
71 |
|
72 if (++iter == aEnd || !SVGContentUtils::IsDigit(*iter)) { |
|
73 return false; |
|
74 } |
|
75 |
|
76 uint32_t value = 10 * SVGContentUtils::DecimalDigitValue(*aIter) + |
|
77 SVGContentUtils::DecimalDigitValue(*iter); |
|
78 if (value > 59) { |
|
79 return false; |
|
80 } |
|
81 if (++iter != aEnd && SVGContentUtils::IsDigit(*iter)) { |
|
82 return false; |
|
83 } |
|
84 |
|
85 aValue = value; |
|
86 aIter = iter; |
|
87 return true; |
|
88 } |
|
89 |
|
90 inline bool |
|
91 ParseClockMetric(RangedPtr<const char16_t>& aIter, |
|
92 const RangedPtr<const char16_t>& aEnd, |
|
93 uint32_t& aMultiplier) |
|
94 { |
|
95 if (aIter == aEnd) { |
|
96 aMultiplier = MSEC_PER_SEC; |
|
97 return true; |
|
98 } |
|
99 |
|
100 switch (*aIter) { |
|
101 case 'h': |
|
102 if (++aIter == aEnd) { |
|
103 aMultiplier = MSEC_PER_HOUR; |
|
104 return true; |
|
105 } |
|
106 return false; |
|
107 case 'm': |
|
108 { |
|
109 const nsAString& metric = Substring(aIter.get(), aEnd.get()); |
|
110 if (metric.EqualsLiteral("min")) { |
|
111 aMultiplier = MSEC_PER_MIN; |
|
112 aIter = aEnd; |
|
113 return true; |
|
114 } |
|
115 if (metric.EqualsLiteral("ms")) { |
|
116 aMultiplier = 1; |
|
117 aIter = aEnd; |
|
118 return true; |
|
119 } |
|
120 } |
|
121 return false; |
|
122 case 's': |
|
123 if (++aIter == aEnd) { |
|
124 aMultiplier = MSEC_PER_SEC; |
|
125 return true; |
|
126 } |
|
127 } |
|
128 return false; |
|
129 } |
|
130 |
|
131 /** |
|
132 * See http://www.w3.org/TR/SVG/animate.html#ClockValueSyntax |
|
133 */ |
|
134 bool |
|
135 ParseClockValue(RangedPtr<const char16_t>& aIter, |
|
136 const RangedPtr<const char16_t>& aEnd, |
|
137 nsSMILTimeValue* aResult) |
|
138 { |
|
139 if (aIter == aEnd) { |
|
140 return false; |
|
141 } |
|
142 |
|
143 // TIMECOUNT_VALUE ::= Timecount ("." Fraction)? (Metric)? |
|
144 // PARTIAL_CLOCK_VALUE ::= Minutes ":" Seconds ("." Fraction)? |
|
145 // FULL_CLOCK_VALUE ::= Hours ":" Minutes ":" Seconds ("." Fraction)? |
|
146 enum ClockType { |
|
147 TIMECOUNT_VALUE, |
|
148 PARTIAL_CLOCK_VALUE, |
|
149 FULL_CLOCK_VALUE |
|
150 }; |
|
151 |
|
152 int32_t clockType = TIMECOUNT_VALUE; |
|
153 |
|
154 RangedPtr<const char16_t> iter(aIter); |
|
155 |
|
156 // Determine which type of clock value we have by counting the number |
|
157 // of colons in the string. |
|
158 do { |
|
159 switch (*iter) { |
|
160 case ':': |
|
161 if (clockType == FULL_CLOCK_VALUE) { |
|
162 return false; |
|
163 } |
|
164 ++clockType; |
|
165 break; |
|
166 case 'e': |
|
167 case 'E': |
|
168 case '-': |
|
169 case '+': |
|
170 // Exclude anything invalid (for clock values) |
|
171 // that number parsing might otherwise allow. |
|
172 return false; |
|
173 } |
|
174 ++iter; |
|
175 } while (iter != aEnd); |
|
176 |
|
177 iter = aIter; |
|
178 |
|
179 int32_t hours = 0, timecount; |
|
180 double fraction = 0.0; |
|
181 uint32_t minutes, seconds, multiplier; |
|
182 |
|
183 switch (clockType) { |
|
184 case FULL_CLOCK_VALUE: |
|
185 if (!SVGContentUtils::ParseInteger(iter, aEnd, hours) || |
|
186 !ParseColon(iter, aEnd)) { |
|
187 return false; |
|
188 } |
|
189 // intentional fall through |
|
190 case PARTIAL_CLOCK_VALUE: |
|
191 if (!ParseSecondsOrMinutes(iter, aEnd, minutes) || |
|
192 !ParseColon(iter, aEnd) || |
|
193 !ParseSecondsOrMinutes(iter, aEnd, seconds)) { |
|
194 return false; |
|
195 } |
|
196 if (iter != aEnd && |
|
197 (*iter != '.' || |
|
198 !SVGContentUtils::ParseNumber(iter, aEnd, fraction))) { |
|
199 return false; |
|
200 } |
|
201 aResult->SetMillis(nsSMILTime(hours) * MSEC_PER_HOUR + |
|
202 minutes * MSEC_PER_MIN + |
|
203 seconds * MSEC_PER_SEC + |
|
204 NS_round(fraction * MSEC_PER_SEC)); |
|
205 aIter = iter; |
|
206 return true; |
|
207 case TIMECOUNT_VALUE: |
|
208 if (!SVGContentUtils::ParseInteger(iter, aEnd, timecount)) { |
|
209 return false; |
|
210 } |
|
211 if (iter != aEnd && *iter == '.' && |
|
212 !SVGContentUtils::ParseNumber(iter, aEnd, fraction)) { |
|
213 return false; |
|
214 } |
|
215 if (!ParseClockMetric(iter, aEnd, multiplier)) { |
|
216 return false; |
|
217 } |
|
218 aResult->SetMillis(nsSMILTime(timecount) * multiplier + |
|
219 NS_round(fraction * multiplier)); |
|
220 aIter = iter; |
|
221 return true; |
|
222 } |
|
223 |
|
224 return false; |
|
225 } |
|
226 |
|
227 bool |
|
228 ParseOffsetValue(RangedPtr<const char16_t>& aIter, |
|
229 const RangedPtr<const char16_t>& aEnd, |
|
230 nsSMILTimeValue* aResult) |
|
231 { |
|
232 RangedPtr<const char16_t> iter(aIter); |
|
233 |
|
234 int32_t sign; |
|
235 if (!SVGContentUtils::ParseOptionalSign(iter, aEnd, sign) || |
|
236 !SkipWhitespace(iter, aEnd) || |
|
237 !ParseClockValue(iter, aEnd, aResult)) { |
|
238 return false; |
|
239 } |
|
240 if (sign == -1) { |
|
241 aResult->SetMillis(-aResult->GetMillis()); |
|
242 } |
|
243 aIter = iter; |
|
244 return true; |
|
245 } |
|
246 |
|
247 bool |
|
248 ParseOffsetValue(const nsAString& aSpec, |
|
249 nsSMILTimeValue* aResult) |
|
250 { |
|
251 RangedPtr<const char16_t> iter(SVGContentUtils::GetStartRangedPtr(aSpec)); |
|
252 const RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec)); |
|
253 |
|
254 return ParseOffsetValue(iter, end, aResult) && iter == end; |
|
255 } |
|
256 |
|
257 bool |
|
258 ParseOptionalOffset(RangedPtr<const char16_t>& aIter, |
|
259 const RangedPtr<const char16_t>& aEnd, |
|
260 nsSMILTimeValue* aResult) |
|
261 { |
|
262 if (aIter == aEnd) { |
|
263 aResult->SetMillis(0L); |
|
264 return true; |
|
265 } |
|
266 |
|
267 return SkipWhitespace(aIter, aEnd) && |
|
268 ParseOffsetValue(aIter, aEnd, aResult); |
|
269 } |
|
270 |
|
271 bool |
|
272 ParseAccessKey(const nsAString& aSpec, nsSMILTimeValueSpecParams& aResult) |
|
273 { |
|
274 NS_ABORT_IF_FALSE(StringBeginsWith(aSpec, ACCESSKEY_PREFIX_CC) || |
|
275 StringBeginsWith(aSpec, ACCESSKEY_PREFIX_LC), |
|
276 "Calling ParseAccessKey on non-accesskey-type spec"); |
|
277 |
|
278 nsSMILTimeValueSpecParams result; |
|
279 result.mType = nsSMILTimeValueSpecParams::ACCESSKEY; |
|
280 |
|
281 NS_ABORT_IF_FALSE( |
|
282 ACCESSKEY_PREFIX_LC.Length() == ACCESSKEY_PREFIX_CC.Length(), |
|
283 "Case variations for accesskey prefix differ in length"); |
|
284 |
|
285 RangedPtr<const char16_t> iter(SVGContentUtils::GetStartRangedPtr(aSpec)); |
|
286 RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec)); |
|
287 |
|
288 iter += ACCESSKEY_PREFIX_LC.Length(); |
|
289 |
|
290 // Expecting at least <accesskey> + ')' |
|
291 if (end - iter < 2) |
|
292 return false; |
|
293 |
|
294 uint32_t c = *iter++; |
|
295 |
|
296 // Process 32-bit codepoints |
|
297 if (NS_IS_HIGH_SURROGATE(c)) { |
|
298 if (end - iter < 2) // Expecting at least low-surrogate + ')' |
|
299 return false; |
|
300 uint32_t lo = *iter++; |
|
301 if (!NS_IS_LOW_SURROGATE(lo)) |
|
302 return false; |
|
303 c = SURROGATE_TO_UCS4(c, lo); |
|
304 // XML 1.1 says that 0xFFFE and 0xFFFF are not valid characters |
|
305 } else if (NS_IS_LOW_SURROGATE(c) || c == 0xFFFE || c == 0xFFFF) { |
|
306 return false; |
|
307 } |
|
308 |
|
309 result.mRepeatIterationOrAccessKey = c; |
|
310 |
|
311 if (*iter++ != ')') |
|
312 return false; |
|
313 |
|
314 if (!ParseOptionalOffset(iter, end, &result.mOffset) || iter != end) { |
|
315 return false; |
|
316 } |
|
317 aResult = result; |
|
318 return true; |
|
319 } |
|
320 |
|
321 void |
|
322 MoveToNextToken(RangedPtr<const char16_t>& aIter, |
|
323 const RangedPtr<const char16_t>& aEnd, |
|
324 bool aBreakOnDot, |
|
325 bool& aIsAnyCharEscaped) |
|
326 { |
|
327 aIsAnyCharEscaped = false; |
|
328 |
|
329 bool isCurrentCharEscaped = false; |
|
330 |
|
331 while (aIter != aEnd && !IsSVGWhitespace(*aIter)) { |
|
332 if (isCurrentCharEscaped) { |
|
333 isCurrentCharEscaped = false; |
|
334 } else { |
|
335 if (*aIter == '+' || *aIter == '-' || |
|
336 (aBreakOnDot && *aIter == '.')) { |
|
337 break; |
|
338 } |
|
339 if (*aIter == '\\') { |
|
340 isCurrentCharEscaped = true; |
|
341 aIsAnyCharEscaped = true; |
|
342 } |
|
343 } |
|
344 ++aIter; |
|
345 } |
|
346 } |
|
347 |
|
348 already_AddRefed<nsIAtom> |
|
349 ConvertUnescapedTokenToAtom(const nsAString& aToken) |
|
350 { |
|
351 // Whether the token is an id-ref or event-symbol it should be a valid NCName |
|
352 if (aToken.IsEmpty() || NS_FAILED(nsContentUtils::CheckQName(aToken, false))) |
|
353 return nullptr; |
|
354 return do_GetAtom(aToken); |
|
355 } |
|
356 |
|
357 already_AddRefed<nsIAtom> |
|
358 ConvertTokenToAtom(const nsAString& aToken, |
|
359 bool aUnescapeToken) |
|
360 { |
|
361 // Unescaping involves making a copy of the string which we'd like to avoid if possible |
|
362 if (!aUnescapeToken) { |
|
363 return ConvertUnescapedTokenToAtom(aToken); |
|
364 } |
|
365 |
|
366 nsAutoString token(aToken); |
|
367 |
|
368 const char16_t* read = token.BeginReading(); |
|
369 const char16_t* const end = token.EndReading(); |
|
370 char16_t* write = token.BeginWriting(); |
|
371 bool escape = false; |
|
372 |
|
373 while (read != end) { |
|
374 NS_ABORT_IF_FALSE(write <= read, "Writing past where we've read"); |
|
375 if (!escape && *read == '\\') { |
|
376 escape = true; |
|
377 ++read; |
|
378 } else { |
|
379 *write++ = *read++; |
|
380 escape = false; |
|
381 } |
|
382 } |
|
383 token.Truncate(write - token.BeginReading()); |
|
384 |
|
385 return ConvertUnescapedTokenToAtom(token); |
|
386 } |
|
387 |
|
388 bool |
|
389 ParseElementBaseTimeValueSpec(const nsAString& aSpec, |
|
390 nsSMILTimeValueSpecParams& aResult) |
|
391 { |
|
392 nsSMILTimeValueSpecParams result; |
|
393 |
|
394 // |
|
395 // The spec will probably look something like one of these |
|
396 // |
|
397 // element-name.begin |
|
398 // element-name.event-name |
|
399 // event-name |
|
400 // element-name.repeat(3) |
|
401 // event\.name |
|
402 // |
|
403 // Technically `repeat(3)' is permitted but the behaviour in this case is not |
|
404 // defined (for SMIL Animation) so we don't support it here. |
|
405 // |
|
406 |
|
407 RangedPtr<const char16_t> start(SVGContentUtils::GetStartRangedPtr(aSpec)); |
|
408 RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec)); |
|
409 |
|
410 if (start == end) { |
|
411 return false; |
|
412 } |
|
413 |
|
414 RangedPtr<const char16_t> tokenEnd(start); |
|
415 |
|
416 bool requiresUnescaping; |
|
417 MoveToNextToken(tokenEnd, end, true, requiresUnescaping); |
|
418 |
|
419 nsRefPtr<nsIAtom> atom = |
|
420 ConvertTokenToAtom(Substring(start.get(), tokenEnd.get()), |
|
421 requiresUnescaping); |
|
422 if (atom == nullptr) { |
|
423 return false; |
|
424 } |
|
425 |
|
426 // Parse the second token if there is one |
|
427 if (tokenEnd != end && *tokenEnd == '.') { |
|
428 result.mDependentElemID = atom; |
|
429 |
|
430 ++tokenEnd; |
|
431 start = tokenEnd; |
|
432 MoveToNextToken(tokenEnd, end, false, requiresUnescaping); |
|
433 |
|
434 const nsAString& token2 = Substring(start.get(), tokenEnd.get()); |
|
435 |
|
436 // element-name.begin |
|
437 if (token2.EqualsLiteral("begin")) { |
|
438 result.mType = nsSMILTimeValueSpecParams::SYNCBASE; |
|
439 result.mSyncBegin = true; |
|
440 // element-name.end |
|
441 } else if (token2.EqualsLiteral("end")) { |
|
442 result.mType = nsSMILTimeValueSpecParams::SYNCBASE; |
|
443 result.mSyncBegin = false; |
|
444 // element-name.repeat(digit+) |
|
445 } else if (StringBeginsWith(token2, REPEAT_PREFIX)) { |
|
446 start += REPEAT_PREFIX.Length(); |
|
447 int32_t repeatValue; |
|
448 if (start == tokenEnd || *start == '+' || *start == '-' || |
|
449 !SVGContentUtils::ParseInteger(start, tokenEnd, repeatValue)) { |
|
450 return false; |
|
451 } |
|
452 if (start == tokenEnd || *start != ')') { |
|
453 return false; |
|
454 } |
|
455 result.mType = nsSMILTimeValueSpecParams::REPEAT; |
|
456 result.mRepeatIterationOrAccessKey = repeatValue; |
|
457 // element-name.event-symbol |
|
458 } else { |
|
459 atom = ConvertTokenToAtom(token2, requiresUnescaping); |
|
460 if (atom == nullptr) { |
|
461 return false; |
|
462 } |
|
463 result.mType = nsSMILTimeValueSpecParams::EVENT; |
|
464 result.mEventSymbol = atom; |
|
465 } |
|
466 } else { |
|
467 // event-symbol |
|
468 result.mType = nsSMILTimeValueSpecParams::EVENT; |
|
469 result.mEventSymbol = atom; |
|
470 } |
|
471 |
|
472 // We've reached the end of the token, so we should now be either looking at |
|
473 // a '+', '-' (possibly with whitespace before it), or the end. |
|
474 if (!ParseOptionalOffset(tokenEnd, end, &result.mOffset) || tokenEnd != end) { |
|
475 return false; |
|
476 } |
|
477 aResult = result; |
|
478 return true; |
|
479 } |
|
480 |
|
481 } // end anonymous namespace block |
|
482 |
|
483 //------------------------------------------------------------------------------ |
|
484 // Implementation |
|
485 |
|
486 const nsDependentSubstring |
|
487 nsSMILParserUtils::TrimWhitespace(const nsAString& aString) |
|
488 { |
|
489 nsAString::const_iterator start, end; |
|
490 |
|
491 aString.BeginReading(start); |
|
492 aString.EndReading(end); |
|
493 |
|
494 // Skip whitespace characters at the beginning |
|
495 while (start != end && IsSVGWhitespace(*start)) { |
|
496 ++start; |
|
497 } |
|
498 |
|
499 // Skip whitespace characters at the end. |
|
500 while (end != start) { |
|
501 --end; |
|
502 |
|
503 if (!IsSVGWhitespace(*end)) { |
|
504 // Step back to the last non-whitespace character. |
|
505 ++end; |
|
506 |
|
507 break; |
|
508 } |
|
509 } |
|
510 |
|
511 return Substring(start, end); |
|
512 } |
|
513 |
|
514 bool |
|
515 nsSMILParserUtils::ParseKeySplines(const nsAString& aSpec, |
|
516 FallibleTArray<nsSMILKeySpline>& aKeySplines) |
|
517 { |
|
518 nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> controlPointTokenizer(aSpec, ';'); |
|
519 while (controlPointTokenizer.hasMoreTokens()) { |
|
520 |
|
521 nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> |
|
522 tokenizer(controlPointTokenizer.nextToken(), ',', |
|
523 nsCharSeparatedTokenizer::SEPARATOR_OPTIONAL); |
|
524 |
|
525 double values[4]; |
|
526 for (int i = 0 ; i < 4; i++) { |
|
527 if (!tokenizer.hasMoreTokens() || |
|
528 !SVGContentUtils::ParseNumber(tokenizer.nextToken(), values[i]) || |
|
529 values[i] > 1.0 || values[i] < 0.0) { |
|
530 return false; |
|
531 } |
|
532 } |
|
533 if (tokenizer.hasMoreTokens() || |
|
534 tokenizer.separatorAfterCurrentToken() || |
|
535 !aKeySplines.AppendElement(nsSMILKeySpline(values[0], |
|
536 values[1], |
|
537 values[2], |
|
538 values[3]))) { |
|
539 return false; |
|
540 } |
|
541 } |
|
542 |
|
543 return !aKeySplines.IsEmpty(); |
|
544 } |
|
545 |
|
546 bool |
|
547 nsSMILParserUtils::ParseSemicolonDelimitedProgressList(const nsAString& aSpec, |
|
548 bool aNonDecreasing, |
|
549 FallibleTArray<double>& aArray) |
|
550 { |
|
551 nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> tokenizer(aSpec, ';'); |
|
552 |
|
553 double previousValue = -1.0; |
|
554 |
|
555 while (tokenizer.hasMoreTokens()) { |
|
556 double value; |
|
557 if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), value)) { |
|
558 return false; |
|
559 } |
|
560 |
|
561 if (value > 1.0 || value < 0.0 || |
|
562 (aNonDecreasing && value < previousValue)) { |
|
563 return false; |
|
564 } |
|
565 |
|
566 if (!aArray.AppendElement(value)) { |
|
567 return false; |
|
568 } |
|
569 previousValue = value; |
|
570 } |
|
571 |
|
572 return !aArray.IsEmpty(); |
|
573 } |
|
574 |
|
575 // Helper class for ParseValues |
|
576 class MOZ_STACK_CLASS SMILValueParser : |
|
577 public nsSMILParserUtils::GenericValueParser |
|
578 { |
|
579 public: |
|
580 SMILValueParser(const SVGAnimationElement* aSrcElement, |
|
581 const nsISMILAttr* aSMILAttr, |
|
582 FallibleTArray<nsSMILValue>* aValuesArray, |
|
583 bool* aPreventCachingOfSandwich) : |
|
584 mSrcElement(aSrcElement), |
|
585 mSMILAttr(aSMILAttr), |
|
586 mValuesArray(aValuesArray), |
|
587 mPreventCachingOfSandwich(aPreventCachingOfSandwich) |
|
588 {} |
|
589 |
|
590 virtual bool Parse(const nsAString& aValueStr) MOZ_OVERRIDE { |
|
591 nsSMILValue newValue; |
|
592 bool tmpPreventCachingOfSandwich = false; |
|
593 if (NS_FAILED(mSMILAttr->ValueFromString(aValueStr, mSrcElement, newValue, |
|
594 tmpPreventCachingOfSandwich))) |
|
595 return false; |
|
596 |
|
597 if (!mValuesArray->AppendElement(newValue)) { |
|
598 return false; |
|
599 } |
|
600 if (tmpPreventCachingOfSandwich) { |
|
601 *mPreventCachingOfSandwich = true; |
|
602 } |
|
603 return true; |
|
604 } |
|
605 protected: |
|
606 const SVGAnimationElement* mSrcElement; |
|
607 const nsISMILAttr* mSMILAttr; |
|
608 FallibleTArray<nsSMILValue>* mValuesArray; |
|
609 bool* mPreventCachingOfSandwich; |
|
610 }; |
|
611 |
|
612 bool |
|
613 nsSMILParserUtils::ParseValues(const nsAString& aSpec, |
|
614 const SVGAnimationElement* aSrcElement, |
|
615 const nsISMILAttr& aAttribute, |
|
616 FallibleTArray<nsSMILValue>& aValuesArray, |
|
617 bool& aPreventCachingOfSandwich) |
|
618 { |
|
619 // Assume all results can be cached, until we find one that can't. |
|
620 aPreventCachingOfSandwich = false; |
|
621 SMILValueParser valueParser(aSrcElement, &aAttribute, |
|
622 &aValuesArray, &aPreventCachingOfSandwich); |
|
623 return ParseValuesGeneric(aSpec, valueParser); |
|
624 } |
|
625 |
|
626 bool |
|
627 nsSMILParserUtils::ParseValuesGeneric(const nsAString& aSpec, |
|
628 GenericValueParser& aParser) |
|
629 { |
|
630 nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> tokenizer(aSpec, ';'); |
|
631 if (!tokenizer.hasMoreTokens()) { // Empty list |
|
632 return false; |
|
633 } |
|
634 |
|
635 while (tokenizer.hasMoreTokens()) { |
|
636 if (!aParser.Parse(tokenizer.nextToken())) { |
|
637 return false; |
|
638 } |
|
639 } |
|
640 |
|
641 return true; |
|
642 } |
|
643 |
|
644 bool |
|
645 nsSMILParserUtils::ParseRepeatCount(const nsAString& aSpec, |
|
646 nsSMILRepeatCount& aResult) |
|
647 { |
|
648 const nsAString& spec = |
|
649 nsSMILParserUtils::TrimWhitespace(aSpec); |
|
650 |
|
651 if (spec.EqualsLiteral("indefinite")) { |
|
652 aResult.SetIndefinite(); |
|
653 return true; |
|
654 } |
|
655 |
|
656 double value; |
|
657 if (!SVGContentUtils::ParseNumber(spec, value) || value <= 0.0) { |
|
658 return false; |
|
659 } |
|
660 aResult = value; |
|
661 return true; |
|
662 } |
|
663 |
|
664 bool |
|
665 nsSMILParserUtils::ParseTimeValueSpecParams(const nsAString& aSpec, |
|
666 nsSMILTimeValueSpecParams& aResult) |
|
667 { |
|
668 const nsAString& spec = TrimWhitespace(aSpec); |
|
669 |
|
670 if (spec.EqualsLiteral("indefinite")) { |
|
671 aResult.mType = nsSMILTimeValueSpecParams::INDEFINITE; |
|
672 return true; |
|
673 } |
|
674 |
|
675 // offset type |
|
676 if (ParseOffsetValue(spec, &aResult.mOffset)) { |
|
677 aResult.mType = nsSMILTimeValueSpecParams::OFFSET; |
|
678 return true; |
|
679 } |
|
680 |
|
681 // wallclock type |
|
682 if (StringBeginsWith(spec, WALLCLOCK_PREFIX)) { |
|
683 return false; // Wallclock times not implemented |
|
684 } |
|
685 |
|
686 // accesskey type |
|
687 if (StringBeginsWith(spec, ACCESSKEY_PREFIX_LC) || |
|
688 StringBeginsWith(spec, ACCESSKEY_PREFIX_CC)) { |
|
689 return ParseAccessKey(spec, aResult); |
|
690 } |
|
691 |
|
692 // event, syncbase, or repeat |
|
693 return ParseElementBaseTimeValueSpec(spec, aResult); |
|
694 } |
|
695 |
|
696 bool |
|
697 nsSMILParserUtils::ParseClockValue(const nsAString& aSpec, |
|
698 nsSMILTimeValue* aResult) |
|
699 { |
|
700 RangedPtr<const char16_t> iter(SVGContentUtils::GetStartRangedPtr(aSpec)); |
|
701 RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec)); |
|
702 |
|
703 return ::ParseClockValue(iter, end, aResult) && iter == end; |
|
704 } |
|
705 |
|
706 int32_t |
|
707 nsSMILParserUtils::CheckForNegativeNumber(const nsAString& aStr) |
|
708 { |
|
709 int32_t absValLocation = -1; |
|
710 |
|
711 nsAString::const_iterator start, end; |
|
712 aStr.BeginReading(start); |
|
713 aStr.EndReading(end); |
|
714 |
|
715 // Skip initial whitespace |
|
716 while (start != end && IsSVGWhitespace(*start)) { |
|
717 ++start; |
|
718 } |
|
719 |
|
720 // Check for dash |
|
721 if (start != end && *start == '-') { |
|
722 ++start; |
|
723 // Check for numeric character |
|
724 if (start != end && SVGContentUtils::IsDigit(*start)) { |
|
725 absValLocation = start.get() - start.start(); |
|
726 } |
|
727 } |
|
728 return absValLocation; |
|
729 } |