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