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