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