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