idlharness.js (65579B)
1 /* 2 Distributed under both the W3C Test Suite License [1] and the W3C 3 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the 4 policies and contribution forms [3]. 5 6 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license 7 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license 8 [3] http://www.w3.org/2004/10/27-testcases 9 */ 10 11 /* For user documentation see docs/idlharness.md */ 12 13 /** 14 * Notes for people who want to edit this file (not just use it as a library): 15 * 16 * Most of the interesting stuff happens in the derived classes of IdlObject, 17 * especially IdlInterface. The entry point for all IdlObjects is .test(), 18 * which is called by IdlArray.test(). An IdlObject is conceptually just 19 * "thing we want to run tests on", and an IdlArray is an array of IdlObjects 20 * with some additional data thrown in. 21 * 22 * The object model is based on what WebIDLParser.js produces, which is in turn 23 * based on its pegjs grammar. If you want to figure out what properties an 24 * object will have from WebIDLParser.js, the best way is to look at the 25 * grammar: 26 * 27 * https://github.com/darobin/webidl.js/blob/master/lib/grammar.peg 28 * 29 * So for instance: 30 * 31 * // interface definition 32 * interface 33 * = extAttrs:extendedAttributeList? S? "interface" S name:identifier w herit:ifInheritance? w "{" w mem:ifMember* w "}" w ";" w 34 * { return { type: "interface", name: name, inheritance: herit, members: mem, extAttrs: extAttrs }; } 35 * 36 * This means that an "interface" object will have a .type property equal to 37 * the string "interface", a .name property equal to the identifier that the 38 * parser found, an .inheritance property equal to either null or the result of 39 * the "ifInheritance" production found elsewhere in the grammar, and so on. 40 * After each grammatical production is a JavaScript function in curly braces 41 * that gets called with suitable arguments and returns some JavaScript value. 42 * 43 * (Note that the version of WebIDLParser.js we use might sometimes be 44 * out-of-date or forked.) 45 * 46 * The members and methods of the classes defined by this file are all at least 47 * briefly documented, hopefully. 48 */ 49 (function(){ 50 "use strict"; 51 /// Helpers /// 52 function constValue (cnt) { 53 if (cnt.type === "null") return null; 54 if (cnt.type === "NaN") return NaN; 55 if (cnt.type === "Infinity") return cnt.negative ? -Infinity : Infinity; 56 return cnt.value; 57 } 58 59 function minOverloadLength(overloads) { 60 if (!overloads.length) { 61 return 0; 62 } 63 64 return overloads.map(function(attr) { 65 return attr.arguments ? attr.arguments.filter(function(arg) { 66 return !arg.optional && !arg.variadic; 67 }).length : 0; 68 }) 69 .reduce(function(m, n) { return Math.min(m, n); }); 70 } 71 72 /// IdlArray /// 73 // Entry point 74 self.IdlArray = function() 75 //@{ 76 { 77 /** 78 * A map from strings to the corresponding named IdlObject, such as 79 * IdlInterface or IdlException. These are the things that test() will run 80 * tests on. 81 */ 82 this.members = {}; 83 84 /** 85 * A map from strings to arrays of strings. The keys are interface or 86 * exception names, and are expected to also exist as keys in this.members 87 * (otherwise they'll be ignored). This is populated by add_objects() -- 88 * see documentation at the start of the file. The actual tests will be 89 * run by calling this.members[name].test_object(obj) for each obj in 90 * this.objects[name]. obj is a string that will be eval'd to produce a 91 * JavaScript value, which is supposed to be an object implementing the 92 * given IdlObject (interface, exception, etc.). 93 */ 94 this.objects = {}; 95 96 /** 97 * When adding multiple collections of IDLs one at a time, an earlier one 98 * might contain a partial interface or implements statement that depends 99 * on a later one. Save these up and handle them right before we run 100 * tests. 101 * 102 * .partials is simply an array of objects from WebIDLParser.js' 103 * "partialinterface" production. .implements maps strings to arrays of 104 * strings, such that 105 * 106 * A implements B; 107 * A implements C; 108 * D implements E; 109 * 110 * results in { A: ["B", "C"], D: ["E"] }. 111 */ 112 this.partials = []; 113 this["implements"] = {}; 114 }; 115 116 //@} 117 IdlArray.prototype.add_idls = function(raw_idls) 118 //@{ 119 { 120 /** Entry point. See documentation at beginning of file. */ 121 this.internal_add_idls(WebIDL2.parse(raw_idls)); 122 }; 123 124 //@} 125 IdlArray.prototype.add_untested_idls = function(raw_idls) 126 //@{ 127 { 128 /** Entry point. See documentation at beginning of file. */ 129 var parsed_idls = WebIDL2.parse(raw_idls); 130 for (var i = 0; i < parsed_idls.length; i++) 131 { 132 parsed_idls[i].untested = true; 133 if ("members" in parsed_idls[i]) 134 { 135 for (var j = 0; j < parsed_idls[i].members.length; j++) 136 { 137 parsed_idls[i].members[j].untested = true; 138 } 139 } 140 } 141 this.internal_add_idls(parsed_idls); 142 }; 143 144 //@} 145 IdlArray.prototype.internal_add_idls = function(parsed_idls) 146 //@{ 147 { 148 /** 149 * Internal helper called by add_idls() and add_untested_idls(). 150 * parsed_idls is an array of objects that come from WebIDLParser.js's 151 * "definitions" production. The add_untested_idls() entry point 152 * additionally sets an .untested property on each object (and its 153 * .members) so that they'll be skipped by test() -- they'll only be 154 * used for base interfaces of tested interfaces, return types, etc. 155 */ 156 parsed_idls.forEach(parsed_idl => { 157 if (parsed_idl.type == "interface" && parsed_idl.partial) 158 { 159 this.partials.push(parsed_idl); 160 return; 161 } 162 163 if (parsed_idl.type == "implements") 164 { 165 if (!(parsed_idl.target in this["implements"])) 166 { 167 this["implements"][parsed_idl.target] = []; 168 } 169 this["implements"][parsed_idl.target].push(parsed_idl["implements"]); 170 return; 171 } 172 173 parsed_idl.array = this; 174 if (parsed_idl.name in this.members) 175 { 176 throw "Duplicate identifier " + parsed_idl.name; 177 } 178 switch(parsed_idl.type) 179 { 180 case "interface": 181 this.members[parsed_idl.name] = 182 new IdlInterface(parsed_idl, /* is_callback = */ false); 183 break; 184 185 case "dictionary": 186 // Nothing to test, but we need the dictionary info around for type 187 // checks 188 this.members[parsed_idl.name] = new IdlDictionary(parsed_idl); 189 break; 190 191 case "typedef": 192 this.members[parsed_idl.name] = new IdlTypedef(parsed_idl); 193 break; 194 195 case "callback": 196 // TODO 197 console.log("callback not yet supported"); 198 break; 199 200 case "enum": 201 this.members[parsed_idl.name] = new IdlEnum(parsed_idl); 202 break; 203 204 case "callback interface": 205 this.members[parsed_idl.name] = 206 new IdlInterface(parsed_idl, /* is_callback = */ true); 207 break; 208 209 default: 210 throw parsed_idl.name + ": " + parsed_idl.type + " not yet supported"; 211 } 212 }); 213 }; 214 215 //@} 216 IdlArray.prototype.add_objects = function(dict) 217 //@{ 218 { 219 /** Entry point. See documentation at beginning of file. */ 220 for (var k in dict) 221 { 222 if (k in this.objects) 223 { 224 this.objects[k] = this.objects[k].concat(dict[k]); 225 } 226 else 227 { 228 this.objects[k] = dict[k]; 229 } 230 } 231 }; 232 233 //@} 234 IdlArray.prototype.prevent_multiple_testing = function(name) 235 //@{ 236 { 237 /** Entry point. See documentation at beginning of file. */ 238 this.members[name].prevent_multiple_testing = true; 239 }; 240 241 //@} 242 IdlArray.prototype.recursively_get_implements = function(interface_name) 243 //@{ 244 { 245 /** 246 * Helper function for test(). Returns an array of things that implement 247 * interface_name, so if the IDL contains 248 * 249 * A implements B; 250 * B implements C; 251 * B implements D; 252 * 253 * then recursively_get_implements("A") should return ["B", "C", "D"]. 254 */ 255 var ret = this["implements"][interface_name]; 256 if (ret === undefined) 257 { 258 return []; 259 } 260 for (var i = 0; i < this["implements"][interface_name].length; i++) 261 { 262 ret = ret.concat(this.recursively_get_implements(ret[i])); 263 if (ret.indexOf(ret[i]) != ret.lastIndexOf(ret[i])) 264 { 265 throw "Circular implements statements involving " + ret[i]; 266 } 267 } 268 return ret; 269 }; 270 271 //@} 272 IdlArray.prototype.test = function() 273 //@{ 274 { 275 /** Entry point. See documentation at beginning of file. */ 276 277 // First merge in all the partial interfaces and implements statements we 278 // encountered. 279 this.partials.forEach(parsed_idl => { 280 if (!(parsed_idl.name in this.members) 281 || !(this.members[parsed_idl.name] instanceof IdlInterface)) 282 { 283 throw "Partial interface " + parsed_idl.name + " with no original interface"; 284 } 285 if (parsed_idl.extAttrs) 286 { 287 parsed_idl.extAttrs.forEach(extAttr => { 288 this.members[parsed_idl.name].extAttrs.push(extAttr); 289 }); 290 } 291 parsed_idl.members.forEach(member => { 292 this.members[parsed_idl.name].members.push(new IdlInterfaceMember(member)); 293 }); 294 }); 295 this.partials = []; 296 297 for (var lhs in this["implements"]) 298 { 299 this.recursively_get_implements(lhs).forEach(rhs => { 300 var errStr = lhs + " implements " + rhs + ", but "; 301 if (!(lhs in this.members)) throw errStr + lhs + " is undefined."; 302 if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface."; 303 if (!(rhs in this.members)) throw errStr + rhs + " is undefined."; 304 if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface."; 305 this.members[rhs].members.forEach(member => { 306 this.members[lhs].members.push(new IdlInterfaceMember(member)); 307 }); 308 }); 309 } 310 this["implements"] = {}; 311 312 // Now run test() on every member, and test_object() for every object. 313 for (var name in this.members) 314 { 315 this.members[name].test(); 316 if (name in this.objects) 317 { 318 this.objects[name].forEach(str => { 319 this.members[name].test_object(str); 320 }); 321 } 322 } 323 }; 324 325 //@} 326 IdlArray.prototype.assert_type_is = function(value, type) 327 //@{ 328 { 329 /** 330 * Helper function that tests that value is an instance of type according 331 * to the rules of WebIDL. value is any JavaScript value, and type is an 332 * object produced by WebIDLParser.js' "type" production. That production 333 * is fairly elaborate due to the complexity of WebIDL's types, so it's 334 * best to look at the grammar to figure out what properties it might have. 335 */ 336 if (type.idlType == "any") 337 { 338 // No assertions to make 339 return; 340 } 341 342 if (type.nullable && value === null) 343 { 344 // This is fine 345 return; 346 } 347 348 if (type.array) 349 { 350 // TODO: not supported yet 351 return; 352 } 353 354 if (type.sequence) 355 { 356 assert_true(Array.isArray(value), "is not array"); 357 if (!value.length) 358 { 359 // Nothing we can do. 360 return; 361 } 362 this.assert_type_is(value[0], type.idlType.idlType); 363 return; 364 } 365 366 type = type.idlType; 367 368 switch(type) 369 { 370 case "void": 371 assert_equals(value, undefined); 372 return; 373 374 case "boolean": 375 assert_equals(typeof value, "boolean"); 376 return; 377 378 case "byte": 379 assert_equals(typeof value, "number"); 380 assert_equals(value, Math.floor(value), "not an integer"); 381 assert_true(-128 <= value && value <= 127, "byte " + value + " not in range [-128, 127]"); 382 return; 383 384 case "octet": 385 assert_equals(typeof value, "number"); 386 assert_equals(value, Math.floor(value), "not an integer"); 387 assert_true(0 <= value && value <= 255, "octet " + value + " not in range [0, 255]"); 388 return; 389 390 case "short": 391 assert_equals(typeof value, "number"); 392 assert_equals(value, Math.floor(value), "not an integer"); 393 assert_true(-32768 <= value && value <= 32767, "short " + value + " not in range [-32768, 32767]"); 394 return; 395 396 case "unsigned short": 397 assert_equals(typeof value, "number"); 398 assert_equals(value, Math.floor(value), "not an integer"); 399 assert_true(0 <= value && value <= 65535, "unsigned short " + value + " not in range [0, 65535]"); 400 return; 401 402 case "long": 403 assert_equals(typeof value, "number"); 404 assert_equals(value, Math.floor(value), "not an integer"); 405 assert_true(-2147483648 <= value && value <= 2147483647, "long " + value + " not in range [-2147483648, 2147483647]"); 406 return; 407 408 case "unsigned long": 409 assert_equals(typeof value, "number"); 410 assert_equals(value, Math.floor(value), "not an integer"); 411 assert_true(0 <= value && value <= 4294967295, "unsigned long " + value + " not in range [0, 4294967295]"); 412 return; 413 414 case "long long": 415 assert_equals(typeof value, "number"); 416 return; 417 418 case "unsigned long long": 419 case "EpochTimeStamp": 420 case "DOMTimeStamp": 421 assert_equals(typeof value, "number"); 422 assert_true(0 <= value, "unsigned long long is negative"); 423 return; 424 425 case "float": 426 case "double": 427 case "DOMHighResTimeStamp": 428 case "unrestricted float": 429 case "unrestricted double": 430 // TODO: distinguish these cases 431 assert_equals(typeof value, "number"); 432 return; 433 434 case "DOMString": 435 case "ByteString": 436 case "USVString": 437 // TODO: https://github.com/w3c/testharness.js/issues/92 438 assert_equals(typeof value, "string"); 439 return; 440 441 case "object": 442 assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function"); 443 return; 444 } 445 446 if (!(type in this.members)) 447 { 448 throw "Unrecognized type " + type; 449 } 450 451 if (this.members[type] instanceof IdlInterface) 452 { 453 // We don't want to run the full 454 // IdlInterface.prototype.test_instance_of, because that could result 455 // in an infinite loop. TODO: This means we don't have tests for 456 // NoInterfaceObject interfaces, and we also can't test objects that 457 // come from another self. 458 assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function"); 459 if (value instanceof Object 460 && !this.members[type].has_extended_attribute("NoInterfaceObject") 461 && type in self) 462 { 463 assert_true(value instanceof self[type], "not instanceof " + type); 464 } 465 } 466 else if (this.members[type] instanceof IdlEnum) 467 { 468 assert_equals(typeof value, "string"); 469 } 470 else if (this.members[type] instanceof IdlDictionary) 471 { 472 // TODO: Test when we actually have something to test this on 473 } 474 else if (this.members[type] instanceof IdlTypedef) 475 { 476 // TODO: Test when we actually have something to test this on 477 } 478 else 479 { 480 throw "Type " + type + " isn't an interface or dictionary"; 481 } 482 }; 483 //@} 484 485 /// IdlObject /// 486 function IdlObject() {} 487 IdlObject.prototype.test = function() 488 //@{ 489 { 490 /** 491 * By default, this does nothing, so no actual tests are run for IdlObjects 492 * that don't define any (e.g., IdlDictionary at the time of this writing). 493 */ 494 }; 495 496 //@} 497 IdlObject.prototype.has_extended_attribute = function(name) 498 //@{ 499 { 500 /** 501 * This is only meaningful for things that support extended attributes, 502 * such as interfaces, exceptions, and members. 503 */ 504 return this.extAttrs.some(function(o) 505 { 506 return o.name == name; 507 }); 508 }; 509 510 //@} 511 512 /// IdlDictionary /// 513 // Used for IdlArray.prototype.assert_type_is 514 function IdlDictionary(obj) 515 //@{ 516 { 517 /** 518 * obj is an object produced by the WebIDLParser.js "dictionary" 519 * production. 520 */ 521 522 /** Self-explanatory. */ 523 this.name = obj.name; 524 525 /** An array of objects produced by the "dictionaryMember" production. */ 526 this.members = obj.members; 527 528 /** 529 * The name (as a string) of the dictionary type we inherit from, or null 530 * if there is none. 531 */ 532 this.base = obj.inheritance; 533 } 534 535 //@} 536 IdlDictionary.prototype = Object.create(IdlObject.prototype); 537 538 /// IdlInterface /// 539 function IdlInterface(obj, is_callback) { 540 /** 541 * obj is an object produced by the WebIDLParser.js "exception" or 542 * "interface" production, as appropriate. 543 */ 544 545 /** Self-explanatory. */ 546 this.name = obj.name; 547 548 /** A back-reference to our IdlArray. */ 549 this.array = obj.array; 550 551 /** 552 * An indicator of whether we should run tests on the (exception) interface 553 * object and (exception) interface prototype object. Tests on members are 554 * controlled by .untested on each member, not this. 555 */ 556 this.untested = obj.untested; 557 558 /** An array of objects produced by the "ExtAttr" production. */ 559 this.extAttrs = obj.extAttrs; 560 561 /** An array of IdlInterfaceMembers. */ 562 this.members = obj.members.map(function(m){return new IdlInterfaceMember(m); }); 563 if (this.has_extended_attribute("Unforgeable")) { 564 this.members 565 .filter(function(m) { return !m["static"] && (m.type == "attribute" || m.type == "operation"); }) 566 .forEach(function(m) { return m.isUnforgeable = true; }); 567 } 568 569 /** 570 * The name (as a string) of the type we inherit from, or null if there is 571 * none. 572 */ 573 this.base = obj.inheritance; 574 575 this._is_callback = is_callback; 576 } 577 IdlInterface.prototype = Object.create(IdlObject.prototype); 578 IdlInterface.prototype.is_callback = function() 579 //@{ 580 { 581 return this._is_callback; 582 }; 583 //@} 584 585 IdlInterface.prototype.has_constants = function() 586 //@{ 587 { 588 return this.members.some(function(member) { 589 return member.type === "const"; 590 }); 591 }; 592 //@} 593 594 IdlInterface.prototype.is_global = function() 595 //@{ 596 { 597 return this.extAttrs.some(function(attribute) { 598 return attribute.name === "Global" || 599 attribute.name === "PrimaryGlobal"; 600 }); 601 }; 602 //@} 603 604 IdlInterface.prototype.test = function() 605 //@{ 606 { 607 if (this.has_extended_attribute("NoInterfaceObject")) 608 { 609 // No tests to do without an instance. TODO: We should still be able 610 // to run tests on the prototype object, if we obtain one through some 611 // other means. 612 return; 613 } 614 615 if (!this.untested) 616 { 617 // First test things to do with the exception/interface object and 618 // exception/interface prototype object. 619 this.test_self(); 620 } 621 // Then test things to do with its members (constants, fields, attributes, 622 // operations, . . .). These are run even if .untested is true, because 623 // members might themselves be marked as .untested. This might happen to 624 // interfaces if the interface itself is untested but a partial interface 625 // that extends it is tested -- then the interface itself and its initial 626 // members will be marked as untested, but the members added by the partial 627 // interface are still tested. 628 this.test_members(); 629 }; 630 //@} 631 632 IdlInterface.prototype.test_self = function() 633 //@{ 634 { 635 test(() => { 636 // This function tests WebIDL as of 2015-01-13. 637 // TODO: Consider [Exposed]. 638 639 // "For every interface that is exposed in a given ECMAScript global 640 // environment and: 641 // * is a callback interface that has constants declared on it, or 642 // * is a non-callback interface that is not declared with the 643 // [NoInterfaceObject] extended attribute, 644 // a corresponding property MUST exist on the ECMAScript global object. 645 // The name of the property is the identifier of the interface, and its 646 // value is an object called the interface object. 647 // The property has the attributes { [[Writable]]: true, 648 // [[Enumerable]]: false, [[Configurable]]: true }." 649 if (this.is_callback() && !this.has_constants()) { 650 return; 651 } 652 653 // TODO: Should we test here that the property is actually writable 654 // etc., or trust getOwnPropertyDescriptor? 655 assert_own_property(self, this.name, 656 "self does not have own property " + format_value(this.name)); 657 var desc = Object.getOwnPropertyDescriptor(self, this.name); 658 assert_false("get" in desc, "self's property " + format_value(this.name) + " has getter"); 659 assert_false("set" in desc, "self's property " + format_value(this.name) + " has setter"); 660 assert_true(desc.writable, "self's property " + format_value(this.name) + " is not writable"); 661 assert_false(desc.enumerable, "self's property " + format_value(this.name) + " is enumerable"); 662 assert_true(desc.configurable, "self's property " + format_value(this.name) + " is not configurable"); 663 664 if (this.is_callback()) { 665 // "The internal [[Prototype]] property of an interface object for 666 // a callback interface MUST be the Object.prototype object." 667 assert_equals(Object.getPrototypeOf(self[this.name]), Object.prototype, 668 "prototype of self's property " + format_value(this.name) + " is not Object.prototype"); 669 670 return; 671 } 672 673 // "The interface object for a given non-callback interface is a 674 // function object." 675 // "If an object is defined to be a function object, then it has 676 // characteristics as follows:" 677 678 // Its [[Prototype]] internal property is otherwise specified (see 679 // below). 680 681 // "* Its [[Get]] internal property is set as described in ECMA-262 682 // section 9.1.8." 683 // Not much to test for this. 684 685 // "* Its [[Construct]] internal property is set as described in 686 // ECMA-262 section 19.2.2.3." 687 // Tested below if no constructor is defined. TODO: test constructors 688 // if defined. 689 690 // "* Its @@hasInstance property is set as described in ECMA-262 691 // section 19.2.3.8, unless otherwise specified." 692 // TODO 693 694 // ES6 (rev 30) 19.1.3.6: 695 // "Else, if O has a [[Call]] internal method, then let builtinTag be 696 // "Function"." 697 assert_class_string(self[this.name], "Function", "class string of " + this.name); 698 699 // "The [[Prototype]] internal property of an interface object for a 700 // non-callback interface is determined as follows:" 701 var prototype = Object.getPrototypeOf(self[this.name]); 702 if (this.base) { 703 // "* If the interface inherits from some other interface, the 704 // value of [[Prototype]] is the interface object for that other 705 // interface." 706 var has_interface_object = 707 !this.array 708 .members[this.base] 709 .has_extended_attribute("NoInterfaceObject"); 710 if (has_interface_object) { 711 assert_own_property(self, this.base, 712 'should inherit from ' + this.base + 713 ', but self has no such property'); 714 assert_equals(prototype, self[this.base], 715 'prototype of ' + this.name + ' is not ' + 716 this.base); 717 } 718 } else { 719 // "If the interface doesn't inherit from any other interface, the 720 // value of [[Prototype]] is %FunctionPrototype% ([ECMA-262], 721 // section 6.1.7.4)." 722 assert_equals(prototype, Function.prototype, 723 "prototype of self's property " + format_value(this.name) + " is not Function.prototype"); 724 } 725 726 if (!this.has_extended_attribute("Constructor")) { 727 // "The internal [[Call]] method of the interface object behaves as 728 // follows . . . 729 // 730 // "If I was not declared with a [Constructor] extended attribute, 731 // then throw a TypeError." 732 assert_throws(new TypeError(), () => { 733 self[this.name](); 734 }, "interface object didn't throw TypeError when called as a function"); 735 assert_throws(new TypeError(), () => { 736 new self[this.name](); 737 }, "interface object didn't throw TypeError when called as a constructor"); 738 } 739 }, this.name + " interface: existence and properties of interface object"); 740 741 if (!this.is_callback()) { 742 test(() => { 743 // This function tests WebIDL as of 2014-10-25. 744 // https://heycam.github.io/webidl/#es-interface-call 745 746 assert_own_property(self, this.name, 747 "self does not have own property " + format_value(this.name)); 748 749 // "Interface objects for non-callback interfaces MUST have a 750 // property named “length” with attributes { [[Writable]]: false, 751 // [[Enumerable]]: false, [[Configurable]]: true } whose value is 752 // a Number." 753 assert_own_property(self[this.name], "length"); 754 var desc = Object.getOwnPropertyDescriptor(self[this.name], "length"); 755 assert_false("get" in desc, this.name + ".length has getter"); 756 assert_false("set" in desc, this.name + ".length has setter"); 757 assert_false(desc.writable, this.name + ".length is writable"); 758 assert_false(desc.enumerable, this.name + ".length is enumerable"); 759 assert_true(desc.configurable, this.name + ".length is not configurable"); 760 761 var constructors = this.extAttrs 762 .filter(function(attr) { return attr.name == "Constructor"; }); 763 var expected_length = minOverloadLength(constructors); 764 assert_equals(self[this.name].length, expected_length, "wrong value for " + this.name + ".length"); 765 }, this.name + " interface object length"); 766 } 767 768 // TODO: Test named constructors if I find any interfaces that have them. 769 770 test(() => { 771 // This function tests WebIDL as of 2015-01-21. 772 // https://heycam.github.io/webidl/#interface-object 773 774 if (this.is_callback() && !this.has_constants()) { 775 return; 776 } 777 778 assert_own_property(self, this.name, 779 "self does not have own property " + format_value(this.name)); 780 781 if (this.is_callback()) { 782 assert_false("prototype" in self[this.name], 783 this.name + ' should not have a "prototype" property'); 784 return; 785 } 786 787 // "An interface object for a non-callback interface must have a 788 // property named “prototype” with attributes { [[Writable]]: false, 789 // [[Enumerable]]: false, [[Configurable]]: false } whose value is an 790 // object called the interface prototype object. This object has 791 // properties that correspond to the regular attributes and regular 792 // operations defined on the interface, and is described in more detail 793 // in section 4.5.4 below." 794 assert_own_property(self[this.name], "prototype", 795 'interface "' + this.name + '" does not have own property "prototype"'); 796 var desc = Object.getOwnPropertyDescriptor(self[this.name], "prototype"); 797 assert_false("get" in desc, this.name + ".prototype has getter"); 798 assert_false("set" in desc, this.name + ".prototype has setter"); 799 assert_false(desc.writable, this.name + ".prototype is writable"); 800 assert_false(desc.enumerable, this.name + ".prototype is enumerable"); 801 assert_false(desc.configurable, this.name + ".prototype is configurable"); 802 803 // Next, test that the [[Prototype]] of the interface prototype object 804 // is correct. (This is made somewhat difficult by the existence of 805 // [NoInterfaceObject].) 806 // TODO: Aryeh thinks there's at least other place in this file where 807 // we try to figure out if an interface prototype object is 808 // correct. Consolidate that code. 809 810 // "The interface prototype object for a given interface A must have an 811 // internal [[Prototype]] property whose value is returned from the 812 // following steps: 813 // "If A is declared with the [Global] or [PrimaryGlobal] extended 814 // attribute, and A supports named properties, then return the named 815 // properties object for A, as defined in section 4.5.5 below. 816 // "Otherwise, if A is declared to inherit from another interface, then 817 // return the interface prototype object for the inherited interface. 818 // "Otherwise, if A is declared with the [ArrayClass] extended 819 // attribute, then return %ArrayPrototype% ([ECMA-262], section 820 // 6.1.7.4). 821 // "Otherwise, return %ObjectPrototype% ([ECMA-262], section 6.1.7.4). 822 // ([ECMA-262], section 15.2.4). 823 if (this.name === "Window") { 824 assert_class_string(Object.getPrototypeOf(self[this.name].prototype), 825 'WindowProperties', 826 'Class name for prototype of Window' + 827 '.prototype is not "WindowProperties"'); 828 } else { 829 var inherit_interface, inherit_interface_has_interface_object; 830 if (this.base) { 831 inherit_interface = this.base; 832 inherit_interface_has_interface_object = 833 !this.array 834 .members[inherit_interface] 835 .has_extended_attribute("NoInterfaceObject"); 836 } else if (this.has_extended_attribute('ArrayClass')) { 837 inherit_interface = 'Array'; 838 inherit_interface_has_interface_object = true; 839 } else { 840 inherit_interface = 'Object'; 841 inherit_interface_has_interface_object = true; 842 } 843 if (inherit_interface_has_interface_object) { 844 assert_own_property(self, inherit_interface, 845 'should inherit from ' + inherit_interface + ', but self has no such property'); 846 assert_own_property(self[inherit_interface], 'prototype', 847 'should inherit from ' + inherit_interface + ', but that object has no "prototype" property'); 848 assert_equals(Object.getPrototypeOf(self[this.name].prototype), 849 self[inherit_interface].prototype, 850 'prototype of ' + this.name + '.prototype is not ' + inherit_interface + '.prototype'); 851 } else { 852 // We can't test that we get the correct object, because this is the 853 // only way to get our hands on it. We only test that its class 854 // string, at least, is correct. 855 assert_class_string(Object.getPrototypeOf(self[this.name].prototype), 856 inherit_interface + 'Prototype', 857 'Class name for prototype of ' + this.name + 858 '.prototype is not "' + inherit_interface + 'Prototype"'); 859 } 860 } 861 862 // "The class string of an interface prototype object is the 863 // concatenation of the interface’s identifier and the string 864 // “Prototype”." 865 assert_class_string(self[this.name].prototype, this.name + "Prototype", 866 "class string of " + this.name + ".prototype"); 867 // String() should end up calling {}.toString if nothing defines a 868 // stringifier. 869 if (!this.has_stringifier()) { 870 assert_equals(String(self[this.name].prototype), "[object " + this.name + "Prototype]", 871 "String(" + this.name + ".prototype)"); 872 } 873 }, this.name + " interface: existence and properties of interface prototype object"); 874 875 test(() => { 876 if (this.is_callback() && !this.has_constants()) { 877 return; 878 } 879 880 assert_own_property(self, this.name, 881 "self does not have own property " + format_value(this.name)); 882 883 if (this.is_callback()) { 884 assert_false("prototype" in self[this.name], 885 this.name + ' should not have a "prototype" property'); 886 return; 887 } 888 889 assert_own_property(self[this.name], "prototype", 890 'interface "' + this.name + '" does not have own property "prototype"'); 891 892 // "If the [NoInterfaceObject] extended attribute was not specified on 893 // the interface, then the interface prototype object must also have a 894 // property named “constructor” with attributes { [[Writable]]: true, 895 // [[Enumerable]]: false, [[Configurable]]: true } whose value is a 896 // reference to the interface object for the interface." 897 assert_own_property(self[this.name].prototype, "constructor", 898 this.name + '.prototype does not have own property "constructor"'); 899 var desc = Object.getOwnPropertyDescriptor(self[this.name].prototype, "constructor"); 900 assert_false("get" in desc, this.name + ".prototype.constructor has getter"); 901 assert_false("set" in desc, this.name + ".prototype.constructor has setter"); 902 assert_true(desc.writable, this.name + ".prototype.constructor is not writable"); 903 assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable"); 904 assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable"); 905 assert_equals(self[this.name].prototype.constructor, self[this.name], 906 this.name + '.prototype.constructor is not the same object as ' + this.name); 907 }, this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property'); 908 }; 909 910 //@} 911 IdlInterface.prototype.test_member_const = function(member) 912 //@{ 913 { 914 test(() => { 915 if (this.is_callback() && !this.has_constants()) { 916 return; 917 } 918 919 assert_own_property(self, this.name, 920 "self does not have own property " + format_value(this.name)); 921 922 // "For each constant defined on an interface A, there must be 923 // a corresponding property on the interface object, if it 924 // exists." 925 assert_own_property(self[this.name], member.name); 926 // "The value of the property is that which is obtained by 927 // converting the constant’s IDL value to an ECMAScript 928 // value." 929 assert_equals(self[this.name][member.name], constValue(member.value), 930 "property has wrong value"); 931 // "The property has attributes { [[Writable]]: false, 932 // [[Enumerable]]: true, [[Configurable]]: false }." 933 var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name); 934 assert_false("get" in desc, "property has getter"); 935 assert_false("set" in desc, "property has setter"); 936 assert_false(desc.writable, "property is writable"); 937 assert_true(desc.enumerable, "property is not enumerable"); 938 assert_false(desc.configurable, "property is configurable"); 939 }, this.name + " interface: constant " + member.name + " on interface object"); 940 // "In addition, a property with the same characteristics must 941 // exist on the interface prototype object." 942 test(() => { 943 if (this.is_callback() && !this.has_constants()) { 944 return; 945 } 946 947 assert_own_property(self, this.name, 948 "self does not have own property " + format_value(this.name)); 949 950 if (this.is_callback()) { 951 assert_false("prototype" in self[this.name], 952 this.name + ' should not have a "prototype" property'); 953 return; 954 } 955 956 assert_own_property(self[this.name], "prototype", 957 'interface "' + this.name + '" does not have own property "prototype"'); 958 959 assert_own_property(self[this.name].prototype, member.name); 960 assert_equals(self[this.name].prototype[member.name], constValue(member.value), 961 "property has wrong value"); 962 var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name); 963 assert_false("get" in desc, "property has getter"); 964 assert_false("set" in desc, "property has setter"); 965 assert_false(desc.writable, "property is writable"); 966 assert_true(desc.enumerable, "property is not enumerable"); 967 assert_false(desc.configurable, "property is configurable"); 968 }, this.name + " interface: constant " + member.name + " on interface prototype object"); 969 }; 970 971 972 //@} 973 IdlInterface.prototype.test_member_attribute = function(member) 974 //@{ 975 { 976 test(() => { 977 if (this.is_callback() && !this.has_constants()) { 978 return; 979 } 980 981 assert_own_property(self, this.name, 982 "self does not have own property " + format_value(this.name)); 983 assert_own_property(self[this.name], "prototype", 984 'interface "' + this.name + '" does not have own property "prototype"'); 985 986 if (member["static"]) { 987 assert_own_property(self[this.name], member.name, 988 "The interface object must have a property " + 989 format_value(member.name)); 990 } else if (this.is_global()) { 991 assert_own_property(self, member.name, 992 "The global object must have a property " + 993 format_value(member.name)); 994 assert_false(member.name in self[this.name].prototype, 995 "The prototype object must not have a property " + 996 format_value(member.name)); 997 998 // Try/catch around the get here, since it can legitimately throw. 999 // If it does, we obviously can't check for equality with direct 1000 // invocation of the getter. 1001 var gotValue; 1002 var propVal; 1003 try { 1004 propVal = self[member.name]; 1005 gotValue = true; 1006 } catch (e) { 1007 gotValue = false; 1008 } 1009 if (gotValue) { 1010 var getter = Object.getOwnPropertyDescriptor(self, member.name).get; 1011 assert_equals(typeof(getter), "function", 1012 format_value(member.name) + " must have a getter"); 1013 assert_equals(propVal, getter.call(undefined), 1014 "Gets on a global should not require an explicit this"); 1015 } 1016 this.do_interface_attribute_asserts(self, member); 1017 } else { 1018 assert_true(member.name in self[this.name].prototype, 1019 "The prototype object must have a property " + 1020 format_value(member.name)); 1021 1022 if (!member.has_extended_attribute("LenientThis")) { 1023 assert_throws(new TypeError(), () => { 1024 self[this.name].prototype[member.name]; 1025 }, "getting property on prototype object must throw TypeError"); 1026 } else { 1027 assert_equals(self[this.name].prototype[member.name], undefined, 1028 "getting property on prototype object must return undefined"); 1029 } 1030 this.do_interface_attribute_asserts(self[this.name].prototype, member); 1031 } 1032 }, this.name + " interface: attribute " + member.name); 1033 }; 1034 1035 //@} 1036 IdlInterface.prototype.test_member_operation = function(member) 1037 //@{ 1038 { 1039 test(() => { 1040 if (this.is_callback() && !this.has_constants()) { 1041 return; 1042 } 1043 1044 assert_own_property(self, this.name, 1045 "self does not have own property " + format_value(this.name)); 1046 1047 if (this.is_callback()) { 1048 assert_false("prototype" in self[this.name], 1049 this.name + ' should not have a "prototype" property'); 1050 return; 1051 } 1052 1053 assert_own_property(self[this.name], "prototype", 1054 'interface "' + this.name + '" does not have own property "prototype"'); 1055 1056 // "For each unique identifier of an operation defined on the 1057 // interface, there must be a corresponding property on the 1058 // interface prototype object (if it is a regular operation) or 1059 // the interface object (if it is a static operation), unless 1060 // the effective overload set for that identifier and operation 1061 // and with an argument count of 0 (for the ECMAScript language 1062 // binding) has no entries." 1063 // 1064 var memberHolderObject; 1065 if (member["static"]) { 1066 assert_own_property(self[this.name], member.name, 1067 "interface object missing static operation"); 1068 memberHolderObject = self[this.name]; 1069 } else if (this.is_global()) { 1070 assert_own_property(self, member.name, 1071 "global object missing non-static operation"); 1072 memberHolderObject = self; 1073 } else { 1074 assert_own_property(self[this.name].prototype, member.name, 1075 "interface prototype object missing non-static operation"); 1076 memberHolderObject = self[this.name].prototype; 1077 } 1078 1079 this.do_member_operation_asserts(memberHolderObject, member); 1080 }, this.name + " interface: operation " + member.name + 1081 "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) + 1082 ")"); 1083 }; 1084 1085 //@} 1086 IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject, member) 1087 //@{ 1088 { 1089 var operationUnforgeable = member.isUnforgeable; 1090 var desc = Object.getOwnPropertyDescriptor(memberHolderObject, member.name); 1091 // "The property has attributes { [[Writable]]: B, 1092 // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the 1093 // operation is unforgeable on the interface, and true otherwise". 1094 assert_false("get" in desc, "property has getter"); 1095 assert_false("set" in desc, "property has setter"); 1096 assert_equals(desc.writable, !operationUnforgeable, 1097 "property should be writable if and only if not unforgeable"); 1098 assert_true(desc.enumerable, "property is not enumerable"); 1099 assert_equals(desc.configurable, !operationUnforgeable, 1100 "property should be configurable if and only if not unforgeable"); 1101 // "The value of the property is a Function object whose 1102 // behavior is as follows . . ." 1103 assert_equals(typeof memberHolderObject[member.name], "function", 1104 "property must be a function"); 1105 // "The value of the Function object’s “length” property is 1106 // a Number determined as follows: 1107 // ". . . 1108 // "Return the length of the shortest argument list of the 1109 // entries in S." 1110 assert_equals(memberHolderObject[member.name].length, 1111 minOverloadLength(this.members.filter(function(m) { 1112 return m.type == "operation" && m.name == member.name; 1113 })), 1114 "property has wrong .length"); 1115 1116 // Make some suitable arguments 1117 var args = member.arguments.map(function(arg) { 1118 return create_suitable_object(arg.idlType); 1119 }); 1120 1121 // "Let O be a value determined as follows: 1122 // ". . . 1123 // "Otherwise, throw a TypeError." 1124 // This should be hit if the operation is not static, there is 1125 // no [ImplicitThis] attribute, and the this value is null. 1126 // 1127 // TODO: We currently ignore the [ImplicitThis] case. Except we manually 1128 // check for globals, since otherwise we'll invoke window.close(). And we 1129 // have to skip this test for anything that on the proto chain of "self", 1130 // since that does in fact have implicit-this behavior. 1131 if (!member["static"]) { 1132 if (!this.is_global() && 1133 memberHolderObject[member.name] != self[member.name]) 1134 { 1135 assert_throws(new TypeError(), function() { 1136 memberHolderObject[member.name].apply(null, args); 1137 }, "calling operation with this = null didn't throw TypeError"); 1138 } 1139 1140 // ". . . If O is not null and is also not a platform object 1141 // that implements interface I, throw a TypeError." 1142 // 1143 // TODO: Test a platform object that implements some other 1144 // interface. (Have to be sure to get inheritance right.) 1145 assert_throws(new TypeError(), function() { 1146 memberHolderObject[member.name].apply({}, args); 1147 }, "calling operation with this = {} didn't throw TypeError"); 1148 } 1149 } 1150 1151 //@} 1152 IdlInterface.prototype.test_member_stringifier = function(member) 1153 //@{ 1154 { 1155 test(() => { 1156 if (this.is_callback() && !this.has_constants()) { 1157 return; 1158 } 1159 1160 assert_own_property(self, this.name, 1161 "self does not have own property " + format_value(this.name)); 1162 1163 if (this.is_callback()) { 1164 assert_false("prototype" in self[this.name], 1165 this.name + ' should not have a "prototype" property'); 1166 return; 1167 } 1168 1169 assert_own_property(self[this.name], "prototype", 1170 'interface "' + this.name + '" does not have own property "prototype"'); 1171 1172 // ". . . the property exists on the interface prototype object." 1173 var interfacePrototypeObject = self[this.name].prototype; 1174 assert_own_property(self[this.name].prototype, "toString", 1175 "interface prototype object missing non-static operation"); 1176 1177 var stringifierUnforgeable = member.isUnforgeable; 1178 var desc = Object.getOwnPropertyDescriptor(interfacePrototypeObject, "toString"); 1179 // "The property has attributes { [[Writable]]: B, 1180 // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the 1181 // stringifier is unforgeable on the interface, and true otherwise." 1182 assert_false("get" in desc, "property has getter"); 1183 assert_false("set" in desc, "property has setter"); 1184 assert_equals(desc.writable, !stringifierUnforgeable, 1185 "property should be writable if and only if not unforgeable"); 1186 assert_true(desc.enumerable, "property is not enumerable"); 1187 assert_equals(desc.configurable, !stringifierUnforgeable, 1188 "property should be configurable if and only if not unforgeable"); 1189 // "The value of the property is a Function object, which behaves as 1190 // follows . . ." 1191 assert_equals(typeof interfacePrototypeObject.toString, "function", 1192 "property must be a function"); 1193 // "The value of the Function object’s “length” property is the Number 1194 // value 0." 1195 assert_equals(interfacePrototypeObject.toString.length, 0, 1196 "property has wrong .length"); 1197 1198 // "Let O be the result of calling ToObject on the this value." 1199 assert_throws(new TypeError(), function() { 1200 self[this.name].prototype.toString.apply(null, []); 1201 }, "calling stringifier with this = null didn't throw TypeError"); 1202 1203 // "If O is not an object that implements the interface on which the 1204 // stringifier was declared, then throw a TypeError." 1205 // 1206 // TODO: Test a platform object that implements some other 1207 // interface. (Have to be sure to get inheritance right.) 1208 assert_throws(new TypeError(), function() { 1209 self[this.name].prototype.toString.apply({}, []); 1210 }, "calling stringifier with this = {} didn't throw TypeError"); 1211 }, this.name + " interface: stringifier"); 1212 }; 1213 1214 //@} 1215 IdlInterface.prototype.test_members = function() 1216 //@{ 1217 { 1218 for (var i = 0; i < this.members.length; i++) 1219 { 1220 var member = this.members[i]; 1221 if (member.untested) { 1222 continue; 1223 } 1224 1225 switch (member.type) { 1226 case "const": 1227 this.test_member_const(member); 1228 break; 1229 1230 case "attribute": 1231 // For unforgeable attributes, we do the checks in 1232 // test_interface_of instead. 1233 if (!member.isUnforgeable) 1234 { 1235 this.test_member_attribute(member); 1236 } 1237 break; 1238 1239 case "operation": 1240 // TODO: Need to correctly handle multiple operations with the same 1241 // identifier. 1242 // For unforgeable operations, we do the checks in 1243 // test_interface_of instead. 1244 if (member.name) { 1245 if (!member.isUnforgeable) 1246 { 1247 this.test_member_operation(member); 1248 } 1249 } else if (member.stringifier) { 1250 this.test_member_stringifier(member); 1251 } 1252 break; 1253 1254 default: 1255 // TODO: check more member types. 1256 break; 1257 } 1258 } 1259 }; 1260 1261 //@} 1262 IdlInterface.prototype.test_object = function(desc) 1263 //@{ 1264 { 1265 var obj, exception = null; 1266 try 1267 { 1268 obj = eval(desc); 1269 } 1270 catch(e) 1271 { 1272 exception = e; 1273 } 1274 1275 // TODO: WebIDLParser doesn't currently support named legacycallers, so I'm 1276 // not sure what those would look like in the AST 1277 var expected_typeof = this.members.some(function(member) 1278 { 1279 return member.legacycaller 1280 || ("idlType" in member && member.idlType.legacycaller) 1281 || ("idlType" in member && typeof member.idlType == "object" 1282 && "idlType" in member.idlType && member.idlType.idlType == "legacycaller"); 1283 }) ? "function" : "object"; 1284 1285 this.test_primary_interface_of(desc, obj, exception, expected_typeof); 1286 var current_interface = this; 1287 while (current_interface) 1288 { 1289 if (!(current_interface.name in this.array.members)) 1290 { 1291 throw "Interface " + current_interface.name + " not found (inherited by " + this.name + ")"; 1292 } 1293 if (current_interface.prevent_multiple_testing && current_interface.already_tested) 1294 { 1295 return; 1296 } 1297 current_interface.test_interface_of(desc, obj, exception, expected_typeof); 1298 current_interface = this.array.members[current_interface.base]; 1299 } 1300 }; 1301 1302 //@} 1303 IdlInterface.prototype.test_primary_interface_of = function(desc, obj, exception, expected_typeof) 1304 //@{ 1305 { 1306 // We can't easily test that its prototype is correct if there's no 1307 // interface object, or the object is from a different global environment 1308 // (not instanceof Object). TODO: test in this case that its prototype at 1309 // least looks correct, even if we can't test that it's actually correct. 1310 if (!this.has_extended_attribute("NoInterfaceObject") 1311 && (typeof obj != expected_typeof || obj instanceof Object)) 1312 { 1313 test(() => { 1314 assert_equals(exception, null, "Unexpected exception when evaluating object"); 1315 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 1316 assert_own_property(self, this.name, 1317 "self does not have own property " + format_value(this.name)); 1318 assert_own_property(self[this.name], "prototype", 1319 'interface "' + this.name + '" does not have own property "prototype"'); 1320 1321 // "The value of the internal [[Prototype]] property of the 1322 // platform object is the interface prototype object of the primary 1323 // interface from the platform object’s associated global 1324 // environment." 1325 assert_equals(Object.getPrototypeOf(obj), 1326 self[this.name].prototype, 1327 desc + "'s prototype is not " + this.name + ".prototype"); 1328 }, this.name + " must be primary interface of " + desc); 1329 } 1330 1331 // "The class string of a platform object that implements one or more 1332 // interfaces must be the identifier of the primary interface of the 1333 // platform object." 1334 test(() => { 1335 assert_equals(exception, null, "Unexpected exception when evaluating object"); 1336 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 1337 assert_class_string(obj, this.name, "class string of " + desc); 1338 if (!this.has_stringifier()) 1339 { 1340 assert_equals(String(obj), "[object " + this.name + "]", "String(" + desc + ")"); 1341 } 1342 }, "Stringification of " + desc); 1343 }; 1344 1345 //@} 1346 IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expected_typeof) 1347 //@{ 1348 { 1349 // TODO: Indexed and named properties, more checks on interface members 1350 this.already_tested = true; 1351 1352 for (var i = 0; i < this.members.length; i++) 1353 { 1354 var member = this.members[i]; 1355 if (member.type == "attribute" && member.isUnforgeable) 1356 { 1357 test(() => { 1358 assert_equals(exception, null, "Unexpected exception when evaluating object"); 1359 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 1360 this.do_interface_attribute_asserts(obj, member); 1361 }, this.name + " interface: " + desc + ' must have own property "' + member.name + '"'); 1362 } 1363 else if (member.type == "operation" && 1364 member.name && 1365 member.isUnforgeable) 1366 { 1367 test(() => { 1368 assert_equals(exception, null, "Unexpected exception when evaluating object"); 1369 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 1370 assert_own_property(obj, member.name, 1371 "Doesn't have the unforgeable operation property"); 1372 this.do_member_operation_asserts(obj, member); 1373 }, this.name + " interface: " + desc + ' must have own property "' + member.name + '"'); 1374 } 1375 else if ((member.type == "const" 1376 || member.type == "attribute" 1377 || member.type == "operation") 1378 && member.name) 1379 { 1380 test(() => { 1381 assert_equals(exception, null, "Unexpected exception when evaluating object"); 1382 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 1383 if (!member["static"]) { 1384 if (!this.is_global()) { 1385 assert_inherits(obj, member.name); 1386 } else { 1387 assert_own_property(obj, member.name); 1388 } 1389 1390 if (member.type == "const") 1391 { 1392 assert_equals(obj[member.name], constValue(member.value)); 1393 } 1394 if (member.type == "attribute") 1395 { 1396 // Attributes are accessor properties, so they might 1397 // legitimately throw an exception rather than returning 1398 // anything. 1399 var property, thrown = false; 1400 try 1401 { 1402 property = obj[member.name]; 1403 } 1404 catch (e) 1405 { 1406 thrown = true; 1407 } 1408 if (!thrown) 1409 { 1410 this.array.assert_type_is(property, member.idlType); 1411 } 1412 } 1413 if (member.type == "operation") 1414 { 1415 assert_equals(typeof obj[member.name], "function"); 1416 } 1417 } 1418 }, this.name + " interface: " + desc + ' must inherit property "' + member.name + '" with the proper type (' + i + ')'); 1419 } 1420 // TODO: This is wrong if there are multiple operations with the same 1421 // identifier. 1422 // TODO: Test passing arguments of the wrong type. 1423 if (member.type == "operation" && member.name && member.arguments.length) 1424 { 1425 test(() => { 1426 assert_equals(exception, null, "Unexpected exception when evaluating object"); 1427 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 1428 if (!member["static"]) { 1429 if (!this.is_global() && !member.isUnforgeable) { 1430 assert_inherits(obj, member.name); 1431 } else { 1432 assert_own_property(obj, member.name); 1433 } 1434 } 1435 else 1436 { 1437 assert_false(member.name in obj); 1438 } 1439 1440 var minLength = minOverloadLength(this.members.filter(function(m) { 1441 return m.type == "operation" && m.name == member.name; 1442 })); 1443 var args = []; 1444 for (var i = 0; i < minLength; i++) { 1445 assert_throws(new TypeError(), () => { 1446 obj[member.name].apply(obj, args); 1447 }, "Called with " + i + " arguments"); 1448 1449 args.push(create_suitable_object(member.arguments[i].idlType)); 1450 } 1451 }, this.name + " interface: calling " + member.name + 1452 "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) + 1453 ") on " + desc + " with too few arguments must throw TypeError"); 1454 } 1455 } 1456 }; 1457 1458 //@} 1459 IdlInterface.prototype.has_stringifier = function() 1460 //@{ 1461 { 1462 if (this.members.some(function(member) { return member.stringifier; })) { 1463 return true; 1464 } 1465 if (this.base && 1466 this.array.members[this.base].has_stringifier()) { 1467 return true; 1468 } 1469 return false; 1470 }; 1471 1472 //@} 1473 IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member) 1474 //@{ 1475 { 1476 // This function tests WebIDL as of 2015-01-27. 1477 // TODO: Consider [Exposed]. 1478 1479 // This is called by test_member_attribute() with the prototype as obj if 1480 // it is not a global, and the global otherwise, and by test_interface_of() 1481 // with the object as obj. 1482 1483 // "For each exposed attribute of the interface, whether it was declared on 1484 // the interface itself or one of its consequential interfaces, there MUST 1485 // exist a corresponding property. The characteristics of this property are 1486 // as follows:" 1487 1488 // "The name of the property is the identifier of the attribute." 1489 assert_own_property(obj, member.name); 1490 1491 // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]: 1492 // true, [[Configurable]]: configurable }, where: 1493 // "configurable is false if the attribute was declared with the 1494 // [Unforgeable] extended attribute and true otherwise; 1495 // "G is the attribute getter, defined below; and 1496 // "S is the attribute setter, also defined below." 1497 var desc = Object.getOwnPropertyDescriptor(obj, member.name); 1498 assert_false("value" in desc, 'property descriptor has value but is supposed to be accessor'); 1499 assert_false("writable" in desc, 'property descriptor has "writable" field but is supposed to be accessor'); 1500 assert_true(desc.enumerable, "property is not enumerable"); 1501 if (member.isUnforgeable) 1502 { 1503 assert_false(desc.configurable, "[Unforgeable] property must not be configurable"); 1504 } 1505 else 1506 { 1507 assert_true(desc.configurable, "property must be configurable"); 1508 } 1509 1510 1511 // "The attribute getter is a Function object whose behavior when invoked 1512 // is as follows:" 1513 assert_equals(typeof desc.get, "function", "getter must be Function"); 1514 1515 // "If the attribute is a regular attribute, then:" 1516 if (!member["static"]) { 1517 // "If O is not a platform object that implements I, then: 1518 // "If the attribute was specified with the [LenientThis] extended 1519 // attribute, then return undefined. 1520 // "Otherwise, throw a TypeError." 1521 if (!member.has_extended_attribute("LenientThis")) { 1522 assert_throws(new TypeError(), () => { 1523 desc.get.call({}); 1524 }, "calling getter on wrong object type must throw TypeError"); 1525 } else { 1526 assert_equals(desc.get.call({}), undefined, 1527 "calling getter on wrong object type must return undefined"); 1528 } 1529 } 1530 1531 // "The value of the Function object’s “length” property is the Number 1532 // value 0." 1533 assert_equals(desc.get.length, 0, "getter length must be 0"); 1534 1535 1536 // TODO: Test calling setter on the interface prototype (should throw 1537 // TypeError in most cases). 1538 if (member.readonly 1539 && !member.has_extended_attribute("PutForwards") 1540 && !member.has_extended_attribute("Replaceable")) 1541 { 1542 // "The attribute setter is undefined if the attribute is declared 1543 // readonly and has neither a [PutForwards] nor a [Replaceable] 1544 // extended attribute declared on it." 1545 assert_equals(desc.set, undefined, "setter must be undefined for readonly attributes"); 1546 } 1547 else 1548 { 1549 // "Otherwise, it is a Function object whose behavior when 1550 // invoked is as follows:" 1551 assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes"); 1552 1553 // "If the attribute is a regular attribute, then:" 1554 if (!member["static"]) { 1555 // "If /validThis/ is false and the attribute was not specified 1556 // with the [LenientThis] extended attribute, then throw a 1557 // TypeError." 1558 // "If the attribute is declared with a [Replaceable] extended 1559 // attribute, then: ..." 1560 // "If validThis is false, then return." 1561 if (!member.has_extended_attribute("LenientThis")) { 1562 assert_throws(new TypeError(), () => { 1563 desc.set.call({}); 1564 }, "calling setter on wrong object type must throw TypeError"); 1565 } else { 1566 assert_equals(desc.set.call({}), undefined, 1567 "calling setter on wrong object type must return undefined"); 1568 } 1569 } 1570 1571 // "The value of the Function object’s “length” property is the Number 1572 // value 1." 1573 assert_equals(desc.set.length, 1, "setter length must be 1"); 1574 } 1575 } 1576 //@} 1577 1578 /// IdlInterfaceMember /// 1579 function IdlInterfaceMember(obj) 1580 //@{ 1581 { 1582 /** 1583 * obj is an object produced by the WebIDLParser.js "ifMember" production. 1584 * We just forward all properties to this object without modification, 1585 * except for special extAttrs handling. 1586 */ 1587 for (var k in obj) 1588 { 1589 this[k] = obj[k]; 1590 } 1591 if (!("extAttrs" in this)) 1592 { 1593 this.extAttrs = []; 1594 } 1595 1596 this.isUnforgeable = this.has_extended_attribute("Unforgeable"); 1597 } 1598 1599 //@} 1600 IdlInterfaceMember.prototype = Object.create(IdlObject.prototype); 1601 1602 /// Internal helper functions /// 1603 function create_suitable_object(type) 1604 //@{ 1605 { 1606 /** 1607 * type is an object produced by the WebIDLParser.js "type" production. We 1608 * return a JavaScript value that matches the type, if we can figure out 1609 * how. 1610 */ 1611 if (type.nullable) 1612 { 1613 return null; 1614 } 1615 switch (type.idlType) 1616 { 1617 case "any": 1618 case "boolean": 1619 return true; 1620 1621 case "byte": case "octet": case "short": case "unsigned short": 1622 case "long": case "unsigned long": case "long long": 1623 case "unsigned long long": case "float": case "double": 1624 case "unrestricted float": case "unrestricted double": 1625 return 7; 1626 1627 case "DOMString": 1628 case "ByteString": 1629 case "USVString": 1630 return "foo"; 1631 1632 case "object": 1633 return {a: "b"}; 1634 1635 case "Node": 1636 return document.createTextNode("abc"); 1637 } 1638 return null; 1639 } 1640 //@} 1641 1642 /// IdlEnum /// 1643 // Used for IdlArray.prototype.assert_type_is 1644 function IdlEnum(obj) 1645 //@{ 1646 { 1647 /** 1648 * obj is an object produced by the WebIDLParser.js "dictionary" 1649 * production. 1650 */ 1651 1652 /** Self-explanatory. */ 1653 this.name = obj.name; 1654 1655 /** An array of values produced by the "enum" production. */ 1656 this.values = obj.values; 1657 1658 } 1659 //@} 1660 1661 IdlEnum.prototype = Object.create(IdlObject.prototype); 1662 1663 /// IdlTypedef /// 1664 // Used for IdlArray.prototype.assert_type_is 1665 function IdlTypedef(obj) 1666 //@{ 1667 { 1668 /** 1669 * obj is an object produced by the WebIDLParser.js "typedef" 1670 * production. 1671 */ 1672 1673 /** Self-explanatory. */ 1674 this.name = obj.name; 1675 1676 /** An array of values produced by the "typedef" production. */ 1677 this.values = obj.values; 1678 1679 } 1680 //@} 1681 1682 IdlTypedef.prototype = Object.create(IdlObject.prototype); 1683 1684 }()); 1685 // vim: set expandtab shiftwidth=4 tabstop=4 foldmarker=@{,@} foldmethod=marker: