|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
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 /* |
|
8 * The Intl module specified by standard ECMA-402, |
|
9 * ECMAScript Internationalization API Specification. |
|
10 */ |
|
11 |
|
12 #include "builtin/Intl.h" |
|
13 |
|
14 #include <string.h> |
|
15 |
|
16 #include "jsapi.h" |
|
17 #include "jsatom.h" |
|
18 #include "jscntxt.h" |
|
19 #include "jsobj.h" |
|
20 |
|
21 #if ENABLE_INTL_API |
|
22 #include "unicode/locid.h" |
|
23 #include "unicode/numsys.h" |
|
24 #include "unicode/ucal.h" |
|
25 #include "unicode/ucol.h" |
|
26 #include "unicode/udat.h" |
|
27 #include "unicode/udatpg.h" |
|
28 #include "unicode/uenum.h" |
|
29 #include "unicode/unum.h" |
|
30 #include "unicode/ustring.h" |
|
31 #endif |
|
32 #include "vm/DateTime.h" |
|
33 #include "vm/GlobalObject.h" |
|
34 #include "vm/Interpreter.h" |
|
35 #include "vm/Stack.h" |
|
36 #include "vm/StringBuffer.h" |
|
37 |
|
38 #include "jsobjinlines.h" |
|
39 |
|
40 #include "vm/ObjectImpl-inl.h" |
|
41 |
|
42 using namespace js; |
|
43 |
|
44 using mozilla::IsFinite; |
|
45 using mozilla::IsNegativeZero; |
|
46 |
|
47 #if ENABLE_INTL_API |
|
48 using icu::Locale; |
|
49 using icu::NumberingSystem; |
|
50 #endif |
|
51 |
|
52 |
|
53 /* |
|
54 * Pervasive note: ICU functions taking a UErrorCode in/out parameter always |
|
55 * test that parameter before doing anything, and will return immediately if |
|
56 * the value indicates that a failure occurred in a prior ICU call, |
|
57 * without doing anything else. See |
|
58 * http://userguide.icu-project.org/design#TOC-Error-Handling |
|
59 */ |
|
60 |
|
61 |
|
62 /******************** ICU stubs ********************/ |
|
63 |
|
64 #if !ENABLE_INTL_API |
|
65 |
|
66 /* |
|
67 * When the Internationalization API isn't enabled, we also shouldn't link |
|
68 * against ICU. However, we still want to compile this code in order to prevent |
|
69 * bit rot. The following stub implementations for ICU functions make this |
|
70 * possible. The functions using them should never be called, so they assert |
|
71 * and return error codes. Signatures adapted from ICU header files locid.h, |
|
72 * numsys.h, ucal.h, ucol.h, udat.h, udatpg.h, uenum.h, unum.h; see the ICU |
|
73 * directory for license. |
|
74 */ |
|
75 |
|
76 static int32_t |
|
77 u_strlen(const UChar *s) |
|
78 { |
|
79 MOZ_ASSUME_UNREACHABLE("u_strlen: Intl API disabled"); |
|
80 } |
|
81 |
|
82 struct UEnumeration; |
|
83 |
|
84 static int32_t |
|
85 uenum_count(UEnumeration *en, UErrorCode *status) |
|
86 { |
|
87 MOZ_ASSUME_UNREACHABLE("uenum_count: Intl API disabled"); |
|
88 } |
|
89 |
|
90 static const char * |
|
91 uenum_next(UEnumeration *en, int32_t *resultLength, UErrorCode *status) |
|
92 { |
|
93 MOZ_ASSUME_UNREACHABLE("uenum_next: Intl API disabled"); |
|
94 } |
|
95 |
|
96 static void |
|
97 uenum_close(UEnumeration *en) |
|
98 { |
|
99 MOZ_ASSUME_UNREACHABLE("uenum_close: Intl API disabled"); |
|
100 } |
|
101 |
|
102 struct UCollator; |
|
103 |
|
104 enum UColAttribute { |
|
105 UCOL_ALTERNATE_HANDLING, |
|
106 UCOL_CASE_FIRST, |
|
107 UCOL_CASE_LEVEL, |
|
108 UCOL_NORMALIZATION_MODE, |
|
109 UCOL_STRENGTH, |
|
110 UCOL_NUMERIC_COLLATION, |
|
111 }; |
|
112 |
|
113 enum UColAttributeValue { |
|
114 UCOL_DEFAULT = -1, |
|
115 UCOL_PRIMARY = 0, |
|
116 UCOL_SECONDARY = 1, |
|
117 UCOL_TERTIARY = 2, |
|
118 UCOL_OFF = 16, |
|
119 UCOL_ON = 17, |
|
120 UCOL_SHIFTED = 20, |
|
121 UCOL_LOWER_FIRST = 24, |
|
122 UCOL_UPPER_FIRST = 25, |
|
123 }; |
|
124 |
|
125 enum UCollationResult { |
|
126 UCOL_EQUAL = 0, |
|
127 UCOL_GREATER = 1, |
|
128 UCOL_LESS = -1 |
|
129 }; |
|
130 |
|
131 static int32_t |
|
132 ucol_countAvailable(void) |
|
133 { |
|
134 MOZ_ASSUME_UNREACHABLE("ucol_countAvailable: Intl API disabled"); |
|
135 } |
|
136 |
|
137 static const char * |
|
138 ucol_getAvailable(int32_t localeIndex) |
|
139 { |
|
140 MOZ_ASSUME_UNREACHABLE("ucol_getAvailable: Intl API disabled"); |
|
141 } |
|
142 |
|
143 static UCollator * |
|
144 ucol_open(const char *loc, UErrorCode *status) |
|
145 { |
|
146 MOZ_ASSUME_UNREACHABLE("ucol_open: Intl API disabled"); |
|
147 } |
|
148 |
|
149 static void |
|
150 ucol_setAttribute(UCollator *coll, UColAttribute attr, UColAttributeValue value, UErrorCode *status) |
|
151 { |
|
152 MOZ_ASSUME_UNREACHABLE("ucol_setAttribute: Intl API disabled"); |
|
153 } |
|
154 |
|
155 static UCollationResult |
|
156 ucol_strcoll(const UCollator *coll, const UChar *source, int32_t sourceLength, |
|
157 const UChar *target, int32_t targetLength) |
|
158 { |
|
159 MOZ_ASSUME_UNREACHABLE("ucol_strcoll: Intl API disabled"); |
|
160 } |
|
161 |
|
162 static void |
|
163 ucol_close(UCollator *coll) |
|
164 { |
|
165 MOZ_ASSUME_UNREACHABLE("ucol_close: Intl API disabled"); |
|
166 } |
|
167 |
|
168 static UEnumeration * |
|
169 ucol_getKeywordValuesForLocale(const char *key, const char *locale, UBool commonlyUsed, |
|
170 UErrorCode *status) |
|
171 { |
|
172 MOZ_ASSUME_UNREACHABLE("ucol_getKeywordValuesForLocale: Intl API disabled"); |
|
173 } |
|
174 |
|
175 struct UParseError; |
|
176 struct UFieldPosition; |
|
177 typedef void *UNumberFormat; |
|
178 |
|
179 enum UNumberFormatStyle { |
|
180 UNUM_DECIMAL = 1, |
|
181 UNUM_CURRENCY, |
|
182 UNUM_PERCENT, |
|
183 UNUM_CURRENCY_ISO, |
|
184 UNUM_CURRENCY_PLURAL, |
|
185 }; |
|
186 |
|
187 enum UNumberFormatRoundingMode { |
|
188 UNUM_ROUND_HALFUP, |
|
189 }; |
|
190 |
|
191 enum UNumberFormatAttribute { |
|
192 UNUM_GROUPING_USED, |
|
193 UNUM_MIN_INTEGER_DIGITS, |
|
194 UNUM_MAX_FRACTION_DIGITS, |
|
195 UNUM_MIN_FRACTION_DIGITS, |
|
196 UNUM_ROUNDING_MODE, |
|
197 UNUM_SIGNIFICANT_DIGITS_USED, |
|
198 UNUM_MIN_SIGNIFICANT_DIGITS, |
|
199 UNUM_MAX_SIGNIFICANT_DIGITS, |
|
200 }; |
|
201 |
|
202 enum UNumberFormatTextAttribute { |
|
203 UNUM_CURRENCY_CODE, |
|
204 }; |
|
205 |
|
206 static int32_t |
|
207 unum_countAvailable(void) |
|
208 { |
|
209 MOZ_ASSUME_UNREACHABLE("unum_countAvailable: Intl API disabled"); |
|
210 } |
|
211 |
|
212 static const char * |
|
213 unum_getAvailable(int32_t localeIndex) |
|
214 { |
|
215 MOZ_ASSUME_UNREACHABLE("unum_getAvailable: Intl API disabled"); |
|
216 } |
|
217 |
|
218 static UNumberFormat * |
|
219 unum_open(UNumberFormatStyle style, const UChar *pattern, int32_t patternLength, |
|
220 const char *locale, UParseError *parseErr, UErrorCode *status) |
|
221 { |
|
222 MOZ_ASSUME_UNREACHABLE("unum_open: Intl API disabled"); |
|
223 } |
|
224 |
|
225 static void |
|
226 unum_setAttribute(UNumberFormat *fmt, UNumberFormatAttribute attr, int32_t newValue) |
|
227 { |
|
228 MOZ_ASSUME_UNREACHABLE("unum_setAttribute: Intl API disabled"); |
|
229 } |
|
230 |
|
231 static int32_t |
|
232 unum_formatDouble(const UNumberFormat *fmt, double number, UChar *result, |
|
233 int32_t resultLength, UFieldPosition *pos, UErrorCode *status) |
|
234 { |
|
235 MOZ_ASSUME_UNREACHABLE("unum_formatDouble: Intl API disabled"); |
|
236 } |
|
237 |
|
238 static void |
|
239 unum_close(UNumberFormat *fmt) |
|
240 { |
|
241 MOZ_ASSUME_UNREACHABLE("unum_close: Intl API disabled"); |
|
242 } |
|
243 |
|
244 static void |
|
245 unum_setTextAttribute(UNumberFormat *fmt, UNumberFormatTextAttribute tag, const UChar *newValue, |
|
246 int32_t newValueLength, UErrorCode *status) |
|
247 { |
|
248 MOZ_ASSUME_UNREACHABLE("unum_setTextAttribute: Intl API disabled"); |
|
249 } |
|
250 |
|
251 class Locale { |
|
252 public: |
|
253 Locale(const char *language, const char *country = 0, const char *variant = 0, |
|
254 const char *keywordsAndValues = 0); |
|
255 }; |
|
256 |
|
257 Locale::Locale(const char *language, const char *country, const char *variant, |
|
258 const char *keywordsAndValues) |
|
259 { |
|
260 MOZ_ASSUME_UNREACHABLE("Locale::Locale: Intl API disabled"); |
|
261 } |
|
262 |
|
263 class NumberingSystem { |
|
264 public: |
|
265 static NumberingSystem *createInstance(const Locale &inLocale, UErrorCode &status); |
|
266 const char *getName(); |
|
267 }; |
|
268 |
|
269 NumberingSystem * |
|
270 NumberingSystem::createInstance(const Locale &inLocale, UErrorCode &status) |
|
271 { |
|
272 MOZ_ASSUME_UNREACHABLE("NumberingSystem::createInstance: Intl API disabled"); |
|
273 } |
|
274 |
|
275 const char * |
|
276 NumberingSystem::getName() |
|
277 { |
|
278 MOZ_ASSUME_UNREACHABLE("NumberingSystem::getName: Intl API disabled"); |
|
279 } |
|
280 |
|
281 typedef void *UCalendar; |
|
282 |
|
283 enum UCalendarType { |
|
284 UCAL_TRADITIONAL, |
|
285 UCAL_DEFAULT = UCAL_TRADITIONAL, |
|
286 UCAL_GREGORIAN |
|
287 }; |
|
288 |
|
289 static UCalendar * |
|
290 ucal_open(const UChar *zoneID, int32_t len, const char *locale, |
|
291 UCalendarType type, UErrorCode *status) |
|
292 { |
|
293 MOZ_ASSUME_UNREACHABLE("ucal_open: Intl API disabled"); |
|
294 } |
|
295 |
|
296 static const char * |
|
297 ucal_getType(const UCalendar *cal, UErrorCode *status) |
|
298 { |
|
299 MOZ_ASSUME_UNREACHABLE("ucal_getType: Intl API disabled"); |
|
300 } |
|
301 |
|
302 static UEnumeration * |
|
303 ucal_getKeywordValuesForLocale(const char *key, const char *locale, |
|
304 UBool commonlyUsed, UErrorCode *status) |
|
305 { |
|
306 MOZ_ASSUME_UNREACHABLE("ucal_getKeywordValuesForLocale: Intl API disabled"); |
|
307 } |
|
308 |
|
309 static void |
|
310 ucal_close(UCalendar *cal) |
|
311 { |
|
312 MOZ_ASSUME_UNREACHABLE("ucal_close: Intl API disabled"); |
|
313 } |
|
314 |
|
315 typedef void *UDateTimePatternGenerator; |
|
316 |
|
317 static UDateTimePatternGenerator * |
|
318 udatpg_open(const char *locale, UErrorCode *pErrorCode) |
|
319 { |
|
320 MOZ_ASSUME_UNREACHABLE("udatpg_open: Intl API disabled"); |
|
321 } |
|
322 |
|
323 static int32_t |
|
324 udatpg_getBestPattern(UDateTimePatternGenerator *dtpg, const UChar *skeleton, |
|
325 int32_t length, UChar *bestPattern, int32_t capacity, |
|
326 UErrorCode *pErrorCode) |
|
327 { |
|
328 MOZ_ASSUME_UNREACHABLE("udatpg_getBestPattern: Intl API disabled"); |
|
329 } |
|
330 |
|
331 static void |
|
332 udatpg_close(UDateTimePatternGenerator *dtpg) |
|
333 { |
|
334 MOZ_ASSUME_UNREACHABLE("udatpg_close: Intl API disabled"); |
|
335 } |
|
336 |
|
337 typedef void *UCalendar; |
|
338 typedef void *UDateFormat; |
|
339 |
|
340 enum UDateFormatStyle { |
|
341 UDAT_PATTERN = -2, |
|
342 UDAT_IGNORE = UDAT_PATTERN |
|
343 }; |
|
344 |
|
345 static int32_t |
|
346 udat_countAvailable(void) |
|
347 { |
|
348 MOZ_ASSUME_UNREACHABLE("udat_countAvailable: Intl API disabled"); |
|
349 } |
|
350 |
|
351 static const char * |
|
352 udat_getAvailable(int32_t localeIndex) |
|
353 { |
|
354 MOZ_ASSUME_UNREACHABLE("udat_getAvailable: Intl API disabled"); |
|
355 } |
|
356 |
|
357 static UDateFormat * |
|
358 udat_open(UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const char *locale, |
|
359 const UChar *tzID, int32_t tzIDLength, const UChar *pattern, |
|
360 int32_t patternLength, UErrorCode *status) |
|
361 { |
|
362 MOZ_ASSUME_UNREACHABLE("udat_open: Intl API disabled"); |
|
363 } |
|
364 |
|
365 static const UCalendar * |
|
366 udat_getCalendar(const UDateFormat *fmt) |
|
367 { |
|
368 MOZ_ASSUME_UNREACHABLE("udat_getCalendar: Intl API disabled"); |
|
369 } |
|
370 |
|
371 static void |
|
372 ucal_setGregorianChange(UCalendar *cal, UDate date, UErrorCode *pErrorCode) |
|
373 { |
|
374 MOZ_ASSUME_UNREACHABLE("ucal_setGregorianChange: Intl API disabled"); |
|
375 } |
|
376 |
|
377 static int32_t |
|
378 udat_format(const UDateFormat *format, UDate dateToFormat, UChar *result, |
|
379 int32_t resultLength, UFieldPosition *position, UErrorCode *status) |
|
380 { |
|
381 MOZ_ASSUME_UNREACHABLE("udat_format: Intl API disabled"); |
|
382 } |
|
383 |
|
384 static void |
|
385 udat_close(UDateFormat *format) |
|
386 { |
|
387 MOZ_ASSUME_UNREACHABLE("udat_close: Intl API disabled"); |
|
388 } |
|
389 |
|
390 #endif |
|
391 |
|
392 |
|
393 /******************** Common to Intl constructors ********************/ |
|
394 |
|
395 static bool |
|
396 IntlInitialize(JSContext *cx, HandleObject obj, Handle<PropertyName*> initializer, |
|
397 HandleValue locales, HandleValue options) |
|
398 { |
|
399 RootedValue initializerValue(cx); |
|
400 if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue)) |
|
401 return false; |
|
402 JS_ASSERT(initializerValue.isObject()); |
|
403 JS_ASSERT(initializerValue.toObject().is<JSFunction>()); |
|
404 |
|
405 InvokeArgs args(cx); |
|
406 if (!args.init(3)) |
|
407 return false; |
|
408 |
|
409 args.setCallee(initializerValue); |
|
410 args.setThis(NullValue()); |
|
411 args[0].setObject(*obj); |
|
412 args[1].set(locales); |
|
413 args[2].set(options); |
|
414 |
|
415 return Invoke(cx, args); |
|
416 } |
|
417 |
|
418 // CountAvailable and GetAvailable describe the signatures used for ICU API |
|
419 // to determine available locales for various functionality. |
|
420 typedef int32_t |
|
421 (* CountAvailable)(void); |
|
422 |
|
423 typedef const char * |
|
424 (* GetAvailable)(int32_t localeIndex); |
|
425 |
|
426 static bool |
|
427 intl_availableLocales(JSContext *cx, CountAvailable countAvailable, |
|
428 GetAvailable getAvailable, MutableHandleValue result) |
|
429 { |
|
430 RootedObject locales(cx, NewObjectWithGivenProto(cx, &JSObject::class_, nullptr, nullptr)); |
|
431 if (!locales) |
|
432 return false; |
|
433 |
|
434 #if ENABLE_INTL_API |
|
435 uint32_t count = countAvailable(); |
|
436 RootedValue t(cx, BooleanValue(true)); |
|
437 for (uint32_t i = 0; i < count; i++) { |
|
438 const char *locale = getAvailable(i); |
|
439 ScopedJSFreePtr<char> lang(JS_strdup(cx, locale)); |
|
440 if (!lang) |
|
441 return false; |
|
442 char *p; |
|
443 while ((p = strchr(lang, '_'))) |
|
444 *p = '-'; |
|
445 RootedAtom a(cx, Atomize(cx, lang, strlen(lang))); |
|
446 if (!a) |
|
447 return false; |
|
448 if (!JSObject::defineProperty(cx, locales, a->asPropertyName(), t, |
|
449 JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE)) |
|
450 { |
|
451 return false; |
|
452 } |
|
453 } |
|
454 #endif |
|
455 result.setObject(*locales); |
|
456 return true; |
|
457 } |
|
458 |
|
459 /** |
|
460 * Returns the object holding the internal properties for obj. |
|
461 */ |
|
462 static bool |
|
463 GetInternals(JSContext *cx, HandleObject obj, MutableHandleObject internals) |
|
464 { |
|
465 RootedValue getInternalsValue(cx); |
|
466 if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().getInternals, &getInternalsValue)) |
|
467 return false; |
|
468 JS_ASSERT(getInternalsValue.isObject()); |
|
469 JS_ASSERT(getInternalsValue.toObject().is<JSFunction>()); |
|
470 |
|
471 InvokeArgs args(cx); |
|
472 if (!args.init(1)) |
|
473 return false; |
|
474 |
|
475 args.setCallee(getInternalsValue); |
|
476 args.setThis(NullValue()); |
|
477 args[0].setObject(*obj); |
|
478 |
|
479 if (!Invoke(cx, args)) |
|
480 return false; |
|
481 internals.set(&args.rval().toObject()); |
|
482 return true; |
|
483 } |
|
484 |
|
485 static bool |
|
486 equal(const char *s1, const char *s2) |
|
487 { |
|
488 return !strcmp(s1, s2); |
|
489 } |
|
490 |
|
491 static bool |
|
492 equal(JSAutoByteString &s1, const char *s2) |
|
493 { |
|
494 return !strcmp(s1.ptr(), s2); |
|
495 } |
|
496 |
|
497 static const char * |
|
498 icuLocale(const char *locale) |
|
499 { |
|
500 if (equal(locale, "und")) |
|
501 return ""; // ICU root locale |
|
502 return locale; |
|
503 } |
|
504 |
|
505 // Simple RAII for ICU objects. MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE |
|
506 // unfortunately doesn't work because of namespace incompatibilities |
|
507 // (TypeSpecificDelete cannot be in icu and mozilla at the same time) |
|
508 // and because ICU declares both UNumberFormat and UDateTimePatternGenerator |
|
509 // as void*. |
|
510 template <typename T> |
|
511 class ScopedICUObject |
|
512 { |
|
513 T *ptr_; |
|
514 void (* deleter_)(T*); |
|
515 |
|
516 public: |
|
517 ScopedICUObject(T *ptr, void (*deleter)(T*)) |
|
518 : ptr_(ptr), |
|
519 deleter_(deleter) |
|
520 {} |
|
521 |
|
522 ~ScopedICUObject() { |
|
523 if (ptr_) |
|
524 deleter_(ptr_); |
|
525 } |
|
526 |
|
527 // In cases where an object should be deleted on abnormal exits, |
|
528 // but returned to the caller if everything goes well, call forget() |
|
529 // to transfer the object just before returning. |
|
530 T *forget() { |
|
531 T *tmp = ptr_; |
|
532 ptr_ = nullptr; |
|
533 return tmp; |
|
534 } |
|
535 }; |
|
536 |
|
537 // As a small optimization (not important for correctness), this is the inline |
|
538 // capacity of a StringBuffer. |
|
539 static const size_t INITIAL_STRING_BUFFER_SIZE = 32; |
|
540 |
|
541 |
|
542 /******************** Collator ********************/ |
|
543 |
|
544 static void collator_finalize(FreeOp *fop, JSObject *obj); |
|
545 |
|
546 static const uint32_t UCOLLATOR_SLOT = 0; |
|
547 static const uint32_t COLLATOR_SLOTS_COUNT = 1; |
|
548 |
|
549 static const Class CollatorClass = { |
|
550 js_Object_str, |
|
551 JSCLASS_HAS_RESERVED_SLOTS(COLLATOR_SLOTS_COUNT), |
|
552 JS_PropertyStub, /* addProperty */ |
|
553 JS_DeletePropertyStub, /* delProperty */ |
|
554 JS_PropertyStub, /* getProperty */ |
|
555 JS_StrictPropertyStub, /* setProperty */ |
|
556 JS_EnumerateStub, |
|
557 JS_ResolveStub, |
|
558 JS_ConvertStub, |
|
559 collator_finalize |
|
560 }; |
|
561 |
|
562 #if JS_HAS_TOSOURCE |
|
563 static bool |
|
564 collator_toSource(JSContext *cx, unsigned argc, Value *vp) |
|
565 { |
|
566 CallArgs args = CallArgsFromVp(argc, vp); |
|
567 args.rval().setString(cx->names().Collator); |
|
568 return true; |
|
569 } |
|
570 #endif |
|
571 |
|
572 static const JSFunctionSpec collator_static_methods[] = { |
|
573 JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Collator_supportedLocalesOf", 1, 0), |
|
574 JS_FS_END |
|
575 }; |
|
576 |
|
577 static const JSFunctionSpec collator_methods[] = { |
|
578 JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0), |
|
579 #if JS_HAS_TOSOURCE |
|
580 JS_FN(js_toSource_str, collator_toSource, 0, 0), |
|
581 #endif |
|
582 JS_FS_END |
|
583 }; |
|
584 |
|
585 /** |
|
586 * Collator constructor. |
|
587 * Spec: ECMAScript Internationalization API Specification, 10.1 |
|
588 */ |
|
589 static bool |
|
590 Collator(JSContext *cx, CallArgs args, bool construct) |
|
591 { |
|
592 RootedObject obj(cx); |
|
593 |
|
594 if (!construct) { |
|
595 // 10.1.2.1 step 3 |
|
596 JSObject *intl = cx->global()->getOrCreateIntlObject(cx); |
|
597 if (!intl) |
|
598 return false; |
|
599 RootedValue self(cx, args.thisv()); |
|
600 if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { |
|
601 // 10.1.2.1 step 4 |
|
602 obj = ToObject(cx, self); |
|
603 if (!obj) |
|
604 return false; |
|
605 |
|
606 // 10.1.2.1 step 5 |
|
607 bool extensible; |
|
608 if (!JSObject::isExtensible(cx, obj, &extensible)) |
|
609 return false; |
|
610 if (!extensible) |
|
611 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); |
|
612 } else { |
|
613 // 10.1.2.1 step 3.a |
|
614 construct = true; |
|
615 } |
|
616 } |
|
617 if (construct) { |
|
618 // 10.1.3.1 paragraph 2 |
|
619 RootedObject proto(cx, cx->global()->getOrCreateCollatorPrototype(cx)); |
|
620 if (!proto) |
|
621 return false; |
|
622 obj = NewObjectWithGivenProto(cx, &CollatorClass, proto, cx->global()); |
|
623 if (!obj) |
|
624 return false; |
|
625 |
|
626 obj->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); |
|
627 } |
|
628 |
|
629 // 10.1.2.1 steps 1 and 2; 10.1.3.1 steps 1 and 2 |
|
630 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); |
|
631 RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); |
|
632 |
|
633 // 10.1.2.1 step 6; 10.1.3.1 step 3 |
|
634 if (!IntlInitialize(cx, obj, cx->names().InitializeCollator, locales, options)) |
|
635 return false; |
|
636 |
|
637 // 10.1.2.1 steps 3.a and 7 |
|
638 args.rval().setObject(*obj); |
|
639 return true; |
|
640 } |
|
641 |
|
642 static bool |
|
643 Collator(JSContext *cx, unsigned argc, Value *vp) |
|
644 { |
|
645 CallArgs args = CallArgsFromVp(argc, vp); |
|
646 return Collator(cx, args, args.isConstructing()); |
|
647 } |
|
648 |
|
649 bool |
|
650 js::intl_Collator(JSContext *cx, unsigned argc, Value *vp) |
|
651 { |
|
652 CallArgs args = CallArgsFromVp(argc, vp); |
|
653 JS_ASSERT(args.length() == 2); |
|
654 // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot |
|
655 // be used with "new", but it still has to be treated as a constructor. |
|
656 return Collator(cx, args, true); |
|
657 } |
|
658 |
|
659 static void |
|
660 collator_finalize(FreeOp *fop, JSObject *obj) |
|
661 { |
|
662 UCollator *coll = static_cast<UCollator*>(obj->getReservedSlot(UCOLLATOR_SLOT).toPrivate()); |
|
663 if (coll) |
|
664 ucol_close(coll); |
|
665 } |
|
666 |
|
667 static JSObject * |
|
668 InitCollatorClass(JSContext *cx, HandleObject Intl, Handle<GlobalObject*> global) |
|
669 { |
|
670 RootedFunction ctor(cx, global->createConstructor(cx, &Collator, cx->names().Collator, 0)); |
|
671 if (!ctor) |
|
672 return nullptr; |
|
673 |
|
674 RootedObject proto(cx, global->as<GlobalObject>().getOrCreateCollatorPrototype(cx)); |
|
675 if (!proto) |
|
676 return nullptr; |
|
677 if (!LinkConstructorAndPrototype(cx, ctor, proto)) |
|
678 return nullptr; |
|
679 |
|
680 // 10.2.2 |
|
681 if (!JS_DefineFunctions(cx, ctor, collator_static_methods)) |
|
682 return nullptr; |
|
683 |
|
684 // 10.3.2 and 10.3.3 |
|
685 if (!JS_DefineFunctions(cx, proto, collator_methods)) |
|
686 return nullptr; |
|
687 |
|
688 /* |
|
689 * Install the getter for Collator.prototype.compare, which returns a bound |
|
690 * comparison function for the specified Collator object (suitable for |
|
691 * passing to methods like Array.prototype.sort). |
|
692 */ |
|
693 RootedValue getter(cx); |
|
694 if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CollatorCompareGet, &getter)) |
|
695 return nullptr; |
|
696 if (!JSObject::defineProperty(cx, proto, cx->names().compare, UndefinedHandleValue, |
|
697 JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()), |
|
698 nullptr, JSPROP_GETTER | JSPROP_SHARED)) |
|
699 { |
|
700 return nullptr; |
|
701 } |
|
702 |
|
703 // 10.2.1 and 10.3 |
|
704 if (!IntlInitialize(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, |
|
705 UndefinedHandleValue)) |
|
706 { |
|
707 return nullptr; |
|
708 } |
|
709 |
|
710 // 8.1 |
|
711 RootedValue ctorValue(cx, ObjectValue(*ctor)); |
|
712 if (!JSObject::defineProperty(cx, Intl, cx->names().Collator, ctorValue, |
|
713 JS_PropertyStub, JS_StrictPropertyStub, 0)) |
|
714 { |
|
715 return nullptr; |
|
716 } |
|
717 |
|
718 return ctor; |
|
719 } |
|
720 |
|
721 bool |
|
722 GlobalObject::initCollatorProto(JSContext *cx, Handle<GlobalObject*> global) |
|
723 { |
|
724 RootedObject proto(cx, global->createBlankPrototype(cx, &CollatorClass)); |
|
725 if (!proto) |
|
726 return false; |
|
727 proto->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); |
|
728 global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*proto)); |
|
729 return true; |
|
730 } |
|
731 |
|
732 bool |
|
733 js::intl_Collator_availableLocales(JSContext *cx, unsigned argc, Value *vp) |
|
734 { |
|
735 CallArgs args = CallArgsFromVp(argc, vp); |
|
736 JS_ASSERT(args.length() == 0); |
|
737 |
|
738 RootedValue result(cx); |
|
739 if (!intl_availableLocales(cx, ucol_countAvailable, ucol_getAvailable, &result)) |
|
740 return false; |
|
741 args.rval().set(result); |
|
742 return true; |
|
743 } |
|
744 |
|
745 bool |
|
746 js::intl_availableCollations(JSContext *cx, unsigned argc, Value *vp) |
|
747 { |
|
748 CallArgs args = CallArgsFromVp(argc, vp); |
|
749 JS_ASSERT(args.length() == 1); |
|
750 JS_ASSERT(args[0].isString()); |
|
751 |
|
752 JSAutoByteString locale(cx, args[0].toString()); |
|
753 if (!locale) |
|
754 return false; |
|
755 UErrorCode status = U_ZERO_ERROR; |
|
756 UEnumeration *values = ucol_getKeywordValuesForLocale("co", locale.ptr(), false, &status); |
|
757 if (U_FAILURE(status)) { |
|
758 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
759 return false; |
|
760 } |
|
761 ScopedICUObject<UEnumeration> toClose(values, uenum_close); |
|
762 |
|
763 uint32_t count = uenum_count(values, &status); |
|
764 if (U_FAILURE(status)) { |
|
765 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
766 return false; |
|
767 } |
|
768 |
|
769 RootedObject collations(cx, NewDenseEmptyArray(cx)); |
|
770 if (!collations) |
|
771 return false; |
|
772 |
|
773 uint32_t index = 0; |
|
774 for (uint32_t i = 0; i < count; i++) { |
|
775 const char *collation = uenum_next(values, nullptr, &status); |
|
776 if (U_FAILURE(status)) { |
|
777 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
778 return false; |
|
779 } |
|
780 |
|
781 // Per ECMA-402, 10.2.3, we don't include standard and search: |
|
782 // "The values 'standard' and 'search' must not be used as elements in |
|
783 // any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co |
|
784 // array." |
|
785 if (equal(collation, "standard") || equal(collation, "search")) |
|
786 continue; |
|
787 |
|
788 // ICU returns old-style keyword values; map them to BCP 47 equivalents |
|
789 // (see http://bugs.icu-project.org/trac/ticket/9620). |
|
790 if (equal(collation, "dictionary")) |
|
791 collation = "dict"; |
|
792 else if (equal(collation, "gb2312han")) |
|
793 collation = "gb2312"; |
|
794 else if (equal(collation, "phonebook")) |
|
795 collation = "phonebk"; |
|
796 else if (equal(collation, "traditional")) |
|
797 collation = "trad"; |
|
798 |
|
799 RootedString jscollation(cx, JS_NewStringCopyZ(cx, collation)); |
|
800 if (!jscollation) |
|
801 return false; |
|
802 RootedValue element(cx, StringValue(jscollation)); |
|
803 if (!JSObject::defineElement(cx, collations, index++, element)) |
|
804 return false; |
|
805 } |
|
806 |
|
807 args.rval().setObject(*collations); |
|
808 return true; |
|
809 } |
|
810 |
|
811 /** |
|
812 * Returns a new UCollator with the locale and collation options |
|
813 * of the given Collator. |
|
814 */ |
|
815 static UCollator * |
|
816 NewUCollator(JSContext *cx, HandleObject collator) |
|
817 { |
|
818 RootedValue value(cx); |
|
819 |
|
820 RootedObject internals(cx); |
|
821 if (!GetInternals(cx, collator, &internals)) |
|
822 return nullptr; |
|
823 |
|
824 if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value)) |
|
825 return nullptr; |
|
826 JSAutoByteString locale(cx, value.toString()); |
|
827 if (!locale) |
|
828 return nullptr; |
|
829 |
|
830 // UCollator options with default values. |
|
831 UColAttributeValue uStrength = UCOL_DEFAULT; |
|
832 UColAttributeValue uCaseLevel = UCOL_OFF; |
|
833 UColAttributeValue uAlternate = UCOL_DEFAULT; |
|
834 UColAttributeValue uNumeric = UCOL_OFF; |
|
835 // Normalization is always on to meet the canonical equivalence requirement. |
|
836 UColAttributeValue uNormalization = UCOL_ON; |
|
837 UColAttributeValue uCaseFirst = UCOL_DEFAULT; |
|
838 |
|
839 if (!JSObject::getProperty(cx, internals, internals, cx->names().usage, &value)) |
|
840 return nullptr; |
|
841 JSAutoByteString usage(cx, value.toString()); |
|
842 if (!usage) |
|
843 return nullptr; |
|
844 if (equal(usage, "search")) { |
|
845 // ICU expects search as a Unicode locale extension on locale. |
|
846 // Unicode locale extensions must occur before private use extensions. |
|
847 const char *oldLocale = locale.ptr(); |
|
848 const char *p; |
|
849 size_t index; |
|
850 size_t localeLen = strlen(oldLocale); |
|
851 if ((p = strstr(oldLocale, "-x-"))) |
|
852 index = p - oldLocale; |
|
853 else |
|
854 index = localeLen; |
|
855 |
|
856 const char *insert; |
|
857 if ((p = strstr(oldLocale, "-u-")) && static_cast<size_t>(p - oldLocale) < index) { |
|
858 index = p - oldLocale + 2; |
|
859 insert = "-co-search"; |
|
860 } else { |
|
861 insert = "-u-co-search"; |
|
862 } |
|
863 size_t insertLen = strlen(insert); |
|
864 char *newLocale = cx->pod_malloc<char>(localeLen + insertLen + 1); |
|
865 if (!newLocale) |
|
866 return nullptr; |
|
867 memcpy(newLocale, oldLocale, index); |
|
868 memcpy(newLocale + index, insert, insertLen); |
|
869 memcpy(newLocale + index + insertLen, oldLocale + index, localeLen - index + 1); // '\0' |
|
870 locale.clear(); |
|
871 locale.initBytes(newLocale); |
|
872 } |
|
873 |
|
874 // We don't need to look at the collation property - it can only be set |
|
875 // via the Unicode locale extension and is therefore already set on |
|
876 // locale. |
|
877 |
|
878 if (!JSObject::getProperty(cx, internals, internals, cx->names().sensitivity, &value)) |
|
879 return nullptr; |
|
880 JSAutoByteString sensitivity(cx, value.toString()); |
|
881 if (!sensitivity) |
|
882 return nullptr; |
|
883 if (equal(sensitivity, "base")) { |
|
884 uStrength = UCOL_PRIMARY; |
|
885 } else if (equal(sensitivity, "accent")) { |
|
886 uStrength = UCOL_SECONDARY; |
|
887 } else if (equal(sensitivity, "case")) { |
|
888 uStrength = UCOL_PRIMARY; |
|
889 uCaseLevel = UCOL_ON; |
|
890 } else { |
|
891 JS_ASSERT(equal(sensitivity, "variant")); |
|
892 uStrength = UCOL_TERTIARY; |
|
893 } |
|
894 |
|
895 if (!JSObject::getProperty(cx, internals, internals, cx->names().ignorePunctuation, &value)) |
|
896 return nullptr; |
|
897 // According to the ICU team, UCOL_SHIFTED causes punctuation to be |
|
898 // ignored. Looking at Unicode Technical Report 35, Unicode Locale Data |
|
899 // Markup Language, "shifted" causes whitespace and punctuation to be |
|
900 // ignored - that's a bit more than asked for, but there's no way to get |
|
901 // less. |
|
902 if (value.toBoolean()) |
|
903 uAlternate = UCOL_SHIFTED; |
|
904 |
|
905 if (!JSObject::getProperty(cx, internals, internals, cx->names().numeric, &value)) |
|
906 return nullptr; |
|
907 if (!value.isUndefined() && value.toBoolean()) |
|
908 uNumeric = UCOL_ON; |
|
909 |
|
910 if (!JSObject::getProperty(cx, internals, internals, cx->names().caseFirst, &value)) |
|
911 return nullptr; |
|
912 if (!value.isUndefined()) { |
|
913 JSAutoByteString caseFirst(cx, value.toString()); |
|
914 if (!caseFirst) |
|
915 return nullptr; |
|
916 if (equal(caseFirst, "upper")) |
|
917 uCaseFirst = UCOL_UPPER_FIRST; |
|
918 else if (equal(caseFirst, "lower")) |
|
919 uCaseFirst = UCOL_LOWER_FIRST; |
|
920 else |
|
921 JS_ASSERT(equal(caseFirst, "false")); |
|
922 } |
|
923 |
|
924 UErrorCode status = U_ZERO_ERROR; |
|
925 UCollator *coll = ucol_open(icuLocale(locale.ptr()), &status); |
|
926 if (U_FAILURE(status)) { |
|
927 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
928 return nullptr; |
|
929 } |
|
930 |
|
931 ucol_setAttribute(coll, UCOL_STRENGTH, uStrength, &status); |
|
932 ucol_setAttribute(coll, UCOL_CASE_LEVEL, uCaseLevel, &status); |
|
933 ucol_setAttribute(coll, UCOL_ALTERNATE_HANDLING, uAlternate, &status); |
|
934 ucol_setAttribute(coll, UCOL_NUMERIC_COLLATION, uNumeric, &status); |
|
935 ucol_setAttribute(coll, UCOL_NORMALIZATION_MODE, uNormalization, &status); |
|
936 ucol_setAttribute(coll, UCOL_CASE_FIRST, uCaseFirst, &status); |
|
937 if (U_FAILURE(status)) { |
|
938 ucol_close(coll); |
|
939 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
940 return nullptr; |
|
941 } |
|
942 |
|
943 return coll; |
|
944 } |
|
945 |
|
946 static bool |
|
947 intl_CompareStrings(JSContext *cx, UCollator *coll, HandleString str1, HandleString str2, MutableHandleValue result) |
|
948 { |
|
949 JS_ASSERT(str1); |
|
950 JS_ASSERT(str2); |
|
951 |
|
952 if (str1 == str2) { |
|
953 result.setInt32(0); |
|
954 return true; |
|
955 } |
|
956 |
|
957 size_t length1 = str1->length(); |
|
958 const jschar *chars1 = str1->getChars(cx); |
|
959 if (!chars1) |
|
960 return false; |
|
961 size_t length2 = str2->length(); |
|
962 const jschar *chars2 = str2->getChars(cx); |
|
963 if (!chars2) |
|
964 return false; |
|
965 |
|
966 UCollationResult uresult = ucol_strcoll(coll, JSCharToUChar(chars1), |
|
967 length1, JSCharToUChar(chars2), |
|
968 length2); |
|
969 |
|
970 int32_t res; |
|
971 switch (uresult) { |
|
972 case UCOL_LESS: res = -1; break; |
|
973 case UCOL_EQUAL: res = 0; break; |
|
974 case UCOL_GREATER: res = 1; break; |
|
975 default: MOZ_ASSUME_UNREACHABLE("ucol_strcoll returned bad UCollationResult"); |
|
976 } |
|
977 result.setInt32(res); |
|
978 return true; |
|
979 } |
|
980 |
|
981 bool |
|
982 js::intl_CompareStrings(JSContext *cx, unsigned argc, Value *vp) |
|
983 { |
|
984 CallArgs args = CallArgsFromVp(argc, vp); |
|
985 JS_ASSERT(args.length() == 3); |
|
986 JS_ASSERT(args[0].isObject()); |
|
987 JS_ASSERT(args[1].isString()); |
|
988 JS_ASSERT(args[2].isString()); |
|
989 |
|
990 RootedObject collator(cx, &args[0].toObject()); |
|
991 |
|
992 // Obtain a UCollator object, cached if possible. |
|
993 // XXX Does this handle Collator instances from other globals correctly? |
|
994 bool isCollatorInstance = collator->getClass() == &CollatorClass; |
|
995 UCollator *coll; |
|
996 if (isCollatorInstance) { |
|
997 coll = static_cast<UCollator *>(collator->getReservedSlot(UCOLLATOR_SLOT).toPrivate()); |
|
998 if (!coll) { |
|
999 coll = NewUCollator(cx, collator); |
|
1000 if (!coll) |
|
1001 return false; |
|
1002 collator->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(coll)); |
|
1003 } |
|
1004 } else { |
|
1005 // There's no good place to cache the ICU collator for an object |
|
1006 // that has been initialized as a Collator but is not a Collator |
|
1007 // instance. One possibility might be to add a Collator instance as an |
|
1008 // internal property to each such object. |
|
1009 coll = NewUCollator(cx, collator); |
|
1010 if (!coll) |
|
1011 return false; |
|
1012 } |
|
1013 |
|
1014 // Use the UCollator to actually compare the strings. |
|
1015 RootedString str1(cx, args[1].toString()); |
|
1016 RootedString str2(cx, args[2].toString()); |
|
1017 RootedValue result(cx); |
|
1018 bool success = intl_CompareStrings(cx, coll, str1, str2, &result); |
|
1019 |
|
1020 if (!isCollatorInstance) |
|
1021 ucol_close(coll); |
|
1022 if (!success) |
|
1023 return false; |
|
1024 args.rval().set(result); |
|
1025 return true; |
|
1026 } |
|
1027 |
|
1028 |
|
1029 /******************** NumberFormat ********************/ |
|
1030 |
|
1031 static void numberFormat_finalize(FreeOp *fop, JSObject *obj); |
|
1032 |
|
1033 static const uint32_t UNUMBER_FORMAT_SLOT = 0; |
|
1034 static const uint32_t NUMBER_FORMAT_SLOTS_COUNT = 1; |
|
1035 |
|
1036 static const Class NumberFormatClass = { |
|
1037 js_Object_str, |
|
1038 JSCLASS_HAS_RESERVED_SLOTS(NUMBER_FORMAT_SLOTS_COUNT), |
|
1039 JS_PropertyStub, /* addProperty */ |
|
1040 JS_DeletePropertyStub, /* delProperty */ |
|
1041 JS_PropertyStub, /* getProperty */ |
|
1042 JS_StrictPropertyStub, /* setProperty */ |
|
1043 JS_EnumerateStub, |
|
1044 JS_ResolveStub, |
|
1045 JS_ConvertStub, |
|
1046 numberFormat_finalize |
|
1047 }; |
|
1048 |
|
1049 #if JS_HAS_TOSOURCE |
|
1050 static bool |
|
1051 numberFormat_toSource(JSContext *cx, unsigned argc, Value *vp) |
|
1052 { |
|
1053 CallArgs args = CallArgsFromVp(argc, vp); |
|
1054 args.rval().setString(cx->names().NumberFormat); |
|
1055 return true; |
|
1056 } |
|
1057 #endif |
|
1058 |
|
1059 static const JSFunctionSpec numberFormat_static_methods[] = { |
|
1060 JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_NumberFormat_supportedLocalesOf", 1, 0), |
|
1061 JS_FS_END |
|
1062 }; |
|
1063 |
|
1064 static const JSFunctionSpec numberFormat_methods[] = { |
|
1065 JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0, 0), |
|
1066 #if JS_HAS_TOSOURCE |
|
1067 JS_FN(js_toSource_str, numberFormat_toSource, 0, 0), |
|
1068 #endif |
|
1069 JS_FS_END |
|
1070 }; |
|
1071 |
|
1072 /** |
|
1073 * NumberFormat constructor. |
|
1074 * Spec: ECMAScript Internationalization API Specification, 11.1 |
|
1075 */ |
|
1076 static bool |
|
1077 NumberFormat(JSContext *cx, CallArgs args, bool construct) |
|
1078 { |
|
1079 RootedObject obj(cx); |
|
1080 |
|
1081 if (!construct) { |
|
1082 // 11.1.2.1 step 3 |
|
1083 JSObject *intl = cx->global()->getOrCreateIntlObject(cx); |
|
1084 if (!intl) |
|
1085 return false; |
|
1086 RootedValue self(cx, args.thisv()); |
|
1087 if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { |
|
1088 // 11.1.2.1 step 4 |
|
1089 obj = ToObject(cx, self); |
|
1090 if (!obj) |
|
1091 return false; |
|
1092 |
|
1093 // 11.1.2.1 step 5 |
|
1094 bool extensible; |
|
1095 if (!JSObject::isExtensible(cx, obj, &extensible)) |
|
1096 return false; |
|
1097 if (!extensible) |
|
1098 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); |
|
1099 } else { |
|
1100 // 11.1.2.1 step 3.a |
|
1101 construct = true; |
|
1102 } |
|
1103 } |
|
1104 if (construct) { |
|
1105 // 11.1.3.1 paragraph 2 |
|
1106 RootedObject proto(cx, cx->global()->getOrCreateNumberFormatPrototype(cx)); |
|
1107 if (!proto) |
|
1108 return false; |
|
1109 obj = NewObjectWithGivenProto(cx, &NumberFormatClass, proto, cx->global()); |
|
1110 if (!obj) |
|
1111 return false; |
|
1112 |
|
1113 obj->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); |
|
1114 } |
|
1115 |
|
1116 // 11.1.2.1 steps 1 and 2; 11.1.3.1 steps 1 and 2 |
|
1117 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); |
|
1118 RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); |
|
1119 |
|
1120 // 11.1.2.1 step 6; 11.1.3.1 step 3 |
|
1121 if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options)) |
|
1122 return false; |
|
1123 |
|
1124 // 11.1.2.1 steps 3.a and 7 |
|
1125 args.rval().setObject(*obj); |
|
1126 return true; |
|
1127 } |
|
1128 |
|
1129 static bool |
|
1130 NumberFormat(JSContext *cx, unsigned argc, Value *vp) |
|
1131 { |
|
1132 CallArgs args = CallArgsFromVp(argc, vp); |
|
1133 return NumberFormat(cx, args, args.isConstructing()); |
|
1134 } |
|
1135 |
|
1136 bool |
|
1137 js::intl_NumberFormat(JSContext *cx, unsigned argc, Value *vp) |
|
1138 { |
|
1139 CallArgs args = CallArgsFromVp(argc, vp); |
|
1140 JS_ASSERT(args.length() == 2); |
|
1141 // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it |
|
1142 // cannot be used with "new", but it still has to be treated as a |
|
1143 // constructor. |
|
1144 return NumberFormat(cx, args, true); |
|
1145 } |
|
1146 |
|
1147 static void |
|
1148 numberFormat_finalize(FreeOp *fop, JSObject *obj) |
|
1149 { |
|
1150 UNumberFormat *nf = |
|
1151 static_cast<UNumberFormat*>(obj->getReservedSlot(UNUMBER_FORMAT_SLOT).toPrivate()); |
|
1152 if (nf) |
|
1153 unum_close(nf); |
|
1154 } |
|
1155 |
|
1156 static JSObject * |
|
1157 InitNumberFormatClass(JSContext *cx, HandleObject Intl, Handle<GlobalObject*> global) |
|
1158 { |
|
1159 RootedFunction ctor(cx, global->createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0)); |
|
1160 if (!ctor) |
|
1161 return nullptr; |
|
1162 |
|
1163 RootedObject proto(cx, global->as<GlobalObject>().getOrCreateNumberFormatPrototype(cx)); |
|
1164 if (!proto) |
|
1165 return nullptr; |
|
1166 if (!LinkConstructorAndPrototype(cx, ctor, proto)) |
|
1167 return nullptr; |
|
1168 |
|
1169 // 11.2.2 |
|
1170 if (!JS_DefineFunctions(cx, ctor, numberFormat_static_methods)) |
|
1171 return nullptr; |
|
1172 |
|
1173 // 11.3.2 and 11.3.3 |
|
1174 if (!JS_DefineFunctions(cx, proto, numberFormat_methods)) |
|
1175 return nullptr; |
|
1176 |
|
1177 /* |
|
1178 * Install the getter for NumberFormat.prototype.format, which returns a |
|
1179 * bound formatting function for the specified NumberFormat object (suitable |
|
1180 * for passing to methods like Array.prototype.map). |
|
1181 */ |
|
1182 RootedValue getter(cx); |
|
1183 if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().NumberFormatFormatGet, &getter)) |
|
1184 return nullptr; |
|
1185 if (!JSObject::defineProperty(cx, proto, cx->names().format, UndefinedHandleValue, |
|
1186 JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()), |
|
1187 nullptr, JSPROP_GETTER | JSPROP_SHARED)) |
|
1188 { |
|
1189 return nullptr; |
|
1190 } |
|
1191 |
|
1192 // 11.2.1 and 11.3 |
|
1193 if (!IntlInitialize(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue, |
|
1194 UndefinedHandleValue)) |
|
1195 { |
|
1196 return nullptr; |
|
1197 } |
|
1198 |
|
1199 // 8.1 |
|
1200 RootedValue ctorValue(cx, ObjectValue(*ctor)); |
|
1201 if (!JSObject::defineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, |
|
1202 JS_PropertyStub, JS_StrictPropertyStub, 0)) |
|
1203 { |
|
1204 return nullptr; |
|
1205 } |
|
1206 |
|
1207 return ctor; |
|
1208 } |
|
1209 |
|
1210 bool |
|
1211 GlobalObject::initNumberFormatProto(JSContext *cx, Handle<GlobalObject*> global) |
|
1212 { |
|
1213 RootedObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass)); |
|
1214 if (!proto) |
|
1215 return false; |
|
1216 proto->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); |
|
1217 global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*proto)); |
|
1218 return true; |
|
1219 } |
|
1220 |
|
1221 bool |
|
1222 js::intl_NumberFormat_availableLocales(JSContext *cx, unsigned argc, Value *vp) |
|
1223 { |
|
1224 CallArgs args = CallArgsFromVp(argc, vp); |
|
1225 JS_ASSERT(args.length() == 0); |
|
1226 |
|
1227 RootedValue result(cx); |
|
1228 if (!intl_availableLocales(cx, unum_countAvailable, unum_getAvailable, &result)) |
|
1229 return false; |
|
1230 args.rval().set(result); |
|
1231 return true; |
|
1232 } |
|
1233 |
|
1234 bool |
|
1235 js::intl_numberingSystem(JSContext *cx, unsigned argc, Value *vp) |
|
1236 { |
|
1237 CallArgs args = CallArgsFromVp(argc, vp); |
|
1238 JS_ASSERT(args.length() == 1); |
|
1239 JS_ASSERT(args[0].isString()); |
|
1240 |
|
1241 JSAutoByteString locale(cx, args[0].toString()); |
|
1242 if (!locale) |
|
1243 return false; |
|
1244 |
|
1245 // There's no C API for numbering system, so use the C++ API and hope it |
|
1246 // won't break. http://bugs.icu-project.org/trac/ticket/10039 |
|
1247 Locale ulocale(locale.ptr()); |
|
1248 UErrorCode status = U_ZERO_ERROR; |
|
1249 NumberingSystem *numbers = NumberingSystem::createInstance(ulocale, status); |
|
1250 if (U_FAILURE(status)) { |
|
1251 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1252 return false; |
|
1253 } |
|
1254 const char *name = numbers->getName(); |
|
1255 RootedString jsname(cx, JS_NewStringCopyZ(cx, name)); |
|
1256 delete numbers; |
|
1257 if (!jsname) |
|
1258 return false; |
|
1259 args.rval().setString(jsname); |
|
1260 return true; |
|
1261 } |
|
1262 |
|
1263 /** |
|
1264 * Returns a new UNumberFormat with the locale and number formatting options |
|
1265 * of the given NumberFormat. |
|
1266 */ |
|
1267 static UNumberFormat * |
|
1268 NewUNumberFormat(JSContext *cx, HandleObject numberFormat) |
|
1269 { |
|
1270 RootedValue value(cx); |
|
1271 |
|
1272 RootedObject internals(cx); |
|
1273 if (!GetInternals(cx, numberFormat, &internals)) |
|
1274 return nullptr; |
|
1275 |
|
1276 if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value)) |
|
1277 return nullptr; |
|
1278 JSAutoByteString locale(cx, value.toString()); |
|
1279 if (!locale) |
|
1280 return nullptr; |
|
1281 |
|
1282 // UNumberFormat options with default values |
|
1283 UNumberFormatStyle uStyle = UNUM_DECIMAL; |
|
1284 const UChar *uCurrency = nullptr; |
|
1285 uint32_t uMinimumIntegerDigits = 1; |
|
1286 uint32_t uMinimumFractionDigits = 0; |
|
1287 uint32_t uMaximumFractionDigits = 3; |
|
1288 int32_t uMinimumSignificantDigits = -1; |
|
1289 int32_t uMaximumSignificantDigits = -1; |
|
1290 bool uUseGrouping = true; |
|
1291 |
|
1292 // Sprinkle appropriate rooting flavor over things the GC might care about. |
|
1293 RootedString currency(cx); |
|
1294 |
|
1295 // We don't need to look at numberingSystem - it can only be set via |
|
1296 // the Unicode locale extension and is therefore already set on locale. |
|
1297 |
|
1298 if (!JSObject::getProperty(cx, internals, internals, cx->names().style, &value)) |
|
1299 return nullptr; |
|
1300 JSAutoByteString style(cx, value.toString()); |
|
1301 if (!style) |
|
1302 return nullptr; |
|
1303 |
|
1304 if (equal(style, "currency")) { |
|
1305 if (!JSObject::getProperty(cx, internals, internals, cx->names().currency, &value)) |
|
1306 return nullptr; |
|
1307 currency = value.toString(); |
|
1308 MOZ_ASSERT(currency->length() == 3, "IsWellFormedCurrencyCode permits only length-3 strings"); |
|
1309 // uCurrency remains owned by currency. |
|
1310 uCurrency = JSCharToUChar(JS_GetStringCharsZ(cx, currency)); |
|
1311 if (!uCurrency) |
|
1312 return nullptr; |
|
1313 |
|
1314 if (!JSObject::getProperty(cx, internals, internals, cx->names().currencyDisplay, &value)) |
|
1315 return nullptr; |
|
1316 JSAutoByteString currencyDisplay(cx, value.toString()); |
|
1317 if (!currencyDisplay) |
|
1318 return nullptr; |
|
1319 if (equal(currencyDisplay, "code")) { |
|
1320 uStyle = UNUM_CURRENCY_ISO; |
|
1321 } else if (equal(currencyDisplay, "symbol")) { |
|
1322 uStyle = UNUM_CURRENCY; |
|
1323 } else { |
|
1324 JS_ASSERT(equal(currencyDisplay, "name")); |
|
1325 uStyle = UNUM_CURRENCY_PLURAL; |
|
1326 } |
|
1327 } else if (equal(style, "percent")) { |
|
1328 uStyle = UNUM_PERCENT; |
|
1329 } else { |
|
1330 JS_ASSERT(equal(style, "decimal")); |
|
1331 uStyle = UNUM_DECIMAL; |
|
1332 } |
|
1333 |
|
1334 RootedId id(cx, NameToId(cx->names().minimumSignificantDigits)); |
|
1335 bool hasP; |
|
1336 if (!JSObject::hasProperty(cx, internals, id, &hasP)) |
|
1337 return nullptr; |
|
1338 if (hasP) { |
|
1339 if (!JSObject::getProperty(cx, internals, internals, cx->names().minimumSignificantDigits, |
|
1340 &value)) |
|
1341 { |
|
1342 return nullptr; |
|
1343 } |
|
1344 uMinimumSignificantDigits = int32_t(value.toNumber()); |
|
1345 if (!JSObject::getProperty(cx, internals, internals, cx->names().maximumSignificantDigits, |
|
1346 &value)) |
|
1347 { |
|
1348 return nullptr; |
|
1349 } |
|
1350 uMaximumSignificantDigits = int32_t(value.toNumber()); |
|
1351 } else { |
|
1352 if (!JSObject::getProperty(cx, internals, internals, cx->names().minimumIntegerDigits, |
|
1353 &value)) |
|
1354 { |
|
1355 return nullptr; |
|
1356 } |
|
1357 uMinimumIntegerDigits = int32_t(value.toNumber()); |
|
1358 if (!JSObject::getProperty(cx, internals, internals, cx->names().minimumFractionDigits, |
|
1359 &value)) |
|
1360 { |
|
1361 return nullptr; |
|
1362 } |
|
1363 uMinimumFractionDigits = int32_t(value.toNumber()); |
|
1364 if (!JSObject::getProperty(cx, internals, internals, cx->names().maximumFractionDigits, |
|
1365 &value)) |
|
1366 { |
|
1367 return nullptr; |
|
1368 } |
|
1369 uMaximumFractionDigits = int32_t(value.toNumber()); |
|
1370 } |
|
1371 |
|
1372 if (!JSObject::getProperty(cx, internals, internals, cx->names().useGrouping, &value)) |
|
1373 return nullptr; |
|
1374 uUseGrouping = value.toBoolean(); |
|
1375 |
|
1376 UErrorCode status = U_ZERO_ERROR; |
|
1377 UNumberFormat *nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status); |
|
1378 if (U_FAILURE(status)) { |
|
1379 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1380 return nullptr; |
|
1381 } |
|
1382 ScopedICUObject<UNumberFormat> toClose(nf, unum_close); |
|
1383 |
|
1384 if (uCurrency) { |
|
1385 unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status); |
|
1386 if (U_FAILURE(status)) { |
|
1387 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1388 return nullptr; |
|
1389 } |
|
1390 } |
|
1391 if (uMinimumSignificantDigits != -1) { |
|
1392 unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true); |
|
1393 unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits); |
|
1394 unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits); |
|
1395 } else { |
|
1396 unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits); |
|
1397 unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits); |
|
1398 unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits); |
|
1399 } |
|
1400 unum_setAttribute(nf, UNUM_GROUPING_USED, uUseGrouping); |
|
1401 unum_setAttribute(nf, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP); |
|
1402 |
|
1403 return toClose.forget(); |
|
1404 } |
|
1405 |
|
1406 static bool |
|
1407 intl_FormatNumber(JSContext *cx, UNumberFormat *nf, double x, MutableHandleValue result) |
|
1408 { |
|
1409 // FormatNumber doesn't consider -0.0 to be negative. |
|
1410 if (IsNegativeZero(x)) |
|
1411 x = 0.0; |
|
1412 |
|
1413 StringBuffer chars(cx); |
|
1414 if (!chars.resize(INITIAL_STRING_BUFFER_SIZE)) |
|
1415 return false; |
|
1416 UErrorCode status = U_ZERO_ERROR; |
|
1417 int size = unum_formatDouble(nf, x, JSCharToUChar(chars.begin()), |
|
1418 INITIAL_STRING_BUFFER_SIZE, nullptr, &status); |
|
1419 if (status == U_BUFFER_OVERFLOW_ERROR) { |
|
1420 if (!chars.resize(size)) |
|
1421 return false; |
|
1422 status = U_ZERO_ERROR; |
|
1423 unum_formatDouble(nf, x, JSCharToUChar(chars.begin()), |
|
1424 size, nullptr, &status); |
|
1425 } |
|
1426 if (U_FAILURE(status)) { |
|
1427 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1428 return false; |
|
1429 } |
|
1430 |
|
1431 // Trim any unused characters. |
|
1432 if (!chars.resize(size)) |
|
1433 return false; |
|
1434 |
|
1435 RootedString str(cx, chars.finishString()); |
|
1436 if (!str) |
|
1437 return false; |
|
1438 |
|
1439 result.setString(str); |
|
1440 return true; |
|
1441 } |
|
1442 |
|
1443 bool |
|
1444 js::intl_FormatNumber(JSContext *cx, unsigned argc, Value *vp) |
|
1445 { |
|
1446 CallArgs args = CallArgsFromVp(argc, vp); |
|
1447 JS_ASSERT(args.length() == 2); |
|
1448 JS_ASSERT(args[0].isObject()); |
|
1449 JS_ASSERT(args[1].isNumber()); |
|
1450 |
|
1451 RootedObject numberFormat(cx, &args[0].toObject()); |
|
1452 |
|
1453 // Obtain a UNumberFormat object, cached if possible. |
|
1454 bool isNumberFormatInstance = numberFormat->getClass() == &NumberFormatClass; |
|
1455 UNumberFormat *nf; |
|
1456 if (isNumberFormatInstance) { |
|
1457 nf = static_cast<UNumberFormat*>(numberFormat->getReservedSlot(UNUMBER_FORMAT_SLOT).toPrivate()); |
|
1458 if (!nf) { |
|
1459 nf = NewUNumberFormat(cx, numberFormat); |
|
1460 if (!nf) |
|
1461 return false; |
|
1462 numberFormat->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nf)); |
|
1463 } |
|
1464 } else { |
|
1465 // There's no good place to cache the ICU number format for an object |
|
1466 // that has been initialized as a NumberFormat but is not a |
|
1467 // NumberFormat instance. One possibility might be to add a |
|
1468 // NumberFormat instance as an internal property to each such object. |
|
1469 nf = NewUNumberFormat(cx, numberFormat); |
|
1470 if (!nf) |
|
1471 return false; |
|
1472 } |
|
1473 |
|
1474 // Use the UNumberFormat to actually format the number. |
|
1475 RootedValue result(cx); |
|
1476 bool success = intl_FormatNumber(cx, nf, args[1].toNumber(), &result); |
|
1477 |
|
1478 if (!isNumberFormatInstance) |
|
1479 unum_close(nf); |
|
1480 if (!success) |
|
1481 return false; |
|
1482 args.rval().set(result); |
|
1483 return true; |
|
1484 } |
|
1485 |
|
1486 |
|
1487 /******************** DateTimeFormat ********************/ |
|
1488 |
|
1489 static void dateTimeFormat_finalize(FreeOp *fop, JSObject *obj); |
|
1490 |
|
1491 static const uint32_t UDATE_FORMAT_SLOT = 0; |
|
1492 static const uint32_t DATE_TIME_FORMAT_SLOTS_COUNT = 1; |
|
1493 |
|
1494 static const Class DateTimeFormatClass = { |
|
1495 js_Object_str, |
|
1496 JSCLASS_HAS_RESERVED_SLOTS(DATE_TIME_FORMAT_SLOTS_COUNT), |
|
1497 JS_PropertyStub, /* addProperty */ |
|
1498 JS_DeletePropertyStub, /* delProperty */ |
|
1499 JS_PropertyStub, /* getProperty */ |
|
1500 JS_StrictPropertyStub, /* setProperty */ |
|
1501 JS_EnumerateStub, |
|
1502 JS_ResolveStub, |
|
1503 JS_ConvertStub, |
|
1504 dateTimeFormat_finalize |
|
1505 }; |
|
1506 |
|
1507 #if JS_HAS_TOSOURCE |
|
1508 static bool |
|
1509 dateTimeFormat_toSource(JSContext *cx, unsigned argc, Value *vp) |
|
1510 { |
|
1511 CallArgs args = CallArgsFromVp(argc, vp); |
|
1512 args.rval().setString(cx->names().DateTimeFormat); |
|
1513 return true; |
|
1514 } |
|
1515 #endif |
|
1516 |
|
1517 static const JSFunctionSpec dateTimeFormat_static_methods[] = { |
|
1518 JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_DateTimeFormat_supportedLocalesOf", 1, 0), |
|
1519 JS_FS_END |
|
1520 }; |
|
1521 |
|
1522 static const JSFunctionSpec dateTimeFormat_methods[] = { |
|
1523 JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 0, 0), |
|
1524 #if JS_HAS_TOSOURCE |
|
1525 JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0), |
|
1526 #endif |
|
1527 JS_FS_END |
|
1528 }; |
|
1529 |
|
1530 /** |
|
1531 * DateTimeFormat constructor. |
|
1532 * Spec: ECMAScript Internationalization API Specification, 12.1 |
|
1533 */ |
|
1534 static bool |
|
1535 DateTimeFormat(JSContext *cx, CallArgs args, bool construct) |
|
1536 { |
|
1537 RootedObject obj(cx); |
|
1538 |
|
1539 if (!construct) { |
|
1540 // 12.1.2.1 step 3 |
|
1541 JSObject *intl = cx->global()->getOrCreateIntlObject(cx); |
|
1542 if (!intl) |
|
1543 return false; |
|
1544 RootedValue self(cx, args.thisv()); |
|
1545 if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { |
|
1546 // 12.1.2.1 step 4 |
|
1547 obj = ToObject(cx, self); |
|
1548 if (!obj) |
|
1549 return false; |
|
1550 |
|
1551 // 12.1.2.1 step 5 |
|
1552 bool extensible; |
|
1553 if (!JSObject::isExtensible(cx, obj, &extensible)) |
|
1554 return false; |
|
1555 if (!extensible) |
|
1556 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); |
|
1557 } else { |
|
1558 // 12.1.2.1 step 3.a |
|
1559 construct = true; |
|
1560 } |
|
1561 } |
|
1562 if (construct) { |
|
1563 // 12.1.3.1 paragraph 2 |
|
1564 RootedObject proto(cx, cx->global()->getOrCreateDateTimeFormatPrototype(cx)); |
|
1565 if (!proto) |
|
1566 return false; |
|
1567 obj = NewObjectWithGivenProto(cx, &DateTimeFormatClass, proto, cx->global()); |
|
1568 if (!obj) |
|
1569 return false; |
|
1570 |
|
1571 obj->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); |
|
1572 } |
|
1573 |
|
1574 // 12.1.2.1 steps 1 and 2; 12.1.3.1 steps 1 and 2 |
|
1575 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); |
|
1576 RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); |
|
1577 |
|
1578 // 12.1.2.1 step 6; 12.1.3.1 step 3 |
|
1579 if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options)) |
|
1580 return false; |
|
1581 |
|
1582 // 12.1.2.1 steps 3.a and 7 |
|
1583 args.rval().setObject(*obj); |
|
1584 return true; |
|
1585 } |
|
1586 |
|
1587 static bool |
|
1588 DateTimeFormat(JSContext *cx, unsigned argc, Value *vp) |
|
1589 { |
|
1590 CallArgs args = CallArgsFromVp(argc, vp); |
|
1591 return DateTimeFormat(cx, args, args.isConstructing()); |
|
1592 } |
|
1593 |
|
1594 bool |
|
1595 js::intl_DateTimeFormat(JSContext *cx, unsigned argc, Value *vp) |
|
1596 { |
|
1597 CallArgs args = CallArgsFromVp(argc, vp); |
|
1598 JS_ASSERT(args.length() == 2); |
|
1599 // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it |
|
1600 // cannot be used with "new", but it still has to be treated as a |
|
1601 // constructor. |
|
1602 return DateTimeFormat(cx, args, true); |
|
1603 } |
|
1604 |
|
1605 static void |
|
1606 dateTimeFormat_finalize(FreeOp *fop, JSObject *obj) |
|
1607 { |
|
1608 UDateFormat *df = static_cast<UDateFormat*>(obj->getReservedSlot(UDATE_FORMAT_SLOT).toPrivate()); |
|
1609 if (df) |
|
1610 udat_close(df); |
|
1611 } |
|
1612 |
|
1613 static JSObject * |
|
1614 InitDateTimeFormatClass(JSContext *cx, HandleObject Intl, Handle<GlobalObject*> global) |
|
1615 { |
|
1616 RootedFunction ctor(cx, global->createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0)); |
|
1617 if (!ctor) |
|
1618 return nullptr; |
|
1619 |
|
1620 RootedObject proto(cx, global->as<GlobalObject>().getOrCreateDateTimeFormatPrototype(cx)); |
|
1621 if (!proto) |
|
1622 return nullptr; |
|
1623 if (!LinkConstructorAndPrototype(cx, ctor, proto)) |
|
1624 return nullptr; |
|
1625 |
|
1626 // 12.2.2 |
|
1627 if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) |
|
1628 return nullptr; |
|
1629 |
|
1630 // 12.3.2 and 12.3.3 |
|
1631 if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) |
|
1632 return nullptr; |
|
1633 |
|
1634 /* |
|
1635 * Install the getter for DateTimeFormat.prototype.format, which returns a |
|
1636 * bound formatting function for the specified DateTimeFormat object |
|
1637 * (suitable for passing to methods like Array.prototype.map). |
|
1638 */ |
|
1639 RootedValue getter(cx); |
|
1640 if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet, &getter)) |
|
1641 return nullptr; |
|
1642 if (!JSObject::defineProperty(cx, proto, cx->names().format, UndefinedHandleValue, |
|
1643 JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()), |
|
1644 nullptr, JSPROP_GETTER | JSPROP_SHARED)) |
|
1645 { |
|
1646 return nullptr; |
|
1647 } |
|
1648 |
|
1649 // 12.2.1 and 12.3 |
|
1650 if (!IntlInitialize(cx, proto, cx->names().InitializeDateTimeFormat, UndefinedHandleValue, |
|
1651 UndefinedHandleValue)) |
|
1652 { |
|
1653 return nullptr; |
|
1654 } |
|
1655 |
|
1656 // 8.1 |
|
1657 RootedValue ctorValue(cx, ObjectValue(*ctor)); |
|
1658 if (!JSObject::defineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, |
|
1659 JS_PropertyStub, JS_StrictPropertyStub, 0)) |
|
1660 { |
|
1661 return nullptr; |
|
1662 } |
|
1663 |
|
1664 return ctor; |
|
1665 } |
|
1666 |
|
1667 bool |
|
1668 GlobalObject::initDateTimeFormatProto(JSContext *cx, Handle<GlobalObject*> global) |
|
1669 { |
|
1670 RootedObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass)); |
|
1671 if (!proto) |
|
1672 return false; |
|
1673 proto->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); |
|
1674 global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*proto)); |
|
1675 return true; |
|
1676 } |
|
1677 |
|
1678 bool |
|
1679 js::intl_DateTimeFormat_availableLocales(JSContext *cx, unsigned argc, Value *vp) |
|
1680 { |
|
1681 CallArgs args = CallArgsFromVp(argc, vp); |
|
1682 JS_ASSERT(args.length() == 0); |
|
1683 |
|
1684 RootedValue result(cx); |
|
1685 if (!intl_availableLocales(cx, udat_countAvailable, udat_getAvailable, &result)) |
|
1686 return false; |
|
1687 args.rval().set(result); |
|
1688 return true; |
|
1689 } |
|
1690 |
|
1691 // ICU returns old-style keyword values; map them to BCP 47 equivalents |
|
1692 // (see http://bugs.icu-project.org/trac/ticket/9620). |
|
1693 static const char * |
|
1694 bcp47CalendarName(const char *icuName) |
|
1695 { |
|
1696 if (equal(icuName, "ethiopic-amete-alem")) |
|
1697 return "ethioaa"; |
|
1698 if (equal(icuName, "gregorian")) |
|
1699 return "gregory"; |
|
1700 if (equal(icuName, "islamic-civil")) |
|
1701 return "islamicc"; |
|
1702 return icuName; |
|
1703 } |
|
1704 |
|
1705 bool |
|
1706 js::intl_availableCalendars(JSContext *cx, unsigned argc, Value *vp) |
|
1707 { |
|
1708 CallArgs args = CallArgsFromVp(argc, vp); |
|
1709 JS_ASSERT(args.length() == 1); |
|
1710 JS_ASSERT(args[0].isString()); |
|
1711 |
|
1712 JSAutoByteString locale(cx, args[0].toString()); |
|
1713 if (!locale) |
|
1714 return false; |
|
1715 |
|
1716 RootedObject calendars(cx, NewDenseEmptyArray(cx)); |
|
1717 if (!calendars) |
|
1718 return false; |
|
1719 uint32_t index = 0; |
|
1720 |
|
1721 // We need the default calendar for the locale as the first result. |
|
1722 UErrorCode status = U_ZERO_ERROR; |
|
1723 UCalendar *cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status); |
|
1724 const char *calendar = ucal_getType(cal, &status); |
|
1725 if (U_FAILURE(status)) { |
|
1726 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1727 return false; |
|
1728 } |
|
1729 ucal_close(cal); |
|
1730 RootedString jscalendar(cx, JS_NewStringCopyZ(cx, bcp47CalendarName(calendar))); |
|
1731 if (!jscalendar) |
|
1732 return false; |
|
1733 RootedValue element(cx, StringValue(jscalendar)); |
|
1734 if (!JSObject::defineElement(cx, calendars, index++, element)) |
|
1735 return false; |
|
1736 |
|
1737 // Now get the calendars that "would make a difference", i.e., not the default. |
|
1738 UEnumeration *values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status); |
|
1739 if (U_FAILURE(status)) { |
|
1740 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1741 return false; |
|
1742 } |
|
1743 ScopedICUObject<UEnumeration> toClose(values, uenum_close); |
|
1744 |
|
1745 uint32_t count = uenum_count(values, &status); |
|
1746 if (U_FAILURE(status)) { |
|
1747 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1748 return false; |
|
1749 } |
|
1750 |
|
1751 for (; count > 0; count--) { |
|
1752 calendar = uenum_next(values, nullptr, &status); |
|
1753 if (U_FAILURE(status)) { |
|
1754 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1755 return false; |
|
1756 } |
|
1757 |
|
1758 jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar)); |
|
1759 if (!jscalendar) |
|
1760 return false; |
|
1761 element = StringValue(jscalendar); |
|
1762 if (!JSObject::defineElement(cx, calendars, index++, element)) |
|
1763 return false; |
|
1764 } |
|
1765 |
|
1766 args.rval().setObject(*calendars); |
|
1767 return true; |
|
1768 } |
|
1769 |
|
1770 bool |
|
1771 js::intl_patternForSkeleton(JSContext *cx, unsigned argc, Value *vp) |
|
1772 { |
|
1773 CallArgs args = CallArgsFromVp(argc, vp); |
|
1774 JS_ASSERT(args.length() == 2); |
|
1775 JS_ASSERT(args[0].isString()); |
|
1776 JS_ASSERT(args[1].isString()); |
|
1777 |
|
1778 JSAutoByteString locale(cx, args[0].toString()); |
|
1779 if (!locale) |
|
1780 return false; |
|
1781 RootedString jsskeleton(cx, args[1].toString()); |
|
1782 const jschar *skeleton = JS_GetStringCharsZ(cx, jsskeleton); |
|
1783 if (!skeleton) |
|
1784 return false; |
|
1785 uint32_t skeletonLen = u_strlen(JSCharToUChar(skeleton)); |
|
1786 |
|
1787 UErrorCode status = U_ZERO_ERROR; |
|
1788 UDateTimePatternGenerator *gen = udatpg_open(icuLocale(locale.ptr()), &status); |
|
1789 if (U_FAILURE(status)) { |
|
1790 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1791 return false; |
|
1792 } |
|
1793 ScopedICUObject<UDateTimePatternGenerator> toClose(gen, udatpg_close); |
|
1794 |
|
1795 int32_t size = udatpg_getBestPattern(gen, JSCharToUChar(skeleton), |
|
1796 skeletonLen, nullptr, 0, &status); |
|
1797 if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) { |
|
1798 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1799 return false; |
|
1800 } |
|
1801 ScopedJSFreePtr<UChar> pattern(cx->pod_malloc<UChar>(size + 1)); |
|
1802 if (!pattern) |
|
1803 return false; |
|
1804 pattern[size] = '\0'; |
|
1805 status = U_ZERO_ERROR; |
|
1806 udatpg_getBestPattern(gen, JSCharToUChar(skeleton), |
|
1807 skeletonLen, pattern, size, &status); |
|
1808 if (U_FAILURE(status)) { |
|
1809 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1810 return false; |
|
1811 } |
|
1812 |
|
1813 RootedString str(cx, JS_NewUCStringCopyZ(cx, reinterpret_cast<jschar*>(pattern.get()))); |
|
1814 if (!str) |
|
1815 return false; |
|
1816 args.rval().setString(str); |
|
1817 return true; |
|
1818 } |
|
1819 |
|
1820 /** |
|
1821 * Returns a new UDateFormat with the locale and date-time formatting options |
|
1822 * of the given DateTimeFormat. |
|
1823 */ |
|
1824 static UDateFormat * |
|
1825 NewUDateFormat(JSContext *cx, HandleObject dateTimeFormat) |
|
1826 { |
|
1827 RootedValue value(cx); |
|
1828 |
|
1829 RootedObject internals(cx); |
|
1830 if (!GetInternals(cx, dateTimeFormat, &internals)) |
|
1831 return nullptr; |
|
1832 |
|
1833 if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value)) { |
|
1834 return nullptr; |
|
1835 } |
|
1836 JSAutoByteString locale(cx, value.toString()); |
|
1837 if (!locale) |
|
1838 return nullptr; |
|
1839 |
|
1840 // UDateFormat options with default values. |
|
1841 const UChar *uTimeZone = nullptr; |
|
1842 uint32_t uTimeZoneLength = 0; |
|
1843 const UChar *uPattern = nullptr; |
|
1844 uint32_t uPatternLength = 0; |
|
1845 |
|
1846 // We don't need to look at calendar and numberingSystem - they can only be |
|
1847 // set via the Unicode locale extension and are therefore already set on |
|
1848 // locale. |
|
1849 |
|
1850 RootedId id(cx, NameToId(cx->names().timeZone)); |
|
1851 bool hasP; |
|
1852 if (!JSObject::hasProperty(cx, internals, id, &hasP)) |
|
1853 return nullptr; |
|
1854 if (hasP) { |
|
1855 if (!JSObject::getProperty(cx, internals, internals, cx->names().timeZone, &value)) |
|
1856 return nullptr; |
|
1857 if (!value.isUndefined()) { |
|
1858 uTimeZone = JSCharToUChar(JS_GetStringCharsZ(cx, value.toString())); |
|
1859 if (!uTimeZone) |
|
1860 return nullptr; |
|
1861 uTimeZoneLength = u_strlen(uTimeZone); |
|
1862 } |
|
1863 } |
|
1864 if (!JSObject::getProperty(cx, internals, internals, cx->names().pattern, &value)) |
|
1865 return nullptr; |
|
1866 uPattern = JSCharToUChar(JS_GetStringCharsZ(cx, value.toString())); |
|
1867 if (!uPattern) |
|
1868 return nullptr; |
|
1869 uPatternLength = u_strlen(uPattern); |
|
1870 |
|
1871 UErrorCode status = U_ZERO_ERROR; |
|
1872 |
|
1873 // If building with ICU headers before 50.1, use UDAT_IGNORE instead of |
|
1874 // UDAT_PATTERN. |
|
1875 UDateFormat *df = |
|
1876 udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()), uTimeZone, uTimeZoneLength, |
|
1877 uPattern, uPatternLength, &status); |
|
1878 if (U_FAILURE(status)) { |
|
1879 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1880 return nullptr; |
|
1881 } |
|
1882 |
|
1883 // ECMAScript requires the Gregorian calendar to be used from the beginning |
|
1884 // of ECMAScript time. |
|
1885 UCalendar *cal = const_cast<UCalendar*>(udat_getCalendar(df)); |
|
1886 ucal_setGregorianChange(cal, StartOfTime, &status); |
|
1887 |
|
1888 // An error here means the calendar is not Gregorian, so we don't care. |
|
1889 |
|
1890 return df; |
|
1891 } |
|
1892 |
|
1893 static bool |
|
1894 intl_FormatDateTime(JSContext *cx, UDateFormat *df, double x, MutableHandleValue result) |
|
1895 { |
|
1896 if (!IsFinite(x)) { |
|
1897 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE); |
|
1898 return false; |
|
1899 } |
|
1900 |
|
1901 StringBuffer chars(cx); |
|
1902 if (!chars.resize(INITIAL_STRING_BUFFER_SIZE)) |
|
1903 return false; |
|
1904 UErrorCode status = U_ZERO_ERROR; |
|
1905 int size = udat_format(df, x, JSCharToUChar(chars.begin()), INITIAL_STRING_BUFFER_SIZE, nullptr, &status); |
|
1906 if (status == U_BUFFER_OVERFLOW_ERROR) { |
|
1907 if (!chars.resize(size)) |
|
1908 return false; |
|
1909 status = U_ZERO_ERROR; |
|
1910 udat_format(df, x, JSCharToUChar(chars.begin()), size, nullptr, &status); |
|
1911 } |
|
1912 if (U_FAILURE(status)) { |
|
1913 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); |
|
1914 return false; |
|
1915 } |
|
1916 |
|
1917 // Trim any unused characters. |
|
1918 if (!chars.resize(size)) |
|
1919 return false; |
|
1920 |
|
1921 RootedString str(cx, chars.finishString()); |
|
1922 if (!str) |
|
1923 return false; |
|
1924 |
|
1925 result.setString(str); |
|
1926 return true; |
|
1927 } |
|
1928 |
|
1929 bool |
|
1930 js::intl_FormatDateTime(JSContext *cx, unsigned argc, Value *vp) |
|
1931 { |
|
1932 CallArgs args = CallArgsFromVp(argc, vp); |
|
1933 JS_ASSERT(args.length() == 2); |
|
1934 JS_ASSERT(args[0].isObject()); |
|
1935 JS_ASSERT(args[1].isNumber()); |
|
1936 |
|
1937 RootedObject dateTimeFormat(cx, &args[0].toObject()); |
|
1938 |
|
1939 // Obtain a UDateFormat object, cached if possible. |
|
1940 bool isDateTimeFormatInstance = dateTimeFormat->getClass() == &DateTimeFormatClass; |
|
1941 UDateFormat *df; |
|
1942 if (isDateTimeFormatInstance) { |
|
1943 df = static_cast<UDateFormat*>(dateTimeFormat->getReservedSlot(UDATE_FORMAT_SLOT).toPrivate()); |
|
1944 if (!df) { |
|
1945 df = NewUDateFormat(cx, dateTimeFormat); |
|
1946 if (!df) |
|
1947 return false; |
|
1948 dateTimeFormat->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(df)); |
|
1949 } |
|
1950 } else { |
|
1951 // There's no good place to cache the ICU date-time format for an object |
|
1952 // that has been initialized as a DateTimeFormat but is not a |
|
1953 // DateTimeFormat instance. One possibility might be to add a |
|
1954 // DateTimeFormat instance as an internal property to each such object. |
|
1955 df = NewUDateFormat(cx, dateTimeFormat); |
|
1956 if (!df) |
|
1957 return false; |
|
1958 } |
|
1959 |
|
1960 // Use the UDateFormat to actually format the time stamp. |
|
1961 RootedValue result(cx); |
|
1962 bool success = intl_FormatDateTime(cx, df, args[1].toNumber(), &result); |
|
1963 |
|
1964 if (!isDateTimeFormatInstance) |
|
1965 udat_close(df); |
|
1966 if (!success) |
|
1967 return false; |
|
1968 args.rval().set(result); |
|
1969 return true; |
|
1970 } |
|
1971 |
|
1972 |
|
1973 /******************** Intl ********************/ |
|
1974 |
|
1975 const Class js::IntlClass = { |
|
1976 js_Object_str, |
|
1977 JSCLASS_HAS_CACHED_PROTO(JSProto_Intl), |
|
1978 JS_PropertyStub, /* addProperty */ |
|
1979 JS_DeletePropertyStub, /* delProperty */ |
|
1980 JS_PropertyStub, /* getProperty */ |
|
1981 JS_StrictPropertyStub, /* setProperty */ |
|
1982 JS_EnumerateStub, |
|
1983 JS_ResolveStub, |
|
1984 JS_ConvertStub |
|
1985 }; |
|
1986 |
|
1987 #if JS_HAS_TOSOURCE |
|
1988 static bool |
|
1989 intl_toSource(JSContext *cx, unsigned argc, Value *vp) |
|
1990 { |
|
1991 CallArgs args = CallArgsFromVp(argc, vp); |
|
1992 args.rval().setString(cx->names().Intl); |
|
1993 return true; |
|
1994 } |
|
1995 #endif |
|
1996 |
|
1997 static const JSFunctionSpec intl_static_methods[] = { |
|
1998 #if JS_HAS_TOSOURCE |
|
1999 JS_FN(js_toSource_str, intl_toSource, 0, 0), |
|
2000 #endif |
|
2001 JS_FS_END |
|
2002 }; |
|
2003 |
|
2004 /** |
|
2005 * Initializes the Intl Object and its standard built-in properties. |
|
2006 * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1 |
|
2007 */ |
|
2008 JSObject * |
|
2009 js_InitIntlClass(JSContext *cx, HandleObject obj) |
|
2010 { |
|
2011 JS_ASSERT(obj->is<GlobalObject>()); |
|
2012 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); |
|
2013 |
|
2014 // The constructors above need to be able to determine whether they've been |
|
2015 // called with this being "the standard built-in Intl object". The global |
|
2016 // object reserves slots to track standard built-in objects, but doesn't |
|
2017 // normally keep references to non-constructors. This makes sure there is one. |
|
2018 RootedObject Intl(cx, global->getOrCreateIntlObject(cx)); |
|
2019 if (!Intl) |
|
2020 return nullptr; |
|
2021 |
|
2022 RootedValue IntlValue(cx, ObjectValue(*Intl)); |
|
2023 if (!JSObject::defineProperty(cx, global, cx->names().Intl, IntlValue, |
|
2024 JS_PropertyStub, JS_StrictPropertyStub, 0)) |
|
2025 { |
|
2026 return nullptr; |
|
2027 } |
|
2028 |
|
2029 if (!JS_DefineFunctions(cx, Intl, intl_static_methods)) |
|
2030 return nullptr; |
|
2031 |
|
2032 // Skip initialization of the Intl constructors during initialization of the |
|
2033 // self-hosting global as we may get here before self-hosted code is compiled, |
|
2034 // and no core code refers to the Intl classes. |
|
2035 if (!cx->runtime()->isSelfHostingGlobal(cx->global())) { |
|
2036 if (!InitCollatorClass(cx, Intl, global)) |
|
2037 return nullptr; |
|
2038 if (!InitNumberFormatClass(cx, Intl, global)) |
|
2039 return nullptr; |
|
2040 if (!InitDateTimeFormatClass(cx, Intl, global)) |
|
2041 return nullptr; |
|
2042 } |
|
2043 |
|
2044 global->setConstructor(JSProto_Intl, ObjectValue(*Intl)); |
|
2045 |
|
2046 return Intl; |
|
2047 } |
|
2048 |
|
2049 bool |
|
2050 GlobalObject::initIntlObject(JSContext *cx, Handle<GlobalObject*> global) |
|
2051 { |
|
2052 RootedObject Intl(cx); |
|
2053 Intl = NewObjectWithGivenProto(cx, &IntlClass, global->getOrCreateObjectPrototype(cx), |
|
2054 global, SingletonObject); |
|
2055 if (!Intl) |
|
2056 return false; |
|
2057 |
|
2058 global->setConstructor(JSProto_Intl, ObjectValue(*Intl)); |
|
2059 return true; |
|
2060 } |