tor-browser

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

idlharness.js (65579B)


      1 /*
      2 Distributed under both the W3C Test Suite License [1] and the W3C
      3 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
      4 policies and contribution forms [3].
      5 
      6 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
      7 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
      8 [3] http://www.w3.org/2004/10/27-testcases
      9 */
     10 
     11 /* For user documentation see docs/idlharness.md */
     12 
     13 /**
     14 * Notes for people who want to edit this file (not just use it as a library):
     15 *
     16 * Most of the interesting stuff happens in the derived classes of IdlObject,
     17 * especially IdlInterface.  The entry point for all IdlObjects is .test(),
     18 * which is called by IdlArray.test().  An IdlObject is conceptually just
     19 * "thing we want to run tests on", and an IdlArray is an array of IdlObjects
     20 * with some additional data thrown in.
     21 *
     22 * The object model is based on what WebIDLParser.js produces, which is in turn
     23 * based on its pegjs grammar.  If you want to figure out what properties an
     24 * object will have from WebIDLParser.js, the best way is to look at the
     25 * grammar:
     26 *
     27 *   https://github.com/darobin/webidl.js/blob/master/lib/grammar.peg
     28 *
     29 * So for instance:
     30 *
     31 *   // interface definition
     32 *   interface
     33 *       =   extAttrs:extendedAttributeList? S? "interface" S name:identifier w herit:ifInheritance? w "{" w mem:ifMember* w "}" w ";" w
     34 *           { return { type: "interface", name: name, inheritance: herit, members: mem, extAttrs: extAttrs }; }
     35 *
     36 * This means that an "interface" object will have a .type property equal to
     37 * the string "interface", a .name property equal to the identifier that the
     38 * parser found, an .inheritance property equal to either null or the result of
     39 * the "ifInheritance" production found elsewhere in the grammar, and so on.
     40 * After each grammatical production is a JavaScript function in curly braces
     41 * that gets called with suitable arguments and returns some JavaScript value.
     42 *
     43 * (Note that the version of WebIDLParser.js we use might sometimes be
     44 * out-of-date or forked.)
     45 *
     46 * The members and methods of the classes defined by this file are all at least
     47 * briefly documented, hopefully.
     48 */
     49 (function(){
     50 "use strict";
     51 /// Helpers ///
     52 function constValue (cnt) {
     53    if (cnt.type === "null") return null;
     54    if (cnt.type === "NaN") return NaN;
     55    if (cnt.type === "Infinity") return cnt.negative ? -Infinity : Infinity;
     56    return cnt.value;
     57 }
     58 
     59 function minOverloadLength(overloads) {
     60    if (!overloads.length) {
     61        return 0;
     62    }
     63 
     64    return overloads.map(function(attr) {
     65        return attr.arguments ? attr.arguments.filter(function(arg) {
     66            return !arg.optional && !arg.variadic;
     67        }).length : 0;
     68    })
     69    .reduce(function(m, n) { return Math.min(m, n); });
     70 }
     71 
     72 /// IdlArray ///
     73 // Entry point
     74 self.IdlArray = function()
     75 //@{
     76 {
     77    /**
     78     * A map from strings to the corresponding named IdlObject, such as
     79     * IdlInterface or IdlException.  These are the things that test() will run
     80     * tests on.
     81     */
     82    this.members = {};
     83 
     84    /**
     85     * A map from strings to arrays of strings.  The keys are interface or
     86     * exception names, and are expected to also exist as keys in this.members
     87     * (otherwise they'll be ignored).  This is populated by add_objects() --
     88     * see documentation at the start of the file.  The actual tests will be
     89     * run by calling this.members[name].test_object(obj) for each obj in
     90     * this.objects[name].  obj is a string that will be eval'd to produce a
     91     * JavaScript value, which is supposed to be an object implementing the
     92     * given IdlObject (interface, exception, etc.).
     93     */
     94    this.objects = {};
     95 
     96    /**
     97     * When adding multiple collections of IDLs one at a time, an earlier one
     98     * might contain a partial interface or implements statement that depends
     99     * on a later one.  Save these up and handle them right before we run
    100     * tests.
    101     *
    102     * .partials is simply an array of objects from WebIDLParser.js'
    103     * "partialinterface" production.  .implements maps strings to arrays of
    104     * strings, such that
    105     *
    106     *   A implements B;
    107     *   A implements C;
    108     *   D implements E;
    109     *
    110     * results in { A: ["B", "C"], D: ["E"] }.
    111     */
    112    this.partials = [];
    113    this["implements"] = {};
    114 };
    115 
    116 //@}
    117 IdlArray.prototype.add_idls = function(raw_idls)
    118 //@{
    119 {
    120    /** Entry point.  See documentation at beginning of file. */
    121    this.internal_add_idls(WebIDL2.parse(raw_idls));
    122 };
    123 
    124 //@}
    125 IdlArray.prototype.add_untested_idls = function(raw_idls)
    126 //@{
    127 {
    128    /** Entry point.  See documentation at beginning of file. */
    129    var parsed_idls = WebIDL2.parse(raw_idls);
    130    for (var i = 0; i < parsed_idls.length; i++)
    131    {
    132        parsed_idls[i].untested = true;
    133        if ("members" in parsed_idls[i])
    134        {
    135            for (var j = 0; j < parsed_idls[i].members.length; j++)
    136            {
    137                parsed_idls[i].members[j].untested = true;
    138            }
    139        }
    140    }
    141    this.internal_add_idls(parsed_idls);
    142 };
    143 
    144 //@}
    145 IdlArray.prototype.internal_add_idls = function(parsed_idls)
    146 //@{
    147 {
    148    /**
    149     * Internal helper called by add_idls() and add_untested_idls().
    150     * parsed_idls is an array of objects that come from WebIDLParser.js's
    151     * "definitions" production.  The add_untested_idls() entry point
    152     * additionally sets an .untested property on each object (and its
    153     * .members) so that they'll be skipped by test() -- they'll only be
    154     * used for base interfaces of tested interfaces, return types, etc.
    155     */
    156    parsed_idls.forEach(parsed_idl => {
    157        if (parsed_idl.type == "interface" && parsed_idl.partial)
    158        {
    159            this.partials.push(parsed_idl);
    160            return;
    161        }
    162 
    163        if (parsed_idl.type == "implements")
    164        {
    165            if (!(parsed_idl.target in this["implements"]))
    166            {
    167                this["implements"][parsed_idl.target] = [];
    168            }
    169            this["implements"][parsed_idl.target].push(parsed_idl["implements"]);
    170            return;
    171        }
    172 
    173        parsed_idl.array = this;
    174        if (parsed_idl.name in this.members)
    175        {
    176            throw "Duplicate identifier " + parsed_idl.name;
    177        }
    178        switch(parsed_idl.type)
    179        {
    180        case "interface":
    181            this.members[parsed_idl.name] =
    182                new IdlInterface(parsed_idl, /* is_callback = */ false);
    183            break;
    184 
    185        case "dictionary":
    186            // Nothing to test, but we need the dictionary info around for type
    187            // checks
    188            this.members[parsed_idl.name] = new IdlDictionary(parsed_idl);
    189            break;
    190 
    191        case "typedef":
    192            this.members[parsed_idl.name] = new IdlTypedef(parsed_idl);
    193            break;
    194 
    195        case "callback":
    196            // TODO
    197            console.log("callback not yet supported");
    198            break;
    199 
    200        case "enum":
    201            this.members[parsed_idl.name] = new IdlEnum(parsed_idl);
    202            break;
    203 
    204        case "callback interface":
    205            this.members[parsed_idl.name] =
    206                new IdlInterface(parsed_idl, /* is_callback = */ true);
    207            break;
    208 
    209        default:
    210            throw parsed_idl.name + ": " + parsed_idl.type + " not yet supported";
    211        }
    212    });
    213 };
    214 
    215 //@}
    216 IdlArray.prototype.add_objects = function(dict)
    217 //@{
    218 {
    219    /** Entry point.  See documentation at beginning of file. */
    220    for (var k in dict)
    221    {
    222        if (k in this.objects)
    223        {
    224            this.objects[k] = this.objects[k].concat(dict[k]);
    225        }
    226        else
    227        {
    228            this.objects[k] = dict[k];
    229        }
    230    }
    231 };
    232 
    233 //@}
    234 IdlArray.prototype.prevent_multiple_testing = function(name)
    235 //@{
    236 {
    237    /** Entry point.  See documentation at beginning of file. */
    238    this.members[name].prevent_multiple_testing = true;
    239 };
    240 
    241 //@}
    242 IdlArray.prototype.recursively_get_implements = function(interface_name)
    243 //@{
    244 {
    245    /**
    246     * Helper function for test().  Returns an array of things that implement
    247     * interface_name, so if the IDL contains
    248     *
    249     *   A implements B;
    250     *   B implements C;
    251     *   B implements D;
    252     *
    253     * then recursively_get_implements("A") should return ["B", "C", "D"].
    254     */
    255    var ret = this["implements"][interface_name];
    256    if (ret === undefined)
    257    {
    258        return [];
    259    }
    260    for (var i = 0; i < this["implements"][interface_name].length; i++)
    261    {
    262        ret = ret.concat(this.recursively_get_implements(ret[i]));
    263        if (ret.indexOf(ret[i]) != ret.lastIndexOf(ret[i]))
    264        {
    265            throw "Circular implements statements involving " + ret[i];
    266        }
    267    }
    268    return ret;
    269 };
    270 
    271 //@}
    272 IdlArray.prototype.test = function()
    273 //@{
    274 {
    275    /** Entry point.  See documentation at beginning of file. */
    276 
    277    // First merge in all the partial interfaces and implements statements we
    278    // encountered.
    279    this.partials.forEach(parsed_idl => {
    280        if (!(parsed_idl.name in this.members)
    281        || !(this.members[parsed_idl.name] instanceof IdlInterface))
    282        {
    283            throw "Partial interface " + parsed_idl.name + " with no original interface";
    284        }
    285        if (parsed_idl.extAttrs)
    286        {
    287            parsed_idl.extAttrs.forEach(extAttr => {
    288                this.members[parsed_idl.name].extAttrs.push(extAttr);
    289            });
    290        }
    291        parsed_idl.members.forEach(member => {
    292            this.members[parsed_idl.name].members.push(new IdlInterfaceMember(member));
    293        });
    294    });
    295    this.partials = [];
    296 
    297    for (var lhs in this["implements"])
    298    {
    299        this.recursively_get_implements(lhs).forEach(rhs => {
    300            var errStr = lhs + " implements " + rhs + ", but ";
    301            if (!(lhs in this.members)) throw errStr + lhs + " is undefined.";
    302            if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface.";
    303            if (!(rhs in this.members)) throw errStr + rhs + " is undefined.";
    304            if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface.";
    305            this.members[rhs].members.forEach(member => {
    306                this.members[lhs].members.push(new IdlInterfaceMember(member));
    307            });
    308        });
    309    }
    310    this["implements"] = {};
    311 
    312    // Now run test() on every member, and test_object() for every object.
    313    for (var name in this.members)
    314    {
    315        this.members[name].test();
    316        if (name in this.objects)
    317        {
    318            this.objects[name].forEach(str => {
    319                this.members[name].test_object(str);
    320            });
    321        }
    322    }
    323 };
    324 
    325 //@}
    326 IdlArray.prototype.assert_type_is = function(value, type)
    327 //@{
    328 {
    329    /**
    330     * Helper function that tests that value is an instance of type according
    331     * to the rules of WebIDL.  value is any JavaScript value, and type is an
    332     * object produced by WebIDLParser.js' "type" production.  That production
    333     * is fairly elaborate due to the complexity of WebIDL's types, so it's
    334     * best to look at the grammar to figure out what properties it might have.
    335     */
    336    if (type.idlType == "any")
    337    {
    338        // No assertions to make
    339        return;
    340    }
    341 
    342    if (type.nullable && value === null)
    343    {
    344        // This is fine
    345        return;
    346    }
    347 
    348    if (type.array)
    349    {
    350        // TODO: not supported yet
    351        return;
    352    }
    353 
    354    if (type.sequence)
    355    {
    356        assert_true(Array.isArray(value), "is not array");
    357        if (!value.length)
    358        {
    359            // Nothing we can do.
    360            return;
    361        }
    362        this.assert_type_is(value[0], type.idlType.idlType);
    363        return;
    364    }
    365 
    366    type = type.idlType;
    367 
    368    switch(type)
    369    {
    370        case "void":
    371            assert_equals(value, undefined);
    372            return;
    373 
    374        case "boolean":
    375            assert_equals(typeof value, "boolean");
    376            return;
    377 
    378        case "byte":
    379            assert_equals(typeof value, "number");
    380            assert_equals(value, Math.floor(value), "not an integer");
    381            assert_true(-128 <= value && value <= 127, "byte " + value + " not in range [-128, 127]");
    382            return;
    383 
    384        case "octet":
    385            assert_equals(typeof value, "number");
    386            assert_equals(value, Math.floor(value), "not an integer");
    387            assert_true(0 <= value && value <= 255, "octet " + value + " not in range [0, 255]");
    388            return;
    389 
    390        case "short":
    391            assert_equals(typeof value, "number");
    392            assert_equals(value, Math.floor(value), "not an integer");
    393            assert_true(-32768 <= value && value <= 32767, "short " + value + " not in range [-32768, 32767]");
    394            return;
    395 
    396        case "unsigned short":
    397            assert_equals(typeof value, "number");
    398            assert_equals(value, Math.floor(value), "not an integer");
    399            assert_true(0 <= value && value <= 65535, "unsigned short " + value + " not in range [0, 65535]");
    400            return;
    401 
    402        case "long":
    403            assert_equals(typeof value, "number");
    404            assert_equals(value, Math.floor(value), "not an integer");
    405            assert_true(-2147483648 <= value && value <= 2147483647, "long " + value + " not in range [-2147483648, 2147483647]");
    406            return;
    407 
    408        case "unsigned long":
    409            assert_equals(typeof value, "number");
    410            assert_equals(value, Math.floor(value), "not an integer");
    411            assert_true(0 <= value && value <= 4294967295, "unsigned long " + value + " not in range [0, 4294967295]");
    412            return;
    413 
    414        case "long long":
    415            assert_equals(typeof value, "number");
    416            return;
    417 
    418        case "unsigned long long":
    419        case "EpochTimeStamp":
    420        case "DOMTimeStamp":
    421            assert_equals(typeof value, "number");
    422            assert_true(0 <= value, "unsigned long long is negative");
    423            return;
    424 
    425        case "float":
    426        case "double":
    427        case "DOMHighResTimeStamp":
    428        case "unrestricted float":
    429        case "unrestricted double":
    430            // TODO: distinguish these cases
    431            assert_equals(typeof value, "number");
    432            return;
    433 
    434        case "DOMString":
    435        case "ByteString":
    436        case "USVString":
    437            // TODO: https://github.com/w3c/testharness.js/issues/92
    438            assert_equals(typeof value, "string");
    439            return;
    440 
    441        case "object":
    442            assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function");
    443            return;
    444    }
    445 
    446    if (!(type in this.members))
    447    {
    448        throw "Unrecognized type " + type;
    449    }
    450 
    451    if (this.members[type] instanceof IdlInterface)
    452    {
    453        // We don't want to run the full
    454        // IdlInterface.prototype.test_instance_of, because that could result
    455        // in an infinite loop.  TODO: This means we don't have tests for
    456        // NoInterfaceObject interfaces, and we also can't test objects that
    457        // come from another self.
    458        assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function");
    459        if (value instanceof Object
    460        && !this.members[type].has_extended_attribute("NoInterfaceObject")
    461        && type in self)
    462        {
    463            assert_true(value instanceof self[type], "not instanceof " + type);
    464        }
    465    }
    466    else if (this.members[type] instanceof IdlEnum)
    467    {
    468        assert_equals(typeof value, "string");
    469    }
    470    else if (this.members[type] instanceof IdlDictionary)
    471    {
    472        // TODO: Test when we actually have something to test this on
    473    }
    474    else if (this.members[type] instanceof IdlTypedef)
    475    {
    476        // TODO: Test when we actually have something to test this on
    477    }
    478    else
    479    {
    480        throw "Type " + type + " isn't an interface or dictionary";
    481    }
    482 };
    483 //@}
    484 
    485 /// IdlObject ///
    486 function IdlObject() {}
    487 IdlObject.prototype.test = function()
    488 //@{
    489 {
    490    /**
    491     * By default, this does nothing, so no actual tests are run for IdlObjects
    492     * that don't define any (e.g., IdlDictionary at the time of this writing).
    493     */
    494 };
    495 
    496 //@}
    497 IdlObject.prototype.has_extended_attribute = function(name)
    498 //@{
    499 {
    500    /**
    501     * This is only meaningful for things that support extended attributes,
    502     * such as interfaces, exceptions, and members.
    503     */
    504    return this.extAttrs.some(function(o)
    505    {
    506        return o.name == name;
    507    });
    508 };
    509 
    510 //@}
    511 
    512 /// IdlDictionary ///
    513 // Used for IdlArray.prototype.assert_type_is
    514 function IdlDictionary(obj)
    515 //@{
    516 {
    517    /**
    518     * obj is an object produced by the WebIDLParser.js "dictionary"
    519     * production.
    520     */
    521 
    522    /** Self-explanatory. */
    523    this.name = obj.name;
    524 
    525    /** An array of objects produced by the "dictionaryMember" production. */
    526    this.members = obj.members;
    527 
    528    /**
    529     * The name (as a string) of the dictionary type we inherit from, or null
    530     * if there is none.
    531     */
    532    this.base = obj.inheritance;
    533 }
    534 
    535 //@}
    536 IdlDictionary.prototype = Object.create(IdlObject.prototype);
    537 
    538 /// IdlInterface ///
    539 function IdlInterface(obj, is_callback) {
    540    /**
    541     * obj is an object produced by the WebIDLParser.js "exception" or
    542     * "interface" production, as appropriate.
    543     */
    544 
    545    /** Self-explanatory. */
    546    this.name = obj.name;
    547 
    548    /** A back-reference to our IdlArray. */
    549    this.array = obj.array;
    550 
    551    /**
    552     * An indicator of whether we should run tests on the (exception) interface
    553     * object and (exception) interface prototype object.  Tests on members are
    554     * controlled by .untested on each member, not this.
    555     */
    556    this.untested = obj.untested;
    557 
    558    /** An array of objects produced by the "ExtAttr" production. */
    559    this.extAttrs = obj.extAttrs;
    560 
    561    /** An array of IdlInterfaceMembers. */
    562    this.members = obj.members.map(function(m){return new IdlInterfaceMember(m); });
    563    if (this.has_extended_attribute("Unforgeable")) {
    564        this.members
    565            .filter(function(m) { return !m["static"] && (m.type == "attribute" || m.type == "operation"); })
    566            .forEach(function(m) { return m.isUnforgeable = true; });
    567    }
    568 
    569    /**
    570     * The name (as a string) of the type we inherit from, or null if there is
    571     * none.
    572     */
    573    this.base = obj.inheritance;
    574 
    575    this._is_callback = is_callback;
    576 }
    577 IdlInterface.prototype = Object.create(IdlObject.prototype);
    578 IdlInterface.prototype.is_callback = function()
    579 //@{
    580 {
    581    return this._is_callback;
    582 };
    583 //@}
    584 
    585 IdlInterface.prototype.has_constants = function()
    586 //@{
    587 {
    588    return this.members.some(function(member) {
    589        return member.type === "const";
    590    });
    591 };
    592 //@}
    593 
    594 IdlInterface.prototype.is_global = function()
    595 //@{
    596 {
    597    return this.extAttrs.some(function(attribute) {
    598        return attribute.name === "Global" ||
    599               attribute.name === "PrimaryGlobal";
    600    });
    601 };
    602 //@}
    603 
    604 IdlInterface.prototype.test = function()
    605 //@{
    606 {
    607    if (this.has_extended_attribute("NoInterfaceObject"))
    608    {
    609        // No tests to do without an instance.  TODO: We should still be able
    610        // to run tests on the prototype object, if we obtain one through some
    611        // other means.
    612        return;
    613    }
    614 
    615    if (!this.untested)
    616    {
    617        // First test things to do with the exception/interface object and
    618        // exception/interface prototype object.
    619        this.test_self();
    620    }
    621    // Then test things to do with its members (constants, fields, attributes,
    622    // operations, . . .).  These are run even if .untested is true, because
    623    // members might themselves be marked as .untested.  This might happen to
    624    // interfaces if the interface itself is untested but a partial interface
    625    // that extends it is tested -- then the interface itself and its initial
    626    // members will be marked as untested, but the members added by the partial
    627    // interface are still tested.
    628    this.test_members();
    629 };
    630 //@}
    631 
    632 IdlInterface.prototype.test_self = function()
    633 //@{
    634 {
    635    test(() => {
    636        // This function tests WebIDL as of 2015-01-13.
    637        // TODO: Consider [Exposed].
    638 
    639        // "For every interface that is exposed in a given ECMAScript global
    640        // environment and:
    641        // * is a callback interface that has constants declared on it, or
    642        // * is a non-callback interface that is not declared with the
    643        //   [NoInterfaceObject] extended attribute,
    644        // a corresponding property MUST exist on the ECMAScript global object.
    645        // The name of the property is the identifier of the interface, and its
    646        // value is an object called the interface object.
    647        // The property has the attributes { [[Writable]]: true,
    648        // [[Enumerable]]: false, [[Configurable]]: true }."
    649        if (this.is_callback() && !this.has_constants()) {
    650            return;
    651        }
    652 
    653        // TODO: Should we test here that the property is actually writable
    654        // etc., or trust getOwnPropertyDescriptor?
    655        assert_own_property(self, this.name,
    656                            "self does not have own property " + format_value(this.name));
    657        var desc = Object.getOwnPropertyDescriptor(self, this.name);
    658        assert_false("get" in desc, "self's property " + format_value(this.name) + " has getter");
    659        assert_false("set" in desc, "self's property " + format_value(this.name) + " has setter");
    660        assert_true(desc.writable, "self's property " + format_value(this.name) + " is not writable");
    661        assert_false(desc.enumerable, "self's property " + format_value(this.name) + " is enumerable");
    662        assert_true(desc.configurable, "self's property " + format_value(this.name) + " is not configurable");
    663 
    664        if (this.is_callback()) {
    665            // "The internal [[Prototype]] property of an interface object for
    666            // a callback interface MUST be the Object.prototype object."
    667            assert_equals(Object.getPrototypeOf(self[this.name]), Object.prototype,
    668                          "prototype of self's property " + format_value(this.name) + " is not Object.prototype");
    669 
    670            return;
    671        }
    672 
    673        // "The interface object for a given non-callback interface is a
    674        // function object."
    675        // "If an object is defined to be a function object, then it has
    676        // characteristics as follows:"
    677 
    678        // Its [[Prototype]] internal property is otherwise specified (see
    679        // below).
    680 
    681        // "* Its [[Get]] internal property is set as described in ECMA-262
    682        //    section 9.1.8."
    683        // Not much to test for this.
    684 
    685        // "* Its [[Construct]] internal property is set as described in
    686        //    ECMA-262 section 19.2.2.3."
    687        // Tested below if no constructor is defined.  TODO: test constructors
    688        // if defined.
    689 
    690        // "* Its @@hasInstance property is set as described in ECMA-262
    691        //    section 19.2.3.8, unless otherwise specified."
    692        // TODO
    693 
    694        // ES6 (rev 30) 19.1.3.6:
    695        // "Else, if O has a [[Call]] internal method, then let builtinTag be
    696        // "Function"."
    697        assert_class_string(self[this.name], "Function", "class string of " + this.name);
    698 
    699        // "The [[Prototype]] internal property of an interface object for a
    700        // non-callback interface is determined as follows:"
    701        var prototype = Object.getPrototypeOf(self[this.name]);
    702        if (this.base) {
    703            // "* If the interface inherits from some other interface, the
    704            //    value of [[Prototype]] is the interface object for that other
    705            //    interface."
    706            var has_interface_object =
    707                !this.array
    708                     .members[this.base]
    709                     .has_extended_attribute("NoInterfaceObject");
    710            if (has_interface_object) {
    711                assert_own_property(self, this.base,
    712                                    'should inherit from ' + this.base +
    713                                    ', but self has no such property');
    714                assert_equals(prototype, self[this.base],
    715                              'prototype of ' + this.name + ' is not ' +
    716                              this.base);
    717            }
    718        } else {
    719            // "If the interface doesn't inherit from any other interface, the
    720            // value of [[Prototype]] is %FunctionPrototype% ([ECMA-262],
    721            // section 6.1.7.4)."
    722            assert_equals(prototype, Function.prototype,
    723                          "prototype of self's property " + format_value(this.name) + " is not Function.prototype");
    724        }
    725 
    726        if (!this.has_extended_attribute("Constructor")) {
    727            // "The internal [[Call]] method of the interface object behaves as
    728            // follows . . .
    729            //
    730            // "If I was not declared with a [Constructor] extended attribute,
    731            // then throw a TypeError."
    732            assert_throws(new TypeError(), () => {
    733                self[this.name]();
    734            }, "interface object didn't throw TypeError when called as a function");
    735            assert_throws(new TypeError(), () => {
    736                new self[this.name]();
    737            }, "interface object didn't throw TypeError when called as a constructor");
    738        }
    739    }, this.name + " interface: existence and properties of interface object");
    740 
    741    if (!this.is_callback()) {
    742        test(() => {
    743            // This function tests WebIDL as of 2014-10-25.
    744            // https://heycam.github.io/webidl/#es-interface-call
    745 
    746            assert_own_property(self, this.name,
    747                                "self does not have own property " + format_value(this.name));
    748 
    749            // "Interface objects for non-callback interfaces MUST have a
    750            // property named “length” with attributes { [[Writable]]: false,
    751            // [[Enumerable]]: false, [[Configurable]]: true } whose value is
    752            // a Number."
    753            assert_own_property(self[this.name], "length");
    754            var desc = Object.getOwnPropertyDescriptor(self[this.name], "length");
    755            assert_false("get" in desc, this.name + ".length has getter");
    756            assert_false("set" in desc, this.name + ".length has setter");
    757            assert_false(desc.writable, this.name + ".length is writable");
    758            assert_false(desc.enumerable, this.name + ".length is enumerable");
    759            assert_true(desc.configurable, this.name + ".length is not configurable");
    760 
    761            var constructors = this.extAttrs
    762                .filter(function(attr) { return attr.name == "Constructor"; });
    763            var expected_length = minOverloadLength(constructors);
    764            assert_equals(self[this.name].length, expected_length, "wrong value for " + this.name + ".length");
    765        }, this.name + " interface object length");
    766    }
    767 
    768    // TODO: Test named constructors if I find any interfaces that have them.
    769 
    770    test(() => {
    771        // This function tests WebIDL as of 2015-01-21.
    772        // https://heycam.github.io/webidl/#interface-object
    773 
    774        if (this.is_callback() && !this.has_constants()) {
    775            return;
    776        }
    777 
    778        assert_own_property(self, this.name,
    779                            "self does not have own property " + format_value(this.name));
    780 
    781        if (this.is_callback()) {
    782            assert_false("prototype" in self[this.name],
    783                         this.name + ' should not have a "prototype" property');
    784            return;
    785        }
    786 
    787        // "An interface object for a non-callback interface must have a
    788        // property named “prototype” with attributes { [[Writable]]: false,
    789        // [[Enumerable]]: false, [[Configurable]]: false } whose value is an
    790        // object called the interface prototype object. This object has
    791        // properties that correspond to the regular attributes and regular
    792        // operations defined on the interface, and is described in more detail
    793        // in section 4.5.4 below."
    794        assert_own_property(self[this.name], "prototype",
    795                            'interface "' + this.name + '" does not have own property "prototype"');
    796        var desc = Object.getOwnPropertyDescriptor(self[this.name], "prototype");
    797        assert_false("get" in desc, this.name + ".prototype has getter");
    798        assert_false("set" in desc, this.name + ".prototype has setter");
    799        assert_false(desc.writable, this.name + ".prototype is writable");
    800        assert_false(desc.enumerable, this.name + ".prototype is enumerable");
    801        assert_false(desc.configurable, this.name + ".prototype is configurable");
    802 
    803        // Next, test that the [[Prototype]] of the interface prototype object
    804        // is correct. (This is made somewhat difficult by the existence of
    805        // [NoInterfaceObject].)
    806        // TODO: Aryeh thinks there's at least other place in this file where
    807        //       we try to figure out if an interface prototype object is
    808        //       correct. Consolidate that code.
    809 
    810        // "The interface prototype object for a given interface A must have an
    811        // internal [[Prototype]] property whose value is returned from the
    812        // following steps:
    813        // "If A is declared with the [Global] or [PrimaryGlobal] extended
    814        // attribute, and A supports named properties, then return the named
    815        // properties object for A, as defined in section 4.5.5 below.
    816        // "Otherwise, if A is declared to inherit from another interface, then
    817        // return the interface prototype object for the inherited interface.
    818        // "Otherwise, if A is declared with the [ArrayClass] extended
    819        // attribute, then return %ArrayPrototype% ([ECMA-262], section
    820        // 6.1.7.4).
    821        // "Otherwise, return %ObjectPrototype% ([ECMA-262], section 6.1.7.4).
    822        // ([ECMA-262], section 15.2.4).
    823        if (this.name === "Window") {
    824            assert_class_string(Object.getPrototypeOf(self[this.name].prototype),
    825                                'WindowProperties',
    826                                'Class name for prototype of Window' +
    827                                '.prototype is not "WindowProperties"');
    828        } else {
    829            var inherit_interface, inherit_interface_has_interface_object;
    830            if (this.base) {
    831                inherit_interface = this.base;
    832                inherit_interface_has_interface_object =
    833                    !this.array
    834                         .members[inherit_interface]
    835                         .has_extended_attribute("NoInterfaceObject");
    836            } else if (this.has_extended_attribute('ArrayClass')) {
    837                inherit_interface = 'Array';
    838                inherit_interface_has_interface_object = true;
    839            } else {
    840                inherit_interface = 'Object';
    841                inherit_interface_has_interface_object = true;
    842            }
    843            if (inherit_interface_has_interface_object) {
    844                assert_own_property(self, inherit_interface,
    845                                    'should inherit from ' + inherit_interface + ', but self has no such property');
    846                assert_own_property(self[inherit_interface], 'prototype',
    847                                    'should inherit from ' + inherit_interface + ', but that object has no "prototype" property');
    848                assert_equals(Object.getPrototypeOf(self[this.name].prototype),
    849                              self[inherit_interface].prototype,
    850                              'prototype of ' + this.name + '.prototype is not ' + inherit_interface + '.prototype');
    851            } else {
    852                // We can't test that we get the correct object, because this is the
    853                // only way to get our hands on it. We only test that its class
    854                // string, at least, is correct.
    855                assert_class_string(Object.getPrototypeOf(self[this.name].prototype),
    856                                    inherit_interface + 'Prototype',
    857                                    'Class name for prototype of ' + this.name +
    858                                    '.prototype is not "' + inherit_interface + 'Prototype"');
    859            }
    860        }
    861 
    862        // "The class string of an interface prototype object is the
    863        // concatenation of the interface’s identifier and the string
    864        // “Prototype”."
    865        assert_class_string(self[this.name].prototype, this.name + "Prototype",
    866                            "class string of " + this.name + ".prototype");
    867        // String() should end up calling {}.toString if nothing defines a
    868        // stringifier.
    869        if (!this.has_stringifier()) {
    870            assert_equals(String(self[this.name].prototype), "[object " + this.name + "Prototype]",
    871                    "String(" + this.name + ".prototype)");
    872        }
    873    }, this.name + " interface: existence and properties of interface prototype object");
    874 
    875    test(() => {
    876        if (this.is_callback() && !this.has_constants()) {
    877            return;
    878        }
    879 
    880        assert_own_property(self, this.name,
    881                            "self does not have own property " + format_value(this.name));
    882 
    883        if (this.is_callback()) {
    884            assert_false("prototype" in self[this.name],
    885                         this.name + ' should not have a "prototype" property');
    886            return;
    887        }
    888 
    889        assert_own_property(self[this.name], "prototype",
    890                            'interface "' + this.name + '" does not have own property "prototype"');
    891 
    892        // "If the [NoInterfaceObject] extended attribute was not specified on
    893        // the interface, then the interface prototype object must also have a
    894        // property named “constructor” with attributes { [[Writable]]: true,
    895        // [[Enumerable]]: false, [[Configurable]]: true } whose value is a
    896        // reference to the interface object for the interface."
    897        assert_own_property(self[this.name].prototype, "constructor",
    898                            this.name + '.prototype does not have own property "constructor"');
    899        var desc = Object.getOwnPropertyDescriptor(self[this.name].prototype, "constructor");
    900        assert_false("get" in desc, this.name + ".prototype.constructor has getter");
    901        assert_false("set" in desc, this.name + ".prototype.constructor has setter");
    902        assert_true(desc.writable, this.name + ".prototype.constructor is not writable");
    903        assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable");
    904        assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable");
    905        assert_equals(self[this.name].prototype.constructor, self[this.name],
    906                      this.name + '.prototype.constructor is not the same object as ' + this.name);
    907    }, this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property');
    908 };
    909 
    910 //@}
    911 IdlInterface.prototype.test_member_const = function(member)
    912 //@{
    913 {
    914    test(() => {
    915        if (this.is_callback() && !this.has_constants()) {
    916            return;
    917        }
    918 
    919        assert_own_property(self, this.name,
    920                            "self does not have own property " + format_value(this.name));
    921 
    922        // "For each constant defined on an interface A, there must be
    923        // a corresponding property on the interface object, if it
    924        // exists."
    925        assert_own_property(self[this.name], member.name);
    926        // "The value of the property is that which is obtained by
    927        // converting the constant’s IDL value to an ECMAScript
    928        // value."
    929        assert_equals(self[this.name][member.name], constValue(member.value),
    930                      "property has wrong value");
    931        // "The property has attributes { [[Writable]]: false,
    932        // [[Enumerable]]: true, [[Configurable]]: false }."
    933        var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name);
    934        assert_false("get" in desc, "property has getter");
    935        assert_false("set" in desc, "property has setter");
    936        assert_false(desc.writable, "property is writable");
    937        assert_true(desc.enumerable, "property is not enumerable");
    938        assert_false(desc.configurable, "property is configurable");
    939    }, this.name + " interface: constant " + member.name + " on interface object");
    940    // "In addition, a property with the same characteristics must
    941    // exist on the interface prototype object."
    942    test(() => {
    943        if (this.is_callback() && !this.has_constants()) {
    944            return;
    945        }
    946 
    947        assert_own_property(self, this.name,
    948                            "self does not have own property " + format_value(this.name));
    949 
    950        if (this.is_callback()) {
    951            assert_false("prototype" in self[this.name],
    952                         this.name + ' should not have a "prototype" property');
    953            return;
    954        }
    955 
    956        assert_own_property(self[this.name], "prototype",
    957                            'interface "' + this.name + '" does not have own property "prototype"');
    958 
    959        assert_own_property(self[this.name].prototype, member.name);
    960        assert_equals(self[this.name].prototype[member.name], constValue(member.value),
    961                      "property has wrong value");
    962        var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name);
    963        assert_false("get" in desc, "property has getter");
    964        assert_false("set" in desc, "property has setter");
    965        assert_false(desc.writable, "property is writable");
    966        assert_true(desc.enumerable, "property is not enumerable");
    967        assert_false(desc.configurable, "property is configurable");
    968    }, this.name + " interface: constant " + member.name + " on interface prototype object");
    969 };
    970 
    971 
    972 //@}
    973 IdlInterface.prototype.test_member_attribute = function(member)
    974 //@{
    975 {
    976    test(() => {
    977        if (this.is_callback() && !this.has_constants()) {
    978            return;
    979        }
    980 
    981        assert_own_property(self, this.name,
    982                            "self does not have own property " + format_value(this.name));
    983        assert_own_property(self[this.name], "prototype",
    984                            'interface "' + this.name + '" does not have own property "prototype"');
    985 
    986        if (member["static"]) {
    987            assert_own_property(self[this.name], member.name,
    988                "The interface object must have a property " +
    989                format_value(member.name));
    990        } else if (this.is_global()) {
    991            assert_own_property(self, member.name,
    992                "The global object must have a property " +
    993                format_value(member.name));
    994            assert_false(member.name in self[this.name].prototype,
    995                "The prototype object must not have a property " +
    996                format_value(member.name));
    997 
    998            // Try/catch around the get here, since it can legitimately throw.
    999            // If it does, we obviously can't check for equality with direct
   1000            // invocation of the getter.
   1001            var gotValue;
   1002            var propVal;
   1003            try {
   1004                propVal = self[member.name];
   1005                gotValue = true;
   1006            } catch (e) {
   1007                gotValue = false;
   1008            }
   1009            if (gotValue) {
   1010                var getter = Object.getOwnPropertyDescriptor(self, member.name).get;
   1011                assert_equals(typeof(getter), "function",
   1012                              format_value(member.name) + " must have a getter");
   1013                assert_equals(propVal, getter.call(undefined),
   1014                              "Gets on a global should not require an explicit this");
   1015            }
   1016            this.do_interface_attribute_asserts(self, member);
   1017        } else {
   1018            assert_true(member.name in self[this.name].prototype,
   1019                "The prototype object must have a property " +
   1020                format_value(member.name));
   1021 
   1022            if (!member.has_extended_attribute("LenientThis")) {
   1023                assert_throws(new TypeError(), () => {
   1024                    self[this.name].prototype[member.name];
   1025                }, "getting property on prototype object must throw TypeError");
   1026            } else {
   1027                assert_equals(self[this.name].prototype[member.name], undefined,
   1028                              "getting property on prototype object must return undefined");
   1029            }
   1030            this.do_interface_attribute_asserts(self[this.name].prototype, member);
   1031        }
   1032    }, this.name + " interface: attribute " + member.name);
   1033 };
   1034 
   1035 //@}
   1036 IdlInterface.prototype.test_member_operation = function(member)
   1037 //@{
   1038 {
   1039    test(() => {
   1040        if (this.is_callback() && !this.has_constants()) {
   1041            return;
   1042        }
   1043 
   1044        assert_own_property(self, this.name,
   1045                            "self does not have own property " + format_value(this.name));
   1046 
   1047        if (this.is_callback()) {
   1048            assert_false("prototype" in self[this.name],
   1049                         this.name + ' should not have a "prototype" property');
   1050            return;
   1051        }
   1052 
   1053        assert_own_property(self[this.name], "prototype",
   1054                            'interface "' + this.name + '" does not have own property "prototype"');
   1055 
   1056        // "For each unique identifier of an operation defined on the
   1057        // interface, there must be a corresponding property on the
   1058        // interface prototype object (if it is a regular operation) or
   1059        // the interface object (if it is a static operation), unless
   1060        // the effective overload set for that identifier and operation
   1061        // and with an argument count of 0 (for the ECMAScript language
   1062        // binding) has no entries."
   1063        //
   1064        var memberHolderObject;
   1065        if (member["static"]) {
   1066            assert_own_property(self[this.name], member.name,
   1067                    "interface object missing static operation");
   1068            memberHolderObject = self[this.name];
   1069        } else if (this.is_global()) {
   1070            assert_own_property(self, member.name,
   1071                    "global object missing non-static operation");
   1072            memberHolderObject = self;
   1073        } else {
   1074            assert_own_property(self[this.name].prototype, member.name,
   1075                    "interface prototype object missing non-static operation");
   1076            memberHolderObject = self[this.name].prototype;
   1077        }
   1078 
   1079        this.do_member_operation_asserts(memberHolderObject, member);
   1080    }, this.name + " interface: operation " + member.name +
   1081    "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
   1082    ")");
   1083 };
   1084 
   1085 //@}
   1086 IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject, member)
   1087 //@{
   1088 {
   1089    var operationUnforgeable = member.isUnforgeable;
   1090    var desc = Object.getOwnPropertyDescriptor(memberHolderObject, member.name);
   1091    // "The property has attributes { [[Writable]]: B,
   1092    // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the
   1093    // operation is unforgeable on the interface, and true otherwise".
   1094    assert_false("get" in desc, "property has getter");
   1095    assert_false("set" in desc, "property has setter");
   1096    assert_equals(desc.writable, !operationUnforgeable,
   1097                  "property should be writable if and only if not unforgeable");
   1098    assert_true(desc.enumerable, "property is not enumerable");
   1099    assert_equals(desc.configurable, !operationUnforgeable,
   1100                  "property should be configurable if and only if not unforgeable");
   1101    // "The value of the property is a Function object whose
   1102    // behavior is as follows . . ."
   1103    assert_equals(typeof memberHolderObject[member.name], "function",
   1104                  "property must be a function");
   1105    // "The value of the Function object’s “length” property is
   1106    // a Number determined as follows:
   1107    // ". . .
   1108    // "Return the length of the shortest argument list of the
   1109    // entries in S."
   1110    assert_equals(memberHolderObject[member.name].length,
   1111        minOverloadLength(this.members.filter(function(m) {
   1112            return m.type == "operation" && m.name == member.name;
   1113        })),
   1114        "property has wrong .length");
   1115 
   1116    // Make some suitable arguments
   1117    var args = member.arguments.map(function(arg) {
   1118        return create_suitable_object(arg.idlType);
   1119    });
   1120 
   1121    // "Let O be a value determined as follows:
   1122    // ". . .
   1123    // "Otherwise, throw a TypeError."
   1124    // This should be hit if the operation is not static, there is
   1125    // no [ImplicitThis] attribute, and the this value is null.
   1126    //
   1127    // TODO: We currently ignore the [ImplicitThis] case.  Except we manually
   1128    // check for globals, since otherwise we'll invoke window.close().  And we
   1129    // have to skip this test for anything that on the proto chain of "self",
   1130    // since that does in fact have implicit-this behavior.
   1131    if (!member["static"]) {
   1132        if (!this.is_global() &&
   1133            memberHolderObject[member.name] != self[member.name])
   1134        {
   1135            assert_throws(new TypeError(), function() {
   1136                memberHolderObject[member.name].apply(null, args);
   1137            }, "calling operation with this = null didn't throw TypeError");
   1138        }
   1139 
   1140        // ". . . If O is not null and is also not a platform object
   1141        // that implements interface I, throw a TypeError."
   1142        //
   1143        // TODO: Test a platform object that implements some other
   1144        // interface.  (Have to be sure to get inheritance right.)
   1145        assert_throws(new TypeError(), function() {
   1146            memberHolderObject[member.name].apply({}, args);
   1147        }, "calling operation with this = {} didn't throw TypeError");
   1148    }
   1149 }
   1150 
   1151 //@}
   1152 IdlInterface.prototype.test_member_stringifier = function(member)
   1153 //@{
   1154 {
   1155    test(() => {
   1156        if (this.is_callback() && !this.has_constants()) {
   1157            return;
   1158        }
   1159 
   1160        assert_own_property(self, this.name,
   1161                            "self does not have own property " + format_value(this.name));
   1162 
   1163        if (this.is_callback()) {
   1164            assert_false("prototype" in self[this.name],
   1165                         this.name + ' should not have a "prototype" property');
   1166            return;
   1167        }
   1168 
   1169        assert_own_property(self[this.name], "prototype",
   1170                            'interface "' + this.name + '" does not have own property "prototype"');
   1171 
   1172        // ". . . the property exists on the interface prototype object."
   1173        var interfacePrototypeObject = self[this.name].prototype;
   1174        assert_own_property(self[this.name].prototype, "toString",
   1175                "interface prototype object missing non-static operation");
   1176 
   1177        var stringifierUnforgeable = member.isUnforgeable;
   1178        var desc = Object.getOwnPropertyDescriptor(interfacePrototypeObject, "toString");
   1179        // "The property has attributes { [[Writable]]: B,
   1180        // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the
   1181        // stringifier is unforgeable on the interface, and true otherwise."
   1182        assert_false("get" in desc, "property has getter");
   1183        assert_false("set" in desc, "property has setter");
   1184        assert_equals(desc.writable, !stringifierUnforgeable,
   1185                      "property should be writable if and only if not unforgeable");
   1186        assert_true(desc.enumerable, "property is not enumerable");
   1187        assert_equals(desc.configurable, !stringifierUnforgeable,
   1188                      "property should be configurable if and only if not unforgeable");
   1189        // "The value of the property is a Function object, which behaves as
   1190        // follows . . ."
   1191        assert_equals(typeof interfacePrototypeObject.toString, "function",
   1192                      "property must be a function");
   1193        // "The value of the Function object’s “length” property is the Number
   1194        // value 0."
   1195        assert_equals(interfacePrototypeObject.toString.length, 0,
   1196            "property has wrong .length");
   1197 
   1198        // "Let O be the result of calling ToObject on the this value."
   1199        assert_throws(new TypeError(), function() {
   1200            self[this.name].prototype.toString.apply(null, []);
   1201        }, "calling stringifier with this = null didn't throw TypeError");
   1202 
   1203        // "If O is not an object that implements the interface on which the
   1204        // stringifier was declared, then throw a TypeError."
   1205        //
   1206        // TODO: Test a platform object that implements some other
   1207        // interface.  (Have to be sure to get inheritance right.)
   1208        assert_throws(new TypeError(), function() {
   1209            self[this.name].prototype.toString.apply({}, []);
   1210        }, "calling stringifier with this = {} didn't throw TypeError");
   1211    }, this.name + " interface: stringifier");
   1212 };
   1213 
   1214 //@}
   1215 IdlInterface.prototype.test_members = function()
   1216 //@{
   1217 {
   1218    for (var i = 0; i < this.members.length; i++)
   1219    {
   1220        var member = this.members[i];
   1221        if (member.untested) {
   1222            continue;
   1223        }
   1224 
   1225        switch (member.type) {
   1226        case "const":
   1227            this.test_member_const(member);
   1228            break;
   1229 
   1230        case "attribute":
   1231            // For unforgeable attributes, we do the checks in
   1232            // test_interface_of instead.
   1233            if (!member.isUnforgeable)
   1234            {
   1235                this.test_member_attribute(member);
   1236            }
   1237            break;
   1238 
   1239        case "operation":
   1240            // TODO: Need to correctly handle multiple operations with the same
   1241            // identifier.
   1242            // For unforgeable operations, we do the checks in
   1243            // test_interface_of instead.
   1244            if (member.name) {
   1245                if (!member.isUnforgeable)
   1246                {
   1247                    this.test_member_operation(member);
   1248                }
   1249            } else if (member.stringifier) {
   1250                this.test_member_stringifier(member);
   1251            }
   1252            break;
   1253 
   1254        default:
   1255            // TODO: check more member types.
   1256            break;
   1257        }
   1258    }
   1259 };
   1260 
   1261 //@}
   1262 IdlInterface.prototype.test_object = function(desc)
   1263 //@{
   1264 {
   1265    var obj, exception = null;
   1266    try
   1267    {
   1268        obj = eval(desc);
   1269    }
   1270    catch(e)
   1271    {
   1272        exception = e;
   1273    }
   1274 
   1275    // TODO: WebIDLParser doesn't currently support named legacycallers, so I'm
   1276    // not sure what those would look like in the AST
   1277    var expected_typeof = this.members.some(function(member)
   1278    {
   1279        return member.legacycaller
   1280            || ("idlType" in member && member.idlType.legacycaller)
   1281            || ("idlType" in member && typeof member.idlType == "object"
   1282            && "idlType" in member.idlType && member.idlType.idlType == "legacycaller");
   1283    }) ? "function" : "object";
   1284 
   1285    this.test_primary_interface_of(desc, obj, exception, expected_typeof);
   1286    var current_interface = this;
   1287    while (current_interface)
   1288    {
   1289        if (!(current_interface.name in this.array.members))
   1290        {
   1291            throw "Interface " + current_interface.name + " not found (inherited by " + this.name + ")";
   1292        }
   1293        if (current_interface.prevent_multiple_testing && current_interface.already_tested)
   1294        {
   1295            return;
   1296        }
   1297        current_interface.test_interface_of(desc, obj, exception, expected_typeof);
   1298        current_interface = this.array.members[current_interface.base];
   1299    }
   1300 };
   1301 
   1302 //@}
   1303 IdlInterface.prototype.test_primary_interface_of = function(desc, obj, exception, expected_typeof)
   1304 //@{
   1305 {
   1306    // We can't easily test that its prototype is correct if there's no
   1307    // interface object, or the object is from a different global environment
   1308    // (not instanceof Object).  TODO: test in this case that its prototype at
   1309    // least looks correct, even if we can't test that it's actually correct.
   1310    if (!this.has_extended_attribute("NoInterfaceObject")
   1311    && (typeof obj != expected_typeof || obj instanceof Object))
   1312    {
   1313        test(() => {
   1314            assert_equals(exception, null, "Unexpected exception when evaluating object");
   1315            assert_equals(typeof obj, expected_typeof, "wrong typeof object");
   1316            assert_own_property(self, this.name,
   1317                                "self does not have own property " + format_value(this.name));
   1318            assert_own_property(self[this.name], "prototype",
   1319                                'interface "' + this.name + '" does not have own property "prototype"');
   1320 
   1321            // "The value of the internal [[Prototype]] property of the
   1322            // platform object is the interface prototype object of the primary
   1323            // interface from the platform object’s associated global
   1324            // environment."
   1325            assert_equals(Object.getPrototypeOf(obj),
   1326                          self[this.name].prototype,
   1327                          desc + "'s prototype is not " + this.name + ".prototype");
   1328        }, this.name + " must be primary interface of " + desc);
   1329    }
   1330 
   1331    // "The class string of a platform object that implements one or more
   1332    // interfaces must be the identifier of the primary interface of the
   1333    // platform object."
   1334    test(() => {
   1335        assert_equals(exception, null, "Unexpected exception when evaluating object");
   1336        assert_equals(typeof obj, expected_typeof, "wrong typeof object");
   1337        assert_class_string(obj, this.name, "class string of " + desc);
   1338        if (!this.has_stringifier())
   1339        {
   1340            assert_equals(String(obj), "[object " + this.name + "]", "String(" + desc + ")");
   1341        }
   1342    }, "Stringification of " + desc);
   1343 };
   1344 
   1345 //@}
   1346 IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expected_typeof)
   1347 //@{
   1348 {
   1349    // TODO: Indexed and named properties, more checks on interface members
   1350    this.already_tested = true;
   1351 
   1352    for (var i = 0; i < this.members.length; i++)
   1353    {
   1354        var member = this.members[i];
   1355        if (member.type == "attribute" && member.isUnforgeable)
   1356        {
   1357            test(() => {
   1358                assert_equals(exception, null, "Unexpected exception when evaluating object");
   1359                assert_equals(typeof obj, expected_typeof, "wrong typeof object");
   1360                this.do_interface_attribute_asserts(obj, member);
   1361            }, this.name + " interface: " + desc + ' must have own property "' + member.name + '"');
   1362        }
   1363        else if (member.type == "operation" &&
   1364                 member.name &&
   1365                 member.isUnforgeable)
   1366        {
   1367            test(() => {
   1368                assert_equals(exception, null, "Unexpected exception when evaluating object");
   1369                assert_equals(typeof obj, expected_typeof, "wrong typeof object");
   1370                assert_own_property(obj, member.name,
   1371                                    "Doesn't have the unforgeable operation property");
   1372                this.do_member_operation_asserts(obj, member);
   1373            }, this.name + " interface: " + desc + ' must have own property "' + member.name + '"');
   1374        }
   1375        else if ((member.type == "const"
   1376        || member.type == "attribute"
   1377        || member.type == "operation")
   1378        && member.name)
   1379        {
   1380            test(() => {
   1381                assert_equals(exception, null, "Unexpected exception when evaluating object");
   1382                assert_equals(typeof obj, expected_typeof, "wrong typeof object");
   1383                if (!member["static"]) {
   1384                    if (!this.is_global()) {
   1385                        assert_inherits(obj, member.name);
   1386                    } else {
   1387                        assert_own_property(obj, member.name);
   1388                    }
   1389 
   1390                    if (member.type == "const")
   1391                    {
   1392                        assert_equals(obj[member.name], constValue(member.value));
   1393                    }
   1394                    if (member.type == "attribute")
   1395                    {
   1396                        // Attributes are accessor properties, so they might
   1397                        // legitimately throw an exception rather than returning
   1398                        // anything.
   1399                        var property, thrown = false;
   1400                        try
   1401                        {
   1402                            property = obj[member.name];
   1403                        }
   1404                        catch (e)
   1405                        {
   1406                            thrown = true;
   1407                        }
   1408                        if (!thrown)
   1409                        {
   1410                            this.array.assert_type_is(property, member.idlType);
   1411                        }
   1412                    }
   1413                    if (member.type == "operation")
   1414                    {
   1415                        assert_equals(typeof obj[member.name], "function");
   1416                    }
   1417                }
   1418            }, this.name + " interface: " + desc + ' must inherit property "' + member.name + '" with the proper type (' + i + ')');
   1419        }
   1420        // TODO: This is wrong if there are multiple operations with the same
   1421        // identifier.
   1422        // TODO: Test passing arguments of the wrong type.
   1423        if (member.type == "operation" && member.name && member.arguments.length)
   1424        {
   1425            test(() => {
   1426                assert_equals(exception, null, "Unexpected exception when evaluating object");
   1427                assert_equals(typeof obj, expected_typeof, "wrong typeof object");
   1428                if (!member["static"]) {
   1429                    if (!this.is_global() && !member.isUnforgeable) {
   1430                        assert_inherits(obj, member.name);
   1431                    } else {
   1432                        assert_own_property(obj, member.name);
   1433                    }
   1434                }
   1435                else
   1436                {
   1437                    assert_false(member.name in obj);
   1438                }
   1439 
   1440                var minLength = minOverloadLength(this.members.filter(function(m) {
   1441                    return m.type == "operation" && m.name == member.name;
   1442                }));
   1443                var args = [];
   1444                for (var i = 0; i < minLength; i++) {
   1445                    assert_throws(new TypeError(), () => {
   1446                        obj[member.name].apply(obj, args);
   1447                    }, "Called with " + i + " arguments");
   1448 
   1449                    args.push(create_suitable_object(member.arguments[i].idlType));
   1450                }
   1451            }, this.name + " interface: calling " + member.name +
   1452            "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
   1453            ") on " + desc + " with too few arguments must throw TypeError");
   1454        }
   1455    }
   1456 };
   1457 
   1458 //@}
   1459 IdlInterface.prototype.has_stringifier = function()
   1460 //@{
   1461 {
   1462    if (this.members.some(function(member) { return member.stringifier; })) {
   1463        return true;
   1464    }
   1465    if (this.base &&
   1466        this.array.members[this.base].has_stringifier()) {
   1467        return true;
   1468    }
   1469    return false;
   1470 };
   1471 
   1472 //@}
   1473 IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member)
   1474 //@{
   1475 {
   1476    // This function tests WebIDL as of 2015-01-27.
   1477    // TODO: Consider [Exposed].
   1478 
   1479    // This is called by test_member_attribute() with the prototype as obj if
   1480    // it is not a global, and the global otherwise, and by test_interface_of()
   1481    // with the object as obj.
   1482 
   1483    // "For each exposed attribute of the interface, whether it was declared on
   1484    // the interface itself or one of its consequential interfaces, there MUST
   1485    // exist a corresponding property. The characteristics of this property are
   1486    // as follows:"
   1487 
   1488    // "The name of the property is the identifier of the attribute."
   1489    assert_own_property(obj, member.name);
   1490 
   1491    // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]:
   1492    // true, [[Configurable]]: configurable }, where:
   1493    // "configurable is false if the attribute was declared with the
   1494    // [Unforgeable] extended attribute and true otherwise;
   1495    // "G is the attribute getter, defined below; and
   1496    // "S is the attribute setter, also defined below."
   1497    var desc = Object.getOwnPropertyDescriptor(obj, member.name);
   1498    assert_false("value" in desc, 'property descriptor has value but is supposed to be accessor');
   1499    assert_false("writable" in desc, 'property descriptor has "writable" field but is supposed to be accessor');
   1500    assert_true(desc.enumerable, "property is not enumerable");
   1501    if (member.isUnforgeable)
   1502    {
   1503        assert_false(desc.configurable, "[Unforgeable] property must not be configurable");
   1504    }
   1505    else
   1506    {
   1507        assert_true(desc.configurable, "property must be configurable");
   1508    }
   1509 
   1510 
   1511    // "The attribute getter is a Function object whose behavior when invoked
   1512    // is as follows:"
   1513    assert_equals(typeof desc.get, "function", "getter must be Function");
   1514 
   1515    // "If the attribute is a regular attribute, then:"
   1516    if (!member["static"]) {
   1517        // "If O is not a platform object that implements I, then:
   1518        // "If the attribute was specified with the [LenientThis] extended
   1519        // attribute, then return undefined.
   1520        // "Otherwise, throw a TypeError."
   1521        if (!member.has_extended_attribute("LenientThis")) {
   1522            assert_throws(new TypeError(), () => {
   1523                desc.get.call({});
   1524            }, "calling getter on wrong object type must throw TypeError");
   1525        } else {
   1526            assert_equals(desc.get.call({}), undefined,
   1527                          "calling getter on wrong object type must return undefined");
   1528        }
   1529    }
   1530 
   1531    // "The value of the Function object’s “length” property is the Number
   1532    // value 0."
   1533    assert_equals(desc.get.length, 0, "getter length must be 0");
   1534 
   1535 
   1536    // TODO: Test calling setter on the interface prototype (should throw
   1537    // TypeError in most cases).
   1538    if (member.readonly
   1539    && !member.has_extended_attribute("PutForwards")
   1540    && !member.has_extended_attribute("Replaceable"))
   1541    {
   1542        // "The attribute setter is undefined if the attribute is declared
   1543        // readonly and has neither a [PutForwards] nor a [Replaceable]
   1544        // extended attribute declared on it."
   1545        assert_equals(desc.set, undefined, "setter must be undefined for readonly attributes");
   1546    }
   1547    else
   1548    {
   1549        // "Otherwise, it is a Function object whose behavior when
   1550        // invoked is as follows:"
   1551        assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes");
   1552 
   1553        // "If the attribute is a regular attribute, then:"
   1554        if (!member["static"]) {
   1555            // "If /validThis/ is false and the attribute was not specified
   1556            // with the [LenientThis] extended attribute, then throw a
   1557            // TypeError."
   1558            // "If the attribute is declared with a [Replaceable] extended
   1559            // attribute, then: ..."
   1560            // "If validThis is false, then return."
   1561            if (!member.has_extended_attribute("LenientThis")) {
   1562                assert_throws(new TypeError(), () => {
   1563                    desc.set.call({});
   1564                }, "calling setter on wrong object type must throw TypeError");
   1565            } else {
   1566                assert_equals(desc.set.call({}), undefined,
   1567                              "calling setter on wrong object type must return undefined");
   1568            }
   1569        }
   1570 
   1571        // "The value of the Function object’s “length” property is the Number
   1572        // value 1."
   1573        assert_equals(desc.set.length, 1, "setter length must be 1");
   1574    }
   1575 }
   1576 //@}
   1577 
   1578 /// IdlInterfaceMember ///
   1579 function IdlInterfaceMember(obj)
   1580 //@{
   1581 {
   1582    /**
   1583     * obj is an object produced by the WebIDLParser.js "ifMember" production.
   1584     * We just forward all properties to this object without modification,
   1585     * except for special extAttrs handling.
   1586     */
   1587    for (var k in obj)
   1588    {
   1589        this[k] = obj[k];
   1590    }
   1591    if (!("extAttrs" in this))
   1592    {
   1593        this.extAttrs = [];
   1594    }
   1595 
   1596    this.isUnforgeable = this.has_extended_attribute("Unforgeable");
   1597 }
   1598 
   1599 //@}
   1600 IdlInterfaceMember.prototype = Object.create(IdlObject.prototype);
   1601 
   1602 /// Internal helper functions ///
   1603 function create_suitable_object(type)
   1604 //@{
   1605 {
   1606    /**
   1607     * type is an object produced by the WebIDLParser.js "type" production.  We
   1608     * return a JavaScript value that matches the type, if we can figure out
   1609     * how.
   1610     */
   1611    if (type.nullable)
   1612    {
   1613        return null;
   1614    }
   1615    switch (type.idlType)
   1616    {
   1617        case "any":
   1618        case "boolean":
   1619            return true;
   1620 
   1621        case "byte": case "octet": case "short": case "unsigned short":
   1622        case "long": case "unsigned long": case "long long":
   1623        case "unsigned long long": case "float": case "double":
   1624        case "unrestricted float": case "unrestricted double":
   1625            return 7;
   1626 
   1627        case "DOMString":
   1628        case "ByteString":
   1629        case "USVString":
   1630            return "foo";
   1631 
   1632        case "object":
   1633            return {a: "b"};
   1634 
   1635        case "Node":
   1636            return document.createTextNode("abc");
   1637    }
   1638    return null;
   1639 }
   1640 //@}
   1641 
   1642 /// IdlEnum ///
   1643 // Used for IdlArray.prototype.assert_type_is
   1644 function IdlEnum(obj)
   1645 //@{
   1646 {
   1647    /**
   1648     * obj is an object produced by the WebIDLParser.js "dictionary"
   1649     * production.
   1650     */
   1651 
   1652    /** Self-explanatory. */
   1653    this.name = obj.name;
   1654 
   1655    /** An array of values produced by the "enum" production. */
   1656    this.values = obj.values;
   1657 
   1658 }
   1659 //@}
   1660 
   1661 IdlEnum.prototype = Object.create(IdlObject.prototype);
   1662 
   1663 /// IdlTypedef ///
   1664 // Used for IdlArray.prototype.assert_type_is
   1665 function IdlTypedef(obj)
   1666 //@{
   1667 {
   1668    /**
   1669     * obj is an object produced by the WebIDLParser.js "typedef"
   1670     * production.
   1671     */
   1672 
   1673    /** Self-explanatory. */
   1674    this.name = obj.name;
   1675 
   1676    /** An array of values produced by the "typedef" production. */
   1677    this.values = obj.values;
   1678 
   1679 }
   1680 //@}
   1681 
   1682 IdlTypedef.prototype = Object.create(IdlObject.prototype);
   1683 
   1684 }());
   1685 // vim: set expandtab shiftwidth=4 tabstop=4 foldmarker=@{,@} foldmethod=marker: