Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 // Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
2 // Copyright 2012-2013 Mozilla Corporation. All rights reserved.
3 // This code is governed by the BSD license found in the LICENSE file.
5 /**
6 * This file contains shared functions for the tests in the conformance test
7 * suite for the ECMAScript Internationalization API.
8 * @author Norbert Lindenberg
9 */
12 /**
13 * @description Calls the provided function for every service constructor in
14 * the Intl object, until f returns a falsy value. It returns the result of the
15 * last call to f, mapped to a boolean.
16 * @param {Function} f the function to call for each service constructor in
17 * the Intl object.
18 * @param {Function} Constructor the constructor object to test with.
19 * @result {Boolean} whether the test succeeded.
20 */
21 function testWithIntlConstructors(f) {
22 var constructors = ["Collator", "NumberFormat", "DateTimeFormat"];
23 return constructors.every(function (constructor) {
24 var Constructor = Intl[constructor];
25 var result;
26 try {
27 result = f(Constructor);
28 } catch (e) {
29 e.message += " (Testing with " + constructor + ".)";
30 throw e;
31 }
32 return result;
33 });
34 }
37 /**
38 * Returns the name of the given constructor object, which must be one of
39 * Intl.Collator, Intl.NumberFormat, or Intl.DateTimeFormat.
40 * @param {object} Constructor a constructor
41 * @return {string} the name of the constructor
42 */
43 function getConstructorName(Constructor) {
44 switch (Constructor) {
45 case Intl.Collator:
46 return "Collator";
47 case Intl.NumberFormat:
48 return "NumberFormat";
49 case Intl.DateTimeFormat:
50 return "DateTimeFormat";
51 default:
52 $ERROR("test internal error: unknown Constructor");
53 }
54 }
57 /**
58 * Taints a named data property of the given object by installing
59 * a setter that throws an exception.
60 * @param {object} obj the object whose data property to taint
61 * @param {string} property the property to taint
62 */
63 function taintDataProperty(obj, property) {
64 Object.defineProperty(obj, property, {
65 set: function(value) {
66 $ERROR("Client code can adversely affect behavior: setter for " + property + ".");
67 },
68 enumerable: false,
69 configurable: true
70 });
71 }
74 /**
75 * Taints a named method of the given object by replacing it with a function
76 * that throws an exception.
77 * @param {object} obj the object whose method to taint
78 * @param {string} property the name of the method to taint
79 */
80 function taintMethod(obj, property) {
81 Object.defineProperty(obj, property, {
82 value: function() {
83 $ERROR("Client code can adversely affect behavior: method " + property + ".");
84 },
85 writable: true,
86 enumerable: false,
87 configurable: true
88 });
89 }
92 /**
93 * Taints the given properties (and similarly named properties) by installing
94 * setters on Object.prototype that throw exceptions.
95 * @param {Array} properties an array of property names to taint
96 */
97 function taintProperties(properties) {
98 properties.forEach(function (property) {
99 var adaptedProperties = [property, "__" + property, "_" + property, property + "_", property + "__"];
100 adaptedProperties.forEach(function (property) {
101 taintDataProperty(Object.prototype, property);
102 });
103 });
104 }
107 /**
108 * Taints the Array object by creating a setter for the property "0" and
109 * replacing some key methods with functions that throw exceptions.
110 */
111 function taintArray() {
112 taintDataProperty(Array.prototype, "0");
113 taintMethod(Array.prototype, "indexOf");
114 taintMethod(Array.prototype, "join");
115 taintMethod(Array.prototype, "push");
116 taintMethod(Array.prototype, "slice");
117 taintMethod(Array.prototype, "sort");
118 }
121 // auxiliary data for getLocaleSupportInfo
122 var languages = ["zh", "es", "en", "hi", "ur", "ar", "ja", "pa"];
123 var scripts = ["Latn", "Hans", "Deva", "Arab", "Jpan", "Hant"];
124 var countries = ["CN", "IN", "US", "PK", "JP", "TW", "HK", "SG"];
125 var localeSupportInfo = {};
128 /**
129 * Gets locale support info for the given constructor object, which must be one
130 * of Intl.Collator, Intl.NumberFormat, Intl.DateTimeFormat.
131 * @param {object} Constructor the constructor for which to get locale support info
132 * @return {object} locale support info with the following properties:
133 * supported: array of fully supported language tags
134 * byFallback: array of language tags that are supported through fallbacks
135 * unsupported: array of unsupported language tags
136 */
137 function getLocaleSupportInfo(Constructor) {
138 var constructorName = getConstructorName(Constructor);
139 if (localeSupportInfo[constructorName] !== undefined) {
140 return localeSupportInfo[constructorName];
141 }
143 var allTags = [];
144 var i, j, k;
145 var language, script, country;
146 for (i = 0; i < languages.length; i++) {
147 language = languages[i];
148 allTags.push(language);
149 for (j = 0; j < scripts.length; j++) {
150 script = scripts[j];
151 allTags.push(language + "-" + script);
152 for (k = 0; k < countries.length; k++) {
153 country = countries[k];
154 allTags.push(language + "-" + script + "-" + country);
155 }
156 }
157 for (k = 0; k < countries.length; k++) {
158 country = countries[k];
159 allTags.push(language + "-" + country);
160 }
161 }
163 var supported = [];
164 var byFallback = [];
165 var unsupported = [];
166 for (i = 0; i < allTags.length; i++) {
167 var request = allTags[i];
168 var result = new Constructor([request], {localeMatcher: "lookup"}).resolvedOptions().locale;
169 if (request === result) {
170 supported.push(request);
171 } else if (request.indexOf(result) === 0) {
172 byFallback.push(request);
173 } else {
174 unsupported.push(request);
175 }
176 }
178 localeSupportInfo[constructorName] = {
179 supported: supported,
180 byFallback: byFallback,
181 unsupported: unsupported
182 };
184 return localeSupportInfo[constructorName];
185 }
188 /**
189 * @description Tests whether locale is a String value representing a
190 * structurally valid and canonicalized BCP 47 language tag, as defined in
191 * sections 6.2.2 and 6.2.3 of the ECMAScript Internationalization API
192 * Specification.
193 * @param {String} locale the string to be tested.
194 * @result {Boolean} whether the test succeeded.
195 */
196 function isCanonicalizedStructurallyValidLanguageTag(locale) {
198 /**
199 * Regular expression defining BCP 47 language tags.
200 *
201 * Spec: RFC 5646 section 2.1.
202 */
203 var alpha = "[a-zA-Z]",
204 digit = "[0-9]",
205 alphanum = "(" + alpha + "|" + digit + ")",
206 regular = "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)",
207 irregular = "(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)",
208 grandfathered = "(" + irregular + "|" + regular + ")",
209 privateuse = "(x(-[a-z0-9]{1,8})+)",
210 singleton = "(" + digit + "|[A-WY-Za-wy-z])",
211 extension = "(" + singleton + "(-" + alphanum + "{2,8})+)",
212 variant = "(" + alphanum + "{5,8}|(" + digit + alphanum + "{3}))",
213 region = "(" + alpha + "{2}|" + digit + "{3})",
214 script = "(" + alpha + "{4})",
215 extlang = "(" + alpha + "{3}(-" + alpha + "{3}){0,2})",
216 language = "(" + alpha + "{2,3}(-" + extlang + ")?|" + alpha + "{4}|" + alpha + "{5,8})",
217 langtag = language + "(-" + script + ")?(-" + region + ")?(-" + variant + ")*(-" + extension + ")*(-" + privateuse + ")?",
218 languageTag = "^(" + langtag + "|" + privateuse + "|" + grandfathered + ")$",
219 languageTagRE = new RegExp(languageTag, "i");
220 var duplicateSingleton = "-" + singleton + "-(.*-)?\\1(?!" + alphanum + ")",
221 duplicateSingletonRE = new RegExp(duplicateSingleton, "i"),
222 duplicateVariant = "(" + alphanum + "{2,8}-)+" + variant + "-(" + alphanum + "{2,8}-)*\\3(?!" + alphanum + ")",
223 duplicateVariantRE = new RegExp(duplicateVariant, "i");
226 /**
227 * Verifies that the given string is a well-formed BCP 47 language tag
228 * with no duplicate variant or singleton subtags.
229 *
230 * Spec: ECMAScript Internationalization API Specification, draft, 6.2.2.
231 */
232 function isStructurallyValidLanguageTag(locale) {
233 if (!languageTagRE.test(locale)) {
234 return false;
235 }
236 locale = locale.split(/-x-/)[0];
237 return !duplicateSingletonRE.test(locale) && !duplicateVariantRE.test(locale);
238 }
241 /**
242 * Mappings from complete tags to preferred values.
243 *
244 * Spec: IANA Language Subtag Registry.
245 */
246 var __tagMappings = {
247 // property names must be in lower case; values in canonical form
249 // grandfathered tags from IANA language subtag registry, file date 2011-08-25
250 "art-lojban": "jbo",
251 "cel-gaulish": "cel-gaulish",
252 "en-gb-oed": "en-GB-oed",
253 "i-ami": "ami",
254 "i-bnn": "bnn",
255 "i-default": "i-default",
256 "i-enochian": "i-enochian",
257 "i-hak": "hak",
258 "i-klingon": "tlh",
259 "i-lux": "lb",
260 "i-mingo": "i-mingo",
261 "i-navajo": "nv",
262 "i-pwn": "pwn",
263 "i-tao": "tao",
264 "i-tay": "tay",
265 "i-tsu": "tsu",
266 "no-bok": "nb",
267 "no-nyn": "nn",
268 "sgn-be-fr": "sfb",
269 "sgn-be-nl": "vgt",
270 "sgn-ch-de": "sgg",
271 "zh-guoyu": "cmn",
272 "zh-hakka": "hak",
273 "zh-min": "zh-min",
274 "zh-min-nan": "nan",
275 "zh-xiang": "hsn",
276 // deprecated redundant tags from IANA language subtag registry, file date 2011-08-25
277 "sgn-br": "bzs",
278 "sgn-co": "csn",
279 "sgn-de": "gsg",
280 "sgn-dk": "dsl",
281 "sgn-es": "ssp",
282 "sgn-fr": "fsl",
283 "sgn-gb": "bfi",
284 "sgn-gr": "gss",
285 "sgn-ie": "isg",
286 "sgn-it": "ise",
287 "sgn-jp": "jsl",
288 "sgn-mx": "mfs",
289 "sgn-ni": "ncs",
290 "sgn-nl": "dse",
291 "sgn-no": "nsl",
292 "sgn-pt": "psr",
293 "sgn-se": "swl",
294 "sgn-us": "ase",
295 "sgn-za": "sfs",
296 "zh-cmn": "cmn",
297 "zh-cmn-hans": "cmn-Hans",
298 "zh-cmn-hant": "cmn-Hant",
299 "zh-gan": "gan",
300 "zh-wuu": "wuu",
301 "zh-yue": "yue",
302 // deprecated variant with prefix from IANA language subtag registry, file date 2011-08-25
303 "ja-latn-hepburn-heploc": "ja-Latn-alalc97"
304 };
307 /**
308 * Mappings from non-extlang subtags to preferred values.
309 *
310 * Spec: IANA Language Subtag Registry.
311 */
312 var __subtagMappings = {
313 // property names and values must be in canonical case
314 // language subtags with Preferred-Value mappings from IANA language subtag registry, file date 2011-08-25
315 "in": "id",
316 "iw": "he",
317 "ji": "yi",
318 "jw": "jv",
319 "mo": "ro",
320 "ayx": "nun",
321 "cjr": "mom",
322 "cmk": "xch",
323 "drh": "khk",
324 "drw": "prs",
325 "gav": "dev",
326 "mst": "mry",
327 "myt": "mry",
328 "tie": "ras",
329 "tkk": "twm",
330 "tnf": "prs",
331 // region subtags with Preferred-Value mappings from IANA language subtag registry, file date 2011-08-25
332 "BU": "MM",
333 "DD": "DE",
334 "FX": "FR",
335 "TP": "TL",
336 "YD": "YE",
337 "ZR": "CD"
338 };
341 /**
342 * Mappings from extlang subtags to preferred values.
343 *
344 * Spec: IANA Language Subtag Registry.
345 */
346 var __extlangMappings = {
347 // extlang subtags with Preferred-Value mappings from IANA language subtag registry, file date 2011-08-25
348 // values are arrays with [0] the replacement value, [1] (if present) the prefix to be removed
349 "aao": ["aao", "ar"],
350 "abh": ["abh", "ar"],
351 "abv": ["abv", "ar"],
352 "acm": ["acm", "ar"],
353 "acq": ["acq", "ar"],
354 "acw": ["acw", "ar"],
355 "acx": ["acx", "ar"],
356 "acy": ["acy", "ar"],
357 "adf": ["adf", "ar"],
358 "ads": ["ads", "sgn"],
359 "aeb": ["aeb", "ar"],
360 "aec": ["aec", "ar"],
361 "aed": ["aed", "sgn"],
362 "aen": ["aen", "sgn"],
363 "afb": ["afb", "ar"],
364 "afg": ["afg", "sgn"],
365 "ajp": ["ajp", "ar"],
366 "apc": ["apc", "ar"],
367 "apd": ["apd", "ar"],
368 "arb": ["arb", "ar"],
369 "arq": ["arq", "ar"],
370 "ars": ["ars", "ar"],
371 "ary": ["ary", "ar"],
372 "arz": ["arz", "ar"],
373 "ase": ["ase", "sgn"],
374 "asf": ["asf", "sgn"],
375 "asp": ["asp", "sgn"],
376 "asq": ["asq", "sgn"],
377 "asw": ["asw", "sgn"],
378 "auz": ["auz", "ar"],
379 "avl": ["avl", "ar"],
380 "ayh": ["ayh", "ar"],
381 "ayl": ["ayl", "ar"],
382 "ayn": ["ayn", "ar"],
383 "ayp": ["ayp", "ar"],
384 "bbz": ["bbz", "ar"],
385 "bfi": ["bfi", "sgn"],
386 "bfk": ["bfk", "sgn"],
387 "bjn": ["bjn", "ms"],
388 "bog": ["bog", "sgn"],
389 "bqn": ["bqn", "sgn"],
390 "bqy": ["bqy", "sgn"],
391 "btj": ["btj", "ms"],
392 "bve": ["bve", "ms"],
393 "bvl": ["bvl", "sgn"],
394 "bvu": ["bvu", "ms"],
395 "bzs": ["bzs", "sgn"],
396 "cdo": ["cdo", "zh"],
397 "cds": ["cds", "sgn"],
398 "cjy": ["cjy", "zh"],
399 "cmn": ["cmn", "zh"],
400 "coa": ["coa", "ms"],
401 "cpx": ["cpx", "zh"],
402 "csc": ["csc", "sgn"],
403 "csd": ["csd", "sgn"],
404 "cse": ["cse", "sgn"],
405 "csf": ["csf", "sgn"],
406 "csg": ["csg", "sgn"],
407 "csl": ["csl", "sgn"],
408 "csn": ["csn", "sgn"],
409 "csq": ["csq", "sgn"],
410 "csr": ["csr", "sgn"],
411 "czh": ["czh", "zh"],
412 "czo": ["czo", "zh"],
413 "doq": ["doq", "sgn"],
414 "dse": ["dse", "sgn"],
415 "dsl": ["dsl", "sgn"],
416 "dup": ["dup", "ms"],
417 "ecs": ["ecs", "sgn"],
418 "esl": ["esl", "sgn"],
419 "esn": ["esn", "sgn"],
420 "eso": ["eso", "sgn"],
421 "eth": ["eth", "sgn"],
422 "fcs": ["fcs", "sgn"],
423 "fse": ["fse", "sgn"],
424 "fsl": ["fsl", "sgn"],
425 "fss": ["fss", "sgn"],
426 "gan": ["gan", "zh"],
427 "gom": ["gom", "kok"],
428 "gse": ["gse", "sgn"],
429 "gsg": ["gsg", "sgn"],
430 "gsm": ["gsm", "sgn"],
431 "gss": ["gss", "sgn"],
432 "gus": ["gus", "sgn"],
433 "hab": ["hab", "sgn"],
434 "haf": ["haf", "sgn"],
435 "hak": ["hak", "zh"],
436 "hds": ["hds", "sgn"],
437 "hji": ["hji", "ms"],
438 "hks": ["hks", "sgn"],
439 "hos": ["hos", "sgn"],
440 "hps": ["hps", "sgn"],
441 "hsh": ["hsh", "sgn"],
442 "hsl": ["hsl", "sgn"],
443 "hsn": ["hsn", "zh"],
444 "icl": ["icl", "sgn"],
445 "ils": ["ils", "sgn"],
446 "inl": ["inl", "sgn"],
447 "ins": ["ins", "sgn"],
448 "ise": ["ise", "sgn"],
449 "isg": ["isg", "sgn"],
450 "isr": ["isr", "sgn"],
451 "jak": ["jak", "ms"],
452 "jax": ["jax", "ms"],
453 "jcs": ["jcs", "sgn"],
454 "jhs": ["jhs", "sgn"],
455 "jls": ["jls", "sgn"],
456 "jos": ["jos", "sgn"],
457 "jsl": ["jsl", "sgn"],
458 "jus": ["jus", "sgn"],
459 "kgi": ["kgi", "sgn"],
460 "knn": ["knn", "kok"],
461 "kvb": ["kvb", "ms"],
462 "kvk": ["kvk", "sgn"],
463 "kvr": ["kvr", "ms"],
464 "kxd": ["kxd", "ms"],
465 "lbs": ["lbs", "sgn"],
466 "lce": ["lce", "ms"],
467 "lcf": ["lcf", "ms"],
468 "liw": ["liw", "ms"],
469 "lls": ["lls", "sgn"],
470 "lsg": ["lsg", "sgn"],
471 "lsl": ["lsl", "sgn"],
472 "lso": ["lso", "sgn"],
473 "lsp": ["lsp", "sgn"],
474 "lst": ["lst", "sgn"],
475 "lsy": ["lsy", "sgn"],
476 "ltg": ["ltg", "lv"],
477 "lvs": ["lvs", "lv"],
478 "lzh": ["lzh", "zh"],
479 "max": ["max", "ms"],
480 "mdl": ["mdl", "sgn"],
481 "meo": ["meo", "ms"],
482 "mfa": ["mfa", "ms"],
483 "mfb": ["mfb", "ms"],
484 "mfs": ["mfs", "sgn"],
485 "min": ["min", "ms"],
486 "mnp": ["mnp", "zh"],
487 "mqg": ["mqg", "ms"],
488 "mre": ["mre", "sgn"],
489 "msd": ["msd", "sgn"],
490 "msi": ["msi", "ms"],
491 "msr": ["msr", "sgn"],
492 "mui": ["mui", "ms"],
493 "mzc": ["mzc", "sgn"],
494 "mzg": ["mzg", "sgn"],
495 "mzy": ["mzy", "sgn"],
496 "nan": ["nan", "zh"],
497 "nbs": ["nbs", "sgn"],
498 "ncs": ["ncs", "sgn"],
499 "nsi": ["nsi", "sgn"],
500 "nsl": ["nsl", "sgn"],
501 "nsp": ["nsp", "sgn"],
502 "nsr": ["nsr", "sgn"],
503 "nzs": ["nzs", "sgn"],
504 "okl": ["okl", "sgn"],
505 "orn": ["orn", "ms"],
506 "ors": ["ors", "ms"],
507 "pel": ["pel", "ms"],
508 "pga": ["pga", "ar"],
509 "pks": ["pks", "sgn"],
510 "prl": ["prl", "sgn"],
511 "prz": ["prz", "sgn"],
512 "psc": ["psc", "sgn"],
513 "psd": ["psd", "sgn"],
514 "pse": ["pse", "ms"],
515 "psg": ["psg", "sgn"],
516 "psl": ["psl", "sgn"],
517 "pso": ["pso", "sgn"],
518 "psp": ["psp", "sgn"],
519 "psr": ["psr", "sgn"],
520 "pys": ["pys", "sgn"],
521 "rms": ["rms", "sgn"],
522 "rsi": ["rsi", "sgn"],
523 "rsl": ["rsl", "sgn"],
524 "sdl": ["sdl", "sgn"],
525 "sfb": ["sfb", "sgn"],
526 "sfs": ["sfs", "sgn"],
527 "sgg": ["sgg", "sgn"],
528 "sgx": ["sgx", "sgn"],
529 "shu": ["shu", "ar"],
530 "slf": ["slf", "sgn"],
531 "sls": ["sls", "sgn"],
532 "sqs": ["sqs", "sgn"],
533 "ssh": ["ssh", "ar"],
534 "ssp": ["ssp", "sgn"],
535 "ssr": ["ssr", "sgn"],
536 "svk": ["svk", "sgn"],
537 "swc": ["swc", "sw"],
538 "swh": ["swh", "sw"],
539 "swl": ["swl", "sgn"],
540 "syy": ["syy", "sgn"],
541 "tmw": ["tmw", "ms"],
542 "tse": ["tse", "sgn"],
543 "tsm": ["tsm", "sgn"],
544 "tsq": ["tsq", "sgn"],
545 "tss": ["tss", "sgn"],
546 "tsy": ["tsy", "sgn"],
547 "tza": ["tza", "sgn"],
548 "ugn": ["ugn", "sgn"],
549 "ugy": ["ugy", "sgn"],
550 "ukl": ["ukl", "sgn"],
551 "uks": ["uks", "sgn"],
552 "urk": ["urk", "ms"],
553 "uzn": ["uzn", "uz"],
554 "uzs": ["uzs", "uz"],
555 "vgt": ["vgt", "sgn"],
556 "vkk": ["vkk", "ms"],
557 "vkt": ["vkt", "ms"],
558 "vsi": ["vsi", "sgn"],
559 "vsl": ["vsl", "sgn"],
560 "vsv": ["vsv", "sgn"],
561 "wuu": ["wuu", "zh"],
562 "xki": ["xki", "sgn"],
563 "xml": ["xml", "sgn"],
564 "xmm": ["xmm", "ms"],
565 "xms": ["xms", "sgn"],
566 "yds": ["yds", "sgn"],
567 "ysl": ["ysl", "sgn"],
568 "yue": ["yue", "zh"],
569 "zib": ["zib", "sgn"],
570 "zlm": ["zlm", "ms"],
571 "zmi": ["zmi", "ms"],
572 "zsl": ["zsl", "sgn"],
573 "zsm": ["zsm", "ms"]
574 };
577 /**
578 * Canonicalizes the given well-formed BCP 47 language tag, including regularized case of subtags.
579 *
580 * Spec: ECMAScript Internationalization API Specification, draft, 6.2.3.
581 * Spec: RFC 5646, section 4.5.
582 */
583 function canonicalizeLanguageTag(locale) {
585 // start with lower case for easier processing, and because most subtags will need to be lower case anyway
586 locale = locale.toLowerCase();
588 // handle mappings for complete tags
589 if (__tagMappings.hasOwnProperty(locale)) {
590 return __tagMappings[locale];
591 }
593 var subtags = locale.split("-");
594 var i = 0;
596 // handle standard part: all subtags before first singleton or "x"
597 while (i < subtags.length) {
598 var subtag = subtags[i];
599 if (subtag.length === 1 && (i > 0 || subtag === "x")) {
600 break;
601 } else if (i !== 0 && subtag.length === 2) {
602 subtag = subtag.toUpperCase();
603 } else if (subtag.length === 4) {
604 subtag = subtag[0].toUpperCase() + subtag.substring(1).toLowerCase();
605 }
606 if (__subtagMappings.hasOwnProperty(subtag)) {
607 subtag = __subtagMappings[subtag];
608 } else if (__extlangMappings.hasOwnProperty(subtag)) {
609 subtag = __extlangMappings[subtag][0];
610 if (i === 1 && __extlangMappings[subtag][1] === subtags[0]) {
611 subtags.shift();
612 i--;
613 }
614 }
615 subtags[i] = subtag;
616 i++;
617 }
618 var normal = subtags.slice(0, i).join("-");
620 // handle extensions
621 var extensions = [];
622 while (i < subtags.length && subtags[i] !== "x") {
623 var extensionStart = i;
624 i++;
625 while (i < subtags.length && subtags[i].length > 1) {
626 i++;
627 }
628 var extension = subtags.slice(extensionStart, i).join("-");
629 extensions.push(extension);
630 }
631 extensions.sort();
633 // handle private use
634 var privateUse;
635 if (i < subtags.length) {
636 privateUse = subtags.slice(i).join("-");
637 }
639 // put everything back together
640 var canonical = normal;
641 if (extensions.length > 0) {
642 canonical += "-" + extensions.join("-");
643 }
644 if (privateUse !== undefined) {
645 if (canonical.length > 0) {
646 canonical += "-" + privateUse;
647 } else {
648 canonical = privateUse;
649 }
650 }
652 return canonical;
653 }
655 return typeof locale === "string" && isStructurallyValidLanguageTag(locale) &&
656 canonicalizeLanguageTag(locale) === locale;
657 }
660 /**
661 * Tests whether the named options property is correctly handled by the given constructor.
662 * @param {object} Constructor the constructor to test.
663 * @param {string} property the name of the options property to test.
664 * @param {string} type the type that values of the property are expected to have
665 * @param {Array} [values] an array of allowed values for the property. Not needed for boolean.
666 * @param {any} fallback the fallback value that the property assumes if not provided.
667 * @param {object} testOptions additional options:
668 * @param {boolean} isOptional whether support for this property is optional for implementations.
669 * @param {boolean} noReturn whether the resulting value of the property is not returned.
670 * @param {boolean} isILD whether the resulting value of the property is implementation and locale dependent.
671 * @param {object} extra additional option to pass along, properties are value -> {option: value}.
672 * @return {boolean} whether the test succeeded.
673 */
674 function testOption(Constructor, property, type, values, fallback, testOptions) {
675 var isOptional = testOptions !== undefined && testOptions.isOptional === true;
676 var noReturn = testOptions !== undefined && testOptions.noReturn === true;
677 var isILD = testOptions !== undefined && testOptions.isILD === true;
679 function addExtraOptions(options, value, testOptions) {
680 if (testOptions !== undefined && testOptions.extra !== undefined) {
681 var extra;
682 if (value !== undefined && testOptions.extra[value] !== undefined) {
683 extra = testOptions.extra[value];
684 } else if (testOptions.extra.any !== undefined) {
685 extra = testOptions.extra.any;
686 }
687 if (extra !== undefined) {
688 Object.getOwnPropertyNames(extra).forEach(function (prop) {
689 options[prop] = extra[prop];
690 });
691 }
692 }
693 }
695 var testValues, options, obj, expected, actual, error;
697 // test that the specified values are accepted. Also add values that convert to specified values.
698 if (type === "boolean") {
699 if (values === undefined) {
700 values = [true, false];
701 }
702 testValues = values.slice(0);
703 testValues.push(888);
704 testValues.push(0);
705 } else if (type === "string") {
706 testValues = values.slice(0);
707 testValues.push({toString: function () { return values[0]; }});
708 }
709 testValues.forEach(function (value) {
710 options = {};
711 options[property] = value;
712 addExtraOptions(options, value, testOptions);
713 obj = new Constructor(undefined, options);
714 if (noReturn) {
715 if (obj.resolvedOptions().hasOwnProperty(property)) {
716 $ERROR("Option property " + property + " is returned, but shouldn't be.");
717 }
718 } else {
719 actual = obj.resolvedOptions()[property];
720 if (isILD) {
721 if (actual !== undefined && values.indexOf(actual) === -1) {
722 $ERROR("Invalid value " + actual + " returned for property " + property + ".");
723 }
724 } else {
725 if (type === "boolean") {
726 expected = Boolean(value);
727 } else if (type === "string") {
728 expected = String(value);
729 }
730 if (actual !== expected && !(isOptional && actual === undefined)) {
731 $ERROR("Option value " + value + " for property " + property +
732 " was not accepted; got " + actual + " instead.");
733 }
734 }
735 }
736 });
738 // test that invalid values are rejected
739 if (type === "string") {
740 var invalidValues = ["invalidValue", -1, null];
741 // assume that we won't have values in caseless scripts
742 if (values[0].toUpperCase() !== values[0]) {
743 invalidValues.push(values[0].toUpperCase());
744 } else {
745 invalidValues.push(values[0].toLowerCase());
746 }
747 invalidValues.forEach(function (value) {
748 options = {};
749 options[property] = value;
750 addExtraOptions(options, value, testOptions);
751 error = undefined;
752 try {
753 obj = new Constructor(undefined, options);
754 } catch (e) {
755 error = e;
756 }
757 if (error === undefined) {
758 $ERROR("Invalid option value " + value + " for property " + property + " was not rejected.");
759 } else if (error.name !== "RangeError") {
760 $ERROR("Invalid option value " + value + " for property " + property + " was rejected with wrong error " + error.name + ".");
761 }
762 });
763 }
765 // test that fallback value or another valid value is used if no options value is provided
766 if (!noReturn) {
767 options = {};
768 addExtraOptions(options, undefined, testOptions);
769 obj = new Constructor(undefined, options);
770 actual = obj.resolvedOptions()[property];
771 if (!(isOptional && actual === undefined)) {
772 if (fallback !== undefined) {
773 if (actual !== fallback) {
774 $ERROR("Option fallback value " + fallback + " for property " + property +
775 " was not used; got " + actual + " instead.");
776 }
777 } else {
778 if (values.indexOf(actual) === -1 && !(isILD && actual === undefined)) {
779 $ERROR("Invalid value " + actual + " returned for property " + property + ".");
780 }
781 }
782 }
783 }
785 return true;
786 }
789 /**
790 * Tests whether the named property of the given object has a valid value
791 * and the default attributes of the properties of an object literal.
792 * @param {Object} obj the object to be tested.
793 * @param {string} property the name of the property
794 * @param {Function|Array} valid either a function that tests value for validity and returns a boolean,
795 * an array of valid values.
796 * @exception if the property has an invalid value.
797 */
798 function testProperty(obj, property, valid) {
799 var desc = Object.getOwnPropertyDescriptor(obj, property);
800 if (!desc.writable) {
801 $ERROR("Property " + property + " must be writable.");
802 }
803 if (!desc.enumerable) {
804 $ERROR("Property " + property + " must be enumerable.");
805 }
806 if (!desc.configurable) {
807 $ERROR("Property " + property + " must be configurable.");
808 }
809 var value = desc.value;
810 var isValid = (typeof valid === "function") ? valid(value) : (valid.indexOf(value) !== -1);
811 if (!isValid) {
812 $ERROR("Property value " + value + " is not allowed for property " + property + ".");
813 }
814 }
817 /**
818 * Tests whether the named property of the given object, if present at all, has a valid value
819 * and the default attributes of the properties of an object literal.
820 * @param {Object} obj the object to be tested.
821 * @param {string} property the name of the property
822 * @param {Function|Array} valid either a function that tests value for validity and returns a boolean,
823 * an array of valid values.
824 * @exception if the property is present and has an invalid value.
825 */
826 function mayHaveProperty(obj, property, valid) {
827 if (obj.hasOwnProperty(property)) {
828 testProperty(obj, property, valid);
829 }
830 }
833 /**
834 * Tests whether the given object has the named property with a valid value
835 * and the default attributes of the properties of an object literal.
836 * @param {Object} obj the object to be tested.
837 * @param {string} property the name of the property
838 * @param {Function|Array} valid either a function that tests value for validity and returns a boolean,
839 * an array of valid values.
840 * @exception if the property is missing or has an invalid value.
841 */
842 function mustHaveProperty(obj, property, valid) {
843 if (!obj.hasOwnProperty(property)) {
844 $ERROR("Object is missing property " + property + ".");
845 }
846 testProperty(obj, property, valid);
847 }
850 /**
851 * Tests whether the given object does not have the named property.
852 * @param {Object} obj the object to be tested.
853 * @param {string} property the name of the property
854 * @exception if the property is present.
855 */
856 function mustNotHaveProperty(obj, property) {
857 if (obj.hasOwnProperty(property)) {
858 $ERROR("Object has property it mustn't have: " + property + ".");
859 }
860 }
863 /**
864 * Properties of the RegExp constructor that may be affected by use of regular
865 * expressions, and the default values of these properties. Properties are from
866 * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Deprecated_and_obsolete_features#RegExp_Properties
867 */
868 var regExpProperties = ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9",
869 "$_", "$*", "$&", "$+", "$`", "$'",
870 "input", "lastMatch", "lastParen", "leftContext", "rightContext"
871 ];
873 var regExpPropertiesDefaultValues = (function () {
874 var values = Object.create(null);
875 regExpProperties.forEach(function (property) {
876 values[property] = RegExp[property];
877 });
878 return values;
879 }());
882 /**
883 * Tests that executing the provided function (which may use regular expressions
884 * in its implementation) does not create or modify unwanted properties on the
885 * RegExp constructor.
886 */
887 function testForUnwantedRegExpChanges(testFunc) {
888 regExpProperties.forEach(function (property) {
889 RegExp[property] = regExpPropertiesDefaultValues[property];
890 });
891 testFunc();
892 regExpProperties.forEach(function (property) {
893 if (RegExp[property] !== regExpPropertiesDefaultValues[property]) {
894 $ERROR("RegExp has unexpected property " + property + " with value " +
895 RegExp[property] + ".");
896 }
897 });
898 }
901 /**
902 * Tests whether name is a valid BCP 47 numbering system name
903 * and not excluded from use in the ECMAScript Internationalization API.
904 * @param {string} name the name to be tested.
905 * @return {boolean} whether name is a valid BCP 47 numbering system name and
906 * allowed for use in the ECMAScript Internationalization API.
907 */
909 function isValidNumberingSystem(name) {
911 // source: CLDR file common/bcp47/number.xml; version CLDR 21.
912 var numberingSystems = [
913 "arab",
914 "arabext",
915 "armn",
916 "armnlow",
917 "bali",
918 "beng",
919 "brah",
920 "cakm",
921 "cham",
922 "deva",
923 "ethi",
924 "finance",
925 "fullwide",
926 "geor",
927 "grek",
928 "greklow",
929 "gujr",
930 "guru",
931 "hanidec",
932 "hans",
933 "hansfin",
934 "hant",
935 "hantfin",
936 "hebr",
937 "java",
938 "jpan",
939 "jpanfin",
940 "kali",
941 "khmr",
942 "knda",
943 "osma",
944 "lana",
945 "lanatham",
946 "laoo",
947 "latn",
948 "lepc",
949 "limb",
950 "mlym",
951 "mong",
952 "mtei",
953 "mymr",
954 "mymrshan",
955 "native",
956 "nkoo",
957 "olck",
958 "orya",
959 "roman",
960 "romanlow",
961 "saur",
962 "shrd",
963 "sora",
964 "sund",
965 "talu",
966 "takr",
967 "taml",
968 "tamldec",
969 "telu",
970 "thai",
971 "tibt",
972 "traditio",
973 "vaii"
974 ];
976 var excluded = [
977 "finance",
978 "native",
979 "traditio"
980 ];
983 return numberingSystems.indexOf(name) !== -1 && excluded.indexOf(name) === -1;
984 }
987 /**
988 * Provides the digits of numbering systems with simple digit mappings,
989 * as specified in 11.3.2.
990 */
992 var numberingSystemDigits = {
993 arab: "٠١٢٣٤٥٦٧٨٩",
994 arabext: "۰۱۲۳۴۵۶۷۸۹",
995 beng: "০১২৩৪৫৬৭৮৯",
996 deva: "०१२३४५६७८९",
997 fullwide: "0123456789",
998 gujr: "૦૧૨૩૪૫૬૭૮૯",
999 guru: "੦੧੨੩੪੫੬੭੮੯",
1000 hanidec: "〇一二三四五六七八九",
1001 khmr: "០១២៣៤៥៦៧៨៩",
1002 knda: "೦೧೨೩೪೫೬೭೮೯",
1003 laoo: "໐໑໒໓໔໕໖໗໘໙",
1004 latn: "0123456789",
1005 mlym: "൦൧൨൩൪൫൬൭൮൯",
1006 mong: "᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙",
1007 mymr: "၀၁၂၃၄၅၆၇၈၉",
1008 orya: "୦୧୨୩୪୫୬୭୮୯",
1009 tamldec: "௦௧௨௩௪௫௬௭௮௯",
1010 telu: "౦౧౨౩౪౫౬౭౮౯",
1011 thai: "๐๑๒๓๔๕๖๗๘๙",
1012 tibt: "༠༡༢༣༤༥༦༧༨༩"
1013 };
1016 /**
1017 * Tests that number formatting is handled correctly. The function checks that the
1018 * digit sequences in formatted output are as specified, converted to the
1019 * selected numbering system, and embedded in consistent localized patterns.
1020 * @param {Array} locales the locales to be tested.
1021 * @param {Array} numberingSystems the numbering systems to be tested.
1022 * @param {Object} options the options to pass to Intl.NumberFormat. Options
1023 * must include {useGrouping: false}, and must cause 1.1 to be formatted
1024 * pre- and post-decimal digits.
1025 * @param {Object} testData maps input data (in ES5 9.3.1 format) to expected output strings
1026 * in unlocalized format with Western digits.
1027 */
1029 function testNumberFormat(locales, numberingSystems, options, testData) {
1030 locales.forEach(function (locale) {
1031 numberingSystems.forEach(function (numbering) {
1032 var digits = numberingSystemDigits[numbering];
1033 var format = new Intl.NumberFormat([locale + "-u-nu-" + numbering], options);
1035 function getPatternParts(positive) {
1036 var n = positive ? 1.1 : -1.1;
1037 var formatted = format.format(n);
1038 var oneoneRE = "([^" + digits + "]*)[" + digits + "]+([^" + digits + "]+)[" + digits + "]+([^" + digits + "]*)";
1039 var match = formatted.match(new RegExp(oneoneRE));
1040 if (match === null) {
1041 $ERROR("Unexpected formatted " + n + " for " +
1042 format.resolvedOptions().locale + " and options " +
1043 JSON.stringify(options) + ": " + formatted);
1044 }
1045 return match;
1046 }
1048 function toNumbering(raw) {
1049 return raw.replace(/[0-9]/g, function (digit) {
1050 return digits[digit.charCodeAt(0) - "0".charCodeAt(0)];
1051 });
1052 }
1054 function buildExpected(raw, patternParts) {
1055 var period = raw.indexOf(".");
1056 if (period === -1) {
1057 return patternParts[1] + toNumbering(raw) + patternParts[3];
1058 } else {
1059 return patternParts[1] +
1060 toNumbering(raw.substring(0, period)) +
1061 patternParts[2] +
1062 toNumbering(raw.substring(period + 1)) +
1063 patternParts[3];
1064 }
1065 }
1067 if (format.resolvedOptions().numberingSystem === numbering) {
1068 // figure out prefixes, infixes, suffixes for positive and negative values
1069 var posPatternParts = getPatternParts(true);
1070 var negPatternParts = getPatternParts(false);
1072 Object.getOwnPropertyNames(testData).forEach(function (input) {
1073 var rawExpected = testData[input];
1074 var patternParts;
1075 if (rawExpected[0] === "-") {
1076 patternParts = negPatternParts;
1077 rawExpected = rawExpected.substring(1);
1078 } else {
1079 patternParts = posPatternParts;
1080 }
1081 var expected = buildExpected(rawExpected, patternParts);
1082 var actual = format.format(input);
1083 if (actual !== expected) {
1084 $ERROR("Formatted value for " + input + ", " +
1085 format.resolvedOptions().locale + " and options " +
1086 JSON.stringify(options) + " is " + actual + "; expected " + expected + ".");
1087 }
1088 });
1089 }
1090 });
1091 });
1092 }
1095 /**
1096 * Return the components of date-time formats.
1097 * @return {Array} an array with all date-time components.
1098 */
1100 function getDateTimeComponents() {
1101 return ["weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName"];
1102 }
1105 /**
1106 * Return the valid values for the given date-time component, as specified
1107 * by the table in section 12.1.1.
1108 * @param {string} component a date-time component.
1109 * @return {Array} an array with the valid values for the component.
1110 */
1112 function getDateTimeComponentValues(component) {
1114 var components = {
1115 weekday: ["narrow", "short", "long"],
1116 era: ["narrow", "short", "long"],
1117 year: ["2-digit", "numeric"],
1118 month: ["2-digit", "numeric", "narrow", "short", "long"],
1119 day: ["2-digit", "numeric"],
1120 hour: ["2-digit", "numeric"],
1121 minute: ["2-digit", "numeric"],
1122 second: ["2-digit", "numeric"],
1123 timeZoneName: ["short", "long"]
1124 };
1126 var result = components[component];
1127 if (result === undefined) {
1128 $ERROR("Internal error: No values defined for date-time component " + component + ".");
1129 }
1130 return result;
1131 }
1134 /**
1135 * Tests that the given value is valid for the given date-time component.
1136 * @param {string} component a date-time component.
1137 * @param {string} value the value to be tested.
1138 * @return {boolean} true if the test succeeds.
1139 * @exception if the test fails.
1140 */
1142 function testValidDateTimeComponentValue(component, value) {
1143 if (getDateTimeComponentValues(component).indexOf(value) === -1) {
1144 $ERROR("Invalid value " + value + " for date-time component " + component + ".");
1145 }
1146 return true;
1147 }
1150 /**
1151 * Verifies that the actual array matches the expected one in length, elements,
1152 * and element order.
1153 * @param {Array} expected the expected array.
1154 * @param {Array} actual the actual array.
1155 * @return {boolean} true if the test succeeds.
1156 * @exception if the test fails.
1157 */
1158 function testArraysAreSame(expected, actual) {
1159 for (i = 0; i < Math.max(actual.length, expected.length); i++) {
1160 if (actual[i] !== expected[i]) {
1161 $ERROR("Result array element at index " + i + " should be \"" +
1162 expected[i] + "\" but is \"" + actual[i] + "\".");
1163 }
1164 }
1165 return true;
1166 }