Unreviewed, rolling out r126169.
[WebKit-https.git] / Source / WebCore / platform / DateComponents.cpp
1 /*
2  * Copyright (C) 2009 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "DateComponents.h"
33
34 #include "PlatformString.h"
35 #include <limits.h>
36 #include <wtf/ASCIICType.h>
37 #include <wtf/DateMath.h>
38 #include <wtf/MathExtras.h>
39
40 using namespace std;
41
42 namespace WebCore {
43
44 // HTML5 uses ISO-8601 format with year >= 1. Gregorian calendar started in
45 // 1582. However, we need to support 0001-01-01 in Gregorian calendar rule.
46 static const int minimumYear = 1;
47 // Date in ECMAScript can't represent dates later than 275760-09-13T00:00Z.
48 // So, we have the same upper limit in HTML5 dates.
49 static const int maximumYear = 275760;
50 static const int maximumMonthInMaximumYear = 8; // This is September, since months are 0 based.
51 static const int maximumDayInMaximumMonth = 13;
52 static const int maximumWeekInMaximumYear = 37; // The week of 275760-09-13
53
54 static const int daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
55
56 static bool isLeapYear(int year)
57 {
58     if (year % 4)
59         return false;
60     if (!(year % 400))
61         return true;
62     if (!(year % 100))
63         return false;
64     return true;
65 }
66
67 // 'month' is 0-based.
68 static int maxDayOfMonth(int year, int month)
69 {
70     if (month != 1) // February?
71         return daysInMonth[month];
72     return isLeapYear(year) ? 29 : 28;
73 }
74
75 // 'month' is 0-based.
76 static int dayOfWeek(int year, int month, int day)
77 {
78     int shiftedMonth = month + 2;
79     // 2:January, 3:Feburuary, 4:March, ...
80
81     // Zeller's congruence
82     if (shiftedMonth <= 3) {
83         shiftedMonth += 12;
84         year--;
85     }
86     // 4:March, ..., 14:January, 15:February
87
88     int highYear = year / 100;
89     int lowYear = year % 100;
90     // We add 6 to make the result Sunday-origin.
91     int result = (day + 13 * shiftedMonth / 5 + lowYear + lowYear / 4 + highYear / 4 + 5 * highYear + 6) % 7;
92     return result;
93 }
94
95 int DateComponents::maxWeekNumberInYear() const
96 {
97     int day = dayOfWeek(m_year, 0, 1); // January 1.
98     return day == Thursday || (day == Wednesday && isLeapYear(m_year)) ? 53 : 52;
99 }
100
101 static unsigned countDigits(const UChar* src, unsigned length, unsigned start)
102 {
103     unsigned index = start;
104     for (; index < length; ++index) {
105         if (!isASCIIDigit(src[index]))
106             break;
107     }
108     return index - start;
109 }
110
111 // Very strict integer parser. Do not allow leading or trailing whitespace unlike charactersToIntStrict().
112 static bool toInt(const UChar* src, unsigned length, unsigned parseStart, unsigned parseLength, int& out)
113 {
114     if (parseStart + parseLength > length || parseLength <= 0)
115         return false;
116     int value = 0;
117     const UChar* current = src + parseStart;
118     const UChar* end = current + parseLength;
119
120     // We don't need to handle negative numbers for ISO 8601.
121     for (; current < end; ++current) {
122         if (!isASCIIDigit(*current))
123             return false;
124         int digit = *current - '0';
125         if (value > (INT_MAX - digit) / 10) // Check for overflow.
126             return false;
127         value = value * 10 + digit;
128     }
129     out = value;
130     return true;
131 }
132
133 bool DateComponents::parseYear(const UChar* src, unsigned length, unsigned start, unsigned& end)
134 {
135     unsigned digitsLength = countDigits(src, length, start);
136     // Needs at least 4 digits according to the standard.
137     if (digitsLength < 4)
138         return false;
139     int year;
140     if (!toInt(src, length, start, digitsLength, year))
141         return false;
142     if (year < minimumYear || year > maximumYear)
143         return false;
144     m_year = year;
145     end = start + digitsLength;
146     return true;
147 }
148
149 static bool withinHTMLDateLimits(int year, int month)
150 {
151     if (year < minimumYear)
152         return false;
153     if (year < maximumYear)
154         return true;
155     return month <= maximumMonthInMaximumYear;
156 }
157
158 static bool withinHTMLDateLimits(int year, int month, int monthDay)
159 {
160     if (year < minimumYear)
161         return false;
162     if (year < maximumYear)
163         return true;
164     if (month < maximumMonthInMaximumYear)
165         return true;
166     return monthDay <= maximumDayInMaximumMonth;
167 }
168
169 static bool withinHTMLDateLimits(int year, int month, int monthDay, int hour, int minute, int second, int millisecond)
170 {
171     if (year < minimumYear)
172         return false;
173     if (year < maximumYear)
174         return true;
175     if (month < maximumMonthInMaximumYear)
176         return true;
177     if (monthDay < maximumDayInMaximumMonth)
178         return true;
179     if (monthDay > maximumDayInMaximumMonth)
180         return false;
181     // (year, month, monthDay) = (maximumYear, maximumMonthInMaximumYear, maximumDayInMaximumMonth)
182     return !hour && !minute && !second && !millisecond;
183 }
184
185 bool DateComponents::addDay(int dayDiff)
186 {
187     ASSERT(m_monthDay);
188
189     int day = m_monthDay + dayDiff;
190     if (day > maxDayOfMonth(m_year, m_month)) {
191         day = m_monthDay;
192         int year = m_year;
193         int month = m_month;
194         int maxDay = maxDayOfMonth(year, month);
195         for (; dayDiff > 0; --dayDiff) {
196             ++day;
197             if (day > maxDay) {
198                 day = 1;
199                 ++month;
200                 if (month >= 12) { // month is 0-origin.
201                     month = 0;
202                     ++year;
203                 }
204                 maxDay = maxDayOfMonth(year, month);
205             }
206         }
207         if (!withinHTMLDateLimits(year, month, day))
208             return false;
209         m_year = year;
210         m_month = month;
211     } else if (day < 1) {
212         int month = m_month;
213         int year = m_year;
214         day = m_monthDay;
215         for (; dayDiff < 0; ++dayDiff) {
216             --day;
217             if (day < 1) {
218                 --month;
219                 if (month < 0) {
220                     month = 11;
221                     --year;
222                 }
223                 day = maxDayOfMonth(year, month);
224             }
225         }
226         if (!withinHTMLDateLimits(year, month, day))
227             return false;
228         m_year = year;
229         m_month = month;
230     } else {
231         if (!withinHTMLDateLimits(m_year, m_month, day))
232             return false;
233     }
234     m_monthDay = day;
235     return true;
236 }
237
238 bool DateComponents::addMinute(int minute)
239 {
240     // This function is used to adjust timezone offset. So m_year, m_month,
241     // m_monthDay have values between the lower and higher limits.
242     ASSERT(withinHTMLDateLimits(m_year, m_month, m_monthDay));
243
244     int carry;
245     // minute can be negative or greater than 59.
246     minute += m_minute;
247     if (minute > 59) {
248         carry = minute / 60;
249         minute = minute % 60;
250     } else if (m_minute < 0) {
251         carry = (59 - m_minute) / 60;
252         minute += carry * 60;
253         carry = -carry;
254         ASSERT(minute >= 0 && minute <= 59);
255     } else {
256         if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, minute, m_second, m_millisecond))
257             return false;
258         m_minute = minute;
259         return true;
260     }
261
262     int hour = m_hour + carry;
263     if (hour > 23) {
264         carry = hour / 24;
265         hour = hour % 24;
266     } else if (hour < 0) {
267         carry = (23 - hour) / 24;
268         hour += carry * 24;
269         carry = -carry;
270         ASSERT(hour >= 0 && hour <= 23);
271     } else {
272         if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, hour, minute, m_second, m_millisecond))
273             return false;
274         m_minute = minute;
275         m_hour = hour;
276         return true;
277     }
278     if (!addDay(carry))
279         return false;
280     if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, hour, minute, m_second, m_millisecond))
281         return false;
282     m_minute = minute;
283     m_hour = hour;
284     return true;
285 }
286
287 // Parses a timezone part, and adjust year, month, monthDay, hour, minute, second, millisecond.
288 bool DateComponents::parseTimeZone(const UChar* src, unsigned length, unsigned start, unsigned& end)
289 {
290     if (start >= length)
291         return false;
292     unsigned index = start;
293     if (src[index] == 'Z') {
294         end = index + 1;
295         return true;
296     }
297
298     bool minus;
299     if (src[index] == '+')
300         minus = false;
301     else if (src[index] == '-')
302         minus = true;
303     else
304         return false;
305     ++index;
306
307     int hour;
308     int minute;
309     if (!toInt(src, length, index, 2, hour) || hour < 0 || hour > 23)
310         return false;
311     index += 2;
312
313     if (index >= length || src[index] != ':')
314         return false;
315     ++index;
316
317     if (!toInt(src, length, index, 2, minute) || minute < 0 || minute > 59)
318         return false;
319     index += 2;
320
321     if (minus) {
322         hour = -hour;
323         minute = -minute;
324     }
325
326     // Subtract the timezone offset.
327     if (!addMinute(-(hour * 60 + minute)))
328         return false;
329     end = index;
330     return true;
331 }
332
333 bool DateComponents::parseMonth(const UChar* src, unsigned length, unsigned start, unsigned& end)
334 {
335     ASSERT(src);
336     unsigned index;
337     if (!parseYear(src, length, start, index))
338         return false;
339     if (index >= length || src[index] != '-')
340         return false;
341     ++index;
342
343     int month;
344     if (!toInt(src, length, index, 2, month) || month < 1 || month > 12)
345         return false;
346     --month;
347     if (!withinHTMLDateLimits(m_year, month))
348         return false;
349     m_month = month;
350     end = index + 2;
351     m_type = Month;
352     return true;
353 }
354
355 bool DateComponents::parseDate(const UChar* src, unsigned length, unsigned start, unsigned& end)
356 {
357     ASSERT(src);
358     unsigned index;
359     if (!parseMonth(src, length, start, index))
360         return false;
361     // '-' and 2-digits are needed.
362     if (index + 2 >= length)
363         return false;
364     if (src[index] != '-')
365         return false;
366     ++index;
367
368     int day;
369     if (!toInt(src, length, index, 2, day) || day < 1 || day > maxDayOfMonth(m_year, m_month))
370         return false;
371     if (!withinHTMLDateLimits(m_year, m_month, day))
372         return false;
373     m_monthDay = day;
374     end = index + 2;
375     m_type = Date;
376     return true;
377 }
378
379 bool DateComponents::parseWeek(const UChar* src, unsigned length, unsigned start, unsigned& end)
380 {
381     ASSERT(src);
382     unsigned index;
383     if (!parseYear(src, length, start, index))
384         return false;
385
386     // 4 characters ('-' 'W' digit digit) are needed.
387     if (index + 3 >= length)
388         return false;
389     if (src[index] != '-')
390         return false;
391     ++index;
392     if (src[index] != 'W')
393         return false;
394     ++index;
395
396     int week;
397     if (!toInt(src, length, index, 2, week) || week < 1 || week > maxWeekNumberInYear())
398         return false;
399     if (m_year == maximumYear && week > maximumWeekInMaximumYear)
400         return false;
401     m_week = week;
402     end = index + 2;
403     m_type = Week;
404     return true;
405 }
406
407 bool DateComponents::parseTime(const UChar* src, unsigned length, unsigned start, unsigned& end)
408 {
409     ASSERT(src);
410     int hour;
411     if (!toInt(src, length, start, 2, hour) || hour < 0 || hour > 23)
412         return false;
413     unsigned index = start + 2;
414     if (index >= length)
415         return false;
416     if (src[index] != ':')
417         return false;
418     ++index;
419
420     int minute;
421     if (!toInt(src, length, index, 2, minute) || minute < 0 || minute > 59)
422         return false;
423     index += 2;
424
425     int second = 0;
426     int millisecond = 0;
427     // Optional second part.
428     // Do not return with false because the part is optional.
429     if (index + 2 < length && src[index] == ':') {
430         if (toInt(src, length, index + 1, 2, second) && second >= 0 && second <= 59) {
431             index += 3;
432
433             // Optional fractional second part.
434             if (index < length && src[index] == '.') {
435                 unsigned digitsLength = countDigits(src, length, index + 1);
436                 if (digitsLength >  0) {
437                     ++index;
438                     bool ok;
439                     if (digitsLength == 1) {
440                         ok = toInt(src, length, index, 1, millisecond);
441                         millisecond *= 100;
442                     } else if (digitsLength == 2) {
443                         ok = toInt(src, length, index, 2, millisecond);
444                         millisecond *= 10;
445                     } else // digitsLength >= 3
446                         ok = toInt(src, length, index, 3, millisecond);
447                     ASSERT_UNUSED(ok, ok);
448                     index += digitsLength;
449                 }
450             }
451         }
452     }
453     m_hour = hour;
454     m_minute = minute;
455     m_second = second;
456     m_millisecond = millisecond;
457     end = index;
458     m_type = Time;
459     return true;
460 }
461
462 bool DateComponents::parseDateTimeLocal(const UChar* src, unsigned length, unsigned start, unsigned& end)
463 {
464     ASSERT(src);
465     unsigned index;
466     if (!parseDate(src, length, start, index))
467         return false;
468     if (index >= length)
469         return false;
470     if (src[index] != 'T')
471         return false;
472     ++index;
473     if (!parseTime(src, length, index, end))
474         return false;
475     if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond))
476         return false;
477     m_type = DateTimeLocal;
478     return true;
479 }
480
481 bool DateComponents::parseDateTime(const UChar* src, unsigned length, unsigned start, unsigned& end)
482 {
483     ASSERT(src);
484     unsigned index;
485     if (!parseDate(src, length, start, index))
486         return false;
487     if (index >= length)
488         return false;
489     if (src[index] != 'T')
490         return false;
491     ++index;
492     if (!parseTime(src, length, index, index))
493         return false;
494     if (!parseTimeZone(src, length, index, end))
495         return false;
496     if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond))
497         return false;
498     m_type = DateTime;
499     return true;
500 }
501
502 static inline double positiveFmod(double value, double divider)
503 {
504     double remainder = fmod(value, divider);
505     return remainder < 0 ? remainder + divider : remainder;
506 }
507
508 void DateComponents::setMillisecondsSinceMidnightInternal(double msInDay)
509 {
510     ASSERT(msInDay >= 0 && msInDay < msPerDay);
511     m_millisecond = static_cast<int>(fmod(msInDay, msPerSecond));
512     double value = floor(msInDay / msPerSecond);
513     m_second = static_cast<int>(fmod(value, secondsPerMinute));
514     value = floor(value / secondsPerMinute);
515     m_minute = static_cast<int>(fmod(value, minutesPerHour));
516     m_hour = static_cast<int>(value / minutesPerHour);
517 }
518
519 bool DateComponents::setMillisecondsSinceEpochForDateInternal(double ms)
520 {
521     m_year = msToYear(ms);
522     int yearDay = dayInYear(ms, m_year);
523     m_month = monthFromDayInYear(yearDay, isLeapYear(m_year));
524     m_monthDay = dayInMonthFromDayInYear(yearDay, isLeapYear(m_year));
525     return true;
526 }
527
528 bool DateComponents::setMillisecondsSinceEpochForDate(double ms)
529 {
530     m_type = Invalid;
531     if (!isfinite(ms))
532         return false;
533     if (!setMillisecondsSinceEpochForDateInternal(round(ms)))
534         return false;
535     if (!withinHTMLDateLimits(m_year, m_month, m_monthDay))
536         return false;
537     m_type = Date;
538     return true;
539 }
540
541 bool DateComponents::setMillisecondsSinceEpochForDateTime(double ms)
542 {
543     m_type = Invalid;
544     if (!isfinite(ms))
545         return false;
546     ms = round(ms);
547     setMillisecondsSinceMidnightInternal(positiveFmod(ms, msPerDay));
548     if (!setMillisecondsSinceEpochForDateInternal(ms))
549         return false;
550     if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond))
551         return false;
552     m_type = DateTime;
553     return true;
554 }
555
556 bool DateComponents::setMillisecondsSinceEpochForDateTimeLocal(double ms)
557 {
558     // Internal representation of DateTimeLocal is the same as DateTime except m_type.
559     if (!setMillisecondsSinceEpochForDateTime(ms))
560         return false;
561     m_type = DateTimeLocal;
562     return true;
563 }
564
565 bool DateComponents::setMillisecondsSinceEpochForMonth(double ms)
566 {
567     m_type = Invalid;
568     if (!isfinite(ms))
569         return false;
570     if (!setMillisecondsSinceEpochForDateInternal(round(ms)))
571         return false;
572     if (!withinHTMLDateLimits(m_year, m_month))
573         return false;
574     m_type = Month;
575     return true;
576 }
577
578 bool DateComponents::setMillisecondsSinceMidnight(double ms)
579 {
580     m_type = Invalid;
581     if (!isfinite(ms))
582         return false;
583     setMillisecondsSinceMidnightInternal(positiveFmod(round(ms), msPerDay));
584     m_type = Time;
585     return true;
586 }
587
588 bool DateComponents::setMonthsSinceEpoch(double months)
589 {
590     if (!isfinite(months))
591         return false;
592     months = round(months);
593     double doubleMonth = positiveFmod(months, 12);
594     double doubleYear = 1970 + (months - doubleMonth) / 12;
595     if (doubleYear < minimumYear || maximumYear < doubleYear)
596         return false;
597     int year = static_cast<int>(doubleYear);
598     int month = static_cast<int>(doubleMonth);
599     if (!withinHTMLDateLimits(year, month))
600         return false;
601     m_year = year;
602     m_month = month;
603     m_type = Month;
604     return true;
605 }
606
607 // Offset from January 1st to Monday of the ISO 8601's first week.
608 //   ex. If January 1st is Friday, such Monday is 3 days later. Returns 3.
609 static int offsetTo1stWeekStart(int year)
610 {
611     int offsetTo1stWeekStart = 1 - dayOfWeek(year, 0, 1);
612     if (offsetTo1stWeekStart <= -4)
613         offsetTo1stWeekStart += 7;
614     return offsetTo1stWeekStart;
615 }
616
617 bool DateComponents::setMillisecondsSinceEpochForWeek(double ms)
618 {
619     m_type = Invalid;
620     if (!isfinite(ms))
621         return false;
622     ms = round(ms);
623
624     m_year = msToYear(ms);
625     if (m_year < minimumYear || m_year > maximumYear)
626         return false;
627
628     int yearDay = dayInYear(ms, m_year);
629     int offset = offsetTo1stWeekStart(m_year);
630     if (yearDay < offset) {
631         // The day belongs to the last week of the previous year.
632         m_year--;
633         if (m_year <= minimumYear)
634             return false;
635         m_week = maxWeekNumberInYear();
636     } else {
637         m_week = ((yearDay - offset) / 7) + 1;
638         if (m_week > maxWeekNumberInYear()) {
639             m_year++;
640             m_week = 1;
641         }
642         if (m_year > maximumYear || (m_year == maximumYear && m_week > maximumWeekInMaximumYear))
643             return false;
644     }
645     m_type = Week;
646     return true;
647 }
648
649 double DateComponents::millisecondsSinceEpochForTime() const
650 {
651     ASSERT(m_type == Time || m_type == DateTime || m_type == DateTimeLocal);
652     return ((m_hour * minutesPerHour + m_minute) * secondsPerMinute + m_second) * msPerSecond + m_millisecond;
653 }
654
655 double DateComponents::millisecondsSinceEpoch() const
656 {
657     switch (m_type) {
658     case Date:
659         return dateToDaysFrom1970(m_year, m_month, m_monthDay) * msPerDay;
660     case DateTime:
661     case DateTimeLocal:
662         return dateToDaysFrom1970(m_year, m_month, m_monthDay) * msPerDay + millisecondsSinceEpochForTime();
663     case Month:
664         return dateToDaysFrom1970(m_year, m_month, 1) * msPerDay;
665     case Time:
666         return millisecondsSinceEpochForTime();
667     case Week:
668         return (dateToDaysFrom1970(m_year, 0, 1) + offsetTo1stWeekStart(m_year) + (m_week - 1) * 7) * msPerDay;
669     case Invalid:
670         break;
671     }
672     ASSERT_NOT_REACHED();
673     return invalidMilliseconds();
674 }
675
676 double DateComponents::monthsSinceEpoch() const
677 {
678     ASSERT(m_type == Month);
679     return (m_year - 1970) * 12 + m_month;
680 }
681
682 String DateComponents::toStringForTime(SecondFormat format) const
683 {
684     ASSERT(m_type == DateTime || m_type == DateTimeLocal || m_type == Time);
685     SecondFormat effectiveFormat = format;
686     if (m_millisecond)
687         effectiveFormat = Millisecond;
688     else if (format == None && m_second)
689         effectiveFormat = Second;
690
691     switch (effectiveFormat) {
692     default:
693         ASSERT_NOT_REACHED();
694         // Fallback to None.
695     case None:
696         return String::format("%02d:%02d", m_hour, m_minute);
697     case Second:
698         return String::format("%02d:%02d:%02d", m_hour, m_minute, m_second);
699     case Millisecond:
700         return String::format("%02d:%02d:%02d.%03d", m_hour, m_minute, m_second, m_millisecond);
701     }
702 }
703
704 String DateComponents::toString(SecondFormat format) const
705 {
706     switch (m_type) {
707     case Date:
708         return String::format("%04d-%02d-%02d", m_year, m_month + 1, m_monthDay);
709     case DateTime:
710         return String::format("%04d-%02d-%02dT", m_year, m_month + 1, m_monthDay)
711             + toStringForTime(format) + String("Z");
712     case DateTimeLocal:
713         return String::format("%04d-%02d-%02dT", m_year, m_month + 1, m_monthDay)
714             + toStringForTime(format);
715     case Month:
716         return String::format("%04d-%02d", m_year, m_month + 1);
717     case Time:
718         return toStringForTime(format);
719     case Week:
720         return String::format("%04d-W%02d", m_year, m_week);
721     case Invalid:
722         break;
723     }
724     ASSERT_NOT_REACHED();
725     return String("(Invalid DateComponents)");
726 }
727
728 } // namespace WebCore