js/src/tests/supporting/testIntl.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

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);
  1045                 return match;
  1048             function toNumbering(raw) {
  1049                 return raw.replace(/[0-9]/g, function (digit) {
  1050                     return digits[digit.charCodeAt(0) - "0".charCodeAt(0)];
  1051                 });
  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];
  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;
  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 + ".");
  1088                 });
  1090         });
  1091     });
  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"];
  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 + ".");
  1130     return result;
  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 + ".");
  1146     return true;
  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] + "\".");
  1165     return true;

mercurial