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