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