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