Sync web-platform-tests up to revision a5b95cb31914507088a4eda16f7674bbc6f3313f
[WebKit-https.git] / LayoutTests / imported / w3c / web-platform-tests / html / dom / reflection.js
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 // Used in initializing typeMap
91 var binaryString = "\x00\x01\x02\x03\x04\x05\x06\x07 "
92     + "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f "
93     + "\x10\x11\x12\x13\x14\x15\x16\x17 "
94     + "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f ";
95 var maxInt = 2147483647;
96 var minInt = -2147483648;
97 var maxUnsigned = 4294967295;
98
99 /**
100  * Array containing the tests and other information for each type of reflected
101  * attribute.  Meaning of keys:
102  *
103  *   "jsType": What typeof idlObj[idlName] is supposed to be.
104  *   "defaultVal": The default value to be returned if the attribute is not
105  *       present and no default is specifically set for this attribute.
106  *   "domTests": What values to test with setAttribute().
107  *   "domExpected": What values to expect with IDL get after setAttribute().
108  *       Defaults to the same as domTests.
109  *   "idlTests": What values to test with IDL set.  Defaults to domTests.
110  *   "idlDomExpected": What to expect from getAttribute() after IDL set.
111  *       Defaults to idlTests.
112  *   "idlIdlExpected": What to expect from IDL get after IDL set.  Defaults to
113  *       idlDomExpected.
114  *
115  * Note that all tests/expected values are only baselines, and can be expanded
116  * with additional tests hardcoded into the function for particular types if
117  * necessary. For example, a special codepath is used for enums, and for
118  * IDL setters which throw an exception. null means "defaultVal" is the
119  * expected value. Expected DOM values are cast to strings by adding "".
120  *
121  * TODO: Test strings that aren't valid UTF-16.  Desired behavior is not clear
122  * here at the time of writing, see
123  * http://www.w3.org/Bugs/Public/show_bug.cgi?id=12100
124  *
125  * TODO: Test deleting an IDL attribute, and maybe doing other fun stuff to it.
126  *
127  * TODO: Test IDL sets of integer types to out-of-range or other weird values.
128  * WebIDL says to wrap, but I'm not sure offhand if that's what we want.
129  *
130  * TODO: tokenlist, settable tokenlist, limited
131  */
132
133
134 ReflectionTests.typeMap = {
135     /**
136      * "If a reflecting IDL attribute is a DOMString but doesn't fall into any
137      * of the above categories, then the getting and setting must be done in a
138      * transparent, case-preserving manner."
139      *
140      * The data object passed to reflects() can contain an optional key
141      * treatNullAsEmptyString, whose value is ignored.  If it does contain the
142      * key, null will be cast to "" instead of "null", per WebIDL
143      * [TreatNullAs=EmptyString].
144      */
145     "string": {
146         "jsType": "string",
147         "defaultVal": "",
148         "domTests": ["", " " + binaryString + " foo ", undefined, 7, 1.5, true,
149                      false, {"test": 6}, NaN, +Infinity, -Infinity, "\0", null,
150                      {"toString":function(){return "test-toString";}},
151                      {"valueOf":function(){return "test-valueOf";}, toString:null}
152                 ]
153     },
154     /**
155      * "If a reflecting IDL attribute is a USVString attribute whose content
156      * attribute is defined to contain a URL, then on getting, if the content
157      * attribute is absent, the IDL attribute must return the empty string.
158      * Otherwise, the IDL attribute must parse the value of the content
159      * attribute relative to the element's node document and if that is
160      * successful, return the resulting URL string. If parsing fails, then the
161      * value of the content attribute must be returned instead, converted to a
162      * USVString. On setting, the content attribute must be set to the specified
163      * new value."
164      *
165      * Also HTMLHyperLinkElementUtils href, used by a.href and area.href
166      */
167     "url": {
168         "jsType": "string",
169         "defaultVal": "",
170         "domTests": ["", " foo ", "http://site.example/",
171                      "//site.example/path???@#l", binaryString, undefined, 7, 1.5, true,
172                      false, {"test": 6}, NaN, +Infinity, -Infinity, "\0", null,
173                      {"toString":function(){return "test-toString";}},
174                      {"valueOf":function(){return "test-valueOf";}, toString:null}],
175         "domExpected": ReflectionTests.resolveUrl,
176         "idlIdlExpected": ReflectionTests.resolveUrl
177     },
178     /**
179      * "If a reflecting IDL attribute is a DOMString whose content attribute is
180      * an enumerated attribute, and the IDL attribute is limited to only known
181      * values, then, on getting, the IDL attribute must return the conforming
182      * value associated with the state the attribute is in (in its canonical
183      * case), or the empty string if the attribute is in a state that has no
184      * associated keyword value; and on setting, if the new value is an ASCII
185      * case-insensitive match for one of the keywords given for that attribute,
186      * then the content attribute must be set to the conforming value
187      * associated with the state that the attribute would be in if set to the
188      * given new value, otherwise, if the new value is the empty string, then
189      * the content attribute must be removed, otherwise, the content attribute
190      * must be set to the given new value."
191      *
192      * "Some attributes are defined as taking one of a finite set of keywords.
193      * Such attributes are called enumerated attributes. The keywords are each
194      * defined to map to a particular state (several keywords might map to the
195      * same state, in which case some of the keywords are synonyms of each
196      * other; additionally, some of the keywords can be said to be
197      * non-conforming, and are only in the specification for historical
198      * reasons). In addition, two default states can be given. The first is the
199      * invalid value default, the second is the missing value default.
200      *
201      * . . .
202      *
203      * When the attribute is specified, if its value is an ASCII
204      * case-insensitive match for one of the given keywords then that keyword's
205      * state is the state that the attribute represents. If the attribute value
206      * matches none of the given keywords, but the attribute has an invalid
207      * value default, then the attribute represents that state. Otherwise, if
208      * the attribute value matches none of the keywords but there is a missing
209      * value default state defined, then that is the state represented by the
210      * attribute.  Otherwise, there is no default, and invalid values must be
211      * ignored.
212      *
213      * When the attribute is not specified, if there is a missing value default
214      * state defined, then that is the state represented by the (missing)
215      * attribute. Otherwise, the absence of the attribute means that there is
216      * no state represented."
217      *
218      * This is only used for enums that are limited to known values, not other
219      * enums (those are treated as generic strings by the spec).  The data
220      * object passed to reflects() can contain these keys:
221      *
222      *   "defaultVal": missing value default (defaults to "")
223      *   "invalidVal": invalid value default (defaults to defaultVal)
224      *   "keywords": array of keywords as given by the spec (required)
225      *   "nonCanon": dictionary mapping non-canonical values to their
226      *     canonical equivalents (defaults to {})
227      *   "isNullable": Indicates if attribute is nullable (defaults to false)
228      *
229      * Tests are mostly hardcoded into reflects(), since they depend on the
230      * keywords.  All expected values are computed in reflects() using a helper
231      * function.
232      */
233     "enum": {
234         "jsType": "string",
235         "defaultVal": "",
236         "domTests": ["", " " + binaryString + " foo ", undefined, 7, 1.5, true,
237                  false, {"test": 6}, NaN, +Infinity, -Infinity, "\0", null,
238                  {"toString":function(){return "test-toString";}},
239                  {"valueOf":function(){return "test-valueOf";}, toString:null}]
240     },
241     /**
242      * "If a reflecting IDL attribute is a boolean attribute, then on getting
243      * the IDL attribute must return true if the content attribute is set, and
244      * false if it is absent. On setting, the content attribute must be removed
245      * if the IDL attribute is set to false, and must be set to the empty
246      * string if the IDL attribute is set to true. (This corresponds to the
247      * rules for boolean content attributes.)"
248      */
249     "boolean": {
250         "jsType": "boolean",
251         "defaultVal": false,
252         "domTests": ["", " foo ", undefined, null, 7, 1.5, true, false,
253                      {"test": 6}, NaN, +Infinity, -Infinity, "\0",
254                      {"toString":function(){return "test-toString";}},
255                      {"valueOf":function(){return "test-valueOf";}, toString:null}],
256         "domExpected": function(val) {
257             return true;
258         }
259     },
260     /**
261      * "If a reflecting IDL attribute is a signed integer type (long) then, on
262      * getting, the content attribute must be parsed according to the rules for
263      * parsing signed integers, and if that is successful, and the value is in
264      * the range of the IDL attribute's type, the resulting value must be
265      * returned. If, on the other hand, it fails or returns an out of range
266      * value, or if the attribute is absent, then the default value must be
267      * returned instead, or 0 if there is no default value. On setting, the
268      * given value must be converted to the shortest possible string
269      * representing the number as a valid integer and then that string must be
270      * used as the new content attribute value."
271      */
272     "long": {
273         "jsType": "number",
274         "defaultVal": 0,
275         "domTests": [-36, -1, 0, 1, maxInt, minInt, maxInt + 1, minInt - 1,
276                      maxUnsigned, maxUnsigned + 1, "", "-1", "-0", "0", "1",
277                      " " + binaryString + " foo ",
278                      // Test various different whitespace. Only 20, 9, A, C,
279                      // and D are whitespace.
280                      "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
281                      "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
282                      "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
283                      "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7",
284                      "\u30007",
285                      undefined, 1.5, true, false, {"test": 6}, NaN, +Infinity,
286                      -Infinity, "\0",
287                      {toString:function() {return 2;}, valueOf: null},
288                      {valueOf:function() {return 3;}}],
289         "domExpected": function(val) {
290             var parsed = ReflectionTests.parseInt(String(val));
291             if (parsed === false || parsed > maxInt || parsed < minInt) {
292                 return null;
293             }
294             return parsed;
295         },
296         "idlTests":       [-36, -1, 0, 1, 2147483647, -2147483648],
297         "idlDomExpected": [-36, -1, 0, 1, 2147483647, -2147483648]
298     },
299     /**
300      * "If a reflecting IDL attribute is a signed integer type (long) that is
301      * limited to only non-negative numbers then, on getting, the content
302      * attribute must be parsed according to the rules for parsing non-negative
303      * integers, and if that is successful, and the value is in the range of
304      * the IDL attribute's type, the resulting value must be returned. If, on
305      * the other hand, it fails or returns an out of range value, or if the
306      * attribute is absent, the default value must be returned instead, or −1
307      * if there is no default value. On setting, if the value is negative, the
308      * user agent must fire an INDEX_SIZE_ERR exception. Otherwise, the given
309      * value must be converted to the shortest possible string representing the
310      * number as a valid non-negative integer and then that string must be used
311      * as the new content attribute value."
312      */
313     "limited long": {
314         "jsType": "number",
315         "defaultVal": -1,
316         "domTests": [minInt - 1, minInt, -36, -1, -0, 0, 1, maxInt, maxInt + 1,
317                      maxUnsigned, maxUnsigned + 1, "", "-1", "-0", "0", "1",
318                      " " + binaryString + " foo ",
319                      "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
320                      "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
321                      "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
322                      "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7",
323                      "\u30007",
324                      undefined, 1.5, true, false, {"test": 6}, NaN, +Infinity,
325                      -Infinity, "\0",
326                      {toString:function() {return 2;}, valueOf: null},
327                      {valueOf:function() {return 3;}}],
328         "domExpected": function(val) {
329             var parsed = ReflectionTests.parseNonneg(String(val));
330             if (parsed === false || parsed > maxInt || parsed < minInt) {
331                 return null;
332             }
333             return parsed;
334         },
335         "idlTests":       [minInt, -36,  -1, 0, 1, maxInt],
336         "idlDomExpected": [null/*exception*/, null/*exception*/, null/*exception*/, 0, 1, maxInt]
337     },
338     /**
339      * "If a reflecting IDL attribute is an unsigned integer type (unsigned
340      * long) then, on getting, the content attribute must be parsed according
341      * to the rules for parsing non-negative integers, and if that is
342      * successful, and the value is in the range 0 to 2147483647 inclusive, the
343      * resulting value must be returned. If, on the other hand, it fails or
344      * returns an out of range value, or if the attribute is absent, the
345      * default value must be returned instead, or 0 if there is no default
346      * value. On setting, the given value must be converted to the shortest
347      * possible string representing the number as a valid non-negative integer
348      * and then that string must be used as the new content attribute value."
349      */
350     "unsigned long": {
351         "jsType": "number",
352         "defaultVal": 0,
353         "domTests": [minInt - 1, minInt, -36,  -1,   0, 1, 257, maxInt,
354                      maxInt + 1, maxUnsigned, maxUnsigned + 1, "", "-1", "-0", "0", "1",
355                      "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
356                      "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
357                      "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
358                      "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7",
359                      "\u30007",
360                      " " + binaryString + " foo ", undefined, 1.5, true, false,
361                      {"test": 6}, NaN, +Infinity, -Infinity, "\0",
362                      {toString:function() {return 2;}, valueOf: null},
363                      {valueOf:function() {return 3;}}],
364         "domExpected": function(val) {
365             var parsed = ReflectionTests.parseNonneg(String(val));
366             // Note maxInt, not maxUnsigned.
367             if (parsed === false || parsed < 0 || parsed > maxInt) {
368                 return null;
369             }
370             return parsed;
371         },
372         "idlTests": [0, 1, 257, maxInt, "-0", maxInt + 1, maxUnsigned],
373         "idlIdlExpected": [0, 1, 257, maxInt, 0, null, null],
374         "idlDomExpected": [0, 1, 257, maxInt, 0, null, null],
375     },
376     /**
377      * "If a reflecting IDL attribute is an unsigned integer type (unsigned
378      * long) that is limited to only non-negative numbers greater than zero,
379      * then the behavior is similar to the previous case, but zero is not
380      * allowed. On getting, the content attribute must first be parsed
381      * according to the rules for parsing non-negative integers, and if that is
382      * successful, and the value is in the range 1 to 2147483647 inclusive, the
383      * resulting value must be returned. If, on the other hand, it fails or
384      * returns an out of range value, or if the attribute is absent, the
385      * default value must be returned instead, or 1 if there is no default
386      * value. On setting, if the value is zero, the user agent must fire an
387      * INDEX_SIZE_ERR exception. Otherwise, the given value must be converted
388      * to the shortest possible string representing the number as a valid
389      * non-negative integer and then that string must be used as the new
390      * content attribute value."
391      */
392     "limited unsigned long": {
393         "jsType": "number",
394         "defaultVal": 1,
395         "domTests": [minInt - 1, minInt, -36,  -1,   0,    1, maxInt,
396                      maxInt + 1, maxUnsigned, maxUnsigned + 1, "", "-1", "-0", "0", "1",
397                      "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
398                      "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
399                      "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
400                      "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7",
401                      "\u30007",
402                      " " + binaryString + " foo ", undefined, 1.5, true, false,
403                      {"test": 6}, NaN, +Infinity, -Infinity, "\0",
404                      {toString:function() {return 2;}, valueOf: null},
405                      {valueOf:function() {return 3;}}],
406         "domExpected": function(val) {
407             var parsed = ReflectionTests.parseNonneg(String(val));
408             // Note maxInt, not maxUnsigned.
409             if (parsed === false || parsed < 1 || parsed > maxInt) {
410                 return null;
411             }
412             return parsed;
413         },
414         "idlTests":       [0, 1, maxInt, maxInt + 1, maxUnsigned],
415         "idlDomExpected": [null/*exception*/, 1, maxInt, null, null]
416     },
417     /**
418      * "If a reflecting IDL attribute has an unsigned integer type (unsigned
419      * long) that is limited to only non-negative numbers greater than zero
420      * with fallback, then the behaviour is similar to the previous case, but
421      * disallowed values are converted to the default value.  On getting, the
422      * content attribute must first be parsed according to the rules for
423      * parsing non-negative integers, and if that is successful, and the value
424      * is in the range 1 to 2147483647 inclusive, the resulting value must be
425      * returned.  If, on the other hand, it fails or returns an out of range
426      * value, or if the attribute is absent, the default value must be returned
427      * instead.  On setting, first, if the new value is in the range 1 to
428      * 2147483647, then let n be the new value, otherwise let n be the default
429      * value; then, n must be converted to the shortest possible string
430      * representing the number as a valid non-negative integer and that string
431      * must be used as the new content attribute value."
432      */
433     "limited unsigned long with fallback": {
434         "jsType": "number",
435             "domTests": [minInt - 1, minInt, -36,  -1,   0,    1, maxInt,
436                          maxInt + 1, maxUnsigned, maxUnsigned + 1, "", "-1", "-0", "0", "1",
437                          "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
438                          "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
439                          "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
440                          "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7",
441                          "\u30007",
442                          " " + binaryString + " foo ", undefined, 1.5, true, false,
443                          {"test": 6}, NaN, +Infinity, -Infinity, "\0",
444                          {toString:function() {return 2;}, valueOf: null},
445                          {valueOf:function() {return 3;}}],
446             "domExpected": function(val) {
447                 var parsed = ReflectionTests.parseNonneg(String(val));
448                 // Note maxInt, not maxUnsigned.
449                 if (parsed === false || parsed < 1 || parsed > maxInt) {
450                     return null;
451                 }
452                 return parsed;
453             },
454             "idlTests":       [0, 1, maxInt, maxInt + 1, maxUnsigned],
455             "idlDomExpected": [null, 1, maxInt, null, null]
456     },
457     /**
458      * "If a reflecting IDL attribute is a floating point number type (double),
459      * then, on getting, the content attribute must be parsed according to the
460      * rules for parsing floating point number values, and if that is
461      * successful, the resulting value must be returned. If, on the other hand,
462      * it fails, or if the attribute is absent, the default value must be
463      * returned instead, or 0.0 if there is no default value. On setting, the
464      * given value must be converted to the best representation of the number
465      * as a floating point number and then that string must be used as the new
466      * content attribute value."
467      *
468      * TODO: Check this:
469      *
470      * "Except where otherwise specified, if an IDL attribute that is a
471      * floating point number type (double) is assigned an Infinity or
472      * Not-a-Number (NaN) value, a NOT_SUPPORTED_ERR exception must be raised."
473      *
474      * TODO: Implement the actual algorithm so we can run lots more tests.  For
475      * now we're stuck with manually setting up expected values.  Of course,
476      * a lot of care has to be taken in checking equality for floats . . .
477      * maybe we should have some tolerance for comparing them.  I'm not even
478      * sure whether setting the content attribute to 0 should return 0.0 or
479      * -0.0 (the former, I hope).
480      */
481     "double": {
482         "jsType": "number",
483         "defaultVal": 0.0,
484         "domTests": [minInt - 1, minInt, -36, -1, 0, 1, maxInt,
485             maxInt + 1, maxUnsigned, maxUnsigned + 1, "",
486             "\u00097", "\u000B7", "\u000C7", "\u00207", "\u00A07", "\uFEFF7",
487             "\u000A7", "\u000D7", "\u20287", "\u20297", "\u16807", "\u180E7",
488             "\u20007", "\u20017", "\u20027", "\u20037", "\u20047", "\u20057",
489             "\u20067", "\u20077", "\u20087", "\u20097", "\u200A7", "\u202F7",
490             "\u30007",
491             " " + binaryString + " foo ", undefined, 1.5, true, false,
492             {"test": 6}, NaN, +Infinity, -Infinity, "\0",
493             {toString:function() {return 2;}, valueOf: null},
494             {valueOf:function() {return 3;}}],
495         "domExpected": [minInt - 1, minInt, -36, -1, 0, 1, maxInt,
496                         maxInt + 1, maxUnsigned, maxUnsigned + 1, null,
497                         // Leading whitespace tests
498                         7, null, 7, 7, null, null,
499                         7, 7, null, null, null, null,
500                         null, null, null, null, null, null,
501                         null, null, null, null, null, null,
502                         null,
503                         // End leading whitespace tests
504                         null, null, 1.5, null, null,
505                         null, null, null, null, null,
506                         2, 3],
507         // I checked that ES ToString is well-defined for all of these (I
508         // think).  Yes, String(-0) == "0".
509         "idlTests":       [ -10000000000,   -1,  -0,   0,   1,   10000000000],
510         "idlDomExpected": ["-10000000000", "-1", "0", "0", "1", "10000000000"],
511         "idlIdlExpected": [ -10000000000,   -1,  -0,   0,   1,   10000000000]
512     }
513 };
514
515 for (var type in ReflectionTests.typeMap) {
516     var props = ReflectionTests.typeMap[type];
517     var cast = window[props.jsType[0].toUpperCase() + props.jsType.slice(1)];
518     if (props.domExpected === undefined) {
519         props.domExpected = props.domTests.map(cast);
520     } else if (typeof props.domExpected == "function") {
521         props.domExpected = props.domTests.map(props.domExpected);
522     }
523     if (props.idlTests === undefined) {
524         props.idlTests = props.domTests;
525     }
526     if (props.idlDomExpected === undefined) {
527         props.idlDomExpected = props.idlTests.map(cast);
528     } else if (typeof props.idlDomExpected == "function") {
529         props.idlDomExpected = props.idlTests.map(props.idlDomExpected);
530     }
531     if (props.idlIdlExpected === undefined) {
532         props.idlIdlExpected = props.idlDomExpected;
533     } else if (typeof props.idlIdlExpected == "function") {
534         props.idlIdlExpected = props.idlTests.map(props.idlIdlExpected);
535     }
536 }
537
538 /**
539  * Tests that the JavaScript attribute named idlName on the object idlObj
540  * reflects the DOM attribute named domName on domObj.  The data argument is an
541  * object that must contain at least one key, "type", which contains the
542  * expected type of the IDL attribute ("string", "enum", etc.).  The "comment"
543  * key will add a parenthesized comment in the type info if there's a test
544  * failure, to indicate that there's something special about the element you're
545  * testing (like it has an attribute set to some value).  Other keys in the
546  * data object are type-specific, e.g., "defaultVal" for numeric types.  If the
547  * data object is a string, it's converted to {"type": data}.  If idlObj is a
548  * string, we set idlObj = domObj = document.createElement(idlObj).
549  */
550 ReflectionTests.reflects = function(data, idlName, idlObj, domName, domObj) {
551     // Do some setup first so that getTypeDescription() works in testWrapper()
552     if (typeof data == "string") {
553         data = {type: data};
554     }
555     if (domName === undefined) {
556         domName = idlName;
557     }
558     if (typeof idlObj == "string") {
559         idlObj = document.createElement(idlObj);
560     }
561     if (domObj === undefined) {
562         domObj = idlObj;
563     }
564
565     // Note: probably a hack?  This kind of assumes that the variables here
566     // won't change over the course of the tests, which is wrong, but it's
567     // probably safe enough.  Just don't read stuff that will change.
568     ReflectionHarness.currentTestInfo = {data: data, idlName: idlName, idlObj: idlObj, domName: domName, domObj: domObj};
569
570     // If we don't recognize the type, testing is impossible.
571     if (this.typeMap[data.type] === undefined) {
572         if (unimplemented.indexOf(data.type) == -1) {
573             unimplemented.push(data.type);
574         }
575         return;
576     }
577
578     var typeInfo = this.typeMap[data.type];
579
580     if (typeof data.isNullable == "undefined") {
581         data.isNullable = false;
582     }
583
584     // Test that typeof idlObj[idlName] is correct.  If not, further tests are
585     // probably pointless, so bail out if we're not running conformance tests.
586     var expectedType = data.isNullable && data.defaultVal === null ? "object"
587                                                                    : typeInfo.jsType;
588     ReflectionHarness.test(function() {
589         ReflectionHarness.assertEquals(typeof idlObj[idlName], expectedType);
590     }, "typeof IDL attribute");
591
592     if (!ReflectionHarness.conformanceTesting &&
593         typeof idlObj[idlName] !== expectedType) {
594         return;
595     }
596
597     // Test default
598     var defaultVal = data.defaultVal;
599     if (defaultVal === undefined) {
600         defaultVal = typeInfo.defaultVal;
601     }
602     if (defaultVal !== null || data.isNullable) {
603         ReflectionHarness.test(function() {
604             ReflectionHarness.assertEquals(idlObj[idlName], defaultVal);
605         }, "IDL get with DOM attribute unset");
606     }
607
608     var domTests = typeInfo.domTests.slice(0);
609     var domExpected = typeInfo.domExpected.map(function(val) { return val === null ? defaultVal : val; });
610     var idlTests = typeInfo.idlTests.slice(0);
611     var idlDomExpected = typeInfo.idlDomExpected.map(function(val) { return val === null ? defaultVal : val; });
612     var idlIdlExpected = typeInfo.idlIdlExpected.map(function(val) { return val === null ? defaultVal : val; });
613     switch (data.type) {
614         // Extra tests and other special-casing
615         case "boolean":
616         domTests.push(domName);
617         domExpected.push(true);
618         break;
619
620         case "enum":
621         // Whee, enum is complicated.
622         if (typeof data.invalidVal == "undefined") {
623             data.invalidVal = defaultVal;
624         }
625         if (typeof data.nonCanon == "undefined") {
626             data.nonCanon = {};
627         }
628         for (var i = 0; i < data.keywords.length; i++) {
629             if (data.keywords[i] != "") {
630                 domTests.push(data.keywords[i], "x" + data.keywords[i], data.keywords[i] + "\0");
631                 idlTests.push(data.keywords[i], "x" + data.keywords[i], data.keywords[i] + "\0");
632             }
633
634             if (data.keywords[i].length > 1) {
635                 domTests.push(data.keywords[i].slice(1));
636                 idlTests.push(data.keywords[i].slice(1));
637             }
638
639             if (data.keywords[i] != data.keywords[i].toLowerCase()) {
640                 domTests.push(data.keywords[i].toLowerCase());
641                 idlTests.push(data.keywords[i].toLowerCase());
642             }
643             if (data.keywords[i] != data.keywords[i].toUpperCase()) {
644                 domTests.push(data.keywords[i].toUpperCase());
645                 idlTests.push(data.keywords[i].toUpperCase());
646             }
647         }
648
649         // Per spec, the expected DOM values are the same as the value we set
650         // it to.
651         if (!data.isNullable) {
652             idlDomExpected = idlTests.slice(0);
653         } else {
654             idlDomExpected = [];
655             for (var i = 0; i < idlTests.length; i++) {
656                 idlDomExpected.push((idlTests[i] === null || idlTests[i] === undefined) ? null : idlTests[i]);
657             }
658         }
659
660         // Now we have the fun of calculating what the expected IDL values are.
661         domExpected = [];
662         idlIdlExpected = [];
663         for (var i = 0; i < domTests.length; i++) {
664             domExpected.push(this.enumExpected(data.keywords, data.nonCanon, data.invalidVal, domTests[i]));
665         }
666         for (var i = 0; i < idlTests.length; i++) {
667             if (data.isNullable && (idlTests[i] === null || idlTests[i] === undefined)) {
668                 idlIdlExpected.push(null);
669             } else {
670                 idlIdlExpected.push(this.enumExpected(data.keywords, data.nonCanon, data.invalidVal, idlTests[i]));
671             }
672         }
673         break;
674
675         case "string":
676         if ("treatNullAsEmptyString" in data) {
677             for (var i = 0; i < idlTests.length; i++) {
678                 if (idlTests[i] === null) {
679                     idlDomExpected[i] = idlIdlExpected[i] = "";
680                 }
681             }
682         }
683         break;
684     }
685     if (domObj.tagName.toLowerCase() == "canvas" && (domName == "width" || domName == "height")) {
686         // Opera tries to allocate a canvas with the given width and height, so
687         // it OOMs when given excessive sizes.  This is permissible under the
688         // hardware-limitations clause, so cut out those checks.  TODO: Must be
689         // a way to make this more succinct.
690         domTests = domTests.filter(function(element, index, array) { return domExpected[index] < 1000; });
691         domExpected = domExpected.filter(function(element, index, array) { return element < 1000; });
692         idlTests = idlTests.filter(function(element, index, array) { return idlIdlExpected[index] < 1000; });
693         idlDomExpected = idlDomExpected.filter(function(element, index, array) { return idlIdlExpected[index] < 1000; });
694         idlIdlExpected = idlIdlExpected.filter(function(element, index, array) { return idlIdlExpected[index] < 1000; });
695     }
696
697     if (!data.customGetter) {
698         for (var i = 0; i < domTests.length; i++) {
699             if (domExpected[i] === null && !data.isNullable) {
700                 // If you follow all the complicated logic here, you'll find that
701                 // this will only happen if there's no expected value at all (like
702                 // for tabIndex, where the default is too complicated).  So skip
703                 // the test.
704                 continue;
705             }
706             ReflectionHarness.test(function() {
707                 domObj.setAttribute(domName, domTests[i]);
708                 ReflectionHarness.assertEquals(domObj.getAttribute(domName),
709                     String(domTests[i]), "getAttribute()");
710                 ReflectionHarness.assertEquals(idlObj[idlName], domExpected[i],
711                     "IDL get");
712             }, "setAttribute() to " + ReflectionHarness.stringRep(domTests[i]));
713         }
714     }
715
716     for (var i = 0; i < idlTests.length; i++) {
717         ReflectionHarness.test(function() {
718             if ((data.type == "limited long" && idlTests[i] < 0) ||
719                 (data.type == "limited unsigned long" && idlTests[i] == 0)) {
720                 ReflectionHarness.assertThrows("IndexSizeError", function() {
721                     idlObj[idlName] = idlTests[i];
722                 });
723             } else {
724                 idlObj[idlName] = idlTests[i];
725                 if (data.type == "boolean") {
726                     // Special case yay
727                     ReflectionHarness.assertEquals(domObj.hasAttribute(domName),
728                                                    Boolean(idlTests[i]), "hasAttribute()");
729                 } else if (idlDomExpected[i] !== null || data.isNullable) {
730                     var expected = idlDomExpected[i] + "";
731                     if (data.isNullable && idlDomExpected[i] === null) {
732                         expected = null;
733                     }
734                     ReflectionHarness.assertEquals(domObj.getAttribute(domName), expected,
735                                                    "getAttribute()");
736                 }
737                 if (idlIdlExpected[i] !== null || data.isNullable) {
738                     ReflectionHarness.assertEquals(idlObj[idlName], idlIdlExpected[i], "IDL get");
739                 }
740             }
741         }, "IDL set to " + ReflectionHarness.stringRep(idlTests[i]));
742     }
743 };
744
745 /**
746  * If we have an enumerated attribute limited to the array of values in
747  * keywords, with nonCanon being a map of non-canonical values to their
748  * canonical equivalents, and invalidVal being the invalid value default (or ""
749  * for none), then what would we expect from an IDL get if the content
750  * attribute is equal to contentVal?
751  */
752 ReflectionTests.enumExpected = function(keywords, nonCanon, invalidVal, contentVal) {
753     var ret = invalidVal;
754     for (var i = 0; i < keywords.length; i++) {
755         if (String(contentVal).toLowerCase() == keywords[i].toLowerCase()) {
756             ret = keywords[i];
757             break;
758         }
759     }
760     if (typeof nonCanon[ret] != "undefined") {
761         return nonCanon[ret];
762     }
763     return ret;
764 };
765
766 /**
767  * Now we have the data structures that tell us which elements have which
768  * attributes.
769  *
770  * The elements object (which must have been defined in earlier files) is a map
771  * from element name to an object whose keys are IDL attribute names and whose
772  * values are types.  A type is of the same format as
773  * ReflectionTests.reflects() accepts, except that there's an extra optional
774  * domAttrName key that gets passed as the fourth argument to reflects() if
775  * it's provided.  (TODO: drop the fourth and fifth reflects() arguments and
776  * make it take them from the dictionary instead?)
777  */
778
779 // Now we actually run all the tests.
780 var unimplemented = [];
781 for (var element in elements) {
782     ReflectionTests.reflects("string", "title", element);
783     ReflectionTests.reflects("string", "lang", element);
784     ReflectionTests.reflects({type: "enum", keywords: ["ltr", "rtl", "auto"]}, "dir", element);
785     ReflectionTests.reflects("string", "className", element, "class");
786     ReflectionTests.reflects("tokenlist", "classList", element, "class");
787     ReflectionTests.reflects("boolean", "hidden", element);
788     ReflectionTests.reflects("string", "accessKey", element);
789     // Don't try to test the defaultVal -- it should be either 0 or -1, but the
790     // rules are complicated, and a lot of them are SHOULDs.
791     ReflectionTests.reflects({type: "long", defaultVal: null}, "tabIndex", element);
792     // TODO: classList, contextMenu, itemProp, itemRef
793
794     for (var idlAttrName in elements[element]) {
795         var type = elements[element][idlAttrName];
796         ReflectionTests.reflects(type, idlAttrName, element,
797             typeof type == "object" && "domAttrName" in type ? type.domAttrName : idlAttrName);
798     }
799 }
800
801 for (var i = 0; i < extraTests.length; i++) {
802     extraTests[i]();
803 }
804
805 var time = document.getElementById("time");
806 if (time) {
807     time.innerHTML = (new Date().getTime() - ReflectionTests.start)/1000;
808 }
809
810 if (unimplemented.length) {
811     var p = document.createElement("p");
812     p.textContent = "(Note: missing tests for types " + unimplemented.join(", ") + ".)";
813     document.body.appendChild(p);
814 }