tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

reflection.js (51938B)


      1 ReflectionTests = {};
      2 
      3 ReflectionTests.start = new Date().getTime();
      4 
      5 /**
      6 * Resolve the given URL to an absolute URL, relative to the current document's
      7 * address.  There's no API that I know of that exposes this directly, so we
      8 * actually just create an <a> element, set its href, and stitch together the
      9 * various properties.  Seems to work.  We don't try to reimplement the
     10 * algorithm here, because we're not concerned with its correctness -- we're
     11 * only testing HTML reflection, not Web Addresses.
     12 *
     13 * Return the input if the URL couldn't be resolved, per the spec for
     14 * reflected URL attributes.
     15 *
     16 * It seems like IE9 doesn't implement URL decomposition attributes correctly
     17 * for <a>, which causes all these tests to fail.  Ideally I'd do this in some
     18 * other way, but the failure does stem from an incorrect implementation of
     19 * HTML, so I'll leave it alone for now.
     20 *
     21 * TODO: This relies on reflection to test reflection, so it could mask bugs.
     22 * Either get a JS implementation of the "resolve a URL" algorithm, or just
     23 * specify expected values manually here.  It shouldn't be too hard to write
     24 * special cases for all the values we test.
     25 */
     26 ReflectionTests.resolveUrl = function(url) {
     27    url = String(url);
     28    var el = document.createElement("a");
     29    el.href = url;
     30    var ret = el.protocol + "//" + el.host + el.pathname + el.search + el.hash;
     31    if (ret == "//") {
     32        return url;
     33    } else {
     34        return ret;
     35    }
     36 };
     37 
     38 /**
     39 * The "rules for parsing non-negative integers" from the HTML spec.  They're
     40 * mostly used for reflection, so here seems like as good a place to test them
     41 * as any.  Returns false on error.
     42 */
     43 ReflectionTests.parseNonneg = function(input) {
     44  var value = this.parseInt(input);
     45  if (value === false || value < 0) {
     46      return false;
     47  }
     48  return value;
     49 };
     50 
     51 /**
     52 * The "rules for parsing integers" from the HTML spec.  Returns false on
     53 * error.
     54 */
     55 ReflectionTests.parseInt = function(input) {
     56    var position = 0;
     57    var sign = 1;
     58    // Skip whitespace
     59    while (input.length > position && /^[ \t\n\f\r]$/.test(input[position])) {
     60        position++;
     61    }
     62    if (position >= input.length) {
     63        return false;
     64    }
     65    if (input[position] == "-") {
     66        sign = -1;
     67        position++;
     68    } else if (input[position] == "+") {
     69        position++;
     70    }
     71    if (position >= input.length) {
     72        return false;
     73    }
     74    if (!/^[0-9]$/.test(input[position])) {
     75        return false;
     76    }
     77    var value = 0;
     78    while (input.length > position && /^[0-9]$/.test(input[position])) {
     79        value *= 10;
     80        // Don't use parseInt even for single-digit strings . . .
     81        value += input.charCodeAt(position) - "0".charCodeAt(0);
     82        position++;
     83    }
     84    if (value === 0) {
     85        return 0;
     86    }
     87    return sign * value;
     88 };
     89 
     90 /**
     91 * The "rules for parsing floating-point number values" from the HTML spec.
     92 * Returns false on error.
     93 */
     94 ReflectionTests.parseFloat = function(input) {
     95    var position = 0;
     96    var value = 1;
     97    var divisor = 1;
     98    var exponent = 1;
     99    // Skip whitespace
    100    while (input.length > position && /^[ \t\n\f\r]$/.test(input[position])) {
    101        position++;
    102    }
    103    if (position >= input.length) {
    104        return false;
    105    }
    106    if (input[position] == "-") {
    107        value = -1;
    108        divisor = -1;
    109        position++;
    110    } else if (input[position] == "+") {
    111        position++;
    112    }
    113    if (position >= input.length) {
    114        return false;
    115    }
    116    if (input[position] == "." && position+1 < input.length && /^[0-9]$/.test(input[position+1])) {
    117        value = 0;
    118        // Use "else" branches rather than "jump to label fraction"
    119    } else if (!/^[0-9]$/.test(input[position])) {
    120        return false;
    121    } else {
    122        var val = 0;
    123        while (input.length > position && /^[0-9]$/.test(input[position])) {
    124            val *= 10;
    125            // Don't use parseInt even for single-digit strings . . .
    126            val += input.charCodeAt(position) - "0".charCodeAt(0);
    127            position++;
    128        }
    129        value *= val;
    130    }
    131    // Use nested "if" tests rather than "jump to label conversion" or "skip"
    132    // Fraction:
    133    if (input.length > position && input[position] == ".") {
    134        position++;
    135        while (input.length > position && /^[0-9]$/.test(input[position])) {
    136            divisor *= 10;
    137            // Don't use parseInt even for single-digit strings . . .
    138            value += (input.charCodeAt(position) - "0".charCodeAt(0)) / divisor;
    139            position++;
    140        }
    141    }
    142    if (input.length > position && (input[position] == "e" || input[position] == "E")) {
    143        position++;
    144        if (input.length > position) {
    145            if (input[position] == "-") {
    146                exponent = -1;
    147                position++;
    148            } else if (input[position] == "+") {
    149                position++;
    150            }
    151            if (input.length > position && /^[0-9]$/.test(input[position])) {
    152                var exp = 0;
    153                do {
    154                    exp *= 10;
    155                    // Don't use parseInt even for single-digit strings . . .
    156                    exp += input.charCodeAt(position) - "0".charCodeAt(0);
    157                    position++;
    158                } while (input.length > position && /^[0-9]$/.test(input[position]));
    159                exponent *= exp;
    160                value *= Math.pow(10, exponent);
    161            }
    162        }
    163    }
    164    // Conversion:
    165    if (!Number.isFinite(value)) {
    166      return false;
    167    }
    168    if (value === 0) {
    169        return 0;
    170    }
    171    return value;
    172 }
    173 
    174 // Used in initializing typeMap
    175 var binaryString = "\x00\x01\x02\x03\x04\x05\x06\x07 "
    176    + "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f "
    177    + "\x10\x11\x12\x13\x14\x15\x16\x17 "
    178    + "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f ";
    179 var maxInt = 2147483647;
    180 var minInt = -2147483648;
    181 var maxUnsigned = 4294967295;
    182 
    183 /**
    184 * Array containing the tests and other information for each type of reflected
    185 * attribute.  Meaning of keys:
    186 *
    187 *   "jsType": What typeof idlObj[idlName] is supposed to be.
    188 *   "defaultVal": The default value to be returned if the attribute is not
    189 *       present and no default is specifically set for this attribute. If
    190 *       it is an array then any value in the array is acceptable.
    191 *   "domTests": What values to test with setAttribute().
    192 *   "domExpected": What values to expect with IDL get after setAttribute().
    193 *       Defaults to the same as domTests.
    194 *   "idlTests": What values to test with IDL set.  Defaults to domTests.
    195 *   "idlDomExpected": What to expect from getAttribute() after IDL set.
    196 *       Defaults to idlTests.
    197 *   "idlIdlExpected": What to expect from IDL get after IDL set.  Defaults to
    198 *       idlDomExpected.
    199 *
    200 * Note that all tests/expected values are only baselines, and can be expanded
    201 * with additional tests hardcoded into the function for particular types if
    202 * necessary. For example, a special codepath is used for enums, and for
    203 * IDL setters which throw an exception. null means "defaultVal" is the
    204 * expected value. Expected DOM values are cast to strings by adding "".
    205 *
    206 * TODO: Test strings that aren't valid UTF-16.  Desired behavior is not clear
    207 * here at the time of writing, see
    208 * http://www.w3.org/Bugs/Public/show_bug.cgi?id=12100
    209 *
    210 * TODO: Test deleting an IDL attribute, and maybe doing other fun stuff to it.
    211 *
    212 * TODO: Test IDL sets of integer types to out-of-range or other weird values.
    213 * WebIDL says to wrap, but I'm not sure offhand if that's what we want.
    214 *
    215 * TODO: tokenlist, settable tokenlist, limited
    216 */
    217 
    218 
    219 ReflectionTests.typeMap = {
    220    /**
    221     * "If a reflecting IDL attribute is a DOMString but doesn't fall into any
    222     * of the above categories, then the getting and setting must be done in a
    223     * transparent, case-preserving manner."
    224     *
    225     * The data object passed to reflects() can contain an optional key
    226     * treatNullAsEmptyString, whose value is ignored.  If it does contain the
    227     * key, null will be cast to "" instead of "null", per WebIDL
    228     * [TreatNullAs=EmptyString].
    229     */
    230    "string": {
    231        "jsType": "string",
    232        "defaultVal": "",
    233        "domTests": ["", " " + binaryString + " foo ", undefined, 7, 1.5, "5%", "+100", ".5", true,
    234                     false, {"test": 6}, NaN, +Infinity, -Infinity, "\0", null,
    235                     {"toString":function(){return "test-toString";}},
    236                     {"valueOf":function(){return "test-valueOf";}, toString:null}
    237                ]
    238    },
    239    /**
    240     * "If a reflecting IDL attribute is a USVString attribute whose content
    241     * attribute is defined to contain a URL, then on getting, if the content
    242     * attribute is absent, the IDL attribute must return the empty string.
    243     * Otherwise, the IDL attribute must parse the value of the content
    244     * attribute relative to the element's node document and if that is
    245     * successful, return the resulting URL string. If parsing fails, then the
    246     * value of the content attribute must be returned instead, converted to a
    247     * USVString. On setting, the content attribute must be set to the specified
    248     * new value."
    249     *
    250     * Also HTMLHyperLinkElementUtils href, used by a.href and area.href
    251     */
    252    "url": {
    253        "jsType": "string",
    254        "defaultVal": "",
    255        "domTests": ["", " foo ", "http://site.example/",
    256                     "//site.example/path???@#l", binaryString, undefined, 7, 1.5, "5%", "+100", ".5", true,
    257                     false, {"test": 6}, NaN, +Infinity, -Infinity, "\0", null,
    258                     {"toString":function(){return "test-toString";}},
    259                     {"valueOf":function(){return "test-valueOf";}, toString:null}],
    260        "domExpected": ReflectionTests.resolveUrl,
    261        "idlIdlExpected": ReflectionTests.resolveUrl
    262    },
    263    /**
    264     * "If a reflecting IDL attribute is a DOMString whose content attribute is
    265     * an enumerated attribute, and the IDL attribute is limited to only known
    266     * values, then, on getting, the IDL attribute must return the conforming
    267     * value associated with the state the attribute is in (in its canonical
    268     * case), or the empty string if the attribute is in a state that has no
    269     * associated keyword value; and on setting, if the new value is an ASCII
    270     * case-insensitive match for one of the keywords given for that attribute,
    271     * then the content attribute must be set to the conforming value
    272     * associated with the state that the attribute would be in if set to the
    273     * given new value, otherwise, if the new value is the empty string, then
    274     * the content attribute must be removed, otherwise, the content attribute
    275     * must be set to the given new value."
    276     *
    277     * "Some attributes are defined as taking one of a finite set of keywords.
    278     * Such attributes are called enumerated attributes. The keywords are each
    279     * defined to map to a particular state (several keywords might map to the
    280     * same state, in which case some of the keywords are synonyms of each
    281     * other; additionally, some of the keywords can be said to be
    282     * non-conforming, and are only in the specification for historical
    283     * reasons). In addition, two default states can be given. The first is the
    284     * invalid value default, the second is the missing value default.
    285     *
    286     * . . .
    287     *
    288     * When the attribute is specified, if its value is an ASCII
    289     * case-insensitive match for one of the given keywords then that keyword's
    290     * state is the state that the attribute represents. If the attribute value
    291     * matches none of the given keywords, but the attribute has an invalid
    292     * value default, then the attribute represents that state. Otherwise, if
    293     * the attribute value matches none of the keywords but there is a missing
    294     * value default state defined, then that is the state represented by the
    295     * attribute.  Otherwise, there is no default, and invalid values must be
    296     * ignored.
    297     *
    298     * When the attribute is not specified, if there is a missing value default
    299     * state defined, then that is the state represented by the (missing)
    300     * attribute. Otherwise, the absence of the attribute means that there is
    301     * no state represented."
    302     *
    303     * This is only used for enums that are limited to known values, not other
    304     * enums (those are treated as generic strings by the spec).  The data
    305     * object passed to reflects() can contain these keys:
    306     *
    307     *   "defaultVal": missing value default (defaults to "")
    308     *   "invalidVal": invalid value default (defaults to defaultVal)
    309     *   "keywords": array of keywords as given by the spec (required)
    310     *   "nonCanon": dictionary mapping non-canonical values to their
    311     *     canonical equivalents (defaults to {})
    312     *   "isNullable": Indicates if attribute is nullable (defaults to false)
    313     *
    314     * Tests are mostly hardcoded into reflects(), since they depend on the
    315     * keywords.  All expected values are computed in reflects() using a helper
    316     * function.
    317     */
    318    "enum": {
    319        "jsType": "string",
    320        "defaultVal": "",
    321        "domTests": ["", " " + binaryString + " foo ", undefined, 7, 1.5, "5%", "+100", ".5", true,
    322                 false, {"test": 6}, NaN, +Infinity, -Infinity, "\0", null,
    323                 {"toString":function(){return "test-toString";}},
    324                 {"valueOf":function(){return "test-valueOf";}, toString:null}]
    325    },
    326    /**
    327     * "If a reflecting IDL attribute is a boolean attribute, then on getting
    328     * the IDL attribute must return true if the content attribute is set, and
    329     * false if it is absent. On setting, the content attribute must be removed
    330     * if the IDL attribute is set to false, and must be set to the empty
    331     * string if the IDL attribute is set to true. (This corresponds to the
    332     * rules for boolean content attributes.)"
    333     */
    334    "boolean": {
    335        "jsType": "boolean",
    336        "defaultVal": false,
    337        "domTests": ["", " foo ", undefined, null, 7, 1.5, "5%", "+100", ".5", true, false,
    338                     {"test": 6}, NaN, +Infinity, -Infinity, "\0",
    339                     {"toString":function(){return "test-toString";}},
    340                     {"valueOf":function(){return "test-valueOf";}, toString:null}],
    341        "domExpected": function(val) {
    342            return true;
    343        }
    344    },
    345    /**
    346     * "If a reflecting IDL attribute is a signed integer type (long) then, on
    347     * getting, the content attribute must be parsed according to the rules for
    348     * parsing signed integers, and if that is successful, and the value is in
    349     * the range of the IDL attribute's type, the resulting value must be
    350     * returned. If, on the other hand, it fails or returns an out of range
    351     * value, or if the attribute is absent, then the default value must be
    352     * returned instead, or 0 if there is no default value. On setting, the
    353     * given value must be converted to the shortest possible string
    354     * representing the number as a valid integer and then that string must be
    355     * used as the new content attribute value."
    356     */
    357    "long": {
    358        "jsType": "number",
    359        "defaultVal": 0,
    360        "domTests": [-36, -1, 0, 1, maxInt, minInt, maxInt + 1, minInt - 1,
    361                     maxUnsigned, maxUnsigned + 1, "", "-", "+", "-1", "-0", "0", "1",
    362                     " " + binaryString + " foo ",
    363                     // Test various different whitespace. Only 20, 9, A, C,
    364                     // and D are whitespace.
    365                     "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
    366                     "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
    367                     "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
    368                     "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7", "\u30007",
    369                     "\t\u000B7", "\n\u000B7","\f\u000B7", "\r\u000B7", "\x20\u000B7", "7\u000B",
    370                     undefined, 1.5, "5%", "+100", ".5", true, false, {"test": 6}, NaN, +Infinity,
    371                     -Infinity, "\0",
    372                     {toString:function() {return 2;}, valueOf: null},
    373                     {valueOf:function() {return 3;}, toString: null}],
    374        "domExpected": function(val) {
    375            var parsed = ReflectionTests.parseInt(String(val));
    376            if (parsed === false || parsed > maxInt || parsed < minInt) {
    377                return null;
    378            }
    379            return parsed;
    380        },
    381        "idlTests":       [-36, -1, 0, 1, 2147483647, -2147483648],
    382        "idlDomExpected": [-36, -1, 0, 1, 2147483647, -2147483648]
    383    },
    384    /**
    385     * "If a reflecting IDL attribute is a signed integer type (long) that is
    386     * limited to only non-negative numbers then, on getting, the content
    387     * attribute must be parsed according to the rules for parsing non-negative
    388     * integers, and if that is successful, and the value is in the range of
    389     * the IDL attribute's type, the resulting value must be returned. If, on
    390     * the other hand, it fails or returns an out of range value, or if the
    391     * attribute is absent, the default value must be returned instead, or −1
    392     * if there is no default value. On setting, if the value is negative, the
    393     * user agent must fire an INDEX_SIZE_ERR exception. Otherwise, the given
    394     * value must be converted to the shortest possible string representing the
    395     * number as a valid non-negative integer and then that string must be used
    396     * as the new content attribute value."
    397     */
    398    "limited long": {
    399        "jsType": "number",
    400        "defaultVal": -1,
    401        "domTests": [minInt - 1, minInt, -36, -1, -0, 0, 1, maxInt, maxInt + 1,
    402                     maxUnsigned, maxUnsigned + 1, "", "-", "+", "-1", "-0", "0", "1",
    403                     " " + binaryString + " foo ",
    404                     "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
    405                     "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
    406                     "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
    407                     "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7", "\u30007",
    408                     "\t\u000B7", "\n\u000B7","\f\u000B7", "\r\u000B7", "\x20\u000B7", "7\u000B",
    409                     undefined, 1.5, "5%", "+100", ".5", true, false, {"test": 6}, NaN, +Infinity,
    410                     -Infinity, "\0",
    411                     {toString:function() {return 2;}, valueOf: null},
    412                     {valueOf:function() {return 3;}, toString: null}],
    413        "domExpected": function(val) {
    414            var parsed = ReflectionTests.parseNonneg(String(val));
    415            if (parsed === false || parsed > maxInt || parsed < minInt) {
    416                return null;
    417            }
    418            return parsed;
    419        },
    420        "idlTests":       [minInt, -36,  -1, 0, 1, maxInt],
    421        "idlDomExpected": [null/*exception*/, null/*exception*/, null/*exception*/, 0, 1, maxInt]
    422    },
    423    /**
    424     * "If a reflecting IDL attribute is an unsigned integer type (unsigned
    425     * long) then, on getting, the content attribute must be parsed according
    426     * to the rules for parsing non-negative integers, and if that is
    427     * successful, and the value is in the range 0 to 2147483647 inclusive, the
    428     * resulting value must be returned. If, on the other hand, it fails or
    429     * returns an out of range value, or if the attribute is absent, the
    430     * default value must be returned instead, or 0 if there is no default
    431     * value. On setting, the given value must be converted to the shortest
    432     * possible string representing the number as a valid non-negative integer
    433     * and then that string must be used as the new content attribute value."
    434     */
    435    "unsigned long": {
    436        "jsType": "number",
    437        "defaultVal": 0,
    438        "domTests": [minInt - 1, minInt, -36,  -1,   0, 1, 257, maxInt,
    439                     maxInt + 1, maxUnsigned, maxUnsigned + 1, "", "-", "+", "-1", "-0", "0", "1",
    440                     "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
    441                     "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
    442                     "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
    443                     "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7", "\u30007",
    444                     "\t\u000B7", "\n\u000B7","\f\u000B7", "\r\u000B7", "\x20\u000B7", "7\u000B",
    445                     " " + binaryString + " foo ", undefined, 1.5, "5%", "+100", ".5", true, false,
    446                     {"test": 6}, NaN, +Infinity, -Infinity, "\0",
    447                     {toString:function() {return 2;}, valueOf: null},
    448                     {valueOf:function() {return 3;}, toString: null}],
    449        "domExpected": function(val) {
    450            var parsed = ReflectionTests.parseNonneg(String(val));
    451            // Note maxInt, not maxUnsigned.
    452            if (parsed === false || parsed < 0 || parsed > maxInt) {
    453                return null;
    454            }
    455            return parsed;
    456        },
    457        "idlTests": [0, 1, 257, maxInt, "-0", maxInt + 1, maxUnsigned],
    458        "idlIdlExpected": [0, 1, 257, maxInt, 0, null, null],
    459        "idlDomExpected": [0, 1, 257, maxInt, 0, null, null],
    460    },
    461    /**
    462     * "If a reflecting IDL attribute is an unsigned integer type (unsigned
    463     * long) that is limited to only non-negative numbers greater than zero,
    464     * then the behavior is similar to the previous case, but zero is not
    465     * allowed. On getting, the content attribute must first be parsed
    466     * according to the rules for parsing non-negative integers, and if that is
    467     * successful, and the value is in the range 1 to 2147483647 inclusive, the
    468     * resulting value must be returned. If, on the other hand, it fails or
    469     * returns an out of range value, or if the attribute is absent, the
    470     * default value must be returned instead, or 1 if there is no default
    471     * value. On setting, if the value is zero, the user agent must fire an
    472     * INDEX_SIZE_ERR exception. Otherwise, the given value must be converted
    473     * to the shortest possible string representing the number as a valid
    474     * non-negative integer and then that string must be used as the new
    475     * content attribute value."
    476     */
    477    "limited unsigned long": {
    478        "jsType": "number",
    479        "defaultVal": 1,
    480        "domTests": [minInt - 1, minInt, -36,  -1,   0,    1, maxInt,
    481                     maxInt + 1, maxUnsigned, maxUnsigned + 1, "", "-", "+", "-1", "-0", "0", "1",
    482                     "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
    483                     "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
    484                     "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
    485                     "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7", "\u30007",
    486                     "\t\u000B7", "\n\u000B7","\f\u000B7", "\r\u000B7", "\x20\u000B7", "7\u000B",
    487                     " " + binaryString + " foo ", undefined, 1.5, "5%", "+100", ".5", true, false,
    488                     {"test": 6}, NaN, +Infinity, -Infinity, "\0",
    489                     {toString:function() {return 2;}, valueOf: null},
    490                     {valueOf:function() {return 3;}, toString: null}],
    491        "domExpected": function(val) {
    492            var parsed = ReflectionTests.parseNonneg(String(val));
    493            // Note maxInt, not maxUnsigned.
    494            if (parsed === false || parsed < 1 || parsed > maxInt) {
    495                return null;
    496            }
    497            return parsed;
    498        },
    499        "idlTests":       [0, 1, maxInt, maxInt + 1, maxUnsigned],
    500        "idlDomExpected": [null/*exception*/, 1, maxInt, null, null]
    501    },
    502    /**
    503     * "If a reflecting IDL attribute has an unsigned integer type (unsigned
    504     * long) that is limited to only non-negative numbers greater than zero
    505     * with fallback, then the behaviour is similar to the previous case, but
    506     * disallowed values are converted to the default value.  On getting, the
    507     * content attribute must first be parsed according to the rules for
    508     * parsing non-negative integers, and if that is successful, and the value
    509     * is in the range 1 to 2147483647 inclusive, the resulting value must be
    510     * returned.  If, on the other hand, it fails or returns an out of range
    511     * value, or if the attribute is absent, the default value must be returned
    512     * instead.  On setting, first, if the new value is in the range 1 to
    513     * 2147483647, then let n be the new value, otherwise let n be the default
    514     * value; then, n must be converted to the shortest possible string
    515     * representing the number as a valid non-negative integer and that string
    516     * must be used as the new content attribute value."
    517     */
    518    "limited unsigned long with fallback": {
    519        "jsType": "number",
    520            "domTests": [minInt - 1, minInt, -36,  -1,   0,    1, maxInt,
    521                         maxInt + 1, maxUnsigned, maxUnsigned + 1, "", "-", "+", "-1", "-0", "0", "1",
    522                         "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
    523                         "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
    524                         "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
    525                         "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7", "\u30007",
    526                         "\t\u000B7", "\n\u000B7","\f\u000B7", "\r\u000B7", "\x20\u000B7", "7\u000B",
    527                         " " + binaryString + " foo ", undefined, 1.5, "5%", "+100", ".5", true, false,
    528                         {"test": 6}, NaN, +Infinity, -Infinity, "\0",
    529                         {toString:function() {return 2;}, valueOf: null},
    530                         {valueOf:function() {return 3;}, toString: null}],
    531            "domExpected": function(val) {
    532                var parsed = ReflectionTests.parseNonneg(String(val));
    533                // Note maxInt, not maxUnsigned.
    534                if (parsed === false || parsed < 1 || parsed > maxInt) {
    535                    return null;
    536                }
    537                return parsed;
    538            },
    539            "idlTests":       [0, 1, maxInt, maxInt + 1, maxUnsigned],
    540            "idlDomExpected": [null, 1, maxInt, null, null]
    541    },
    542    /**
    543     * "If a reflecting IDL attribute has an unsigned integer type (unsigned
    544     * long) that is clamped to the range [min, max], then on getting, the
    545     * content attribute must first be parsed according to the rules for
    546     * parsing non-negative integers, and if that is successful, and the value
    547     * is between min and max inclusive, the resulting value must be returned.
    548     * If it fails, the default value must be returned. If it succeeds but the
    549     * value is less than min, min must be returned. If it succeeds but the
    550     * value is greater than max, max must be returned. On setting, it behaves
    551     * the same as a regular reflected unsigned integer."
    552     *
    553     * The data object passed to reflects must contain the keys defaultVal,
    554     * min, and max.  As with enum, domExpected is generated later once we have
    555     * access to the min and max.
    556     */
    557    "clamped unsigned long": {
    558        "jsType": "number",
    559        "domTests": [minInt - 1, minInt, -36,  -1,   0,    1, maxInt,
    560                     maxInt + 1, maxUnsigned, maxUnsigned + 1, "", "-", "+", "-1", "-0", "0", "1",
    561                     "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
    562                     "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
    563                     "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
    564                     "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7", "\u30007",
    565                     "\t\u000B7", "\n\u000B7","\f\u000B7", "\r\u000B7", "\x20\u000B7", "7\u000B",
    566                     " " + binaryString + " foo ", undefined, 1.5, "5%", "+100", ".5", true, false,
    567                     {"test": 6}, NaN, +Infinity, -Infinity, "\0",
    568                     {toString:function() {return 2;}, valueOf: null},
    569                     {valueOf:function() {return 3;}, toString: null}],
    570        "idlTests": [0, 1, 257, maxInt, "-0", maxInt + 1, maxUnsigned],
    571        "idlDomExpected": [0, 1, 257, maxInt, 0, null, null],
    572    },
    573    /**
    574     * "If a reflecting IDL attribute is a floating point number type (double),
    575     * then, on getting, the content attribute must be parsed according to the
    576     * rules for parsing floating point number values, and if that is
    577     * successful, the resulting value must be returned. If, on the other hand,
    578     * it fails, or if the attribute is absent, the default value must be
    579     * returned instead, or 0.0 if there is no default value. On setting, the
    580     * given value must be converted to the best representation of the number
    581     * as a floating point number and then that string must be used as the new
    582     * content attribute value."
    583     *
    584     * TODO: Check this:
    585     *
    586     * "Except where otherwise specified, if an IDL attribute that is a
    587     * floating point number type (double) is assigned an Infinity or
    588     * Not-a-Number (NaN) value, a NOT_SUPPORTED_ERR exception must be raised."
    589     */
    590    "double": {
    591        "jsType": "number",
    592        "defaultVal": 0.0,
    593        "domTests": [minInt - 1, minInt, -36, -1, 0, 1, maxInt,
    594            maxInt + 1, maxUnsigned, maxUnsigned + 1, "", "-", "+",
    595            "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
    596            "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
    597            "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
    598            "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7", "\u30007",
    599            "\t\u000B7", "\n\u000B7","\f\u000B7", "\r\u000B7", "\x20\u000B7", "7\u000B",
    600            " " + binaryString + " foo ", undefined, 1.5, "5%", "+100", ".5", true, false,
    601            "1.", "1e2", "1e+2", "1e-2", "1E2", "1E+2", "1E-2", "1.e2", "1.0e2",
    602            "1. 1", "1 .1", "1. e2", "1 .e2", "1 e2", "1e 2", "1e -2", "1e- 2",
    603            "1.8e308", "-1.8e308",
    604            {"test": 6}, NaN, +Infinity, -Infinity, "\0",
    605            {toString:function() {return 2;}, valueOf: null},
    606            {valueOf:function() {return 3;}, toString: null}],
    607        "domExpected": function (val) {
    608            var parsed = ReflectionTests.parseFloat(String(val));
    609            if (parsed === false) {
    610                return null;
    611            }
    612            return parsed;
    613        },
    614        "idlTests": [ -10000000000, -1, -0, 0, 1, 10000000000,
    615            1e-10, 1e-4, 1.5, 1e25 ],
    616        "idlIdlExpected": function (val) {
    617            // This is a bit heavy-weight but hopefully will give values
    618            // that compare "better" (without introducing some tolerance)
    619            // when the test cases are expanded with more values.
    620            return ReflectionTests.parseFloat(String(val));
    621        }
    622    },
    623    /**
    624     * Reflected IDL attribute of type double, limited to only positive values,
    625     * are similar to the previous case with the following exceptions:
    626     *
    627     *  - on getting, if the parsed value is not greater than 0, then return
    628     *    the default value
    629     *  - on setting, if the value is not greater than 0, then return (leaving)
    630     *    the attribute to its previous value.
    631     */
    632    "limited double": {
    633        "jsType": "number",
    634        "defaultVal": 0.0,
    635        "domTests": [minInt - 1, minInt, -36, -1, 0, 1, maxInt,
    636            maxInt + 1, maxUnsigned, maxUnsigned + 1, "", "-", "+",
    637            "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
    638            "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
    639            "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
    640            "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7", "\u30007",
    641            "\t\u000B7", "\n\u000B7","\f\u000B7", "\r\u000B7", "\x20\u000B7", "7\u000B",
    642            " " + binaryString + " foo ", undefined, 1.5, "5%", "+100", ".5", true, false,
    643            "1.", "1e2", "1e+2", "1e-2", "1E2", "1E+2", "1E-2", "1.e2", "1.0e2",
    644            "1. 1", "1 .1", "1. e2", "1 .e2", "1 e2", "1e 2", "1e -2", "1e- 2",
    645            "1.8e308", "-1.8e308",
    646            {"test": 6}, NaN, +Infinity, -Infinity, "\0",
    647            {toString:function() {return 2;}, valueOf: null},
    648            {valueOf:function() {return 3;}, toString: null}],
    649        "domExpected": function (val) {
    650            var parsed = ReflectionTests.parseFloat(String(val));
    651            if (parsed === false || parsed <= 0) {
    652                return null;
    653            }
    654            return parsed;
    655        },
    656        "idlTests": [ -10000000000, -1, -0, 0, 1, 10000000000,
    657            1e-10, 1e-4, 1.5, 1e25 ],
    658        "idlIdlExpected": function (val) {
    659            // Non-positive values are special-cased below, as they
    660            // should be ignored, leaving the current value unchanged
    661 
    662            // This is a bit heavy-weight but hopefully will give values
    663            // that compare "better" (without introducing some tolerance)
    664            // when the test cases are expanded with more values.
    665            return ReflectionTests.parseFloat(String(val));
    666        }
    667    }
    668 };
    669 
    670 for (var type in ReflectionTests.typeMap) {
    671    var props = ReflectionTests.typeMap[type];
    672    var cast = window[props.jsType[0].toUpperCase() + props.jsType.slice(1)];
    673    if (props.domExpected === undefined) {
    674        props.domExpected = props.domTests.map(cast);
    675    } else if (typeof props.domExpected == "function") {
    676        props.domExpected = props.domTests.map(props.domExpected);
    677    }
    678    if (props.idlTests === undefined) {
    679        props.idlTests = props.domTests;
    680    }
    681    if (props.idlDomExpected === undefined) {
    682        props.idlDomExpected = props.idlTests.map(cast);
    683    } else if (typeof props.idlDomExpected == "function") {
    684        props.idlDomExpected = props.idlTests.map(props.idlDomExpected);
    685    }
    686    if (props.idlIdlExpected === undefined) {
    687        props.idlIdlExpected = props.idlDomExpected;
    688    } else if (typeof props.idlIdlExpected == "function") {
    689        props.idlIdlExpected = props.idlTests.map(props.idlIdlExpected);
    690    }
    691 }
    692 
    693 /**
    694 * Tests that the JavaScript attribute named idlName on the object idlObj
    695 * reflects the DOM attribute named domName on domObj.  The data argument is an
    696 * object that must contain at least one key, "type", which contains the
    697 * expected type of the IDL attribute ("string", "enum", etc.).  The "comment"
    698 * key will add a parenthesized comment in the type info if there's a test
    699 * failure, to indicate that there's something special about the element you're
    700 * testing (like it has an attribute set to some value).  Other keys in the
    701 * data object are type-specific, e.g., "defaultVal" for numeric types.  If the
    702 * data object is a string, it's converted to {"type": data}.  If idlObj is a
    703 * string, we set idlObj = domObj = document.createElement(idlObj).
    704 */
    705 ReflectionTests.reflects = function(data, idlName, idlObj, domName, domObj) {
    706    // Do some setup first so that getTypeDescription() works in testWrapper()
    707    if (typeof data == "string") {
    708        data = {type: data};
    709    }
    710    if (domName === undefined) {
    711        domName = idlName;
    712    }
    713    if (typeof idlObj == "string") {
    714        idlObj = document.createElement(idlObj);
    715    }
    716    if (domObj === undefined) {
    717        domObj = idlObj;
    718    }
    719 
    720    // Note: probably a hack?  This kind of assumes that the variables here
    721    // won't change over the course of the tests, which is wrong, but it's
    722    // probably safe enough.  Just don't read stuff that will change.
    723    ReflectionHarness.currentTestInfo = {data: data, idlName: idlName, idlObj: idlObj, domName: domName, domObj: domObj};
    724 
    725    // If we don't recognize the type, testing is impossible.
    726    if (this.typeMap[data.type] === undefined) {
    727        if (unimplemented.indexOf(data.type) == -1) {
    728            unimplemented.push(data.type);
    729        }
    730        return;
    731    }
    732 
    733    var typeInfo = this.typeMap[data.type];
    734 
    735    if (typeof data.isNullable == "undefined") {
    736        data.isNullable = false;
    737    }
    738 
    739    // Test that typeof idlObj[idlName] is correct.  If not, further tests are
    740    // probably pointless, so bail out if we're not running conformance tests.
    741    var expectedType = data.isNullable && data.defaultVal === null ? "object"
    742                                                                   : typeInfo.jsType;
    743    ReflectionHarness.test(function() {
    744        ReflectionHarness.assertEquals(typeof idlObj[idlName], expectedType);
    745    }, "typeof IDL attribute");
    746 
    747    if (!ReflectionHarness.conformanceTesting &&
    748        typeof idlObj[idlName] !== expectedType) {
    749        return;
    750    }
    751 
    752    // Test default
    753    var defaultVal = data.defaultVal;
    754    if (defaultVal === undefined) {
    755        defaultVal = typeInfo.defaultVal;
    756    }
    757    if ((domObj.localName === "form" && domName === "action") ||
    758        (["button", "input"].includes(domObj.localName) &&
    759         domName === "formAction")) {
    760        // Hard-coded special case
    761        defaultVal = domObj.ownerDocument.URL;
    762    }
    763    if (!data.customGetter && (defaultVal !== null || data.isNullable)) {
    764        ReflectionHarness.test(function() {
    765          // Tests can pass an array of acceptable values
    766          if (Array.isArray(defaultVal)) {
    767            ReflectionHarness.assertInArray(idlObj[idlName], defaultVal);
    768          } else {
    769            ReflectionHarness.assertEquals(idlObj[idlName], defaultVal);
    770          }
    771        }, "IDL get with DOM attribute unset");
    772    }
    773 
    774    var domTests = typeInfo.domTests.slice(0);
    775    var domExpected = typeInfo.domExpected.map(function(val) { return val === null ? defaultVal : val; });
    776    var idlTests = typeInfo.idlTests.slice(0);
    777    var idlDomExpected = typeInfo.idlDomExpected.map(function(val) { return val === null ? defaultVal : val; });
    778    var idlIdlExpected = typeInfo.idlIdlExpected.map(function(val) { return val === null ? defaultVal : val; });
    779    switch (data.type) {
    780        // Extra tests and other special-casing
    781        case "boolean":
    782        domTests.push(domName);
    783        domExpected.push(true);
    784        break;
    785 
    786        case "enum":
    787        // Whee, enum is complicated.
    788        if (typeof data.invalidVal == "undefined") {
    789            data.invalidVal = defaultVal;
    790        }
    791        if (typeof data.nonCanon == "undefined") {
    792            data.nonCanon = {};
    793        }
    794        for (var i = 0; i < data.keywords.length; i++) {
    795            if (data.keywords[i] != "") {
    796                domTests.push(data.keywords[i], "x" + data.keywords[i], data.keywords[i] + "\0");
    797                idlTests.push(data.keywords[i], "x" + data.keywords[i], data.keywords[i] + "\0");
    798            }
    799 
    800            if (data.keywords[i].length > 1) {
    801                var sliced = data.keywords[i].slice(1);
    802                // If slicing a value yields another valid value, then skip it since it results in duplicate tests.
    803                if (data.keywords.indexOf(sliced) == -1) {
    804                    domTests.push(sliced);
    805                    idlTests.push(sliced);
    806                }
    807            }
    808 
    809            if (data.keywords[i] != data.keywords[i].toLowerCase()) {
    810                domTests.push(data.keywords[i].toLowerCase());
    811                idlTests.push(data.keywords[i].toLowerCase());
    812            }
    813            if (data.keywords[i] != data.keywords[i].toUpperCase()) {
    814                domTests.push(data.keywords[i].toUpperCase());
    815                idlTests.push(data.keywords[i].toUpperCase());
    816            }
    817            if (data.keywords[i].indexOf("k") != -1) {
    818                domTests.push(data.keywords[i].replace(/k/g, "\u212A"));
    819                idlTests.push(data.keywords[i].replace(/k/g, "\u212A"));
    820            }
    821            if (data.keywords[i].indexOf("s") != -1) {
    822                domTests.push(data.keywords[i].replace(/s/g, "\u017F"));
    823                idlTests.push(data.keywords[i].replace(/s/g, "\u017F"));
    824            }
    825        }
    826 
    827        // Per spec, the expected DOM values are the same as the value we set
    828        // it to.
    829        if (!data.isNullable) {
    830            idlDomExpected = idlTests.slice(0);
    831        } else {
    832            idlDomExpected = [];
    833            for (var i = 0; i < idlTests.length; i++) {
    834                idlDomExpected.push((idlTests[i] === null || idlTests[i] === undefined) ? null : idlTests[i]);
    835            }
    836        }
    837 
    838        // Now we have the fun of calculating what the expected IDL values are.
    839        domExpected = [];
    840        idlIdlExpected = [];
    841        for (var i = 0; i < domTests.length; i++) {
    842            domExpected.push(this.enumExpected(data.keywords, data.nonCanon, data.invalidVal, domTests[i]));
    843        }
    844        for (var i = 0; i < idlTests.length; i++) {
    845            if (data.isNullable && (idlTests[i] === null || idlTests[i] === undefined)) {
    846                idlIdlExpected.push(null);
    847            } else {
    848                idlIdlExpected.push(this.enumExpected(data.keywords, data.nonCanon, data.invalidVal, idlTests[i]));
    849            }
    850        }
    851        break;
    852 
    853        case "string":
    854        if ("treatNullAsEmptyString" in data) {
    855            for (var i = 0; i < idlTests.length; i++) {
    856                if (idlTests[i] === null) {
    857                    idlDomExpected[i] = idlIdlExpected[i] = "";
    858                }
    859            }
    860        }
    861        break;
    862 
    863        case "clamped unsigned long":
    864        [data.min - 1, data.min, data.max, data.max + 1].forEach(function(val) {
    865          if (domTests.indexOf(val) == -1) {
    866            domTests.push(val);
    867          }
    868          if (idlTests.indexOf(val) == -1 && 0 <= val && val <= maxUnsigned) {
    869            idlTests.push(val);
    870            if (typeof val != "number") {
    871              val = ReflectionTests.parseNonneg(val);
    872            }
    873            idlDomExpected.push(val > maxInt ? null : val);
    874          }
    875        });
    876 
    877        // Rewrite expected values
    878        domExpected = domTests.map(function(val) {
    879            var parsed = ReflectionTests.parseNonneg(String(val));
    880            if (parsed === false) {
    881                return defaultVal;
    882            }
    883            if (parsed < data.min) {
    884              return data.min;
    885            }
    886            if (parsed > data.max) {
    887              return data.max;
    888            }
    889            return parsed;
    890        });
    891        idlIdlExpected = idlTests.map(function(val) {
    892            if (typeof val != "number") {
    893              val = ReflectionTests.parseNonneg(val);
    894            }
    895            if (val < 0 || val > maxUnsigned) {
    896              throw "Test bug: val should be an unsigned long";
    897            }
    898            if (val > maxInt) {
    899              return defaultVal;
    900            }
    901            if (val < data.min) {
    902              return data.min;
    903            }
    904            if (val > data.max) {
    905              return data.max;
    906            }
    907            return val;
    908        });
    909        break;
    910    }
    911    if (domObj.tagName.toLowerCase() == "canvas" && (domName == "width" || domName == "height")) {
    912        // Opera tries to allocate a canvas with the given width and height, so
    913        // it OOMs when given excessive sizes.  This is permissible under the
    914        // hardware-limitations clause, so cut out those checks.  TODO: Must be
    915        // a way to make this more succinct.
    916        domTests = domTests.filter(function(element, index, array) { return domExpected[index] < 1000; });
    917        domExpected = domExpected.filter(function(element, index, array) { return element < 1000; });
    918        idlTests = idlTests.filter(function(element, index, array) { return idlIdlExpected[index] < 1000; });
    919        idlDomExpected = idlDomExpected.filter(function(element, index, array) { return idlIdlExpected[index] < 1000; });
    920        idlIdlExpected = idlIdlExpected.filter(function(element, index, array) { return idlIdlExpected[index] < 1000; });
    921    }
    922    if ((domObj.localName === "form" && domName === "action") ||
    923        (["button", "input"].includes(domObj.localName) &&
    924         domName === "formAction")) {
    925        // Hard-coded special case
    926        for (var i = 0; i < domTests.length; i++) {
    927            if (domTests[i] === "") {
    928                domExpected[i] = domObj.ownerDocument.URL;
    929            }
    930        }
    931        for (var i = 0; i < idlTests.length; i++) {
    932            if (idlTests[i] === "") {
    933                idlIdlExpected[i] = domObj.ownerDocument.URL;
    934            }
    935        }
    936    }
    937    if (data.customGetter) {
    938        // These are reflected only on setting, not getting
    939        domTests = [];
    940        domExpected = [];
    941        idlIdlExpected = idlIdlExpected.map(() => null);
    942    }
    943 
    944    for (var i = 0; i < domTests.length; i++) {
    945        if (domExpected[i] === null && !data.isNullable) {
    946            // If you follow all the complicated logic here, you'll find that
    947            // this will only happen if there's no expected value at all (like
    948            // for tabIndex, where the default is too complicated).  So skip
    949            // the test.
    950            continue;
    951        }
    952        ReflectionHarness.test(function() {
    953            domObj.setAttribute(domName, domTests[i]);
    954            ReflectionHarness.assertEquals(domObj.getAttribute(domName),
    955                String(domTests[i]), "getAttribute()");
    956            // Tests can pass an array of acceptable values
    957            if (Array.isArray(domExpected[i])) {
    958              ReflectionHarness.assertInArray(idlObj[idlName], domExpected[i],
    959                  "IDL get");
    960            } else {
    961              ReflectionHarness.assertEquals(idlObj[idlName], domExpected[i],
    962                  "IDL get");
    963            }
    964        }, "setAttribute() to " + ReflectionHarness.stringRep(domTests[i]));
    965    }
    966 
    967    for (var i = 0; i < idlTests.length; i++) {
    968        ReflectionHarness.test(function() {
    969            if ((data.type == "limited long" && idlTests[i] < 0) ||
    970                (data.type == "limited unsigned long" && idlTests[i] == 0)) {
    971                ReflectionHarness.assertThrows("IndexSizeError", function() {
    972                    idlObj[idlName] = idlTests[i];
    973                });
    974            } else if (data.type == "limited double" && idlTests[i] <= 0) {
    975                domObj.setAttribute(domName, "previous value");
    976                var previousIdl = idlObj[idlName]; // should be the default value
    977                idlObj[idlName] = idlTests[i];
    978                ReflectionHarness.assertEquals(domObj.getAttribute(domName),
    979                                               "previous value", "getAttribute()");
    980                ReflectionHarness.assertEquals(idlObj[idlName], previousIdl, "IDL get");
    981            } else {
    982                var previousValue = domObj.getAttribute(domName);
    983                idlObj[idlName] = idlTests[i];
    984                if (data.type == "boolean") {
    985                    // Special case yay
    986                    ReflectionHarness.assertEquals(domObj.hasAttribute(domName),
    987                                                   Boolean(idlTests[i]), "hasAttribute()");
    988                } else if (idlDomExpected[i] !== null || data.isNullable) {
    989                    var expected = idlDomExpected[i] + "";
    990                    if (data.isNullable && idlDomExpected[i] === null) {
    991                        expected = null;
    992                    } else if (idlName == "nonce") {
    993                        // nonce doesn't reflect the value, as per /content-security-policy/nonce-hiding/
    994                        // tests that confirm that retrieving the nonce value post IDL change does not
    995                        // reflect back to the attribute (for security reasons)
    996                        expected = previousValue;
    997                    }
    998                    ReflectionHarness.assertEquals(domObj.getAttribute(domName), expected,
    999                                                   "getAttribute()");
   1000                }
   1001                // Ensure enumerated attributes never reflect in their non-canonical representations
   1002                if (data.type == "enum" && data.nonCanon[idlObj[idlName]]) {
   1003                  ReflectionHarness.assertEquals(idlObj[idlName], data.nonCanon[idlObj[idlName]], "IDL get canonical");
   1004                }
   1005                // Tests can pass an array of acceptable values
   1006                if (Array.isArray(idlIdlExpected[i])) {
   1007                    ReflectionHarness.assertInArray(idlObj[idlName], idlIdlExpected[i], "IDL get");
   1008                } else if (idlIdlExpected[i] !== null || data.isNullable) {
   1009                    ReflectionHarness.assertEquals(idlObj[idlName], idlIdlExpected[i], "IDL get");
   1010                }
   1011            }
   1012        }, "IDL set to " + ReflectionHarness.stringRep(idlTests[i]));
   1013    }
   1014 };
   1015 
   1016 function toASCIILowerCase(str) {
   1017    return str.replace(/[A-Z]/g, function(m) { return m.toLowerCase(); });
   1018 }
   1019 
   1020 /**
   1021 * If we have an enumerated attribute limited to the array of values in
   1022 * keywords, with nonCanon being a map of non-canonical values to their
   1023 * canonical equivalents, and invalidVal being the invalid value default (or ""
   1024 * for none), then what would we expect from an IDL get if the content
   1025 * attribute is equal to contentVal?
   1026 */
   1027 ReflectionTests.enumExpected = function(keywords, nonCanon, invalidVal, contentVal) {
   1028    var ret = invalidVal;
   1029    for (var i = 0; i < keywords.length; i++) {
   1030        if (toASCIILowerCase(String(contentVal)) === toASCIILowerCase(keywords[i])) {
   1031            ret = keywords[i];
   1032            break;
   1033        }
   1034    }
   1035    if (typeof nonCanon[ret] != "undefined") {
   1036        return nonCanon[ret];
   1037    }
   1038    return ret;
   1039 };
   1040 
   1041 /**
   1042 * Now we have the data structures that tell us which elements have which
   1043 * attributes.
   1044 *
   1045 * The elements object (which must have been defined in earlier files) is a map
   1046 * from element name to an object whose keys are IDL attribute names and whose
   1047 * values are types.  A type is of the same format as
   1048 * ReflectionTests.reflects() accepts, except that there's an extra optional
   1049 * domAttrName key that gets passed as the fourth argument to reflects() if
   1050 * it's provided.  (TODO: drop the fourth and fifth reflects() arguments and
   1051 * make it take them from the dictionary instead?)
   1052 */
   1053 
   1054 // Now we actually run all the tests.
   1055 var unimplemented = [];
   1056 for (var element in elements) {
   1057    ReflectionTests.reflects("string", "title", element);
   1058    ReflectionTests.reflects("string", "lang", element);
   1059    ReflectionTests.reflects({type: "enum", keywords: ["ltr", "rtl", "auto"]}, "dir", element);
   1060    ReflectionTests.reflects("string", "className", element, "class");
   1061    ReflectionTests.reflects("tokenlist", "classList", element, "class");
   1062    ReflectionTests.reflects("boolean", "autofocus", element);
   1063    ReflectionTests.reflects("boolean", "hidden", element);
   1064    ReflectionTests.reflects("string", "accessKey", element);
   1065    // Don't try to test the defaultVal -- it should be either 0 or -1, but the
   1066    // rules are complicated, and a lot of them are SHOULDs.
   1067    ReflectionTests.reflects({type: "long", defaultVal: null}, "tabIndex", element);
   1068    // TODO: classList, contextMenu, itemProp, itemRef
   1069 
   1070    for (var idlAttrName in elements[element]) {
   1071        var type = elements[element][idlAttrName];
   1072        ReflectionTests.reflects(type, idlAttrName, element,
   1073            typeof type == "object" && "domAttrName" in type ? type.domAttrName : idlAttrName);
   1074    }
   1075 }
   1076 
   1077 for (var i = 0; i < extraTests.length; i++) {
   1078    extraTests[i]();
   1079 }
   1080 
   1081 var time = document.getElementById("time");
   1082 if (time) {
   1083    time.innerHTML = (new Date().getTime() - ReflectionTests.start)/1000;
   1084 }
   1085 
   1086 if (unimplemented.length) {
   1087    var p = document.createElement("p");
   1088    p.textContent = "(Note: missing tests for types " + unimplemented.join(", ") + ".)";
   1089    document.body.appendChild(p);
   1090 }