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