| 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 |