tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

idlharness.js (145966B)


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