idlharness.js (145966B)
1 /* For user documentation see docs/_writing-tests/idlharness.md */ 2 3 /** 4 * Notes for people who want to edit this file (not just use it as a library): 5 * 6 * Most of the interesting stuff happens in the derived classes of IdlObject, 7 * especially IdlInterface. The entry point for all IdlObjects is .test(), 8 * which is called by IdlArray.test(). An IdlObject is conceptually just 9 * "thing we want to run tests on", and an IdlArray is an array of IdlObjects 10 * with some additional data thrown in. 11 * 12 * The object model is based on what WebIDLParser.js produces, which is in turn 13 * based on its pegjs grammar. If you want to figure out what properties an 14 * object will have from WebIDLParser.js, the best way is to look at the 15 * grammar: 16 * 17 * https://github.com/darobin/webidl.js/blob/master/lib/grammar.peg 18 * 19 * So for instance: 20 * 21 * // interface definition 22 * interface 23 * = extAttrs:extendedAttributeList? S? "interface" S name:identifier w herit:ifInheritance? w "{" w mem:ifMember* w "}" w ";" w 24 * { return { type: "interface", name: name, inheritance: herit, members: mem, extAttrs: extAttrs }; } 25 * 26 * This means that an "interface" object will have a .type property equal to 27 * the string "interface", a .name property equal to the identifier that the 28 * parser found, an .inheritance property equal to either null or the result of 29 * the "ifInheritance" production found elsewhere in the grammar, and so on. 30 * After each grammatical production is a JavaScript function in curly braces 31 * that gets called with suitable arguments and returns some JavaScript value. 32 * 33 * (Note that the version of WebIDLParser.js we use might sometimes be 34 * out-of-date or forked.) 35 * 36 * The members and methods of the classes defined by this file are all at least 37 * briefly documented, hopefully. 38 */ 39 (function(){ 40 "use strict"; 41 // Support subsetTestByKey from /common/subset-tests-by-key.js, but make it optional 42 if (!('subsetTestByKey' in self)) { 43 self.subsetTestByKey = function(key, callback, ...args) { 44 return callback(...args); 45 } 46 self.shouldRunSubTest = () => true; 47 } 48 /// Helpers /// 49 function constValue (cnt) 50 { 51 if (cnt.type === "null") return null; 52 if (cnt.type === "NaN") return NaN; 53 if (cnt.type === "Infinity") return cnt.negative ? -Infinity : Infinity; 54 if (cnt.type === "number") return +cnt.value; 55 return cnt.value; 56 } 57 58 function minOverloadLength(overloads) 59 { 60 // "The value of the Function object’s “length” property is 61 // a Number determined as follows: 62 // ". . . 63 // "Return the length of the shortest argument list of the 64 // entries in S." 65 if (!overloads.length) { 66 return 0; 67 } 68 69 return overloads.map(function(attr) { 70 return attr.arguments ? attr.arguments.filter(function(arg) { 71 return !arg.optional && !arg.variadic; 72 }).length : 0; 73 }) 74 .reduce(function(m, n) { return Math.min(m, n); }); 75 } 76 77 // A helper to get the global of a Function object. This is needed to determine 78 // which global exceptions the function throws will come from. 79 function globalOf(func) 80 { 81 try { 82 // Use the fact that .constructor for a Function object is normally the 83 // Function constructor, which can be used to mint a new function in the 84 // right global. 85 return func.constructor("return this;")(); 86 } catch (e) { 87 } 88 // If the above fails, because someone gave us a non-function, or a function 89 // with a weird proto chain or weird .constructor property, just fall back 90 // to 'self'. 91 return self; 92 } 93 94 // https://esdiscuss.org/topic/isconstructor#content-11 95 function isConstructor(o) { 96 try { 97 new (new Proxy(o, {construct: () => ({})})); 98 return true; 99 } catch(e) { 100 return false; 101 } 102 } 103 104 function throwOrReject(a_test, operation, fn, obj, args, message, cb) 105 { 106 if (operation.idlType.generic !== "Promise") { 107 assert_throws_js(globalOf(fn).TypeError, function() { 108 fn.apply(obj, args); 109 }, message); 110 cb(); 111 } else { 112 try { 113 promise_rejects_js(a_test, TypeError, fn.apply(obj, args), message).then(cb, cb); 114 } catch (e){ 115 a_test.step(function() { 116 assert_unreached("Throws \"" + e + "\" instead of rejecting promise"); 117 cb(); 118 }); 119 } 120 } 121 } 122 123 function awaitNCallbacks(n, cb, ctx) 124 { 125 var counter = 0; 126 return function() { 127 counter++; 128 if (counter >= n) { 129 cb(); 130 } 131 }; 132 } 133 134 /// IdlHarnessError /// 135 // Entry point 136 self.IdlHarnessError = function(message) 137 { 138 /** 139 * Message to be printed as the error's toString invocation. 140 */ 141 this.message = message; 142 }; 143 144 IdlHarnessError.prototype = Object.create(Error.prototype); 145 146 IdlHarnessError.prototype.toString = function() 147 { 148 return this.message; 149 }; 150 151 152 /// IdlArray /// 153 // Entry point 154 self.IdlArray = function() 155 { 156 /** 157 * A map from strings to the corresponding named IdlObject, such as 158 * IdlInterface or IdlException. These are the things that test() will run 159 * tests on. 160 */ 161 this.members = {}; 162 163 /** 164 * A map from strings to arrays of strings. The keys are interface or 165 * exception names, and are expected to also exist as keys in this.members 166 * (otherwise they'll be ignored). This is populated by add_objects() -- 167 * see documentation at the start of the file. The actual tests will be 168 * run by calling this.members[name].test_object(obj) for each obj in 169 * this.objects[name]. obj is a string that will be eval'd to produce a 170 * JavaScript value, which is supposed to be an object implementing the 171 * given IdlObject (interface, exception, etc.). 172 */ 173 this.objects = {}; 174 175 /** 176 * When adding multiple collections of IDLs one at a time, an earlier one 177 * might contain a partial interface or includes statement that depends 178 * on a later one. Save these up and handle them right before we run 179 * tests. 180 * 181 * Both this.partials and this.includes will be the objects as parsed by 182 * WebIDLParser.js, not wrapped in IdlInterface or similar. 183 */ 184 this.partials = []; 185 this.includes = []; 186 187 /** 188 * Record of skipped IDL items, in case we later realize that they are a 189 * dependency (to retroactively process them). 190 */ 191 this.skipped = new Map(); 192 }; 193 194 IdlArray.prototype.add_idls = function(raw_idls, options) 195 { 196 /** Entry point. See documentation at beginning of file. */ 197 this.internal_add_idls(WebIDL2.parse(raw_idls), options); 198 }; 199 200 IdlArray.prototype.add_untested_idls = function(raw_idls, options) 201 { 202 /** Entry point. See documentation at beginning of file. */ 203 var parsed_idls = WebIDL2.parse(raw_idls); 204 this.mark_as_untested(parsed_idls); 205 this.internal_add_idls(parsed_idls, options); 206 }; 207 208 IdlArray.prototype.mark_as_untested = function (parsed_idls) 209 { 210 for (var i = 0; i < parsed_idls.length; i++) { 211 parsed_idls[i].untested = true; 212 if ("members" in parsed_idls[i]) { 213 for (var j = 0; j < parsed_idls[i].members.length; j++) { 214 parsed_idls[i].members[j].untested = true; 215 } 216 } 217 } 218 }; 219 220 IdlArray.prototype.is_excluded_by_options = function (name, options) 221 { 222 return options && 223 (options.except && options.except.includes(name) 224 || options.only && !options.only.includes(name)); 225 }; 226 227 IdlArray.prototype.add_dependency_idls = function(raw_idls, options) 228 { 229 return this.internal_add_dependency_idls(WebIDL2.parse(raw_idls), options); 230 }; 231 232 IdlArray.prototype.internal_add_dependency_idls = function(parsed_idls, options) 233 { 234 const new_options = { only: [] } 235 236 const all_deps = new Set(); 237 Object.values(this.members).forEach(v => { 238 if (v.base) { 239 all_deps.add(v.base); 240 } 241 }); 242 // Add both 'A' and 'B' for each 'A includes B' entry. 243 this.includes.forEach(i => { 244 all_deps.add(i.target); 245 all_deps.add(i.includes); 246 }); 247 this.partials.forEach(p => all_deps.add(p.name)); 248 // Add 'TypeOfType' for each "typedef TypeOfType MyType;" entry. 249 Object.entries(this.members).forEach(([k, v]) => { 250 if (v instanceof IdlTypedef) { 251 let defs = v.idlType.union 252 ? v.idlType.idlType.map(t => t.idlType) 253 : [v.idlType.idlType]; 254 defs.forEach(d => all_deps.add(d)); 255 } 256 }); 257 258 // Add the attribute idlTypes of all the nested members of idls. 259 const attrDeps = parsedIdls => { 260 return parsedIdls.reduce((deps, parsed) => { 261 if (parsed.members) { 262 for (const attr of Object.values(parsed.members).filter(m => m.type === 'attribute')) { 263 let attrType = attr.idlType; 264 // Check for generic members (e.g. FrozenArray<MyType>) 265 if (attrType.generic) { 266 deps.add(attrType.generic); 267 attrType = attrType.idlType; 268 } 269 deps.add(attrType.idlType); 270 } 271 } 272 if (parsed.base in this.members) { 273 attrDeps([this.members[parsed.base]]).forEach(dep => deps.add(dep)); 274 } 275 return deps; 276 }, new Set()); 277 }; 278 279 const testedMembers = Object.values(this.members).filter(m => !m.untested && m.members); 280 attrDeps(testedMembers).forEach(dep => all_deps.add(dep)); 281 282 const testedPartials = this.partials.filter(m => !m.untested && m.members); 283 attrDeps(testedPartials).forEach(dep => all_deps.add(dep)); 284 285 286 if (options && options.except && options.only) { 287 throw new IdlHarnessError("The only and except options can't be used together."); 288 } 289 290 const defined_or_untested = name => { 291 // NOTE: Deps are untested, so we're lenient, and skip re-encountered definitions. 292 // e.g. for 'idl' containing A:B, B:C, C:D 293 // array.add_idls(idl, {only: ['A','B']}). 294 // array.add_dependency_idls(idl); 295 // B would be encountered as tested, and encountered as a dep, so we ignore. 296 return name in this.members 297 || this.is_excluded_by_options(name, options); 298 } 299 // Maps name -> [parsed_idl, ...] 300 const process = function(parsed) { 301 var deps = []; 302 if (parsed.name) { 303 deps.push(parsed.name); 304 } else if (parsed.type === "includes") { 305 deps.push(parsed.target); 306 deps.push(parsed.includes); 307 } 308 309 deps = deps.filter(function(name) { 310 if (!name 311 || name === parsed.name && defined_or_untested(name) 312 || !all_deps.has(name)) { 313 // Flag as skipped, if it's not already processed, so we can 314 // come back to it later if we retrospectively call it a dep. 315 if (name && !(name in this.members)) { 316 this.skipped.has(name) 317 ? this.skipped.get(name).push(parsed) 318 : this.skipped.set(name, [parsed]); 319 } 320 return false; 321 } 322 return true; 323 }.bind(this)); 324 325 deps.forEach(function(name) { 326 if (!new_options.only.includes(name)) { 327 new_options.only.push(name); 328 } 329 330 const follow_up = new Set(); 331 for (const dep_type of ["inheritance", "includes"]) { 332 if (parsed[dep_type]) { 333 const inheriting = parsed[dep_type]; 334 const inheritor = parsed.name || parsed.target; 335 const deps = [inheriting]; 336 // For A includes B, we can ignore A, unless B (or some of its 337 // members) is being tested. 338 if (dep_type !== "includes" 339 || inheriting in this.members && !this.members[inheriting].untested 340 || this.partials.some(function(p) { 341 return p.name === inheriting; 342 })) { 343 deps.push(inheritor); 344 } 345 for (const dep of deps) { 346 if (!new_options.only.includes(dep)) { 347 new_options.only.push(dep); 348 } 349 all_deps.add(dep); 350 follow_up.add(dep); 351 } 352 } 353 } 354 355 for (const deferred of follow_up) { 356 if (this.skipped.has(deferred)) { 357 const next = this.skipped.get(deferred); 358 this.skipped.delete(deferred); 359 next.forEach(process); 360 } 361 } 362 }.bind(this)); 363 }.bind(this); 364 365 for (let parsed of parsed_idls) { 366 process(parsed); 367 } 368 369 this.mark_as_untested(parsed_idls); 370 371 if (new_options.only.length) { 372 this.internal_add_idls(parsed_idls, new_options); 373 } 374 } 375 376 IdlArray.prototype.internal_add_idls = function(parsed_idls, options) 377 { 378 /** 379 * Internal helper called by add_idls() and add_untested_idls(). 380 * 381 * parsed_idls is an array of objects that come from WebIDLParser.js's 382 * "definitions" production. The add_untested_idls() entry point 383 * additionally sets an .untested property on each object (and its 384 * .members) so that they'll be skipped by test() -- they'll only be 385 * used for base interfaces of tested interfaces, return types, etc. 386 * 387 * options is a dictionary that can have an only or except member which are 388 * arrays. If only is given then only members, partials and interface 389 * targets listed will be added, and if except is given only those that 390 * aren't listed will be added. Only one of only and except can be used. 391 */ 392 393 if (options && options.only && options.except) 394 { 395 throw new IdlHarnessError("The only and except options can't be used together."); 396 } 397 398 var should_skip = name => { 399 return this.is_excluded_by_options(name, options); 400 } 401 402 parsed_idls.forEach(function(parsed_idl) 403 { 404 var partial_types = [ 405 "interface", 406 "interface mixin", 407 "dictionary", 408 "namespace", 409 ]; 410 if (parsed_idl.partial && partial_types.includes(parsed_idl.type)) 411 { 412 if (should_skip(parsed_idl.name)) 413 { 414 return; 415 } 416 this.partials.push(parsed_idl); 417 return; 418 } 419 420 if (parsed_idl.type == "includes") 421 { 422 if (should_skip(parsed_idl.target)) 423 { 424 return; 425 } 426 this.includes.push(parsed_idl); 427 return; 428 } 429 430 parsed_idl.array = this; 431 if (should_skip(parsed_idl.name)) 432 { 433 return; 434 } 435 if (parsed_idl.name in this.members) 436 { 437 throw new IdlHarnessError("Duplicate identifier " + parsed_idl.name); 438 } 439 440 switch(parsed_idl.type) 441 { 442 case "interface": 443 this.members[parsed_idl.name] = 444 new IdlInterface(parsed_idl, /* is_callback = */ false, /* is_mixin = */ false); 445 break; 446 447 case "interface mixin": 448 this.members[parsed_idl.name] = 449 new IdlInterface(parsed_idl, /* is_callback = */ false, /* is_mixin = */ true); 450 break; 451 452 case "dictionary": 453 // Nothing to test, but we need the dictionary info around for type 454 // checks 455 this.members[parsed_idl.name] = new IdlDictionary(parsed_idl); 456 break; 457 458 case "typedef": 459 this.members[parsed_idl.name] = new IdlTypedef(parsed_idl); 460 break; 461 462 case "callback": 463 this.members[parsed_idl.name] = new IdlCallback(parsed_idl); 464 break; 465 466 case "enum": 467 this.members[parsed_idl.name] = new IdlEnum(parsed_idl); 468 break; 469 470 case "callback interface": 471 this.members[parsed_idl.name] = 472 new IdlInterface(parsed_idl, /* is_callback = */ true, /* is_mixin = */ false); 473 break; 474 475 case "namespace": 476 this.members[parsed_idl.name] = new IdlNamespace(parsed_idl); 477 break; 478 479 default: 480 throw parsed_idl.name + ": " + parsed_idl.type + " not yet supported"; 481 } 482 }.bind(this)); 483 }; 484 485 IdlArray.prototype.add_objects = function(dict) 486 { 487 /** Entry point. See documentation at beginning of file. */ 488 for (var k in dict) 489 { 490 if (k in this.objects) 491 { 492 this.objects[k] = this.objects[k].concat(dict[k]); 493 } 494 else 495 { 496 this.objects[k] = dict[k]; 497 } 498 } 499 }; 500 501 IdlArray.prototype.prevent_multiple_testing = function(name) 502 { 503 /** Entry point. See documentation at beginning of file. */ 504 this.members[name].prevent_multiple_testing = true; 505 }; 506 507 IdlArray.prototype.is_json_type = function(type) 508 { 509 /** 510 * Checks whether type is a JSON type as per 511 * https://webidl.spec.whatwg.org/#dfn-json-types 512 */ 513 514 var idlType = type.idlType; 515 516 if (type.generic == "Promise") { return false; } 517 518 // nullable and annotated types don't need to be handled separately, 519 // as webidl2 doesn't represent them wrapped-up (as they're described 520 // in WebIDL). 521 522 // union and record types 523 if (type.union || type.generic == "record") { 524 return idlType.every(this.is_json_type, this); 525 } 526 527 // sequence types 528 if (type.generic == "sequence" || type.generic == "FrozenArray") { 529 return this.is_json_type(idlType[0]); 530 } 531 532 if (typeof idlType != "string") { throw new Error("Unexpected type " + JSON.stringify(idlType)); } 533 534 switch (idlType) 535 { 536 // Numeric types 537 case "byte": 538 case "octet": 539 case "short": 540 case "unsigned short": 541 case "long": 542 case "unsigned long": 543 case "long long": 544 case "unsigned long long": 545 case "float": 546 case "double": 547 case "unrestricted float": 548 case "unrestricted double": 549 // boolean 550 case "boolean": 551 // string types 552 case "DOMString": 553 case "ByteString": 554 case "USVString": 555 // object type 556 case "object": 557 return true; 558 case "Error": 559 case "DOMException": 560 case "Int8Array": 561 case "Int16Array": 562 case "Int32Array": 563 case "Uint8Array": 564 case "Uint16Array": 565 case "Uint32Array": 566 case "Uint8ClampedArray": 567 case "BigInt64Array": 568 case "BigUint64Array": 569 case "Float16Array": 570 case "Float32Array": 571 case "Float64Array": 572 case "ArrayBuffer": 573 case "DataView": 574 case "any": 575 return false; 576 default: 577 var thing = this.members[idlType]; 578 if (!thing) { throw new Error("Type " + idlType + " not found"); } 579 if (thing instanceof IdlEnum) { return true; } 580 581 if (thing instanceof IdlTypedef) { 582 return this.is_json_type(thing.idlType); 583 } 584 585 // dictionaries where all of their members are JSON types 586 if (thing instanceof IdlDictionary) { 587 const map = new Map(); 588 for (const dict of thing.get_reverse_inheritance_stack()) { 589 for (const m of dict.members) { 590 map.set(m.name, m.idlType); 591 } 592 } 593 return Array.from(map.values()).every(this.is_json_type, this); 594 } 595 596 // interface types that have a toJSON operation declared on themselves or 597 // one of their inherited interfaces. 598 if (thing instanceof IdlInterface) { 599 var base; 600 while (thing) 601 { 602 if (thing.has_to_json_regular_operation()) { return true; } 603 var mixins = this.includes[thing.name]; 604 if (mixins) { 605 mixins = mixins.map(function(id) { 606 var mixin = this.members[id]; 607 if (!mixin) { 608 throw new Error("Interface " + id + " not found (implemented by " + thing.name + ")"); 609 } 610 return mixin; 611 }, this); 612 if (mixins.some(function(m) { return m.has_to_json_regular_operation() } )) { return true; } 613 } 614 if (!thing.base) { return false; } 615 base = this.members[thing.base]; 616 if (!base) { 617 throw new Error("Interface " + thing.base + " not found (inherited by " + thing.name + ")"); 618 } 619 thing = base; 620 } 621 return false; 622 } 623 return false; 624 } 625 }; 626 627 function exposure_set(object, default_set) { 628 var exposed = object.extAttrs && object.extAttrs.filter(a => a.name === "Exposed"); 629 if (exposed && exposed.length > 1) { 630 throw new IdlHarnessError( 631 `Multiple 'Exposed' extended attributes on ${object.name}`); 632 } 633 634 let result = default_set || ["Window"]; 635 if (result && !(result instanceof Set)) { 636 result = new Set(result); 637 } 638 if (exposed && exposed.length) { 639 const { rhs } = exposed[0]; 640 // Could be a list or a string. 641 const set = 642 rhs.type === "*" ? 643 [ "*" ] : 644 rhs.type === "identifier-list" ? 645 rhs.value.map(id => id.value) : 646 [ rhs.value ]; 647 result = new Set(set); 648 } 649 if (result && result.has("*")) { 650 return "*"; 651 } 652 if (result && result.has("Worker")) { 653 result.delete("Worker"); 654 result.add("DedicatedWorker"); 655 result.add("ServiceWorker"); 656 result.add("SharedWorker"); 657 } 658 return result; 659 } 660 661 function exposed_in(globals) { 662 if (globals === "*") { 663 return true; 664 } 665 if ('Window' in self) { 666 return globals.has("Window"); 667 } 668 if ('DedicatedWorkerGlobalScope' in self && 669 self instanceof DedicatedWorkerGlobalScope) { 670 return globals.has("DedicatedWorker"); 671 } 672 if ('SharedWorkerGlobalScope' in self && 673 self instanceof SharedWorkerGlobalScope) { 674 return globals.has("SharedWorker"); 675 } 676 if ('ServiceWorkerGlobalScope' in self && 677 self instanceof ServiceWorkerGlobalScope) { 678 return globals.has("ServiceWorker"); 679 } 680 if (Object.getPrototypeOf(self) === Object.prototype) { 681 // ShadowRealm - only exposed with `"*"`. 682 return false; 683 } 684 throw new IdlHarnessError("Unexpected global object"); 685 } 686 687 /** 688 * Asserts that the given error message is thrown for the given function. 689 * @param {string|IdlHarnessError} error Expected Error message. 690 * @param {Function} idlArrayFunc Function operating on an IdlArray that should throw. 691 */ 692 IdlArray.prototype.assert_throws = function(error, idlArrayFunc) 693 { 694 try { 695 idlArrayFunc.call(this, this); 696 } catch (e) { 697 if (e instanceof AssertionError) { 698 throw e; 699 } 700 // Assertions for behaviour of the idlharness.js engine. 701 if (error instanceof IdlHarnessError) { 702 error = error.message; 703 } 704 if (e.message !== error) { 705 throw new IdlHarnessError(`${idlArrayFunc} threw "${e}", not the expected IdlHarnessError "${error}"`); 706 } 707 return; 708 } 709 throw new IdlHarnessError(`${idlArrayFunc} did not throw the expected IdlHarnessError`); 710 } 711 712 IdlArray.prototype.test = function() 713 { 714 /** Entry point. See documentation at beginning of file. */ 715 716 // First merge in all partial definitions and interface mixins. 717 this.merge_partials(); 718 this.merge_mixins(); 719 720 // Assert B defined for A : B 721 for (const member of Object.values(this.members).filter(m => m.base)) { 722 const lhs = member.name; 723 const rhs = member.base; 724 if (!(rhs in this.members)) throw new IdlHarnessError(`${lhs} inherits ${rhs}, but ${rhs} is undefined.`); 725 const lhs_is_interface = this.members[lhs] instanceof IdlInterface; 726 const rhs_is_interface = this.members[rhs] instanceof IdlInterface; 727 if (rhs_is_interface != lhs_is_interface) { 728 if (!lhs_is_interface) throw new IdlHarnessError(`${lhs} inherits ${rhs}, but ${lhs} is not an interface.`); 729 if (!rhs_is_interface) throw new IdlHarnessError(`${lhs} inherits ${rhs}, but ${rhs} is not an interface.`); 730 } 731 // Check for circular dependencies. 732 member.get_reverse_inheritance_stack(); 733 } 734 735 Object.getOwnPropertyNames(this.members).forEach(function(memberName) { 736 var member = this.members[memberName]; 737 if (!(member instanceof IdlInterface || member instanceof IdlNamespace)) { 738 return; 739 } 740 741 var globals = exposure_set(member); 742 member.exposed = exposed_in(globals); 743 member.exposureSet = globals; 744 }.bind(this)); 745 746 // Now run test() on every member, and test_object() for every object. 747 for (var name in this.members) 748 { 749 this.members[name].test(); 750 if (name in this.objects) 751 { 752 const objects = this.objects[name]; 753 if (!objects || !Array.isArray(objects)) { 754 throw new IdlHarnessError(`Invalid or empty objects for member ${name}`); 755 } 756 objects.forEach(function(str) 757 { 758 if (!this.members[name] || !(this.members[name] instanceof IdlInterface)) { 759 throw new IdlHarnessError(`Invalid object member name ${name}`); 760 } 761 this.members[name].test_object(str); 762 }.bind(this)); 763 } 764 } 765 }; 766 767 IdlArray.prototype.merge_partials = function() 768 { 769 const testedPartials = new Map(); 770 this.partials.forEach(function(parsed_idl) 771 { 772 const originalExists = parsed_idl.name in this.members 773 && (this.members[parsed_idl.name] instanceof IdlInterface 774 || this.members[parsed_idl.name] instanceof IdlDictionary 775 || this.members[parsed_idl.name] instanceof IdlNamespace); 776 777 // Ensure unique test name in case of multiple partials. 778 let partialTestName = parsed_idl.name; 779 let partialTestCount = 1; 780 if (testedPartials.has(parsed_idl.name)) { 781 partialTestCount += testedPartials.get(parsed_idl.name); 782 partialTestName = `${partialTestName}[${partialTestCount}]`; 783 } 784 testedPartials.set(parsed_idl.name, partialTestCount); 785 786 if (!self.shouldRunSubTest(partialTestName)) { 787 return; 788 } 789 790 if (!parsed_idl.untested) { 791 test(function () { 792 assert_true(originalExists, `Original ${parsed_idl.type} should be defined`); 793 794 var expected; 795 switch (parsed_idl.type) { 796 case 'dictionary': expected = IdlDictionary; break; 797 case 'namespace': expected = IdlNamespace; break; 798 case 'interface': 799 case 'interface mixin': 800 default: 801 expected = IdlInterface; break; 802 } 803 assert_true( 804 expected.prototype.isPrototypeOf(this.members[parsed_idl.name]), 805 `Original ${parsed_idl.name} definition should have type ${parsed_idl.type}`); 806 }.bind(this), `Partial ${parsed_idl.type} ${partialTestName}: original ${parsed_idl.type} defined`); 807 } 808 if (!originalExists) { 809 // Not good.. but keep calm and carry on. 810 return; 811 } 812 813 if (parsed_idl.extAttrs) 814 { 815 // Special-case "Exposed". Must be a subset of original interface's exposure. 816 // Exposed on a partial is the equivalent of having the same Exposed on all nested members. 817 // See https://github.com/heycam/webidl/issues/154 for discrepency between Exposed and 818 // other extended attributes on partial interfaces. 819 const exposureAttr = parsed_idl.extAttrs.find(a => a.name === "Exposed"); 820 if (exposureAttr) { 821 if (!parsed_idl.untested) { 822 test(function () { 823 const partialExposure = exposure_set(parsed_idl); 824 const memberExposure = exposure_set(this.members[parsed_idl.name]); 825 if (memberExposure === "*") { 826 return; 827 } 828 if (partialExposure === "*") { 829 throw new IdlHarnessError( 830 `Partial ${parsed_idl.name} ${parsed_idl.type} is exposed everywhere, the original ${parsed_idl.type} is not.`); 831 } 832 partialExposure.forEach(name => { 833 if (!memberExposure || !memberExposure.has(name)) { 834 throw new IdlHarnessError( 835 `Partial ${parsed_idl.name} ${parsed_idl.type} is exposed to '${name}', the original ${parsed_idl.type} is not.`); 836 } 837 }); 838 }.bind(this), `Partial ${parsed_idl.type} ${partialTestName}: valid exposure set`); 839 } 840 parsed_idl.members.forEach(function (member) { 841 member.extAttrs.push(exposureAttr); 842 }.bind(this)); 843 } 844 845 parsed_idl.extAttrs.forEach(function(extAttr) 846 { 847 // "Exposed" already handled above. 848 if (extAttr.name === "Exposed") { 849 return; 850 } 851 this.members[parsed_idl.name].extAttrs.push(extAttr); 852 }.bind(this)); 853 } 854 if (parsed_idl.members.length) { 855 test(function () { 856 var clash = parsed_idl.members.find(function(member) { 857 return this.members[parsed_idl.name].members.find(function(m) { 858 return this.are_duplicate_members(m, member); 859 }.bind(this)); 860 }.bind(this)); 861 parsed_idl.members.forEach(function(member) 862 { 863 this.members[parsed_idl.name].members.push(new IdlInterfaceMember(member)); 864 }.bind(this)); 865 assert_true(!clash, "member " + (clash && clash.name) + " is unique"); 866 }.bind(this), `Partial ${parsed_idl.type} ${partialTestName}: member names are unique`); 867 } 868 }.bind(this)); 869 this.partials = []; 870 } 871 872 IdlArray.prototype.merge_mixins = function() 873 { 874 for (const parsed_idl of this.includes) 875 { 876 const lhs = parsed_idl.target; 877 const rhs = parsed_idl.includes; 878 const testName = lhs + " includes " + rhs + ": member names are unique"; 879 880 var errStr = lhs + " includes " + rhs + ", but "; 881 if (!(lhs in this.members)) throw errStr + lhs + " is undefined."; 882 if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface."; 883 if (!(rhs in this.members)) throw errStr + rhs + " is undefined."; 884 if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface."; 885 886 if (this.members[rhs].members.length && self.shouldRunSubTest(testName)) { 887 test(function () { 888 var clash = this.members[rhs].members.find(function(member) { 889 return this.members[lhs].members.find(function(m) { 890 return this.are_duplicate_members(m, member); 891 }.bind(this)); 892 }.bind(this)); 893 this.members[rhs].members.forEach(function(member) { 894 assert_true( 895 this.members[lhs].members.every(m => !this.are_duplicate_members(m, member)), 896 "member " + member.name + " is unique"); 897 this.members[lhs].members.push(new IdlInterfaceMember(member)); 898 }.bind(this)); 899 assert_true(!clash, "member " + (clash && clash.name) + " is unique"); 900 }.bind(this), testName); 901 } 902 } 903 this.includes = []; 904 } 905 906 IdlArray.prototype.are_duplicate_members = function(m1, m2) { 907 if (m1.name !== m2.name) { 908 return false; 909 } 910 if (m1.type === 'operation' && m2.type === 'operation' 911 && m1.arguments.length !== m2.arguments.length) { 912 // Method overload. TODO: Deep comparison of arguments. 913 return false; 914 } 915 return true; 916 } 917 918 IdlArray.prototype.assert_type_is = function(value, type) 919 { 920 if (type.idlType in this.members 921 && this.members[type.idlType] instanceof IdlTypedef) { 922 this.assert_type_is(value, this.members[type.idlType].idlType); 923 return; 924 } 925 926 if (type.nullable && value === null) 927 { 928 // This is fine 929 return; 930 } 931 932 if (type.union) { 933 for (var i = 0; i < type.idlType.length; i++) { 934 try { 935 this.assert_type_is(value, type.idlType[i]); 936 // No AssertionError, so we match one type in the union 937 return; 938 } catch(e) { 939 if (e instanceof AssertionError) { 940 // We didn't match this type, let's try some others 941 continue; 942 } 943 throw e; 944 } 945 } 946 // TODO: Is there a nice way to list the union's types in the message? 947 assert_true(false, "Attribute has value " + format_value(value) 948 + " which doesn't match any of the types in the union"); 949 950 } 951 952 /** 953 * Helper function that tests that value is an instance of type according 954 * to the rules of WebIDL. value is any JavaScript value, and type is an 955 * object produced by WebIDLParser.js' "type" production. That production 956 * is fairly elaborate due to the complexity of WebIDL's types, so it's 957 * best to look at the grammar to figure out what properties it might have. 958 */ 959 if (type.idlType == "any") 960 { 961 // No assertions to make 962 return; 963 } 964 965 if (type.array) 966 { 967 // TODO: not supported yet 968 return; 969 } 970 971 if (type.generic === "sequence" || type.generic == "ObservableArray") 972 { 973 assert_true(Array.isArray(value), "should be an Array"); 974 if (!value.length) 975 { 976 // Nothing we can do. 977 return; 978 } 979 this.assert_type_is(value[0], type.idlType[0]); 980 return; 981 } 982 983 if (type.generic === "Promise") { 984 assert_true("then" in value, "Attribute with a Promise type should have a then property"); 985 // TODO: Ideally, we would check on project fulfillment 986 // that we get the right type 987 // but that would require making the type check async 988 return; 989 } 990 991 if (type.generic === "FrozenArray") { 992 assert_true(Array.isArray(value), "Value should be array"); 993 assert_true(Object.isFrozen(value), "Value should be frozen"); 994 if (!value.length) 995 { 996 // Nothing we can do. 997 return; 998 } 999 this.assert_type_is(value[0], type.idlType[0]); 1000 return; 1001 } 1002 1003 type = Array.isArray(type.idlType) ? type.idlType[0] : type.idlType; 1004 1005 switch(type) 1006 { 1007 case "undefined": 1008 assert_equals(value, undefined); 1009 return; 1010 1011 case "boolean": 1012 assert_equals(typeof value, "boolean"); 1013 return; 1014 1015 case "byte": 1016 assert_equals(typeof value, "number"); 1017 assert_equals(value, Math.floor(value), "should be an integer"); 1018 assert_true(-128 <= value && value <= 127, "byte " + value + " should be in range [-128, 127]"); 1019 return; 1020 1021 case "octet": 1022 assert_equals(typeof value, "number"); 1023 assert_equals(value, Math.floor(value), "should be an integer"); 1024 assert_true(0 <= value && value <= 255, "octet " + value + " should be in range [0, 255]"); 1025 return; 1026 1027 case "short": 1028 assert_equals(typeof value, "number"); 1029 assert_equals(value, Math.floor(value), "should be an integer"); 1030 assert_true(-32768 <= value && value <= 32767, "short " + value + " should be in range [-32768, 32767]"); 1031 return; 1032 1033 case "unsigned short": 1034 assert_equals(typeof value, "number"); 1035 assert_equals(value, Math.floor(value), "should be an integer"); 1036 assert_true(0 <= value && value <= 65535, "unsigned short " + value + " should be in range [0, 65535]"); 1037 return; 1038 1039 case "long": 1040 assert_equals(typeof value, "number"); 1041 assert_equals(value, Math.floor(value), "should be an integer"); 1042 assert_true(-2147483648 <= value && value <= 2147483647, "long " + value + " should be in range [-2147483648, 2147483647]"); 1043 return; 1044 1045 case "unsigned long": 1046 assert_equals(typeof value, "number"); 1047 assert_equals(value, Math.floor(value), "should be an integer"); 1048 assert_true(0 <= value && value <= 4294967295, "unsigned long " + value + " should be in range [0, 4294967295]"); 1049 return; 1050 1051 case "long long": 1052 assert_equals(typeof value, "number"); 1053 return; 1054 1055 case "unsigned long long": 1056 case "DOMTimeStamp": 1057 assert_equals(typeof value, "number"); 1058 assert_true(0 <= value, "unsigned long long should be positive"); 1059 return; 1060 1061 case "float": 1062 assert_equals(typeof value, "number"); 1063 assert_equals(value, Math.fround(value), "float rounded to 32-bit float should be itself"); 1064 assert_not_equals(value, Infinity); 1065 assert_not_equals(value, -Infinity); 1066 assert_not_equals(value, NaN); 1067 return; 1068 1069 case "DOMHighResTimeStamp": 1070 case "double": 1071 assert_equals(typeof value, "number"); 1072 assert_not_equals(value, Infinity); 1073 assert_not_equals(value, -Infinity); 1074 assert_not_equals(value, NaN); 1075 return; 1076 1077 case "unrestricted float": 1078 assert_equals(typeof value, "number"); 1079 assert_equals(value, Math.fround(value), "unrestricted float rounded to 32-bit float should be itself"); 1080 return; 1081 1082 case "unrestricted double": 1083 assert_equals(typeof value, "number"); 1084 return; 1085 1086 case "DOMString": 1087 assert_equals(typeof value, "string"); 1088 return; 1089 1090 case "ByteString": 1091 assert_equals(typeof value, "string"); 1092 assert_regexp_match(value, /^[\x00-\x7F]*$/); 1093 return; 1094 1095 case "USVString": 1096 assert_equals(typeof value, "string"); 1097 assert_regexp_match(value, /^([\x00-\ud7ff\ue000-\uffff]|[\ud800-\udbff][\udc00-\udfff])*$/); 1098 return; 1099 1100 case "ArrayBufferView": 1101 assert_true(ArrayBuffer.isView(value)); 1102 return; 1103 1104 case "object": 1105 assert_in_array(typeof value, ["object", "function"], "wrong type: not object or function"); 1106 return; 1107 } 1108 1109 // This is a catch-all for any IDL type name which follows JS class 1110 // semantics. This includes some non-interface IDL types (e.g. Int8Array, 1111 // Function, ...), as well as any interface types that are not in the IDL 1112 // that is fed to the harness. If an IDL type does not follow JS class 1113 // semantics then it should go in the switch statement above. If an IDL 1114 // type needs full checking, then the test should include it in the IDL it 1115 // feeds to the harness. 1116 if (!(type in this.members)) 1117 { 1118 assert_true(value instanceof self[type], "wrong type: not a " + type); 1119 return; 1120 } 1121 1122 if (this.members[type] instanceof IdlInterface) 1123 { 1124 // We don't want to run the full 1125 // IdlInterface.prototype.test_instance_of, because that could result 1126 // in an infinite loop. TODO: This means we don't have tests for 1127 // LegacyNoInterfaceObject interfaces, and we also can't test objects 1128 // that come from another self. 1129 assert_in_array(typeof value, ["object", "function"], "wrong type: not object or function"); 1130 if (value instanceof Object 1131 && !this.members[type].has_extended_attribute("LegacyNoInterfaceObject") 1132 && type in self) 1133 { 1134 assert_true(value instanceof self[type], "instanceof " + type); 1135 } 1136 } 1137 else if (this.members[type] instanceof IdlEnum) 1138 { 1139 assert_equals(typeof value, "string"); 1140 } 1141 else if (this.members[type] instanceof IdlDictionary) 1142 { 1143 // TODO: Test when we actually have something to test this on 1144 } 1145 else if (this.members[type] instanceof IdlCallback) 1146 { 1147 assert_equals(typeof value, "function"); 1148 } 1149 else 1150 { 1151 throw new IdlHarnessError("Type " + type + " isn't an interface, callback or dictionary"); 1152 } 1153 }; 1154 1155 /// IdlObject /// 1156 function IdlObject() {} 1157 IdlObject.prototype.test = function() 1158 { 1159 /** 1160 * By default, this does nothing, so no actual tests are run for IdlObjects 1161 * that don't define any (e.g., IdlDictionary at the time of this writing). 1162 */ 1163 }; 1164 1165 IdlObject.prototype.has_extended_attribute = function(name) 1166 { 1167 /** 1168 * This is only meaningful for things that support extended attributes, 1169 * such as interfaces, exceptions, and members. 1170 */ 1171 return this.extAttrs.some(function(o) 1172 { 1173 return o.name == name; 1174 }); 1175 }; 1176 1177 1178 /// IdlDictionary /// 1179 // Used for IdlArray.prototype.assert_type_is 1180 function IdlDictionary(obj) 1181 { 1182 /** 1183 * obj is an object produced by the WebIDLParser.js "dictionary" 1184 * production. 1185 */ 1186 1187 /** Self-explanatory. */ 1188 this.name = obj.name; 1189 1190 /** A back-reference to our IdlArray. */ 1191 this.array = obj.array; 1192 1193 /** An array of objects produced by the "dictionaryMember" production. */ 1194 this.members = obj.members; 1195 1196 /** 1197 * The name (as a string) of the dictionary type we inherit from, or null 1198 * if there is none. 1199 */ 1200 this.base = obj.inheritance; 1201 } 1202 1203 IdlDictionary.prototype = Object.create(IdlObject.prototype); 1204 1205 IdlDictionary.prototype.get_reverse_inheritance_stack = function() { 1206 return IdlInterface.prototype.get_reverse_inheritance_stack.call(this); 1207 }; 1208 1209 /// IdlInterface /// 1210 function IdlInterface(obj, is_callback, is_mixin) 1211 { 1212 /** 1213 * obj is an object produced by the WebIDLParser.js "interface" production. 1214 */ 1215 1216 /** Self-explanatory. */ 1217 this.name = obj.name; 1218 1219 /** A back-reference to our IdlArray. */ 1220 this.array = obj.array; 1221 1222 /** 1223 * An indicator of whether we should run tests on the interface object and 1224 * interface prototype object. Tests on members are controlled by .untested 1225 * on each member, not this. 1226 */ 1227 this.untested = obj.untested; 1228 1229 /** An array of objects produced by the "ExtAttr" production. */ 1230 this.extAttrs = obj.extAttrs; 1231 1232 /** An array of IdlInterfaceMembers. */ 1233 this.members = obj.members.map(function(m){return new IdlInterfaceMember(m); }); 1234 if (this.has_extended_attribute("LegacyUnforgeable")) { 1235 this.members 1236 .filter(function(m) { return m.special !== "static" && (m.type == "attribute" || m.type == "operation"); }) 1237 .forEach(function(m) { return m.isUnforgeable = true; }); 1238 } 1239 1240 /** 1241 * The name (as a string) of the type we inherit from, or null if there is 1242 * none. 1243 */ 1244 this.base = obj.inheritance; 1245 1246 this._is_callback = is_callback; 1247 this._is_mixin = is_mixin; 1248 } 1249 IdlInterface.prototype = Object.create(IdlObject.prototype); 1250 IdlInterface.prototype.is_callback = function() 1251 { 1252 return this._is_callback; 1253 }; 1254 1255 IdlInterface.prototype.is_mixin = function() 1256 { 1257 return this._is_mixin; 1258 }; 1259 1260 IdlInterface.prototype.has_constants = function() 1261 { 1262 return this.members.some(function(member) { 1263 return member.type === "const"; 1264 }); 1265 }; 1266 1267 IdlInterface.prototype.get_unscopables = function() 1268 { 1269 return this.members.filter(function(member) { 1270 return member.isUnscopable; 1271 }); 1272 }; 1273 1274 IdlInterface.prototype.is_global = function() 1275 { 1276 return this.extAttrs.some(function(attribute) { 1277 return attribute.name === "Global"; 1278 }); 1279 }; 1280 1281 /** 1282 * Value of the LegacyNamespace extended attribute, if any. 1283 * 1284 * https://webidl.spec.whatwg.org/#LegacyNamespace 1285 */ 1286 IdlInterface.prototype.get_legacy_namespace = function() 1287 { 1288 var legacyNamespace = this.extAttrs.find(function(attribute) { 1289 return attribute.name === "LegacyNamespace"; 1290 }); 1291 return legacyNamespace ? legacyNamespace.rhs.value : undefined; 1292 }; 1293 1294 IdlInterface.prototype.get_interface_object_owner = function() 1295 { 1296 var legacyNamespace = this.get_legacy_namespace(); 1297 return legacyNamespace ? self[legacyNamespace] : self; 1298 }; 1299 1300 IdlInterface.prototype.should_have_interface_object = function() 1301 { 1302 // "For every interface that is exposed in a given ECMAScript global 1303 // environment and: 1304 // * is a callback interface that has constants declared on it, or 1305 // * is a non-callback interface that is not declared with the 1306 // [LegacyNoInterfaceObject] extended attribute, 1307 // a corresponding property MUST exist on the ECMAScript global object. 1308 1309 return this.is_callback() ? this.has_constants() : !this.has_extended_attribute("LegacyNoInterfaceObject"); 1310 }; 1311 1312 IdlInterface.prototype.assert_interface_object_exists = function() 1313 { 1314 var owner = this.get_legacy_namespace() || "self"; 1315 assert_own_property(self[owner], this.name, owner + " does not have own property " + format_value(this.name)); 1316 }; 1317 1318 IdlInterface.prototype.get_interface_object = function() { 1319 if (!this.should_have_interface_object()) { 1320 var reason = this.is_callback() ? "lack of declared constants" : "declared [LegacyNoInterfaceObject] attribute"; 1321 throw new IdlHarnessError(this.name + " has no interface object due to " + reason); 1322 } 1323 1324 return this.get_interface_object_owner()[this.name]; 1325 }; 1326 1327 IdlInterface.prototype.get_qualified_name = function() { 1328 // https://webidl.spec.whatwg.org/#qualified-name 1329 var legacyNamespace = this.get_legacy_namespace(); 1330 if (legacyNamespace) { 1331 return legacyNamespace + "." + this.name; 1332 } 1333 return this.name; 1334 }; 1335 1336 IdlInterface.prototype.has_to_json_regular_operation = function() { 1337 return this.members.some(function(m) { 1338 return m.is_to_json_regular_operation(); 1339 }); 1340 }; 1341 1342 IdlInterface.prototype.has_default_to_json_regular_operation = function() { 1343 return this.members.some(function(m) { 1344 return m.is_to_json_regular_operation() && m.has_extended_attribute("Default"); 1345 }); 1346 }; 1347 1348 /** 1349 * Implementation of https://webidl.spec.whatwg.org/#create-an-inheritance-stack 1350 * with the order reversed. 1351 * 1352 * The order is reversed so that the base class comes first in the list, because 1353 * this is what all call sites need. 1354 * 1355 * So given: 1356 * 1357 * A : B {}; 1358 * B : C {}; 1359 * C {}; 1360 * 1361 * then A.get_reverse_inheritance_stack() returns [C, B, A], 1362 * and B.get_reverse_inheritance_stack() returns [C, B]. 1363 * 1364 * Note: as dictionary inheritance is expressed identically by the AST, 1365 * this works just as well for getting a stack of inherited dictionaries. 1366 */ 1367 IdlInterface.prototype.get_reverse_inheritance_stack = function() { 1368 const stack = [this]; 1369 let idl_interface = this; 1370 while (idl_interface.base) { 1371 const base = this.array.members[idl_interface.base]; 1372 if (!base) { 1373 throw new Error(idl_interface.type + " " + idl_interface.base + " not found (inherited by " + idl_interface.name + ")"); 1374 } else if (stack.indexOf(base) > -1) { 1375 stack.unshift(base); 1376 const dep_chain = stack.map(i => i.name).join(','); 1377 throw new IdlHarnessError(`${this.name} has a circular dependency: ${dep_chain}`); 1378 } 1379 idl_interface = base; 1380 stack.unshift(idl_interface); 1381 } 1382 return stack; 1383 }; 1384 1385 /** 1386 * Implementation of 1387 * https://webidl.spec.whatwg.org/#default-tojson-operation 1388 * for testing purposes. 1389 * 1390 * Collects the IDL types of the attributes that meet the criteria 1391 * for inclusion in the default toJSON operation for easy 1392 * comparison with actual value 1393 */ 1394 IdlInterface.prototype.default_to_json_operation = function() { 1395 const map = new Map() 1396 let isDefault = false; 1397 for (const I of this.get_reverse_inheritance_stack()) { 1398 if (I.has_default_to_json_regular_operation()) { 1399 isDefault = true; 1400 for (const m of I.members) { 1401 if (!m.untested && m.special !== "static" && m.type == "attribute" && I.array.is_json_type(m.idlType)) { 1402 map.set(m.name, m.idlType); 1403 } 1404 } 1405 } else if (I.has_to_json_regular_operation()) { 1406 isDefault = false; 1407 } 1408 } 1409 return isDefault ? map : null; 1410 }; 1411 1412 IdlInterface.prototype.test = function() 1413 { 1414 if (this.has_extended_attribute("LegacyNoInterfaceObject") || this.is_mixin()) 1415 { 1416 // No tests to do without an instance. TODO: We should still be able 1417 // to run tests on the prototype object, if we obtain one through some 1418 // other means. 1419 return; 1420 } 1421 1422 // If the interface object is not exposed, only test that. Members can't be 1423 // tested either, but objects could still be tested in |test_object|. 1424 if (!this.exposed) 1425 { 1426 if (!this.untested) 1427 { 1428 subsetTestByKey(this.name, test, function() { 1429 assert_false(this.name in self, this.name + " interface should not exist"); 1430 }.bind(this), this.name + " interface: existence and properties of interface object"); 1431 } 1432 return; 1433 } 1434 1435 if (!this.untested) 1436 { 1437 // First test things to do with the exception/interface object and 1438 // exception/interface prototype object. 1439 this.test_self(); 1440 } 1441 // Then test things to do with its members (constants, fields, attributes, 1442 // operations, . . .). These are run even if .untested is true, because 1443 // members might themselves be marked as .untested. This might happen to 1444 // interfaces if the interface itself is untested but a partial interface 1445 // that extends it is tested -- then the interface itself and its initial 1446 // members will be marked as untested, but the members added by the partial 1447 // interface are still tested. 1448 this.test_members(); 1449 }; 1450 1451 IdlInterface.prototype.constructors = function() 1452 { 1453 return this.members 1454 .filter(function(m) { return m.type == "constructor"; }); 1455 } 1456 1457 IdlInterface.prototype.test_self = function() 1458 { 1459 subsetTestByKey(this.name, test, function() 1460 { 1461 if (!this.should_have_interface_object()) { 1462 return; 1463 } 1464 1465 // The name of the property is the identifier of the interface, and its 1466 // value is an object called the interface object. 1467 // The property has the attributes { [[Writable]]: true, 1468 // [[Enumerable]]: false, [[Configurable]]: true }." 1469 // TODO: Should we test here that the property is actually writable 1470 // etc., or trust getOwnPropertyDescriptor? 1471 this.assert_interface_object_exists(); 1472 var desc = Object.getOwnPropertyDescriptor(this.get_interface_object_owner(), this.name); 1473 assert_false("get" in desc, "self's property " + format_value(this.name) + " should not have a getter"); 1474 assert_false("set" in desc, "self's property " + format_value(this.name) + " should not have a setter"); 1475 assert_true(desc.writable, "self's property " + format_value(this.name) + " should be writable"); 1476 assert_false(desc.enumerable, "self's property " + format_value(this.name) + " should not be enumerable"); 1477 assert_true(desc.configurable, "self's property " + format_value(this.name) + " should be configurable"); 1478 1479 if (this.is_callback()) { 1480 // "The internal [[Prototype]] property of an interface object for 1481 // a callback interface must be the Function.prototype object." 1482 assert_equals(Object.getPrototypeOf(this.get_interface_object()), Function.prototype, 1483 "prototype of self's property " + format_value(this.name) + " is not Object.prototype"); 1484 1485 return; 1486 } 1487 1488 // "The interface object for a given non-callback interface is a 1489 // function object." 1490 // "If an object is defined to be a function object, then it has 1491 // characteristics as follows:" 1492 1493 // Its [[Prototype]] internal property is otherwise specified (see 1494 // below). 1495 1496 // "* Its [[Get]] internal property is set as described in ECMA-262 1497 // section 9.1.8." 1498 // Not much to test for this. 1499 1500 // "* Its [[Construct]] internal property is set as described in 1501 // ECMA-262 section 19.2.2.3." 1502 1503 // "* Its @@hasInstance property is set as described in ECMA-262 1504 // section 19.2.3.8, unless otherwise specified." 1505 // TODO 1506 1507 // ES6 (rev 30) 19.1.3.6: 1508 // "Else, if O has a [[Call]] internal method, then let builtinTag be 1509 // "Function"." 1510 assert_class_string(this.get_interface_object(), "Function", "class string of " + this.name); 1511 1512 // "The [[Prototype]] internal property of an interface object for a 1513 // non-callback interface is determined as follows:" 1514 var prototype = Object.getPrototypeOf(this.get_interface_object()); 1515 if (this.base) { 1516 // "* If the interface inherits from some other interface, the 1517 // value of [[Prototype]] is the interface object for that other 1518 // interface." 1519 var inherited_interface = this.array.members[this.base]; 1520 if (!inherited_interface.has_extended_attribute("LegacyNoInterfaceObject")) { 1521 inherited_interface.assert_interface_object_exists(); 1522 assert_equals(prototype, inherited_interface.get_interface_object(), 1523 'prototype of ' + this.name + ' is not ' + 1524 this.base); 1525 } 1526 } else { 1527 // "If the interface doesn't inherit from any other interface, the 1528 // value of [[Prototype]] is %FunctionPrototype% ([ECMA-262], 1529 // section 6.1.7.4)." 1530 assert_equals(prototype, Function.prototype, 1531 "prototype of self's property " + format_value(this.name) + " is not Function.prototype"); 1532 } 1533 1534 // Always test for [[Construct]]: 1535 // https://github.com/heycam/webidl/issues/698 1536 assert_true(isConstructor(this.get_interface_object()), "interface object must pass IsConstructor check"); 1537 1538 var interface_object = this.get_interface_object(); 1539 assert_throws_js(globalOf(interface_object).TypeError, function() { 1540 interface_object(); 1541 }, "interface object didn't throw TypeError when called as a function"); 1542 1543 if (!this.constructors().length) { 1544 assert_throws_js(globalOf(interface_object).TypeError, function() { 1545 new interface_object(); 1546 }, "interface object didn't throw TypeError when called as a constructor"); 1547 } 1548 }.bind(this), this.name + " interface: existence and properties of interface object"); 1549 1550 if (this.should_have_interface_object() && !this.is_callback()) { 1551 subsetTestByKey(this.name, test, function() { 1552 // This function tests WebIDL as of 2014-10-25. 1553 // https://webidl.spec.whatwg.org/#es-interface-call 1554 1555 this.assert_interface_object_exists(); 1556 1557 // "Interface objects for non-callback interfaces MUST have a 1558 // property named “length” with attributes { [[Writable]]: false, 1559 // [[Enumerable]]: false, [[Configurable]]: true } whose value is 1560 // a Number." 1561 assert_own_property(this.get_interface_object(), "length"); 1562 var desc = Object.getOwnPropertyDescriptor(this.get_interface_object(), "length"); 1563 assert_false("get" in desc, this.name + ".length should not have a getter"); 1564 assert_false("set" in desc, this.name + ".length should not have a setter"); 1565 assert_false(desc.writable, this.name + ".length should not be writable"); 1566 assert_false(desc.enumerable, this.name + ".length should not be enumerable"); 1567 assert_true(desc.configurable, this.name + ".length should be configurable"); 1568 1569 var constructors = this.constructors(); 1570 var expected_length = minOverloadLength(constructors); 1571 assert_equals(this.get_interface_object().length, expected_length, "wrong value for " + this.name + ".length"); 1572 }.bind(this), this.name + " interface object length"); 1573 } 1574 1575 if (this.should_have_interface_object()) { 1576 subsetTestByKey(this.name, test, function() { 1577 // This function tests WebIDL as of 2015-11-17. 1578 // https://webidl.spec.whatwg.org/#interface-object 1579 1580 this.assert_interface_object_exists(); 1581 1582 // "All interface objects must have a property named “name” with 1583 // attributes { [[Writable]]: false, [[Enumerable]]: false, 1584 // [[Configurable]]: true } whose value is the identifier of the 1585 // corresponding interface." 1586 1587 assert_own_property(this.get_interface_object(), "name"); 1588 var desc = Object.getOwnPropertyDescriptor(this.get_interface_object(), "name"); 1589 assert_false("get" in desc, this.name + ".name should not have a getter"); 1590 assert_false("set" in desc, this.name + ".name should not have a setter"); 1591 assert_false(desc.writable, this.name + ".name should not be writable"); 1592 assert_false(desc.enumerable, this.name + ".name should not be enumerable"); 1593 assert_true(desc.configurable, this.name + ".name should be configurable"); 1594 assert_equals(this.get_interface_object().name, this.name, "wrong value for " + this.name + ".name"); 1595 }.bind(this), this.name + " interface object name"); 1596 } 1597 1598 1599 if (this.has_extended_attribute("LegacyWindowAlias")) { 1600 subsetTestByKey(this.name, test, function() 1601 { 1602 var aliasAttrs = this.extAttrs.filter(function(o) { return o.name === "LegacyWindowAlias"; }); 1603 if (aliasAttrs.length > 1) { 1604 throw new IdlHarnessError("Invalid IDL: multiple LegacyWindowAlias extended attributes on " + this.name); 1605 } 1606 if (this.is_callback()) { 1607 throw new IdlHarnessError("Invalid IDL: LegacyWindowAlias extended attribute on non-interface " + this.name); 1608 } 1609 if (!(this.exposureSet === "*" || this.exposureSet.has("Window"))) { 1610 throw new IdlHarnessError("Invalid IDL: LegacyWindowAlias extended attribute on " + this.name + " which is not exposed in Window"); 1611 } 1612 // TODO: when testing of [LegacyNoInterfaceObject] interfaces is supported, 1613 // check that it's not specified together with LegacyWindowAlias. 1614 1615 // TODO: maybe check that [LegacyWindowAlias] is not specified on a partial interface. 1616 1617 var rhs = aliasAttrs[0].rhs; 1618 if (!rhs) { 1619 throw new IdlHarnessError("Invalid IDL: LegacyWindowAlias extended attribute on " + this.name + " without identifier"); 1620 } 1621 var aliases; 1622 if (rhs.type === "identifier-list") { 1623 aliases = rhs.value.map(id => id.value); 1624 } else { // rhs.type === identifier 1625 aliases = [ rhs.value ]; 1626 } 1627 1628 // OK now actually check the aliases... 1629 var alias; 1630 if (exposed_in(exposure_set(this, this.exposureSet)) && 'document' in self) { 1631 for (alias of aliases) { 1632 assert_true(alias in self, alias + " should exist"); 1633 assert_equals(self[alias], this.get_interface_object(), "self." + alias + " should be the same value as self." + this.get_qualified_name()); 1634 var desc = Object.getOwnPropertyDescriptor(self, alias); 1635 assert_equals(desc.value, this.get_interface_object(), "wrong value in " + alias + " property descriptor"); 1636 assert_true(desc.writable, alias + " should be writable"); 1637 assert_false(desc.enumerable, alias + " should not be enumerable"); 1638 assert_true(desc.configurable, alias + " should be configurable"); 1639 assert_false('get' in desc, alias + " should not have a getter"); 1640 assert_false('set' in desc, alias + " should not have a setter"); 1641 } 1642 } else { 1643 for (alias of aliases) { 1644 assert_false(alias in self, alias + " should not exist"); 1645 } 1646 } 1647 1648 }.bind(this), this.name + " interface: legacy window alias"); 1649 } 1650 1651 if (this.has_extended_attribute("LegacyFactoryFunction")) { 1652 var constructors = this.extAttrs 1653 .filter(function(attr) { return attr.name == "LegacyFactoryFunction"; }); 1654 if (constructors.length !== 1) { 1655 throw new IdlHarnessError("Internal error: missing support for multiple LegacyFactoryFunction extended attributes"); 1656 } 1657 var constructor = constructors[0]; 1658 var min_length = minOverloadLength([constructor]); 1659 1660 subsetTestByKey(this.name, test, function() 1661 { 1662 // This function tests WebIDL as of 2019-01-14. 1663 1664 // "for every [LegacyFactoryFunction] extended attribute on an exposed 1665 // interface, a corresponding property must exist on the ECMAScript 1666 // global object. The name of the property is the 1667 // [LegacyFactoryFunction]'s identifier, and its value is an object 1668 // called a named constructor, ... . The property has the attributes 1669 // { [[Writable]]: true, [[Enumerable]]: false, 1670 // [[Configurable]]: true }." 1671 var name = constructor.rhs.value; 1672 assert_own_property(self, name); 1673 var desc = Object.getOwnPropertyDescriptor(self, name); 1674 assert_equals(desc.value, self[name], "wrong value in " + name + " property descriptor"); 1675 assert_true(desc.writable, name + " should be writable"); 1676 assert_false(desc.enumerable, name + " should not be enumerable"); 1677 assert_true(desc.configurable, name + " should be configurable"); 1678 assert_false("get" in desc, name + " should not have a getter"); 1679 assert_false("set" in desc, name + " should not have a setter"); 1680 }.bind(this), this.name + " interface: named constructor"); 1681 1682 subsetTestByKey(this.name, test, function() 1683 { 1684 // This function tests WebIDL as of 2019-01-14. 1685 1686 // "2. Let F be ! CreateBuiltinFunction(realm, steps, 1687 // realm.[[Intrinsics]].[[%FunctionPrototype%]])." 1688 var name = constructor.rhs.value; 1689 var value = self[name]; 1690 assert_equals(typeof value, "function", "type of value in " + name + " property descriptor"); 1691 assert_not_equals(value, this.get_interface_object(), "wrong value in " + name + " property descriptor"); 1692 assert_equals(Object.getPrototypeOf(value), Function.prototype, "wrong value for " + name + "'s prototype"); 1693 }.bind(this), this.name + " interface: named constructor object"); 1694 1695 subsetTestByKey(this.name, test, function() 1696 { 1697 // This function tests WebIDL as of 2019-01-14. 1698 1699 // "7. Let proto be the interface prototype object of interface I 1700 // in realm. 1701 // "8. Perform ! DefinePropertyOrThrow(F, "prototype", 1702 // PropertyDescriptor{ 1703 // [[Value]]: proto, [[Writable]]: false, 1704 // [[Enumerable]]: false, [[Configurable]]: false 1705 // })." 1706 var name = constructor.rhs.value; 1707 var expected = this.get_interface_object().prototype; 1708 var desc = Object.getOwnPropertyDescriptor(self[name], "prototype"); 1709 assert_equals(desc.value, expected, "wrong value for " + name + ".prototype"); 1710 assert_false(desc.writable, "prototype should not be writable"); 1711 assert_false(desc.enumerable, "prototype should not be enumerable"); 1712 assert_false(desc.configurable, "prototype should not be configurable"); 1713 assert_false("get" in desc, "prototype should not have a getter"); 1714 assert_false("set" in desc, "prototype should not have a setter"); 1715 }.bind(this), this.name + " interface: named constructor prototype property"); 1716 1717 subsetTestByKey(this.name, test, function() 1718 { 1719 // This function tests WebIDL as of 2019-01-14. 1720 1721 // "3. Perform ! SetFunctionName(F, id)." 1722 var name = constructor.rhs.value; 1723 var desc = Object.getOwnPropertyDescriptor(self[name], "name"); 1724 assert_equals(desc.value, name, "wrong value for " + name + ".name"); 1725 assert_false(desc.writable, "name should not be writable"); 1726 assert_false(desc.enumerable, "name should not be enumerable"); 1727 assert_true(desc.configurable, "name should be configurable"); 1728 assert_false("get" in desc, "name should not have a getter"); 1729 assert_false("set" in desc, "name should not have a setter"); 1730 }.bind(this), this.name + " interface: named constructor name"); 1731 1732 subsetTestByKey(this.name, test, function() 1733 { 1734 // This function tests WebIDL as of 2019-01-14. 1735 1736 // "4. Initialize S to the effective overload set for constructors 1737 // with identifier id on interface I and with argument count 0. 1738 // "5. Let length be the length of the shortest argument list of 1739 // the entries in S. 1740 // "6. Perform ! SetFunctionLength(F, length)." 1741 var name = constructor.rhs.value; 1742 var desc = Object.getOwnPropertyDescriptor(self[name], "length"); 1743 assert_equals(desc.value, min_length, "wrong value for " + name + ".length"); 1744 assert_false(desc.writable, "length should not be writable"); 1745 assert_false(desc.enumerable, "length should not be enumerable"); 1746 assert_true(desc.configurable, "length should be configurable"); 1747 assert_false("get" in desc, "length should not have a getter"); 1748 assert_false("set" in desc, "length should not have a setter"); 1749 }.bind(this), this.name + " interface: named constructor length"); 1750 1751 subsetTestByKey(this.name, test, function() 1752 { 1753 // This function tests WebIDL as of 2019-01-14. 1754 1755 // "1. Let steps be the following steps: 1756 // " 1. If NewTarget is undefined, then throw a TypeError." 1757 var name = constructor.rhs.value; 1758 var args = constructor.arguments.map(function(arg) { 1759 return create_suitable_object(arg.idlType); 1760 }); 1761 assert_throws_js(globalOf(self[name]).TypeError, function() { 1762 self[name](...args); 1763 }.bind(this)); 1764 }.bind(this), this.name + " interface: named constructor without 'new'"); 1765 } 1766 1767 subsetTestByKey(this.name, test, function() 1768 { 1769 // This function tests WebIDL as of 2015-01-21. 1770 // https://webidl.spec.whatwg.org/#interface-object 1771 1772 if (!this.should_have_interface_object()) { 1773 return; 1774 } 1775 1776 this.assert_interface_object_exists(); 1777 1778 if (this.is_callback()) { 1779 assert_false("prototype" in this.get_interface_object(), 1780 this.name + ' should not have a "prototype" property'); 1781 return; 1782 } 1783 1784 // "An interface object for a non-callback interface must have a 1785 // property named “prototype” with attributes { [[Writable]]: false, 1786 // [[Enumerable]]: false, [[Configurable]]: false } whose value is an 1787 // object called the interface prototype object. This object has 1788 // properties that correspond to the regular attributes and regular 1789 // operations defined on the interface, and is described in more detail 1790 // in section 4.5.4 below." 1791 assert_own_property(this.get_interface_object(), "prototype", 1792 'interface "' + this.name + '" does not have own property "prototype"'); 1793 var desc = Object.getOwnPropertyDescriptor(this.get_interface_object(), "prototype"); 1794 assert_false("get" in desc, this.name + ".prototype should not have a getter"); 1795 assert_false("set" in desc, this.name + ".prototype should not have a setter"); 1796 assert_false(desc.writable, this.name + ".prototype should not be writable"); 1797 assert_false(desc.enumerable, this.name + ".prototype should not be enumerable"); 1798 assert_false(desc.configurable, this.name + ".prototype should not be configurable"); 1799 1800 // Next, test that the [[Prototype]] of the interface prototype object 1801 // is correct. (This is made somewhat difficult by the existence of 1802 // [LegacyNoInterfaceObject].) 1803 // TODO: Aryeh thinks there's at least other place in this file where 1804 // we try to figure out if an interface prototype object is 1805 // correct. Consolidate that code. 1806 1807 // "The interface prototype object for a given interface A must have an 1808 // internal [[Prototype]] property whose value is returned from the 1809 // following steps: 1810 // "If A is declared with the [Global] extended 1811 // attribute, and A supports named properties, then return the named 1812 // properties object for A, as defined in §3.6.4 Named properties 1813 // object. 1814 // "Otherwise, if A is declared to inherit from another interface, then 1815 // return the interface prototype object for the inherited interface. 1816 // "Otherwise, return %ObjectPrototype%. 1817 // 1818 // "In the ECMAScript binding, the DOMException type has some additional 1819 // requirements: 1820 // 1821 // "Unlike normal interface types, the interface prototype object 1822 // for DOMException must have as its [[Prototype]] the intrinsic 1823 // object %ErrorPrototype%." 1824 // 1825 if (this.name === "Window") { 1826 assert_class_string(Object.getPrototypeOf(this.get_interface_object().prototype), 1827 'WindowProperties', 1828 'Class name for prototype of Window' + 1829 '.prototype is not "WindowProperties"'); 1830 } else { 1831 var inherit_interface, inherit_interface_interface_object; 1832 if (this.base) { 1833 inherit_interface = this.base; 1834 var parent = this.array.members[inherit_interface]; 1835 if (!parent.has_extended_attribute("LegacyNoInterfaceObject")) { 1836 parent.assert_interface_object_exists(); 1837 inherit_interface_interface_object = parent.get_interface_object(); 1838 } 1839 } else if (this.name === "DOMException") { 1840 inherit_interface = 'Error'; 1841 inherit_interface_interface_object = self.Error; 1842 } else { 1843 inherit_interface = 'Object'; 1844 inherit_interface_interface_object = self.Object; 1845 } 1846 if (inherit_interface_interface_object) { 1847 assert_not_equals(inherit_interface_interface_object, undefined, 1848 'should inherit from ' + inherit_interface + ', but there is no such property'); 1849 assert_own_property(inherit_interface_interface_object, 'prototype', 1850 'should inherit from ' + inherit_interface + ', but that object has no "prototype" property'); 1851 assert_equals(Object.getPrototypeOf(this.get_interface_object().prototype), 1852 inherit_interface_interface_object.prototype, 1853 'prototype of ' + this.name + '.prototype is not ' + inherit_interface + '.prototype'); 1854 } else { 1855 // We can't test that we get the correct object, because this is the 1856 // only way to get our hands on it. We only test that its class 1857 // string, at least, is correct. 1858 assert_class_string(Object.getPrototypeOf(this.get_interface_object().prototype), 1859 inherit_interface + 'Prototype', 1860 'Class name for prototype of ' + this.name + 1861 '.prototype is not "' + inherit_interface + 'Prototype"'); 1862 } 1863 } 1864 1865 // "The class string of an interface prototype object is the 1866 // concatenation of the interface’s qualified identifier and the string 1867 // “Prototype”." 1868 1869 // Skip these tests for now due to a specification issue about 1870 // prototype name. 1871 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28244 1872 1873 // assert_class_string(this.get_interface_object().prototype, this.get_qualified_name() + "Prototype", 1874 // "class string of " + this.name + ".prototype"); 1875 1876 // String() should end up calling {}.toString if nothing defines a 1877 // stringifier. 1878 if (!this.has_stringifier()) { 1879 // assert_equals(String(this.get_interface_object().prototype), "[object " + this.get_qualified_name() + "Prototype]", 1880 // "String(" + this.name + ".prototype)"); 1881 } 1882 }.bind(this), this.name + " interface: existence and properties of interface prototype object"); 1883 1884 // "If the interface is declared with the [Global] 1885 // extended attribute, or the interface is in the set of inherited 1886 // interfaces for any other interface that is declared with one of these 1887 // attributes, then the interface prototype object must be an immutable 1888 // prototype exotic object." 1889 // https://webidl.spec.whatwg.org/#interface-prototype-object 1890 if (this.is_global()) { 1891 this.test_immutable_prototype("interface prototype object", this.get_interface_object().prototype); 1892 } 1893 1894 subsetTestByKey(this.name, test, function() 1895 { 1896 if (!this.should_have_interface_object()) { 1897 return; 1898 } 1899 1900 this.assert_interface_object_exists(); 1901 1902 if (this.is_callback()) { 1903 assert_false("prototype" in this.get_interface_object(), 1904 this.name + ' should not have a "prototype" property'); 1905 return; 1906 } 1907 1908 assert_own_property(this.get_interface_object(), "prototype", 1909 'interface "' + this.name + '" does not have own property "prototype"'); 1910 1911 // "If the [LegacyNoInterfaceObject] extended attribute was not specified 1912 // on the interface, then the interface prototype object must also have a 1913 // property named “constructor” with attributes { [[Writable]]: true, 1914 // [[Enumerable]]: false, [[Configurable]]: true } whose value is a 1915 // reference to the interface object for the interface." 1916 assert_own_property(this.get_interface_object().prototype, "constructor", 1917 this.name + '.prototype does not have own property "constructor"'); 1918 var desc = Object.getOwnPropertyDescriptor(this.get_interface_object().prototype, "constructor"); 1919 assert_false("get" in desc, this.name + ".prototype.constructor should not have a getter"); 1920 assert_false("set" in desc, this.name + ".prototype.constructor should not have a setter"); 1921 assert_true(desc.writable, this.name + ".prototype.constructor should be writable"); 1922 assert_false(desc.enumerable, this.name + ".prototype.constructor should not be enumerable"); 1923 assert_true(desc.configurable, this.name + ".prototype.constructor should be configurable"); 1924 assert_equals(this.get_interface_object().prototype.constructor, this.get_interface_object(), 1925 this.name + '.prototype.constructor is not the same object as ' + this.name); 1926 }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property'); 1927 1928 1929 subsetTestByKey(this.name, test, function() 1930 { 1931 if (!this.should_have_interface_object()) { 1932 return; 1933 } 1934 1935 this.assert_interface_object_exists(); 1936 1937 if (this.is_callback()) { 1938 assert_false("prototype" in this.get_interface_object(), 1939 this.name + ' should not have a "prototype" property'); 1940 return; 1941 } 1942 1943 assert_own_property(this.get_interface_object(), "prototype", 1944 'interface "' + this.name + '" does not have own property "prototype"'); 1945 1946 // If the interface has any member declared with the [Unscopable] extended 1947 // attribute, then there must be a property on the interface prototype object 1948 // whose name is the @@unscopables symbol, which has the attributes 1949 // { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }, 1950 // and whose value is an object created as follows... 1951 var unscopables = this.get_unscopables().map(m => m.name); 1952 var proto = this.get_interface_object().prototype; 1953 if (unscopables.length != 0) { 1954 assert_own_property( 1955 proto, Symbol.unscopables, 1956 this.name + '.prototype should have an @@unscopables property'); 1957 var desc = Object.getOwnPropertyDescriptor(proto, Symbol.unscopables); 1958 assert_false("get" in desc, 1959 this.name + ".prototype[Symbol.unscopables] should not have a getter"); 1960 assert_false("set" in desc, this.name + ".prototype[Symbol.unscopables] should not have a setter"); 1961 assert_false(desc.writable, this.name + ".prototype[Symbol.unscopables] should not be writable"); 1962 assert_false(desc.enumerable, this.name + ".prototype[Symbol.unscopables] should not be enumerable"); 1963 assert_true(desc.configurable, this.name + ".prototype[Symbol.unscopables] should be configurable"); 1964 assert_equals(desc.value, proto[Symbol.unscopables], 1965 this.name + '.prototype[Symbol.unscopables] should be in the descriptor'); 1966 assert_equals(typeof desc.value, "object", 1967 this.name + '.prototype[Symbol.unscopables] should be an object'); 1968 assert_equals(Object.getPrototypeOf(desc.value), null, 1969 this.name + '.prototype[Symbol.unscopables] should have a null prototype'); 1970 assert_equals(Object.getOwnPropertySymbols(desc.value).length, 1971 0, 1972 this.name + '.prototype[Symbol.unscopables] should have the right number of symbol-named properties'); 1973 1974 // Check that we do not have _extra_ unscopables. Checking that we 1975 // have all the ones we should will happen in the per-member tests. 1976 var observed = Object.getOwnPropertyNames(desc.value); 1977 for (var prop of observed) { 1978 assert_not_equals(unscopables.indexOf(prop), 1979 -1, 1980 this.name + '.prototype[Symbol.unscopables] has unexpected property "' + prop + '"'); 1981 } 1982 } else { 1983 assert_equals(Object.getOwnPropertyDescriptor(this.get_interface_object().prototype, Symbol.unscopables), 1984 undefined, 1985 this.name + '.prototype should not have @@unscopables'); 1986 } 1987 }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s @@unscopables property'); 1988 }; 1989 1990 IdlInterface.prototype.test_immutable_prototype = function(type, obj) 1991 { 1992 if (typeof Object.setPrototypeOf !== "function") { 1993 return; 1994 } 1995 1996 subsetTestByKey(this.name, test, function(t) { 1997 var originalValue = Object.getPrototypeOf(obj); 1998 var newValue = Object.create(null); 1999 2000 t.add_cleanup(function() { 2001 try { 2002 Object.setPrototypeOf(obj, originalValue); 2003 } catch (err) {} 2004 }); 2005 2006 assert_throws_js(TypeError, function() { 2007 Object.setPrototypeOf(obj, newValue); 2008 }); 2009 2010 assert_equals( 2011 Object.getPrototypeOf(obj), 2012 originalValue, 2013 "original value not modified" 2014 ); 2015 }.bind(this), this.name + " interface: internal [[SetPrototypeOf]] method " + 2016 "of " + type + " - setting to a new value via Object.setPrototypeOf " + 2017 "should throw a TypeError"); 2018 2019 subsetTestByKey(this.name, test, function(t) { 2020 var originalValue = Object.getPrototypeOf(obj); 2021 var newValue = Object.create(null); 2022 2023 t.add_cleanup(function() { 2024 let setter = Object.getOwnPropertyDescriptor( 2025 Object.prototype, '__proto__' 2026 ).set; 2027 2028 try { 2029 setter.call(obj, originalValue); 2030 } catch (err) {} 2031 }); 2032 2033 // We need to find the actual setter for the '__proto__' property, so we 2034 // can determine the right global for it. Walk up the prototype chain 2035 // looking for that property until we find it. 2036 let setter; 2037 { 2038 let cur = obj; 2039 while (cur) { 2040 const desc = Object.getOwnPropertyDescriptor(cur, "__proto__"); 2041 if (desc) { 2042 setter = desc.set; 2043 break; 2044 } 2045 cur = Object.getPrototypeOf(cur); 2046 } 2047 } 2048 assert_throws_js(globalOf(setter).TypeError, function() { 2049 obj.__proto__ = newValue; 2050 }); 2051 2052 assert_equals( 2053 Object.getPrototypeOf(obj), 2054 originalValue, 2055 "original value not modified" 2056 ); 2057 }.bind(this), this.name + " interface: internal [[SetPrototypeOf]] method " + 2058 "of " + type + " - setting to a new value via __proto__ " + 2059 "should throw a TypeError"); 2060 2061 subsetTestByKey(this.name, test, function(t) { 2062 var originalValue = Object.getPrototypeOf(obj); 2063 var newValue = Object.create(null); 2064 2065 t.add_cleanup(function() { 2066 try { 2067 Reflect.setPrototypeOf(obj, originalValue); 2068 } catch (err) {} 2069 }); 2070 2071 assert_false(Reflect.setPrototypeOf(obj, newValue)); 2072 2073 assert_equals( 2074 Object.getPrototypeOf(obj), 2075 originalValue, 2076 "original value not modified" 2077 ); 2078 }.bind(this), this.name + " interface: internal [[SetPrototypeOf]] method " + 2079 "of " + type + " - setting to a new value via Reflect.setPrototypeOf " + 2080 "should return false"); 2081 2082 subsetTestByKey(this.name, test, function() { 2083 var originalValue = Object.getPrototypeOf(obj); 2084 2085 Object.setPrototypeOf(obj, originalValue); 2086 }.bind(this), this.name + " interface: internal [[SetPrototypeOf]] method " + 2087 "of " + type + " - setting to its original value via Object.setPrototypeOf " + 2088 "should not throw"); 2089 2090 subsetTestByKey(this.name, test, function() { 2091 var originalValue = Object.getPrototypeOf(obj); 2092 2093 obj.__proto__ = originalValue; 2094 }.bind(this), this.name + " interface: internal [[SetPrototypeOf]] method " + 2095 "of " + type + " - setting to its original value via __proto__ " + 2096 "should not throw"); 2097 2098 subsetTestByKey(this.name, test, function() { 2099 var originalValue = Object.getPrototypeOf(obj); 2100 2101 assert_true(Reflect.setPrototypeOf(obj, originalValue)); 2102 }.bind(this), this.name + " interface: internal [[SetPrototypeOf]] method " + 2103 "of " + type + " - setting to its original value via Reflect.setPrototypeOf " + 2104 "should return true"); 2105 }; 2106 2107 IdlInterface.prototype.test_member_const = function(member) 2108 { 2109 if (!this.has_constants()) { 2110 throw new IdlHarnessError("Internal error: test_member_const called without any constants"); 2111 } 2112 2113 subsetTestByKey(this.name, test, function() 2114 { 2115 this.assert_interface_object_exists(); 2116 2117 // "For each constant defined on an interface A, there must be 2118 // a corresponding property on the interface object, if it 2119 // exists." 2120 assert_own_property(this.get_interface_object(), member.name); 2121 // "The value of the property is that which is obtained by 2122 // converting the constant’s IDL value to an ECMAScript 2123 // value." 2124 assert_equals(this.get_interface_object()[member.name], constValue(member.value), 2125 "property has wrong value"); 2126 // "The property has attributes { [[Writable]]: false, 2127 // [[Enumerable]]: true, [[Configurable]]: false }." 2128 var desc = Object.getOwnPropertyDescriptor(this.get_interface_object(), member.name); 2129 assert_false("get" in desc, "property should not have a getter"); 2130 assert_false("set" in desc, "property should not have a setter"); 2131 assert_false(desc.writable, "property should not be writable"); 2132 assert_true(desc.enumerable, "property should be enumerable"); 2133 assert_false(desc.configurable, "property should not be configurable"); 2134 }.bind(this), this.name + " interface: constant " + member.name + " on interface object"); 2135 2136 // "In addition, a property with the same characteristics must 2137 // exist on the interface prototype object." 2138 subsetTestByKey(this.name, test, function() 2139 { 2140 this.assert_interface_object_exists(); 2141 2142 if (this.is_callback()) { 2143 assert_false("prototype" in this.get_interface_object(), 2144 this.name + ' should not have a "prototype" property'); 2145 return; 2146 } 2147 2148 assert_own_property(this.get_interface_object(), "prototype", 2149 'interface "' + this.name + '" does not have own property "prototype"'); 2150 2151 assert_own_property(this.get_interface_object().prototype, member.name); 2152 assert_equals(this.get_interface_object().prototype[member.name], constValue(member.value), 2153 "property has wrong value"); 2154 var desc = Object.getOwnPropertyDescriptor(this.get_interface_object(), member.name); 2155 assert_false("get" in desc, "property should not have a getter"); 2156 assert_false("set" in desc, "property should not have a setter"); 2157 assert_false(desc.writable, "property should not be writable"); 2158 assert_true(desc.enumerable, "property should be enumerable"); 2159 assert_false(desc.configurable, "property should not be configurable"); 2160 }.bind(this), this.name + " interface: constant " + member.name + " on interface prototype object"); 2161 }; 2162 2163 2164 IdlInterface.prototype.test_member_attribute = function(member) 2165 { 2166 if (!shouldRunSubTest(this.name)) { 2167 return; 2168 } 2169 var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: attribute " + member.name); 2170 a_test.step(function() 2171 { 2172 if (!this.should_have_interface_object()) { 2173 a_test.done(); 2174 return; 2175 } 2176 2177 this.assert_interface_object_exists(); 2178 assert_own_property(this.get_interface_object(), "prototype", 2179 'interface "' + this.name + '" does not have own property "prototype"'); 2180 2181 if (member.special === "static") { 2182 assert_own_property(this.get_interface_object(), member.name, 2183 "The interface object must have a property " + 2184 format_value(member.name)); 2185 a_test.done(); 2186 return; 2187 } 2188 2189 this.do_member_unscopable_asserts(member); 2190 2191 if (this.is_global()) { 2192 assert_own_property(self, member.name, 2193 "The global object must have a property " + 2194 format_value(member.name)); 2195 assert_false(member.name in this.get_interface_object().prototype, 2196 "The prototype object should not have a property " + 2197 format_value(member.name)); 2198 2199 var getter = Object.getOwnPropertyDescriptor(self, member.name).get; 2200 assert_equals(typeof(getter), "function", 2201 format_value(member.name) + " must have a getter"); 2202 2203 // Try/catch around the get here, since it can legitimately throw. 2204 // If it does, we obviously can't check for equality with direct 2205 // invocation of the getter. 2206 var gotValue; 2207 var propVal; 2208 try { 2209 propVal = self[member.name]; 2210 gotValue = true; 2211 } catch (e) { 2212 gotValue = false; 2213 } 2214 if (gotValue) { 2215 assert_equals(propVal, getter.call(undefined), 2216 "Gets on a global should not require an explicit this"); 2217 } 2218 2219 // do_interface_attribute_asserts must be the last thing we do, 2220 // since it will call done() on a_test. 2221 this.do_interface_attribute_asserts(self, member, a_test); 2222 } else { 2223 assert_true(member.name in this.get_interface_object().prototype, 2224 "The prototype object must have a property " + 2225 format_value(member.name)); 2226 2227 if (!member.has_extended_attribute("LegacyLenientThis")) { 2228 if (member.idlType.generic !== "Promise") { 2229 // this.get_interface_object() returns a thing in our global 2230 assert_throws_js(TypeError, function() { 2231 this.get_interface_object().prototype[member.name]; 2232 }.bind(this), "getting property on prototype object must throw TypeError"); 2233 // do_interface_attribute_asserts must be the last thing we 2234 // do, since it will call done() on a_test. 2235 this.do_interface_attribute_asserts(this.get_interface_object().prototype, member, a_test); 2236 } else { 2237 promise_rejects_js(a_test, TypeError, 2238 this.get_interface_object().prototype[member.name]) 2239 .then(a_test.step_func(function() { 2240 // do_interface_attribute_asserts must be the last 2241 // thing we do, since it will call done() on a_test. 2242 this.do_interface_attribute_asserts(this.get_interface_object().prototype, 2243 member, a_test); 2244 }.bind(this))); 2245 } 2246 } else { 2247 assert_equals(this.get_interface_object().prototype[member.name], undefined, 2248 "getting property on prototype object must return undefined"); 2249 // do_interface_attribute_asserts must be the last thing we do, 2250 // since it will call done() on a_test. 2251 this.do_interface_attribute_asserts(this.get_interface_object().prototype, member, a_test); 2252 } 2253 } 2254 }.bind(this)); 2255 }; 2256 2257 IdlInterface.prototype.test_member_operation = function(member) 2258 { 2259 if (!shouldRunSubTest(this.name)) { 2260 return; 2261 } 2262 var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: operation " + member); 2263 a_test.step(function() 2264 { 2265 // This function tests WebIDL as of 2015-12-29. 2266 // https://webidl.spec.whatwg.org/#es-operations 2267 2268 if (!this.should_have_interface_object()) { 2269 a_test.done(); 2270 return; 2271 } 2272 2273 this.assert_interface_object_exists(); 2274 2275 if (this.is_callback()) { 2276 assert_false("prototype" in this.get_interface_object(), 2277 this.name + ' should not have a "prototype" property'); 2278 a_test.done(); 2279 return; 2280 } 2281 2282 assert_own_property(this.get_interface_object(), "prototype", 2283 'interface "' + this.name + '" does not have own property "prototype"'); 2284 2285 // "For each unique identifier of an exposed operation defined on the 2286 // interface, there must exist a corresponding property, unless the 2287 // effective overload set for that identifier and operation and with an 2288 // argument count of 0 has no entries." 2289 2290 // TODO: Consider [Exposed]. 2291 2292 // "The location of the property is determined as follows:" 2293 var memberHolderObject; 2294 // "* If the operation is static, then the property exists on the 2295 // interface object." 2296 if (member.special === "static") { 2297 assert_own_property(this.get_interface_object(), member.name, 2298 "interface object missing static operation"); 2299 memberHolderObject = this.get_interface_object(); 2300 // "* Otherwise, [...] if the interface was declared with the [Global] 2301 // extended attribute, then the property exists 2302 // on every object that implements the interface." 2303 } else if (this.is_global()) { 2304 assert_own_property(self, member.name, 2305 "global object missing non-static operation"); 2306 memberHolderObject = self; 2307 // "* Otherwise, the property exists solely on the interface’s 2308 // interface prototype object." 2309 } else { 2310 assert_own_property(this.get_interface_object().prototype, member.name, 2311 "interface prototype object missing non-static operation"); 2312 memberHolderObject = this.get_interface_object().prototype; 2313 } 2314 this.do_member_unscopable_asserts(member); 2315 this.do_member_operation_asserts(memberHolderObject, member, a_test); 2316 }.bind(this)); 2317 }; 2318 2319 IdlInterface.prototype.do_member_unscopable_asserts = function(member) 2320 { 2321 // Check that if the member is unscopable then it's in the 2322 // @@unscopables object properly. 2323 if (!member.isUnscopable) { 2324 return; 2325 } 2326 2327 var unscopables = this.get_interface_object().prototype[Symbol.unscopables]; 2328 var prop = member.name; 2329 var propDesc = Object.getOwnPropertyDescriptor(unscopables, prop); 2330 assert_equals(typeof propDesc, "object", 2331 this.name + '.prototype[Symbol.unscopables].' + prop + ' must exist') 2332 assert_false("get" in propDesc, 2333 this.name + '.prototype[Symbol.unscopables].' + prop + ' must have no getter'); 2334 assert_false("set" in propDesc, 2335 this.name + '.prototype[Symbol.unscopables].' + prop + ' must have no setter'); 2336 assert_true(propDesc.writable, 2337 this.name + '.prototype[Symbol.unscopables].' + prop + ' must be writable'); 2338 assert_true(propDesc.enumerable, 2339 this.name + '.prototype[Symbol.unscopables].' + prop + ' must be enumerable'); 2340 assert_true(propDesc.configurable, 2341 this.name + '.prototype[Symbol.unscopables].' + prop + ' must be configurable'); 2342 assert_equals(propDesc.value, true, 2343 this.name + '.prototype[Symbol.unscopables].' + prop + ' must have the value `true`'); 2344 }; 2345 2346 IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject, member, a_test) 2347 { 2348 var done = a_test.done.bind(a_test); 2349 var operationUnforgeable = member.isUnforgeable; 2350 var desc = Object.getOwnPropertyDescriptor(memberHolderObject, member.name); 2351 // "The property has attributes { [[Writable]]: B, 2352 // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the 2353 // operation is unforgeable on the interface, and true otherwise". 2354 assert_false("get" in desc, "property should not have a getter"); 2355 assert_false("set" in desc, "property should not have a setter"); 2356 assert_equals(desc.writable, !operationUnforgeable, 2357 "property should be writable if and only if not unforgeable"); 2358 assert_true(desc.enumerable, "property should be enumerable"); 2359 assert_equals(desc.configurable, !operationUnforgeable, 2360 "property should be configurable if and only if not unforgeable"); 2361 // "The value of the property is a Function object whose 2362 // behavior is as follows . . ." 2363 assert_equals(typeof memberHolderObject[member.name], "function", 2364 "property must be a function"); 2365 2366 const operationOverloads = this.members.filter(function(m) { 2367 return m.type == "operation" && m.name == member.name && 2368 (m.special === "static") === (member.special === "static"); 2369 }); 2370 assert_equals( 2371 memberHolderObject[member.name].length, 2372 minOverloadLength(operationOverloads), 2373 "property has wrong .length"); 2374 assert_equals( 2375 memberHolderObject[member.name].name, 2376 member.name, 2377 "property has wrong .name"); 2378 2379 // Make some suitable arguments 2380 var args = member.arguments.map(function(arg) { 2381 return create_suitable_object(arg.idlType); 2382 }); 2383 2384 // "Let O be a value determined as follows: 2385 // ". . . 2386 // "Otherwise, throw a TypeError." 2387 // This should be hit if the operation is not static, there is 2388 // no [ImplicitThis] attribute, and the this value is null. 2389 // 2390 // TODO: We currently ignore the [ImplicitThis] case. Except we manually 2391 // check for globals, since otherwise we'll invoke window.close(). And we 2392 // have to skip this test for anything that on the proto chain of "self", 2393 // since that does in fact have implicit-this behavior. 2394 if (member.special !== "static") { 2395 var cb; 2396 if (!this.is_global() && 2397 memberHolderObject[member.name] != self[member.name]) 2398 { 2399 cb = awaitNCallbacks(2, done); 2400 throwOrReject(a_test, member, memberHolderObject[member.name], null, args, 2401 "calling operation with this = null didn't throw TypeError", cb); 2402 } else { 2403 cb = awaitNCallbacks(1, done); 2404 } 2405 2406 // ". . . If O is not null and is also not a platform object 2407 // that implements interface I, throw a TypeError." 2408 // 2409 // TODO: Test a platform object that implements some other 2410 // interface. (Have to be sure to get inheritance right.) 2411 throwOrReject(a_test, member, memberHolderObject[member.name], {}, args, 2412 "calling operation with this = {} didn't throw TypeError", cb); 2413 } else { 2414 done(); 2415 } 2416 } 2417 2418 IdlInterface.prototype.test_to_json_operation = function(desc, memberHolderObject, member) { 2419 var instanceName = memberHolderObject && memberHolderObject.constructor.name 2420 || member.name + " object"; 2421 if (member.has_extended_attribute("Default")) { 2422 subsetTestByKey(this.name, test, function() { 2423 var map = this.default_to_json_operation(); 2424 var json = memberHolderObject.toJSON(); 2425 map.forEach(function(type, k) { 2426 assert_true(k in json, "property " + JSON.stringify(k) + " should be present in the output of " + this.name + ".prototype.toJSON()"); 2427 var descriptor = Object.getOwnPropertyDescriptor(json, k); 2428 assert_true(descriptor.writable, "property " + k + " should be writable"); 2429 assert_true(descriptor.configurable, "property " + k + " should be configurable"); 2430 assert_true(descriptor.enumerable, "property " + k + " should be enumerable"); 2431 this.array.assert_type_is(json[k], type); 2432 delete json[k]; 2433 }, this); 2434 }.bind(this), this.name + " interface: default toJSON operation on " + desc); 2435 } else { 2436 subsetTestByKey(this.name, test, function() { 2437 assert_true(this.array.is_json_type(member.idlType), JSON.stringify(member.idlType) + " is not an appropriate return value for the toJSON operation of " + instanceName); 2438 this.array.assert_type_is(memberHolderObject.toJSON(), member.idlType); 2439 }.bind(this), this.name + " interface: toJSON operation on " + desc); 2440 } 2441 }; 2442 2443 IdlInterface.prototype.test_member_maplike = function(member) { 2444 subsetTestByKey(this.name, test, () => { 2445 const proto = this.get_interface_object().prototype; 2446 2447 const methods = [ 2448 ["entries", 0], 2449 ["keys", 0], 2450 ["values", 0], 2451 ["forEach", 1], 2452 ["get", 1], 2453 ["has", 1] 2454 ]; 2455 if (!member.readonly) { 2456 methods.push( 2457 ["set", 2], 2458 ["delete", 1], 2459 ["clear", 0] 2460 ); 2461 } 2462 2463 for (const [name, length] of methods) { 2464 const desc = Object.getOwnPropertyDescriptor(proto, name); 2465 assert_equals(typeof desc.value, "function", `${name} should be a function`); 2466 assert_equals(desc.enumerable, true, `${name} enumerable`); 2467 assert_equals(desc.configurable, true, `${name} configurable`); 2468 assert_equals(desc.writable, true, `${name} writable`); 2469 assert_equals(desc.value.length, length, `${name} function object length should be ${length}`); 2470 assert_equals(desc.value.name, name, `${name} function object should have the right name`); 2471 } 2472 2473 const iteratorDesc = Object.getOwnPropertyDescriptor(proto, Symbol.iterator); 2474 assert_equals(iteratorDesc.value, proto.entries, `@@iterator should equal entries`); 2475 assert_equals(iteratorDesc.enumerable, false, `@@iterator enumerable`); 2476 assert_equals(iteratorDesc.configurable, true, `@@iterator configurable`); 2477 assert_equals(iteratorDesc.writable, true, `@@iterator writable`); 2478 2479 const sizeDesc = Object.getOwnPropertyDescriptor(proto, "size"); 2480 assert_equals(typeof sizeDesc.get, "function", `size getter should be a function`); 2481 assert_equals(sizeDesc.set, undefined, `size should not have a setter`); 2482 assert_equals(sizeDesc.enumerable, true, `size enumerable`); 2483 assert_equals(sizeDesc.configurable, true, `size configurable`); 2484 assert_equals(sizeDesc.get.length, 0, `size getter length`); 2485 assert_equals(sizeDesc.get.name, "get size", `size getter name`); 2486 }, `${this.name} interface: maplike<${member.idlType.map(t => t.idlType).join(", ")}>`); 2487 }; 2488 2489 IdlInterface.prototype.test_member_setlike = function(member) { 2490 subsetTestByKey(this.name, test, () => { 2491 const proto = this.get_interface_object().prototype; 2492 2493 const methods = [ 2494 ["entries", 0], 2495 ["keys", 0], 2496 ["values", 0], 2497 ["forEach", 1], 2498 ["has", 1] 2499 ]; 2500 if (!member.readonly) { 2501 methods.push( 2502 ["add", 1], 2503 ["delete", 1], 2504 ["clear", 0] 2505 ); 2506 } 2507 2508 for (const [name, length] of methods) { 2509 const desc = Object.getOwnPropertyDescriptor(proto, name); 2510 assert_equals(typeof desc.value, "function", `${name} should be a function`); 2511 assert_equals(desc.enumerable, true, `${name} enumerable`); 2512 assert_equals(desc.configurable, true, `${name} configurable`); 2513 assert_equals(desc.writable, true, `${name} writable`); 2514 assert_equals(desc.value.length, length, `${name} function object length should be ${length}`); 2515 assert_equals(desc.value.name, name, `${name} function object should have the right name`); 2516 } 2517 2518 const iteratorDesc = Object.getOwnPropertyDescriptor(proto, Symbol.iterator); 2519 assert_equals(iteratorDesc.value, proto.values, `@@iterator should equal values`); 2520 assert_equals(iteratorDesc.enumerable, false, `@@iterator enumerable`); 2521 assert_equals(iteratorDesc.configurable, true, `@@iterator configurable`); 2522 assert_equals(iteratorDesc.writable, true, `@@iterator writable`); 2523 2524 const sizeDesc = Object.getOwnPropertyDescriptor(proto, "size"); 2525 assert_equals(typeof sizeDesc.get, "function", `size getter should be a function`); 2526 assert_equals(sizeDesc.set, undefined, `size should not have a setter`); 2527 assert_equals(sizeDesc.enumerable, true, `size enumerable`); 2528 assert_equals(sizeDesc.configurable, true, `size configurable`); 2529 assert_equals(sizeDesc.get.length, 0, `size getter length`); 2530 assert_equals(sizeDesc.get.name, "get size", `size getter name`); 2531 }, `${this.name} interface: setlike<${member.idlType.map(t => t.idlType).join(", ")}>`); 2532 }; 2533 2534 IdlInterface.prototype.test_member_iterable = function(member) { 2535 subsetTestByKey(this.name, test, () => { 2536 const isPairIterator = member.idlType.length === 2; 2537 const proto = this.get_interface_object().prototype; 2538 2539 const methods = [ 2540 ["entries", 0], 2541 ["keys", 0], 2542 ["values", 0], 2543 ["forEach", 1] 2544 ]; 2545 2546 for (const [name, length] of methods) { 2547 const desc = Object.getOwnPropertyDescriptor(proto, name); 2548 assert_equals(typeof desc.value, "function", `${name} should be a function`); 2549 assert_equals(desc.enumerable, true, `${name} enumerable`); 2550 assert_equals(desc.configurable, true, `${name} configurable`); 2551 assert_equals(desc.writable, true, `${name} writable`); 2552 assert_equals(desc.value.length, length, `${name} function object length should be ${length}`); 2553 assert_equals(desc.value.name, name, `${name} function object should have the right name`); 2554 2555 if (!isPairIterator) { 2556 assert_equals(desc.value, Array.prototype[name], `${name} equality with Array.prototype version`); 2557 } 2558 } 2559 2560 const iteratorDesc = Object.getOwnPropertyDescriptor(proto, Symbol.iterator); 2561 assert_equals(iteratorDesc.enumerable, false, `@@iterator enumerable`); 2562 assert_equals(iteratorDesc.configurable, true, `@@iterator configurable`); 2563 assert_equals(iteratorDesc.writable, true, `@@iterator writable`); 2564 2565 if (isPairIterator) { 2566 assert_equals(iteratorDesc.value, proto.entries, `@@iterator equality with entries`); 2567 } else { 2568 assert_equals(iteratorDesc.value, Array.prototype[Symbol.iterator], `@@iterator equality with Array.prototype version`); 2569 } 2570 }, `${this.name} interface: iterable<${member.idlType.map(t => t.idlType).join(", ")}>`); 2571 }; 2572 2573 IdlInterface.prototype.test_member_async_iterable = function(member) { 2574 subsetTestByKey(this.name, test, () => { 2575 const isPairIterator = member.idlType.length === 2; 2576 const proto = this.get_interface_object().prototype; 2577 2578 // Note that although the spec allows arguments, which will be passed to the @@asyncIterator 2579 // method (which is either values or entries), those arguments must always be optional. So 2580 // length of 0 is still correct for values and entries. 2581 const methods = [ 2582 ["values", 0], 2583 ]; 2584 2585 if (isPairIterator) { 2586 methods.push( 2587 ["entries", 0], 2588 ["keys", 0] 2589 ); 2590 } 2591 2592 for (const [name, length] of methods) { 2593 const desc = Object.getOwnPropertyDescriptor(proto, name); 2594 assert_equals(typeof desc.value, "function", `${name} should be a function`); 2595 assert_equals(desc.enumerable, true, `${name} enumerable`); 2596 assert_equals(desc.configurable, true, `${name} configurable`); 2597 assert_equals(desc.writable, true, `${name} writable`); 2598 assert_equals(desc.value.length, length, `${name} function object length should be ${length}`); 2599 assert_equals(desc.value.name, name, `${name} function object should have the right name`); 2600 } 2601 2602 const iteratorDesc = Object.getOwnPropertyDescriptor(proto, Symbol.asyncIterator); 2603 assert_equals(iteratorDesc.enumerable, false, `@@iterator enumerable`); 2604 assert_equals(iteratorDesc.configurable, true, `@@iterator configurable`); 2605 assert_equals(iteratorDesc.writable, true, `@@iterator writable`); 2606 2607 if (isPairIterator) { 2608 assert_equals(iteratorDesc.value, proto.entries, `@@iterator equality with entries`); 2609 } else { 2610 assert_equals(iteratorDesc.value, proto.values, `@@iterator equality with values`); 2611 } 2612 }, `${this.name} interface: async iterable<${member.idlType.map(t => t.idlType).join(", ")}>`); 2613 }; 2614 2615 IdlInterface.prototype.test_member_stringifier = function(member) 2616 { 2617 subsetTestByKey(this.name, test, function() 2618 { 2619 if (!this.should_have_interface_object()) { 2620 return; 2621 } 2622 2623 this.assert_interface_object_exists(); 2624 2625 if (this.is_callback()) { 2626 assert_false("prototype" in this.get_interface_object(), 2627 this.name + ' should not have a "prototype" property'); 2628 return; 2629 } 2630 2631 assert_own_property(this.get_interface_object(), "prototype", 2632 'interface "' + this.name + '" does not have own property "prototype"'); 2633 2634 // ". . . the property exists on the interface prototype object." 2635 var interfacePrototypeObject = this.get_interface_object().prototype; 2636 assert_own_property(interfacePrototypeObject, "toString", 2637 "interface prototype object missing non-static operation"); 2638 2639 var stringifierUnforgeable = member.isUnforgeable; 2640 var desc = Object.getOwnPropertyDescriptor(interfacePrototypeObject, "toString"); 2641 // "The property has attributes { [[Writable]]: B, 2642 // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the 2643 // stringifier is unforgeable on the interface, and true otherwise." 2644 assert_false("get" in desc, "property should not have a getter"); 2645 assert_false("set" in desc, "property should not have a setter"); 2646 assert_equals(desc.writable, !stringifierUnforgeable, 2647 "property should be writable if and only if not unforgeable"); 2648 assert_true(desc.enumerable, "property should be enumerable"); 2649 assert_equals(desc.configurable, !stringifierUnforgeable, 2650 "property should be configurable if and only if not unforgeable"); 2651 // "The value of the property is a Function object, which behaves as 2652 // follows . . ." 2653 assert_equals(typeof interfacePrototypeObject.toString, "function", 2654 "property must be a function"); 2655 // "The value of the Function object’s “length” property is the Number 2656 // value 0." 2657 assert_equals(interfacePrototypeObject.toString.length, 0, 2658 "property has wrong .length"); 2659 2660 // "Let O be the result of calling ToObject on the this value." 2661 assert_throws_js(globalOf(interfacePrototypeObject.toString).TypeError, function() { 2662 interfacePrototypeObject.toString.apply(null, []); 2663 }, "calling stringifier with this = null didn't throw TypeError"); 2664 2665 // "If O is not an object that implements the interface on which the 2666 // stringifier was declared, then throw a TypeError." 2667 // 2668 // TODO: Test a platform object that implements some other 2669 // interface. (Have to be sure to get inheritance right.) 2670 assert_throws_js(globalOf(interfacePrototypeObject.toString).TypeError, function() { 2671 interfacePrototypeObject.toString.apply({}, []); 2672 }, "calling stringifier with this = {} didn't throw TypeError"); 2673 }.bind(this), this.name + " interface: stringifier"); 2674 }; 2675 2676 IdlInterface.prototype.test_members = function() 2677 { 2678 var unexposed_members = new Set(); 2679 for (var i = 0; i < this.members.length; i++) 2680 { 2681 var member = this.members[i]; 2682 if (member.untested) { 2683 continue; 2684 } 2685 2686 if (!exposed_in(exposure_set(member, this.exposureSet))) { 2687 if (!unexposed_members.has(member.name)) { 2688 unexposed_members.add(member.name); 2689 subsetTestByKey(this.name, test, function() { 2690 // It's not exposed, so we shouldn't find it anywhere. 2691 assert_false(member.name in this.get_interface_object(), 2692 "The interface object must not have a property " + 2693 format_value(member.name)); 2694 assert_false(member.name in this.get_interface_object().prototype, 2695 "The prototype object must not have a property " + 2696 format_value(member.name)); 2697 }.bind(this), this.name + " interface: member " + member.name); 2698 } 2699 continue; 2700 } 2701 2702 switch (member.type) { 2703 case "const": 2704 this.test_member_const(member); 2705 break; 2706 2707 case "attribute": 2708 // For unforgeable attributes, we do the checks in 2709 // test_interface_of instead. 2710 if (!member.isUnforgeable) 2711 { 2712 this.test_member_attribute(member); 2713 } 2714 if (member.special === "stringifier") { 2715 this.test_member_stringifier(member); 2716 } 2717 break; 2718 2719 case "operation": 2720 // TODO: Need to correctly handle multiple operations with the same 2721 // identifier. 2722 // For unforgeable operations, we do the checks in 2723 // test_interface_of instead. 2724 if (member.name) { 2725 if (!member.isUnforgeable) 2726 { 2727 this.test_member_operation(member); 2728 } 2729 } else if (member.special === "stringifier") { 2730 this.test_member_stringifier(member); 2731 } 2732 break; 2733 2734 case "iterable": 2735 if (member.async) { 2736 this.test_member_async_iterable(member); 2737 } else { 2738 this.test_member_iterable(member); 2739 } 2740 break; 2741 case "maplike": 2742 this.test_member_maplike(member); 2743 break; 2744 case "setlike": 2745 this.test_member_setlike(member); 2746 break; 2747 default: 2748 // TODO: check more member types. 2749 break; 2750 } 2751 } 2752 }; 2753 2754 IdlInterface.prototype.test_object = function(desc) 2755 { 2756 var obj, exception = null; 2757 try 2758 { 2759 obj = eval(desc); 2760 } 2761 catch(e) 2762 { 2763 exception = e; 2764 } 2765 2766 var expected_typeof; 2767 if (this.name == "HTMLAllCollection") 2768 { 2769 // Result of [[IsHTMLDDA]] slot 2770 expected_typeof = "undefined"; 2771 } 2772 else 2773 { 2774 expected_typeof = "object"; 2775 } 2776 2777 if (this.is_callback()) { 2778 assert_equals(exception, null, "Unexpected exception when evaluating object"); 2779 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 2780 } else { 2781 this.test_primary_interface_of(desc, obj, exception, expected_typeof); 2782 2783 var current_interface = this; 2784 while (current_interface) 2785 { 2786 if (!(current_interface.name in this.array.members)) 2787 { 2788 throw new IdlHarnessError("Interface " + current_interface.name + " not found (inherited by " + this.name + ")"); 2789 } 2790 if (current_interface.prevent_multiple_testing && current_interface.already_tested) 2791 { 2792 return; 2793 } 2794 current_interface.test_interface_of(desc, obj, exception, expected_typeof); 2795 current_interface = this.array.members[current_interface.base]; 2796 } 2797 } 2798 }; 2799 2800 IdlInterface.prototype.test_primary_interface_of = function(desc, obj, exception, expected_typeof) 2801 { 2802 // Only the object itself, not its members, are tested here, so if the 2803 // interface is untested, there is nothing to do. 2804 if (this.untested) 2805 { 2806 return; 2807 } 2808 2809 // "The internal [[SetPrototypeOf]] method of every platform object that 2810 // implements an interface with the [Global] extended 2811 // attribute must execute the same algorithm as is defined for the 2812 // [[SetPrototypeOf]] internal method of an immutable prototype exotic 2813 // object." 2814 // https://webidl.spec.whatwg.org/#platform-object-setprototypeof 2815 if (this.is_global()) 2816 { 2817 this.test_immutable_prototype("global platform object", obj); 2818 } 2819 2820 2821 // We can't easily test that its prototype is correct if there's no 2822 // interface object, or the object is from a different global environment 2823 // (not instanceof Object). TODO: test in this case that its prototype at 2824 // least looks correct, even if we can't test that it's actually correct. 2825 if (this.should_have_interface_object() 2826 && (typeof obj != expected_typeof || obj instanceof Object)) 2827 { 2828 subsetTestByKey(this.name, test, function() 2829 { 2830 assert_equals(exception, null, "Unexpected exception when evaluating object"); 2831 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 2832 this.assert_interface_object_exists(); 2833 assert_own_property(this.get_interface_object(), "prototype", 2834 'interface "' + this.name + '" does not have own property "prototype"'); 2835 2836 // "The value of the internal [[Prototype]] property of the 2837 // platform object is the interface prototype object of the primary 2838 // interface from the platform object’s associated global 2839 // environment." 2840 assert_equals(Object.getPrototypeOf(obj), 2841 this.get_interface_object().prototype, 2842 desc + "'s prototype is not " + this.name + ".prototype"); 2843 }.bind(this), this.name + " must be primary interface of " + desc); 2844 } 2845 2846 // "The class string of a platform object that implements one or more 2847 // interfaces must be the qualified name of the primary interface of the 2848 // platform object." 2849 subsetTestByKey(this.name, test, function() 2850 { 2851 assert_equals(exception, null, "Unexpected exception when evaluating object"); 2852 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 2853 assert_class_string(obj, this.get_qualified_name(), "class string of " + desc); 2854 if (!this.has_stringifier()) 2855 { 2856 assert_equals(String(obj), "[object " + this.get_qualified_name() + "]", "String(" + desc + ")"); 2857 } 2858 }.bind(this), "Stringification of " + desc); 2859 }; 2860 2861 IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expected_typeof) 2862 { 2863 // TODO: Indexed and named properties, more checks on interface members 2864 this.already_tested = true; 2865 if (!shouldRunSubTest(this.name)) { 2866 return; 2867 } 2868 2869 var unexposed_properties = new Set(); 2870 for (var i = 0; i < this.members.length; i++) 2871 { 2872 var member = this.members[i]; 2873 if (member.untested) { 2874 continue; 2875 } 2876 if (!exposed_in(exposure_set(member, this.exposureSet))) 2877 { 2878 if (!unexposed_properties.has(member.name)) 2879 { 2880 unexposed_properties.add(member.name); 2881 subsetTestByKey(this.name, test, function() { 2882 assert_equals(exception, null, "Unexpected exception when evaluating object"); 2883 assert_false(member.name in obj); 2884 }.bind(this), this.name + " interface: " + desc + ' must not have property "' + member.name + '"'); 2885 } 2886 continue; 2887 } 2888 if (member.type == "attribute" && member.isUnforgeable) 2889 { 2890 var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: " + desc + ' must have own property "' + member.name + '"'); 2891 a_test.step(function() { 2892 assert_equals(exception, null, "Unexpected exception when evaluating object"); 2893 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 2894 // Call do_interface_attribute_asserts last, since it will call a_test.done() 2895 this.do_interface_attribute_asserts(obj, member, a_test); 2896 }.bind(this)); 2897 } 2898 else if (member.type == "operation" && 2899 member.name && 2900 member.isUnforgeable) 2901 { 2902 var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: " + desc + ' must have own property "' + member.name + '"'); 2903 a_test.step(function() 2904 { 2905 assert_equals(exception, null, "Unexpected exception when evaluating object"); 2906 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 2907 assert_own_property(obj, member.name, 2908 "Doesn't have the unforgeable operation property"); 2909 this.do_member_operation_asserts(obj, member, a_test); 2910 }.bind(this)); 2911 } 2912 else if ((member.type == "const" 2913 || member.type == "attribute" 2914 || member.type == "operation") 2915 && member.name) 2916 { 2917 subsetTestByKey(this.name, test, function() 2918 { 2919 assert_equals(exception, null, "Unexpected exception when evaluating object"); 2920 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 2921 if (member.special !== "static") { 2922 if (!this.is_global()) { 2923 assert_inherits(obj, member.name); 2924 } else { 2925 assert_own_property(obj, member.name); 2926 } 2927 2928 if (member.type == "const") 2929 { 2930 assert_equals(obj[member.name], constValue(member.value)); 2931 } 2932 if (member.type == "attribute") 2933 { 2934 // Attributes are accessor properties, so they might 2935 // legitimately throw an exception rather than returning 2936 // anything. 2937 var property, thrown = false; 2938 try 2939 { 2940 property = obj[member.name]; 2941 } 2942 catch (e) 2943 { 2944 thrown = true; 2945 } 2946 if (!thrown) 2947 { 2948 if (this.name == "Document" && member.name == "all") 2949 { 2950 // Result of [[IsHTMLDDA]] slot 2951 assert_equals(typeof property, "undefined"); 2952 } 2953 else 2954 { 2955 this.array.assert_type_is(property, member.idlType); 2956 } 2957 } 2958 } 2959 if (member.type == "operation") 2960 { 2961 assert_equals(typeof obj[member.name], "function"); 2962 } 2963 } 2964 }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + member + '" with the proper type'); 2965 } 2966 // TODO: This is wrong if there are multiple operations with the same 2967 // identifier. 2968 // TODO: Test passing arguments of the wrong type. 2969 if (member.type == "operation" && member.name && member.arguments.length) 2970 { 2971 var description = 2972 this.name + " interface: calling " + member + " on " + desc + 2973 " with too few arguments must throw TypeError"; 2974 var a_test = subsetTestByKey(this.name, async_test, description); 2975 a_test.step(function() 2976 { 2977 assert_equals(exception, null, "Unexpected exception when evaluating object"); 2978 assert_equals(typeof obj, expected_typeof, "wrong typeof object"); 2979 var fn; 2980 if (member.special !== "static") { 2981 if (!this.is_global() && !member.isUnforgeable) { 2982 assert_inherits(obj, member.name); 2983 } else { 2984 assert_own_property(obj, member.name); 2985 } 2986 fn = obj[member.name]; 2987 } 2988 else 2989 { 2990 assert_own_property(obj.constructor, member.name, "interface object must have static operation as own property"); 2991 fn = obj.constructor[member.name]; 2992 } 2993 2994 var minLength = minOverloadLength(this.members.filter(function(m) { 2995 return m.type == "operation" && m.name == member.name; 2996 })); 2997 var args = []; 2998 var cb = awaitNCallbacks(minLength, a_test.done.bind(a_test)); 2999 for (var i = 0; i < minLength; i++) { 3000 throwOrReject(a_test, member, fn, obj, args, "Called with " + i + " arguments", cb); 3001 3002 args.push(create_suitable_object(member.arguments[i].idlType)); 3003 } 3004 if (minLength === 0) { 3005 cb(); 3006 } 3007 }.bind(this)); 3008 } 3009 3010 if (member.is_to_json_regular_operation()) { 3011 this.test_to_json_operation(desc, obj, member); 3012 } 3013 } 3014 }; 3015 3016 IdlInterface.prototype.has_stringifier = function() 3017 { 3018 if (this.name === "DOMException") { 3019 // toString is inherited from Error, so don't assume we have the 3020 // default stringifer 3021 return true; 3022 } 3023 if (this.members.some(function(member) { return member.special === "stringifier"; })) { 3024 return true; 3025 } 3026 if (this.base && 3027 this.array.members[this.base].has_stringifier()) { 3028 return true; 3029 } 3030 return false; 3031 }; 3032 3033 IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_test) 3034 { 3035 // This function tests WebIDL as of 2015-01-27. 3036 // TODO: Consider [Exposed]. 3037 3038 // This is called by test_member_attribute() with the prototype as obj if 3039 // it is not a global, and the global otherwise, and by test_interface_of() 3040 // with the object as obj. 3041 3042 var pendingPromises = []; 3043 3044 // "The name of the property is the identifier of the attribute." 3045 assert_own_property(obj, member.name); 3046 3047 // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]: 3048 // true, [[Configurable]]: configurable }, where: 3049 // "configurable is false if the attribute was declared with the 3050 // [LegacyUnforgeable] extended attribute and true otherwise; 3051 // "G is the attribute getter, defined below; and 3052 // "S is the attribute setter, also defined below." 3053 var desc = Object.getOwnPropertyDescriptor(obj, member.name); 3054 assert_false("value" in desc, 'property descriptor should not have a "value" field'); 3055 assert_false("writable" in desc, 'property descriptor should not have a "writable" field'); 3056 assert_true(desc.enumerable, "property should be enumerable"); 3057 if (member.isUnforgeable) 3058 { 3059 assert_false(desc.configurable, "[LegacyUnforgeable] property must not be configurable"); 3060 } 3061 else 3062 { 3063 assert_true(desc.configurable, "property must be configurable"); 3064 } 3065 3066 3067 // "The attribute getter is a Function object whose behavior when invoked 3068 // is as follows:" 3069 assert_equals(typeof desc.get, "function", "getter must be Function"); 3070 3071 // "If the attribute is a regular attribute, then:" 3072 if (member.special !== "static") { 3073 // "If O is not a platform object that implements I, then: 3074 // "If the attribute was specified with the [LegacyLenientThis] extended 3075 // attribute, then return undefined. 3076 // "Otherwise, throw a TypeError." 3077 if (!member.has_extended_attribute("LegacyLenientThis")) { 3078 if (member.idlType.generic !== "Promise") { 3079 assert_throws_js(globalOf(desc.get).TypeError, function() { 3080 desc.get.call({}); 3081 }.bind(this), "calling getter on wrong object type must throw TypeError"); 3082 } else { 3083 pendingPromises.push( 3084 promise_rejects_js(a_test, TypeError, desc.get.call({}), 3085 "calling getter on wrong object type must reject the return promise with TypeError")); 3086 } 3087 } else { 3088 assert_equals(desc.get.call({}), undefined, 3089 "calling getter on wrong object type must return undefined"); 3090 } 3091 } 3092 3093 // "The value of the Function object’s “length” property is the Number 3094 // value 0." 3095 assert_equals(desc.get.length, 0, "getter length must be 0"); 3096 3097 // "Let name be the string "get " prepended to attribute’s identifier." 3098 // "Perform ! SetFunctionName(F, name)." 3099 assert_equals(desc.get.name, "get " + member.name, 3100 "getter must have the name 'get " + member.name + "'"); 3101 3102 3103 // TODO: Test calling setter on the interface prototype (should throw 3104 // TypeError in most cases). 3105 if (member.readonly 3106 && !member.has_extended_attribute("LegacyLenientSetter") 3107 && !member.has_extended_attribute("PutForwards") 3108 && !member.has_extended_attribute("Replaceable")) 3109 { 3110 // "The attribute setter is undefined if the attribute is declared 3111 // readonly and has neither a [PutForwards] nor a [Replaceable] 3112 // extended attribute declared on it." 3113 assert_equals(desc.set, undefined, "setter must be undefined for readonly attributes"); 3114 } 3115 else 3116 { 3117 // "Otherwise, it is a Function object whose behavior when 3118 // invoked is as follows:" 3119 assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes"); 3120 3121 // "If the attribute is a regular attribute, then:" 3122 if (member.special !== "static") { 3123 // "If /validThis/ is false and the attribute was not specified 3124 // with the [LegacyLenientThis] extended attribute, then throw a 3125 // TypeError." 3126 // "If the attribute is declared with a [Replaceable] extended 3127 // attribute, then: ..." 3128 // "If validThis is false, then return." 3129 if (!member.has_extended_attribute("LegacyLenientThis")) { 3130 assert_throws_js(globalOf(desc.set).TypeError, function() { 3131 desc.set.call({}); 3132 }.bind(this), "calling setter on wrong object type must throw TypeError"); 3133 } else { 3134 assert_equals(desc.set.call({}), undefined, 3135 "calling setter on wrong object type must return undefined"); 3136 } 3137 } 3138 3139 // "The value of the Function object’s “length” property is the Number 3140 // value 1." 3141 assert_equals(desc.set.length, 1, "setter length must be 1"); 3142 3143 // "Let name be the string "set " prepended to id." 3144 // "Perform ! SetFunctionName(F, name)." 3145 assert_equals(desc.set.name, "set " + member.name, 3146 "The attribute setter must have the name 'set " + member.name + "'"); 3147 } 3148 3149 Promise.all(pendingPromises).then(a_test.done.bind(a_test)); 3150 } 3151 3152 /// IdlInterfaceMember /// 3153 function IdlInterfaceMember(obj) 3154 { 3155 /** 3156 * obj is an object produced by the WebIDLParser.js "ifMember" production. 3157 * We just forward all properties to this object without modification, 3158 * except for special extAttrs handling. 3159 */ 3160 for (var k in obj.toJSON()) 3161 { 3162 this[k] = obj[k]; 3163 } 3164 if (!("extAttrs" in this)) 3165 { 3166 this.extAttrs = []; 3167 } 3168 3169 this.isUnforgeable = this.has_extended_attribute("LegacyUnforgeable"); 3170 this.isUnscopable = this.has_extended_attribute("Unscopable"); 3171 } 3172 3173 IdlInterfaceMember.prototype = Object.create(IdlObject.prototype); 3174 3175 IdlInterfaceMember.prototype.toJSON = function() { 3176 return this; 3177 }; 3178 3179 IdlInterfaceMember.prototype.is_to_json_regular_operation = function() { 3180 return this.type == "operation" && this.special !== "static" && this.name == "toJSON"; 3181 }; 3182 3183 IdlInterfaceMember.prototype.toString = function() { 3184 function formatType(type) { 3185 var result; 3186 if (type.generic) { 3187 result = type.generic + "<" + type.idlType.map(formatType).join(", ") + ">"; 3188 } else if (type.union) { 3189 result = "(" + type.subtype.map(formatType).join(" or ") + ")"; 3190 } else { 3191 result = type.idlType; 3192 } 3193 if (type.nullable) { 3194 result += "?" 3195 } 3196 return result; 3197 } 3198 3199 if (this.type === "operation") { 3200 var args = this.arguments.map(function(m) { 3201 return [ 3202 m.optional ? "optional " : "", 3203 formatType(m.idlType), 3204 m.variadic ? "..." : "", 3205 ].join(""); 3206 }).join(", "); 3207 return this.name + "(" + args + ")"; 3208 } 3209 3210 return this.name; 3211 } 3212 3213 /// Internal helper functions /// 3214 function create_suitable_object(type) 3215 { 3216 /** 3217 * type is an object produced by the WebIDLParser.js "type" production. We 3218 * return a JavaScript value that matches the type, if we can figure out 3219 * how. 3220 */ 3221 if (type.nullable) 3222 { 3223 return null; 3224 } 3225 switch (type.idlType) 3226 { 3227 case "any": 3228 case "boolean": 3229 return true; 3230 3231 case "byte": case "octet": case "short": case "unsigned short": 3232 case "long": case "unsigned long": case "long long": 3233 case "unsigned long long": case "float": case "double": 3234 case "unrestricted float": case "unrestricted double": 3235 return 7; 3236 3237 case "DOMString": 3238 case "ByteString": 3239 case "USVString": 3240 return "foo"; 3241 3242 case "object": 3243 return {a: "b"}; 3244 3245 case "Node": 3246 return document.createTextNode("abc"); 3247 } 3248 return null; 3249 } 3250 3251 /// IdlEnum /// 3252 // Used for IdlArray.prototype.assert_type_is 3253 function IdlEnum(obj) 3254 { 3255 /** 3256 * obj is an object produced by the WebIDLParser.js "dictionary" 3257 * production. 3258 */ 3259 3260 /** Self-explanatory. */ 3261 this.name = obj.name; 3262 3263 /** An array of values produced by the "enum" production. */ 3264 this.values = obj.values; 3265 3266 } 3267 3268 IdlEnum.prototype = Object.create(IdlObject.prototype); 3269 3270 /// IdlCallback /// 3271 // Used for IdlArray.prototype.assert_type_is 3272 function IdlCallback(obj) 3273 { 3274 /** 3275 * obj is an object produced by the WebIDLParser.js "callback" 3276 * production. 3277 */ 3278 3279 /** Self-explanatory. */ 3280 this.name = obj.name; 3281 3282 /** Arguments for the callback. */ 3283 this.arguments = obj.arguments; 3284 } 3285 3286 IdlCallback.prototype = Object.create(IdlObject.prototype); 3287 3288 /// IdlTypedef /// 3289 // Used for IdlArray.prototype.assert_type_is 3290 function IdlTypedef(obj) 3291 { 3292 /** 3293 * obj is an object produced by the WebIDLParser.js "typedef" 3294 * production. 3295 */ 3296 3297 /** Self-explanatory. */ 3298 this.name = obj.name; 3299 3300 /** The idlType that we are supposed to be typedeffing to. */ 3301 this.idlType = obj.idlType; 3302 3303 } 3304 3305 IdlTypedef.prototype = Object.create(IdlObject.prototype); 3306 3307 /// IdlNamespace /// 3308 function IdlNamespace(obj) 3309 { 3310 this.name = obj.name; 3311 this.extAttrs = obj.extAttrs; 3312 this.untested = obj.untested; 3313 /** A back-reference to our IdlArray. */ 3314 this.array = obj.array; 3315 3316 /** An array of IdlInterfaceMembers. */ 3317 this.members = obj.members.map(m => new IdlInterfaceMember(m)); 3318 } 3319 3320 IdlNamespace.prototype = Object.create(IdlObject.prototype); 3321 3322 IdlNamespace.prototype.do_member_operation_asserts = function (memberHolderObject, member, a_test) 3323 { 3324 var desc = Object.getOwnPropertyDescriptor(memberHolderObject, member.name); 3325 3326 assert_false("get" in desc, "property should not have a getter"); 3327 assert_false("set" in desc, "property should not have a setter"); 3328 assert_equals( 3329 desc.writable, 3330 !member.isUnforgeable, 3331 "property should be writable if and only if not unforgeable"); 3332 assert_true(desc.enumerable, "property should be enumerable"); 3333 assert_equals( 3334 desc.configurable, 3335 !member.isUnforgeable, 3336 "property should be configurable if and only if not unforgeable"); 3337 3338 assert_equals( 3339 typeof memberHolderObject[member.name], 3340 "function", 3341 "property must be a function"); 3342 3343 assert_equals( 3344 memberHolderObject[member.name].length, 3345 minOverloadLength(this.members.filter(function(m) { 3346 return m.type == "operation" && m.name == member.name; 3347 })), 3348 "operation has wrong .length"); 3349 a_test.done(); 3350 } 3351 3352 IdlNamespace.prototype.test_member_operation = function(member) 3353 { 3354 if (!shouldRunSubTest(this.name)) { 3355 return; 3356 } 3357 var a_test = subsetTestByKey( 3358 this.name, 3359 async_test, 3360 this.name + ' namespace: operation ' + member); 3361 a_test.step(function() { 3362 assert_own_property( 3363 self[this.name], 3364 member.name, 3365 'namespace object missing operation ' + format_value(member.name)); 3366 3367 this.do_member_operation_asserts(self[this.name], member, a_test); 3368 }.bind(this)); 3369 }; 3370 3371 IdlNamespace.prototype.test_member_attribute = function (member) 3372 { 3373 if (!shouldRunSubTest(this.name)) { 3374 return; 3375 } 3376 var a_test = subsetTestByKey( 3377 this.name, 3378 async_test, 3379 this.name + ' namespace: attribute ' + member.name); 3380 a_test.step(function() 3381 { 3382 assert_own_property( 3383 self[this.name], 3384 member.name, 3385 this.name + ' does not have property ' + format_value(member.name)); 3386 3387 var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name); 3388 assert_equals(desc.set, undefined, "setter must be undefined for namespace members"); 3389 a_test.done(); 3390 }.bind(this)); 3391 }; 3392 3393 IdlNamespace.prototype.test_self = function () 3394 { 3395 /** 3396 * TODO(lukebjerring): Assert: 3397 * - "Note that unlike interfaces or dictionaries, namespaces do not create types." 3398 */ 3399 3400 subsetTestByKey(this.name, test, () => { 3401 assert_true(this.extAttrs.every(o => o.name === "Exposed" || o.name === "SecureContext"), 3402 "Only the [Exposed] and [SecureContext] extended attributes are applicable to namespaces"); 3403 assert_true(this.has_extended_attribute("Exposed"), 3404 "Namespaces must be annotated with the [Exposed] extended attribute"); 3405 }, `${this.name} namespace: extended attributes`); 3406 3407 const namespaceObject = self[this.name]; 3408 3409 subsetTestByKey(this.name, test, () => { 3410 const desc = Object.getOwnPropertyDescriptor(self, this.name); 3411 assert_equals(desc.value, namespaceObject, `wrong value for ${this.name} namespace object`); 3412 assert_true(desc.writable, "namespace object should be writable"); 3413 assert_false(desc.enumerable, "namespace object should not be enumerable"); 3414 assert_true(desc.configurable, "namespace object should be configurable"); 3415 assert_false("get" in desc, "namespace object should not have a getter"); 3416 assert_false("set" in desc, "namespace object should not have a setter"); 3417 }, `${this.name} namespace: property descriptor`); 3418 3419 subsetTestByKey(this.name, test, () => { 3420 assert_true(Object.isExtensible(namespaceObject)); 3421 }, `${this.name} namespace: [[Extensible]] is true`); 3422 3423 subsetTestByKey(this.name, test, () => { 3424 assert_true(namespaceObject instanceof Object); 3425 3426 if (this.name === "console") { 3427 // https://console.spec.whatwg.org/#console-namespace 3428 const namespacePrototype = Object.getPrototypeOf(namespaceObject); 3429 assert_equals(Reflect.ownKeys(namespacePrototype).length, 0); 3430 assert_equals(Object.getPrototypeOf(namespacePrototype), Object.prototype); 3431 } else { 3432 assert_equals(Object.getPrototypeOf(namespaceObject), Object.prototype); 3433 } 3434 }, `${this.name} namespace: [[Prototype]] is Object.prototype`); 3435 3436 subsetTestByKey(this.name, test, () => { 3437 assert_equals(typeof namespaceObject, "object"); 3438 }, `${this.name} namespace: typeof is "object"`); 3439 3440 subsetTestByKey(this.name, test, () => { 3441 assert_equals( 3442 Object.getOwnPropertyDescriptor(namespaceObject, "length"), 3443 undefined, 3444 "length property must be undefined" 3445 ); 3446 }, `${this.name} namespace: has no length property`); 3447 3448 subsetTestByKey(this.name, test, () => { 3449 assert_equals( 3450 Object.getOwnPropertyDescriptor(namespaceObject, "name"), 3451 undefined, 3452 "name property must be undefined" 3453 ); 3454 }, `${this.name} namespace: has no name property`); 3455 }; 3456 3457 IdlNamespace.prototype.test = function () 3458 { 3459 // If the namespace object is not exposed, only test that. Members can't be 3460 // tested either 3461 if (!this.exposed) { 3462 if (!this.untested) { 3463 subsetTestByKey(this.name, test, function() { 3464 assert_false(this.name in self, this.name + " namespace should not exist"); 3465 }.bind(this), this.name + " namespace: existence and properties of namespace object"); 3466 } 3467 return; 3468 } 3469 3470 if (!this.untested) { 3471 this.test_self(); 3472 } 3473 3474 for (const v of Object.values(this.members)) { 3475 switch (v.type) { 3476 3477 case 'operation': 3478 this.test_member_operation(v); 3479 break; 3480 3481 case 'attribute': 3482 this.test_member_attribute(v); 3483 break; 3484 3485 default: 3486 throw 'Invalid namespace member ' + v.name + ': ' + v.type + ' not supported'; 3487 } 3488 }; 3489 }; 3490 3491 }()); 3492 3493 /** 3494 * idl_test is a promise_test wrapper that handles the fetching of the IDL, 3495 * avoiding repetitive boilerplate. 3496 * 3497 * @param {String[]} srcs Spec name(s) for source idl files (fetched from 3498 * /interfaces/{name}.idl). 3499 * @param {String[]} deps Spec name(s) for dependency idl files (fetched 3500 * from /interfaces/{name}.idl). Order is important - dependencies from 3501 * each source will only be included if they're already know to be a 3502 * dependency (i.e. have already been seen). 3503 * @param {Function} setup_func Function for extra setup of the idl_array, such 3504 * as adding objects. Do not call idl_array.test() in the setup; it is 3505 * called by this function (idl_test). 3506 */ 3507 function idl_test(srcs, deps, idl_setup_func) { 3508 return promise_test(function (t) { 3509 var idl_array = new IdlArray(); 3510 var setup_error = null; 3511 const validationIgnored = [ 3512 "constructor-member", 3513 "dict-arg-default", 3514 "require-exposed" 3515 ]; 3516 return Promise.all( 3517 srcs.concat(deps).map(globalThis.fetch_spec)) 3518 .then(function(results) { 3519 const astArray = results.map(result => 3520 WebIDL2.parse(result.idl, { sourceName: result.spec }) 3521 ); 3522 test(() => { 3523 const validations = WebIDL2.validate(astArray) 3524 .filter(v => !validationIgnored.includes(v.ruleName)); 3525 if (validations.length) { 3526 const message = validations.map(v => v.message).join("\n\n"); 3527 throw new Error(message); 3528 } 3529 }, "idl_test validation"); 3530 for (var i = 0; i < srcs.length; i++) { 3531 idl_array.internal_add_idls(astArray[i]); 3532 } 3533 for (var i = srcs.length; i < srcs.length + deps.length; i++) { 3534 idl_array.internal_add_dependency_idls(astArray[i]); 3535 } 3536 }) 3537 .then(function() { 3538 if (idl_setup_func) { 3539 return idl_setup_func(idl_array, t); 3540 } 3541 }) 3542 .catch(function(e) { setup_error = e || 'IDL setup failed.'; }) 3543 .then(function () { 3544 var error = setup_error; 3545 try { 3546 idl_array.test(); // Test what we can. 3547 } catch (e) { 3548 // If testing fails hard here, the original setup error 3549 // is more likely to be the real cause. 3550 error = error || e; 3551 } 3552 if (error) { 3553 throw error; 3554 } 3555 }); 3556 }, 'idl_test setup'); 3557 } 3558 globalThis.idl_test = idl_test; 3559 3560 /** 3561 * fetch_spec is a shorthand for a Promise that fetches the spec's content. 3562 * Note: ShadowRealm-specific implementation in testharness-shadowrealm-inner.js 3563 */ 3564 function fetch_spec(spec) { 3565 var url = '/interfaces/' + spec + '.idl'; 3566 return fetch(url).then(function (r) { 3567 if (!r.ok) { 3568 throw new IdlHarnessError("Error fetching " + url + "."); 3569 } 3570 return r.text(); 3571 }).then(idl => ({ spec, idl })); 3572 } 3573 // vim: set expandtab shiftwidth=4 tabstop=4 foldmarker=@{,@} foldmethod=marker: