Bumping manifests a=b2g-bump
[gecko.git] / dom / imptests / idlharness.js
blobb6c02a3f8ddbe7a714d13d59681c5c5e05255dd6
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].
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 */
11 /* For user documentation see docs/idlharness.md */
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;
59 /// IdlArray ///
60 // Entry point
61 window.IdlArray = function()
62 //@{
64     /**
65      * A map from strings to the corresponding named IdlObject, such as
66      * IdlInterface or IdlException.  These are the things that test() will run
67      * tests on.
68      */
69     this.members = {};
71     /**
72      * A map from strings to arrays of strings.  The keys are interface or
73      * exception names, and are expected to also exist as keys in this.members
74      * (otherwise they'll be ignored).  This is populated by add_objects() --
75      * see documentation at the start of the file.  The actual tests will be
76      * run by calling this.members[name].test_object(obj) for each obj in
77      * this.objects[name].  obj is a string that will be eval'd to produce a
78      * JavaScript value, which is supposed to be an object implementing the
79      * given IdlObject (interface, exception, etc.).
80      */
81     this.objects = {};
83     /**
84      * When adding multiple collections of IDLs one at a time, an earlier one
85      * might contain a partial interface or implements statement that depends
86      * on a later one.  Save these up and handle them right before we run
87      * tests.
88      *
89      * .partials is simply an array of objects from WebIDLParser.js'
90      * "partialinterface" production.  .implements maps strings to arrays of
91      * strings, such that
92      *
93      *   A implements B;
94      *   A implements C;
95      *   D implements E;
96      *
97      * results in { A: ["B", "C"], D: ["E"] }.
98      */
99     this.partials = [];
100     this["implements"] = {};
103 //@}
104 IdlArray.prototype.add_idls = function(raw_idls)
105 //@{
107     /** Entry point.  See documentation at beginning of file. */
108     this.internal_add_idls(WebIDL2.parse(raw_idls));
111 //@}
112 IdlArray.prototype.add_untested_idls = function(raw_idls)
113 //@{
115     /** Entry point.  See documentation at beginning of file. */
116     var parsed_idls = WebIDL2.parse(raw_idls);
117     for (var i = 0; i < parsed_idls.length; i++)
118     {
119         parsed_idls[i].untested = true;
120         if ("members" in parsed_idls[i])
121         {
122             for (var j = 0; j < parsed_idls[i].members.length; j++)
123             {
124                 parsed_idls[i].members[j].untested = true;
125             }
126         }
127     }
128     this.internal_add_idls(parsed_idls);
131 //@}
132 IdlArray.prototype.internal_add_idls = function(parsed_idls)
133 //@{
135     /**
136      * Internal helper called by add_idls() and add_untested_idls().
137      * parsed_idls is an array of objects that come from WebIDLParser.js's
138      * "definitions" production.  The add_untested_idls() entry point
139      * additionally sets an .untested property on each object (and its
140      * .members) so that they'll be skipped by test() -- they'll only be
141      * used for base interfaces of tested interfaces, return types, etc.
142      */
143     parsed_idls.forEach(function(parsed_idl)
144     {
145         if (parsed_idl.type == "interface" && parsed_idl.partial)
146         {
147             this.partials.push(parsed_idl);
148             return;
149         }
151         if (parsed_idl.type == "implements")
152         {
153             if (!(parsed_idl.target in this["implements"]))
154             {
155                 this["implements"][parsed_idl.target] = [];
156             }
157             this["implements"][parsed_idl.target].push(parsed_idl["implements"]);
158             return;
159         }
161         parsed_idl.array = this;
162         if (parsed_idl.name in this.members)
163         {
164             throw "Duplicate identifier " + parsed_idl.name;
165         }
166         switch(parsed_idl.type)
167         {
168         case "interface":
169             this.members[parsed_idl.name] = new IdlInterface(parsed_idl);
170             break;
172         case "exception":
173             this.members[parsed_idl.name] = new IdlException(parsed_idl);
174             break;
176         case "dictionary":
177             // Nothing to test, but we need the dictionary info around for type
178             // checks
179             this.members[parsed_idl.name] = new IdlDictionary(parsed_idl);
180             break;
182         case "typedef":
183             this.members[parsed_idl.name] = new IdlTypedef(parsed_idl);
184             break;
186         case "callback":
187             // TODO
188             console.log("callback not yet supported");
189             break;
191         case "enum":
192             this.members[parsed_idl.name] = new IdlEnum(parsed_idl);
193             break;
195         case "callback interface":
196             // TODO
197             console.log("callback interface not yet supported");
198             break;
200         default:
201             throw parsed_idl.name + ": " + parsed_idl.type + " not yet supported";
202         }
203     }.bind(this));
206 //@}
207 IdlArray.prototype.add_objects = function(dict)
208 //@{
210     /** Entry point.  See documentation at beginning of file. */
211     for (var k in dict)
212     {
213         if (k in this.objects)
214         {
215             this.objects[k] = this.objects[k].concat(dict[k]);
216         }
217         else
218         {
219             this.objects[k] = dict[k];
220         }
221     }
224 //@}
225 IdlArray.prototype.prevent_multiple_testing = function(name)
226 //@{
228     /** Entry point.  See documentation at beginning of file. */
229     this.members[name].prevent_multiple_testing = true;
232 //@}
233 IdlArray.prototype.recursively_get_implements = function(interface_name)
234 //@{
236     /**
237      * Helper function for test().  Returns an array of things that implement
238      * interface_name, so if the IDL contains
239      *
240      *   A implements B;
241      *   B implements C;
242      *   B implements D;
243      *
244      * then recursively_get_implements("A") should return ["B", "C", "D"].
245      */
246     var ret = this["implements"][interface_name];
247     if (ret === undefined)
248     {
249         return [];
250     }
251     for (var i = 0; i < this["implements"][interface_name].length; i++)
252     {
253         ret = ret.concat(this.recursively_get_implements(ret[i]));
254         if (ret.indexOf(ret[i]) != ret.lastIndexOf(ret[i]))
255         {
256             throw "Circular implements statements involving " + ret[i];
257         }
258     }
259     return ret;
262 //@}
263 IdlArray.prototype.test = function()
264 //@{
266     /** Entry point.  See documentation at beginning of file. */
268     // First merge in all the partial interfaces and implements statements we
269     // encountered.
270     this.partials.forEach(function(parsed_idl)
271     {
272         if (!(parsed_idl.name in this.members)
273         || !(this.members[parsed_idl.name] instanceof IdlInterface))
274         {
275             throw "Partial interface " + parsed_idl.name + " with no original interface";
276         }
277         if (parsed_idl.extAttrs)
278         {
279             parsed_idl.extAttrs.forEach(function(extAttr)
280             {
281                 this.members[parsed_idl.name].extAttrs.push(extAttr);
282             }.bind(this));
283         }
284         parsed_idl.members.forEach(function(member)
285         {
286             this.members[parsed_idl.name].members.push(new IdlInterfaceMember(member));
287         }.bind(this));
288     }.bind(this));
289     this.partials = [];
291     for (var lhs in this["implements"])
292     {
293         this.recursively_get_implements(lhs).forEach(function(rhs)
294         {
295             var errStr = lhs + " implements " + rhs + ", but ";
296             if (!(lhs in this.members)) throw errStr + lhs + " is undefined.";
297             if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface.";
298             if (!(rhs in this.members)) throw errStr + rhs + " is undefined.";
299             if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface.";
300             this.members[rhs].members.forEach(function(member)
301             {
302                 this.members[lhs].members.push(new IdlInterfaceMember(member));
303             }.bind(this));
304         }.bind(this));
305     }
306     this["implements"] = {};
308     // Now run test() on every member, and test_object() for every object.
309     for (var name in this.members)
310     {
311         this.members[name].test();
312         if (name in this.objects)
313         {
314             this.objects[name].forEach(function(str)
315             {
316                 this.members[name].test_object(str);
317             }.bind(this));
318         }
319     }
322 //@}
323 IdlArray.prototype.assert_type_is = function(value, type)
324 //@{
326     /**
327      * Helper function that tests that value is an instance of type according
328      * to the rules of WebIDL.  value is any JavaScript value, and type is an
329      * object produced by WebIDLParser.js' "type" production.  That production
330      * is fairly elaborate due to the complexity of WebIDL's types, so it's
331      * best to look at the grammar to figure out what properties it might have.
332      */
333     if (type.idlType == "any")
334     {
335         // No assertions to make
336         return;
337     }
339     if (type.nullable && value === null)
340     {
341         // This is fine
342         return;
343     }
345     if (type.array)
346     {
347         // TODO: not supported yet
348         return;
349     }
351     if (type.sequence)
352     {
353         assert_true(Array.isArray(value), "is not array");
354         if (!value.length)
355         {
356             // Nothing we can do.
357             return;
358         }
359         this.assert_type_is(value[0], type.idlType.idlType);
360         return;
361     }
363     type = type.idlType;
365     switch(type)
366     {
367         case "void":
368             assert_equals(value, undefined);
369             return;
371         case "boolean":
372             assert_equals(typeof value, "boolean");
373             return;
375         case "byte":
376             assert_equals(typeof value, "number");
377             assert_equals(value, Math.floor(value), "not an integer");
378             assert_true(-128 <= value && value <= 127, "byte " + value + " not in range [-128, 127]");
379             return;
381         case "octet":
382             assert_equals(typeof value, "number");
383             assert_equals(value, Math.floor(value), "not an integer");
384             assert_true(0 <= value && value <= 255, "octet " + value + " not in range [0, 255]");
385             return;
387         case "short":
388             assert_equals(typeof value, "number");
389             assert_equals(value, Math.floor(value), "not an integer");
390             assert_true(-32768 <= value && value <= 32767, "short " + value + " not in range [-32768, 32767]");
391             return;
393         case "unsigned short":
394             assert_equals(typeof value, "number");
395             assert_equals(value, Math.floor(value), "not an integer");
396             assert_true(0 <= value && value <= 65535, "unsigned short " + value + " not in range [0, 65535]");
397             return;
399         case "long":
400             assert_equals(typeof value, "number");
401             assert_equals(value, Math.floor(value), "not an integer");
402             assert_true(-2147483648 <= value && value <= 2147483647, "long " + value + " not in range [-2147483648, 2147483647]");
403             return;
405         case "unsigned long":
406             assert_equals(typeof value, "number");
407             assert_equals(value, Math.floor(value), "not an integer");
408             assert_true(0 <= value && value <= 4294967295, "unsigned long " + value + " not in range [0, 4294967295]");
409             return;
411         case "long long":
412             assert_equals(typeof value, "number");
413             return;
415         case "unsigned long long":
416             assert_equals(typeof value, "number");
417             assert_true(0 <= value, "unsigned long long is negative");
418             return;
420         case "float":
421         case "double":
422         case "unrestricted float":
423         case "unrestricted double":
424             // TODO: distinguish these cases
425             assert_equals(typeof value, "number");
426             return;
428         case "DOMString":
429         case "ByteString":
430         case "USVString":
431             // TODO: https://github.com/w3c/testharness.js/issues/92
432             assert_equals(typeof value, "string");
433             return;
435         case "object":
436             assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function");
437             return;
438     }
440     if (!(type in this.members))
441     {
442         throw "Unrecognized type " + type;
443     }
445     if (this.members[type] instanceof IdlInterface)
446     {
447         // We don't want to run the full
448         // IdlInterface.prototype.test_instance_of, because that could result
449         // in an infinite loop.  TODO: This means we don't have tests for
450         // NoInterfaceObject interfaces, and we also can't test objects that
451         // come from another window.
452         assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function");
453         if (value instanceof Object
454         && !this.members[type].has_extended_attribute("NoInterfaceObject")
455         && type in window)
456         {
457             assert_true(value instanceof window[type], "not instanceof " + type);
458         }
459     }
460     else if (this.members[type] instanceof IdlEnum)
461     {
462         assert_equals(typeof value, "string");
463     }
464     else if (this.members[type] instanceof IdlDictionary)
465     {
466         // TODO: Test when we actually have something to test this on
467     }
468     else if (this.members[type] instanceof IdlTypedef)
469     {
470         // TODO: Test when we actually have something to test this on
471     }
472     else
473     {
474         throw "Type " + type + " isn't an interface or dictionary";
475     }
477 //@}
479 /// IdlObject ///
480 function IdlObject() {}
481 IdlObject.prototype.test = function()
482 //@{
484     /**
485      * By default, this does nothing, so no actual tests are run for IdlObjects
486      * that don't define any (e.g., IdlDictionary at the time of this writing).
487      */
490 //@}
491 IdlObject.prototype.has_extended_attribute = function(name)
492 //@{
494     /**
495      * This is only meaningful for things that support extended attributes,
496      * such as interfaces, exceptions, and members.
497      */
498     return this.extAttrs.some(function(o)
499     {
500         return o.name == name;
501     });
504 //@}
506 /// IdlDictionary ///
507 // Used for IdlArray.prototype.assert_type_is
508 function IdlDictionary(obj)
509 //@{
511     /**
512      * obj is an object produced by the WebIDLParser.js "dictionary"
513      * production.
514      */
516     /** Self-explanatory. */
517     this.name = obj.name;
519     /** An array of objects produced by the "dictionaryMember" production. */
520     this.members = obj.members;
522     /**
523      * The name (as a string) of the dictionary type we inherit from, or null
524      * if there is none.
525      */
526     this.base = obj.inheritance;
529 //@}
530 IdlDictionary.prototype = Object.create(IdlObject.prototype);
532 /// IdlExceptionOrInterface ///
533 // Code sharing!
534 function IdlExceptionOrInterface(obj)
535 //@{
537     /**
538      * obj is an object produced by the WebIDLParser.js "exception" or
539      * "interface" production, as appropriate.
540      */
542     /** Self-explanatory. */
543     this.name = obj.name;
545     /** A back-reference to our IdlArray. */
546     this.array = obj.array;
548     /**
549      * An indicator of whether we should run tests on the (exception) interface
550      * object and (exception) interface prototype object.  Tests on members are
551      * controlled by .untested on each member, not this.
552      */
553     this.untested = obj.untested;
555     /** An array of objects produced by the "ExtAttr" production. */
556     this.extAttrs = obj.extAttrs;
558     /** An array of IdlInterfaceMembers. */
559     this.members = obj.members.map(function(m){return new IdlInterfaceMember(m); });
561     /**
562      * The name (as a string) of the type we inherit from, or null if there is
563      * none.
564      */
565     this.base = obj.inheritance;
568 //@}
569 IdlExceptionOrInterface.prototype = Object.create(IdlObject.prototype);
570 IdlExceptionOrInterface.prototype.test = function()
571 //@{
573     if (this.has_extended_attribute("NoInterfaceObject"))
574     {
575         // No tests to do without an instance.  TODO: We should still be able
576         // to run tests on the prototype object, if we obtain one through some
577         // other means.
578         return;
579     }
581     if (!this.untested)
582     {
583         // First test things to do with the exception/interface object and
584         // exception/interface prototype object.
585         this.test_self();
586     }
587     // Then test things to do with its members (constants, fields, attributes,
588     // operations, . . .).  These are run even if .untested is true, because
589     // members might themselves be marked as .untested.  This might happen to
590     // interfaces if the interface itself is untested but a partial interface
591     // that extends it is tested -- then the interface itself and its initial
592     // members will be marked as untested, but the members added by the partial
593     // interface are still tested.
594     this.test_members();
597 //@}
599 /// IdlException ///
600 function IdlException(obj) { IdlExceptionOrInterface.call(this, obj); }
601 IdlException.prototype = Object.create(IdlExceptionOrInterface.prototype);
602 IdlException.prototype.test_self = function()
603 //@{
605     test(function()
606     {
607         // "For every exception that is not declared with the
608         // [NoInterfaceObject] extended attribute, a corresponding property
609         // must exist on the exception’s relevant namespace object. The name of
610         // the property is the identifier of the exception, and its value is an
611         // object called the exception interface object, which provides access
612         // to any constants that have been associated with the exception. The
613         // property has the attributes { [[Writable]]: true, [[Enumerable]]:
614         // false, [[Configurable]]: true }."
615         assert_own_property(window, this.name,
616                             "window does not have own property " + format_value(this.name));
617         var desc = Object.getOwnPropertyDescriptor(window, this.name);
618         assert_false("get" in desc, "window's property " + format_value(this.name) + " has getter");
619         assert_false("set" in desc, "window's property " + format_value(this.name) + " has setter");
620         assert_true(desc.writable, "window's property " + format_value(this.name) + " is not writable");
621         assert_false(desc.enumerable, "window's property " + format_value(this.name) + " is enumerable");
622         assert_true(desc.configurable, "window's property " + format_value(this.name) + " is not configurable");
624         // "The exception interface object for a given exception must be a
625         // function object."
626         // "If an object is defined to be a function object, then it has
627         // characteristics as follows:"
628         // "Its [[Prototype]] internal property is the Function prototype
629         // object."
630         // Note: This doesn't match browsers as of December 2011, see
631         // http://www.w3.org/Bugs/Public/show_bug.cgi?id=14813
632         assert_equals(Object.getPrototypeOf(window[this.name]), Function.prototype,
633                       "prototype of window's property " + format_value(this.name) + " is not Function.prototype");
634         // "Its [[Get]] internal property is set as described in ECMA-262
635         // section 15.3.5.4."
636         // Not much to test for this.
637         // "Its [[Construct]] internal property is set as described in ECMA-262
638         // section 13.2.2."
639         // Tested below.
640         // "Its [[HasInstance]] internal property is set as described in
641         // ECMA-262 section 15.3.5.3, unless otherwise specified."
642         // TODO
643         // "Its [[Class]] internal property is “Function”."
644         // String() returns something implementation-dependent, because it
645         // calls Function#toString.
646         assert_class_string(window[this.name], "Function",
647                             "class string of " + this.name);
649         // TODO: Test 4.9.1.1. Exception interface object [[Call]] method
650         // (which does not match browsers:
651         // http://www.w3.org/Bugs/Public/show_bug.cgi?id=14885)
652     }.bind(this), this.name + " exception: existence and properties of exception interface object");
654     test(function()
655     {
656         assert_own_property(window, this.name,
657                             "window does not have own property " + format_value(this.name));
659         // "The exception interface object must also have a property named
660         // “prototype” with attributes { [[Writable]]: false, [[Enumerable]]:
661         // false, [[Configurable]]: false } whose value is an object called the
662         // exception interface prototype object. This object also provides
663         // access to the constants that are declared on the exception."
664         assert_own_property(window[this.name], "prototype",
665                             'exception "' + this.name + '" does not have own property "prototype"');
666         var desc = Object.getOwnPropertyDescriptor(window[this.name], "prototype");
667         assert_false("get" in desc, this.name + ".prototype has getter");
668         assert_false("set" in desc, this.name + ".prototype has setter");
669         assert_false(desc.writable, this.name + ".prototype is writable");
670         assert_false(desc.enumerable, this.name + ".prototype is enumerable");
671         assert_false(desc.configurable, this.name + ".prototype is configurable");
673         // "The exception interface prototype object for a given exception must
674         // have an internal [[Prototype]] property whose value is as follows:
675         //
676         // "If the exception is declared to inherit from another exception,
677         // then the value of the internal [[Prototype]] property is the
678         // exception interface prototype object for the inherited exception.
679         // "Otherwise, the exception is not declared to inherit from another
680         // exception. The value of the internal [[Prototype]] property is the
681         // Error prototype object ([ECMA-262], section 15.11.3.1)."
682         //
683         // Note: This doesn't match browsers as of December 2011, see
684         // https://www.w3.org/Bugs/Public/show_bug.cgi?id=14887.
685         var inherit_exception = this.base ? this.base : "Error";
686         assert_own_property(window, inherit_exception,
687                             'should inherit from ' + inherit_exception + ', but window has no such property');
688         assert_own_property(window[inherit_exception], "prototype",
689                             'should inherit from ' + inherit_exception + ', but that object has no "prototype" property');
690         assert_equals(Object.getPrototypeOf(window[this.name].prototype),
691                       window[inherit_exception].prototype,
692                       'prototype of ' + this.name + '.prototype is not ' + inherit_exception + '.prototype');
694         // "The class string of an exception interface prototype object is the
695         // concatenation of the exception’s identifier and the string
696         // “Prototype”."
697         assert_class_string(window[this.name].prototype, this.name + "Prototype",
698                             "class string of " + this.name + ".prototype");
699         // TODO: Test String(), based on ES definition of
700         // Error.prototype.toString?
701     }.bind(this), this.name + " exception: existence and properties of exception interface prototype object");
703     test(function()
704     {
705         assert_own_property(window, this.name,
706                             "window does not have own property " + format_value(this.name));
707         assert_own_property(window[this.name], "prototype",
708                             'interface "' + this.name + '" does not have own property "prototype"');
710         // "There must be a property named “name” on the exception interface
711         // prototype object with attributes { [[Writable]]: true,
712         // [[Enumerable]]: false, [[Configurable]]: true } and whose value is
713         // the identifier of the exception."
714         assert_own_property(window[this.name].prototype, "name",
715                 'prototype object does not have own property "name"');
716         var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "name");
717         assert_false("get" in desc, this.name + ".prototype.name has getter");
718         assert_false("set" in desc, this.name + ".prototype.name has setter");
719         assert_true(desc.writable, this.name + ".prototype.name is not writable");
720         assert_false(desc.enumerable, this.name + ".prototype.name is enumerable");
721         assert_true(desc.configurable, this.name + ".prototype.name is not configurable");
722         assert_equals(desc.value, this.name, this.name + ".prototype.name has incorrect value");
723     }.bind(this), this.name + " exception: existence and properties of exception interface prototype object's \"name\" property");
725     test(function()
726     {
727         assert_own_property(window, this.name,
728                             "window does not have own property " + format_value(this.name));
729         assert_own_property(window[this.name], "prototype",
730                             'interface "' + this.name + '" does not have own property "prototype"');
732         // "If the [NoInterfaceObject] extended attribute was not specified on
733         // the exception, then there must also be a property named
734         // “constructor” on the exception interface prototype object with
735         // attributes { [[Writable]]: true, [[Enumerable]]: false,
736         // [[Configurable]]: true } and whose value is a reference to the
737         // exception interface object for the exception."
738         assert_own_property(window[this.name].prototype, "constructor",
739                             this.name + '.prototype does not have own property "constructor"');
740         var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "constructor");
741         assert_false("get" in desc, this.name + ".prototype.constructor has getter");
742         assert_false("set" in desc, this.name + ".prototype.constructor has setter");
743         assert_true(desc.writable, this.name + ".prototype.constructor is not writable");
744         assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable");
745         assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable");
746         assert_equals(window[this.name].prototype.constructor, window[this.name],
747                       this.name + '.prototype.constructor is not the same object as ' + this.name);
748     }.bind(this), this.name + " exception: existence and properties of exception interface prototype object's \"constructor\" property");
751 //@}
752 IdlException.prototype.test_members = function()
753 //@{
755     for (var i = 0; i < this.members.length; i++)
756     {
757         var member = this.members[i];
758         if (member.untested)
759         {
760             continue;
761         }
762         if (member.type == "const" && member.name != "prototype")
763         {
764             test(function()
765             {
766                 assert_own_property(window, this.name,
767                                     "window does not have own property " + format_value(this.name));
769                 // "For each constant defined on the exception, there must be a
770                 // corresponding property on the exception interface object, if
771                 // it exists, if the identifier of the constant is not
772                 // “prototype”."
773                 assert_own_property(window[this.name], member.name);
774                 // "The value of the property is the ECMAScript value that is
775                 // equivalent to the constant’s IDL value, according to the
776                 // rules in section 4.2 above."
777                 assert_equals(window[this.name][member.name], constValue(member.value),
778                               "property has wrong value");
779                 // "The property has attributes { [[Writable]]: false,
780                 // [[Enumerable]]: true, [[Configurable]]: false }."
781                 var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
782                 assert_false("get" in desc, "property has getter");
783                 assert_false("set" in desc, "property has setter");
784                 assert_false(desc.writable, "property is writable");
785                 assert_true(desc.enumerable, "property is not enumerable");
786                 assert_false(desc.configurable, "property is configurable");
787             }.bind(this), this.name + " exception: constant " + member.name + " on exception interface object");
788             // "In addition, a property with the same characteristics must
789             // exist on the exception interface prototype object."
790             test(function()
791             {
792                 assert_own_property(window, this.name,
793                                     "window does not have own property " + format_value(this.name));
794                 assert_own_property(window[this.name], "prototype",
795                                     'exception "' + this.name + '" does not have own property "prototype"');
797                 assert_own_property(window[this.name].prototype, member.name);
798                 assert_equals(window[this.name].prototype[member.name], constValue(member.value),
799                               "property has wrong value");
800                 var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, member.name);
801                 assert_false("get" in desc, "property has getter");
802                 assert_false("set" in desc, "property has setter");
803                 assert_false(desc.writable, "property is writable");
804                 assert_true(desc.enumerable, "property is not enumerable");
805                 assert_false(desc.configurable, "property is configurable");
806             }.bind(this), this.name + " exception: constant " + member.name + " on exception interface prototype object");
807         }
808         else if (member.type == "field")
809         {
810             test(function()
811             {
812                 assert_own_property(window, this.name,
813                                     "window does not have own property " + format_value(this.name));
814                 assert_own_property(window[this.name], "prototype",
815                                     'exception "' + this.name + '" does not have own property "prototype"');
817                 // "For each exception field, there must be a corresponding
818                 // property on the exception interface prototype object, whose
819                 // characteristics are as follows:
820                 // "The name of the property is the identifier of the exception
821                 // field."
822                 assert_own_property(window[this.name].prototype, member.name);
823                 // "The property has attributes { [[Get]]: G, [[Enumerable]]:
824                 // true, [[Configurable]]: true }, where G is the exception
825                 // field getter, defined below."
826                 var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, member.name);
827                 assert_false("value" in desc, "property descriptor has value but is supposed to be accessor");
828                 assert_false("writable" in desc, 'property descriptor has "writable" field but is supposed to be accessor');
829                 // TODO: ES5 doesn't seem to say whether desc should have a
830                 // .set property.
831                 assert_true(desc.enumerable, "property is not enumerable");
832                 assert_true(desc.configurable, "property is not configurable");
833                 // "The exception field getter is a Function object whose
834                 // behavior when invoked is as follows:"
835                 assert_equals(typeof desc.get, "function", "typeof getter");
836                 // "The value of the Function object’s “length” property is the
837                 // Number value 0."
838                 // This test is before the TypeError tests so that it's easiest
839                 // to see that Firefox 11a1 only fails one assert in this test.
840                 assert_equals(desc.get.length, 0, "getter length");
841                 // "Let O be the result of calling ToObject on the this value.
842                 // "If O is not a platform object representing an exception for
843                 // the exception on which the exception field was declared,
844                 // then throw a TypeError."
845                 // TODO: Test on a platform object representing an exception.
846                 assert_throws(new TypeError(), function()
847                 {
848                     window[this.name].prototype[member.name];
849                 }.bind(this), "getting property on prototype object must throw TypeError");
850                 assert_throws(new TypeError(), function()
851                 {
852                     desc.get.call({});
853                 }.bind(this), "calling getter on wrong object type must throw TypeError");
854             }.bind(this), this.name + " exception: field " + member.name + " on exception interface prototype object");
855         }
856     }
859 //@}
860 IdlException.prototype.test_object = function(desc)
861 //@{
863     var obj, exception = null;
864     try
865     {
866         obj = eval(desc);
867     }
868     catch(e)
869     {
870         exception = e;
871     }
873     test(function()
874     {
875         assert_equals(exception, null, "Unexpected exception when evaluating object");
876         assert_equals(typeof obj, "object", "wrong typeof object");
878         // We can't easily test that its prototype is correct if there's no
879         // interface object, or the object is from a different global
880         // environment (not instanceof Object).  TODO: test in this case that
881         // its prototype at least looks correct, even if we can't test that
882         // it's actually correct.
883         if (!this.has_extended_attribute("NoInterfaceObject")
884         && (typeof obj != "object" || obj instanceof Object))
885         {
886             assert_own_property(window, this.name,
887                                 "window does not have own property " + format_value(this.name));
888             assert_own_property(window[this.name], "prototype",
889                                 'exception "' + this.name + '" does not have own property "prototype"');
891             // "The value of the internal [[Prototype]] property of the
892             // exception object must be the exception interface prototype
893             // object from the global environment the exception object is
894             // associated with."
895             assert_equals(Object.getPrototypeOf(obj),
896                           window[this.name].prototype,
897                           desc + "'s prototype is not " + this.name + ".prototype");
898         }
900         // "The class string of the exception object must be the identifier of
901         // the exception."
902         assert_class_string(obj, this.name, "class string of " + desc);
903         // Stringifier is not defined for DOMExceptions, because message isn't
904         // defined.
905     }.bind(this), this.name + " must be represented by " + desc);
907     for (var i = 0; i < this.members.length; i++)
908     {
909         var member = this.members[i];
910         test(function()
911         {
912             assert_equals(exception, null, "Unexpected exception when evaluating object");
913             assert_equals(typeof obj, "object", "wrong typeof object");
914             assert_inherits(obj, member.name);
915             if (member.type == "const")
916             {
917                 assert_equals(obj[member.name], constValue(member.value));
918             }
919             if (member.type == "field")
920             {
921                 this.array.assert_type_is(obj[member.name], member.idlType);
922             }
923         }.bind(this), this.name + " exception: " + desc + ' must inherit property "' + member.name + '" with the proper type');
924     }
926 //@}
928 /// IdlInterface ///
929 function IdlInterface(obj) { IdlExceptionOrInterface.call(this, obj); }
930 IdlInterface.prototype = Object.create(IdlExceptionOrInterface.prototype);
931 IdlInterface.prototype.is_callback = function()
932 //@{
934     return this.has_extended_attribute("Callback");
936 //@}
938 IdlInterface.prototype.has_constants = function()
939 //@{
941     return this.members.some(function(member) {
942         return member.type === "const";
943     });
945 //@}
947 IdlInterface.prototype.test_self = function()
948 //@{
950     test(function()
951     {
952         // This function tests WebIDL as of 2012-11-28.
954         // "For every interface that:
955         // * is a callback interface that has constants declared on it, or
956         // * is a non-callback interface that is not declared with the
957         //   [NoInterfaceObject] extended attribute,
958         // a corresponding property MUST exist on the ECMAScript global object.
959         // The name of the property is the identifier of the interface, and its
960         // value is an object called the interface object.
961         // The property has the attributes { [[Writable]]: true,
962         // [[Enumerable]]: false, [[Configurable]]: true }."
963         if (this.is_callback() && !this.has_constants()) {
964             return;
965         }
967         // TODO: Should we test here that the property is actually writable
968         // etc., or trust getOwnPropertyDescriptor?
969         assert_own_property(window, this.name,
970                             "window does not have own property " + format_value(this.name));
971         var desc = Object.getOwnPropertyDescriptor(window, this.name);
972         assert_false("get" in desc, "window's property " + format_value(this.name) + " has getter");
973         assert_false("set" in desc, "window's property " + format_value(this.name) + " has setter");
974         assert_true(desc.writable, "window's property " + format_value(this.name) + " is not writable");
975         assert_false(desc.enumerable, "window's property " + format_value(this.name) + " is enumerable");
976         assert_true(desc.configurable, "window's property " + format_value(this.name) + " is not configurable");
978         if (this.is_callback()) {
979             // "The internal [[Prototype]] property of an interface object for
980             // a callback interface MUST be the Object.prototype object."
981             assert_equals(Object.getPrototypeOf(window[this.name]), Object.prototype,
982                           "prototype of window's property " + format_value(this.name) + " is not Object.prototype");
984             return;
985         }
987         // "The interface object for a given non-callback interface is a
988         // function object."
989         // "If an object is defined to be a function object, then it has
990         // characteristics as follows:"
992         // "* Its [[Prototype]] internal property is the Function prototype
993         //    object."
994         assert_equals(Object.getPrototypeOf(window[this.name]), Function.prototype,
995                       "prototype of window's property " + format_value(this.name) + " is not Function.prototype");
997         // "* Its [[Get]] internal property is set as described in ECMA-262
998         //    section 15.3.5.4."
999         // Not much to test for this.
1001         // "* Its [[Construct]] internal property is set as described in
1002         //    ECMA-262 section 13.2.2."
1003         // Tested below if no constructor is defined.  TODO: test constructors
1004         // if defined.
1006         // "* Its [[HasInstance]] internal property is set as described in
1007         //    ECMA-262 section 15.3.5.3, unless otherwise specified."
1008         // TODO
1010         // "* Its [[NativeBrand]] internal property is “Function”."
1011         // String() returns something implementation-dependent, because it calls
1012         // Function#toString.
1013         assert_class_string(window[this.name], "Function", "class string of " + this.name);
1015         if (!this.has_extended_attribute("Constructor")) {
1016             // "The internal [[Call]] method of the interface object behaves as
1017             // follows . . .
1018             //
1019             // "If I was not declared with a [Constructor] extended attribute,
1020             // then throw a TypeError."
1021             assert_throws(new TypeError(), function() {
1022                 window[this.name]();
1023             }.bind(this), "interface object didn't throw TypeError when called as a function");
1024             assert_throws(new TypeError(), function() {
1025                 new window[this.name]();
1026             }.bind(this), "interface object didn't throw TypeError when called as a constructor");
1027         }
1028     }.bind(this), this.name + " interface: existence and properties of interface object");
1030     if (!this.is_callback()) {
1031         test(function() {
1032             // This function tests WebIDL as of 2014-10-25.
1033             // https://heycam.github.io/webidl/#es-interface-call
1035             assert_own_property(window, this.name,
1036                                 "window does not have own property " + format_value(this.name));
1038             // "Interface objects for non-callback interfaces MUST have a
1039             // property named “length” with attributes { [[Writable]]: false,
1040             // [[Enumerable]]: false, [[Configurable]]: true } whose value is
1041             // a Number."
1042             assert_own_property(window[this.name], "length");
1043             var desc = Object.getOwnPropertyDescriptor(window[this.name], "length");
1044             assert_false("get" in desc, this.name + ".length has getter");
1045             assert_false("set" in desc, this.name + ".length has setter");
1046             assert_false(desc.writable, this.name + ".length is writable");
1047             assert_false(desc.enumerable, this.name + ".length is enumerable");
1048             assert_true(desc.configurable, this.name + ".length is not configurable");
1050             var constructors = this.extAttrs
1051                 .filter(function(attr) { return attr.name == "Constructor"; });
1052             var expected_length;
1053             if (!constructors.length) {
1054                 // "If the [Constructor] extended attribute, does not appear on
1055                 // the interface definition, then the value is 0."
1056                 expected_length = 0;
1057             } else {
1058                 // "Otherwise, the value is determined as follows: . . .
1059                 // "Return the length of the shortest argument list of the
1060                 // entries in S."
1061                 expected_length = constructors.map(function(attr) {
1062                     return attr.arguments ? attr.arguments.filter(function(arg) {
1063                         return !arg.optional;
1064                     }).length : 0;
1065                 })
1066                 .reduce(function(m, n) { return Math.min(m, n); });
1067             }
1068             assert_equals(window[this.name].length, expected_length, "wrong value for " + this.name + ".length");
1069         }.bind(this), this.name + " interface object length");
1070     }
1072     // TODO: Test named constructors if I find any interfaces that have them.
1074     test(function()
1075     {
1076         assert_own_property(window, this.name,
1077                             "window does not have own property " + format_value(this.name));
1079         if (this.has_extended_attribute("Callback")) {
1080             assert_false("prototype" in window[this.name],
1081                          this.name + ' should not have a "prototype" property');
1082             return;
1083         }
1085         // "The interface object must also have a property named “prototype”
1086         // with attributes { [[Writable]]: false, [[Enumerable]]: false,
1087         // [[Configurable]]: false } whose value is an object called the
1088         // interface prototype object. This object has properties that
1089         // correspond to the attributes and operations defined on the
1090         // interface, and is described in more detail in section 4.5.3 below."
1091         assert_own_property(window[this.name], "prototype",
1092                             'interface "' + this.name + '" does not have own property "prototype"');
1093         var desc = Object.getOwnPropertyDescriptor(window[this.name], "prototype");
1094         assert_false("get" in desc, this.name + ".prototype has getter");
1095         assert_false("set" in desc, this.name + ".prototype has setter");
1096         assert_false(desc.writable, this.name + ".prototype is writable");
1097         assert_false(desc.enumerable, this.name + ".prototype is enumerable");
1098         assert_false(desc.configurable, this.name + ".prototype is configurable");
1100         // Next, test that the [[Prototype]] of the interface prototype object
1101         // is correct. (This is made somewhat difficult by the existence of
1102         // [NoInterfaceObject].)
1103         // TODO: Aryeh thinks there's at least other place in this file where
1104         //       we try to figure out if an interface prototype object is
1105         //       correct. Consolidate that code.
1107         // "The interface prototype object for a given interface A must have an
1108         // internal [[Prototype]] property whose value is as follows:
1109         // "If A is not declared to inherit from another interface, then the
1110         // value of the internal [[Prototype]] property of A is the Array
1111         // prototype object ([ECMA-262], section 15.4.4) if the interface was
1112         // declared with ArrayClass, or the Object prototype object otherwise
1113         // ([ECMA-262], section 15.2.4).
1114         // "Otherwise, A does inherit from another interface. The value of the
1115         // internal [[Prototype]] property of A is the interface prototype
1116         // object for the inherited interface."
1117         var inherit_interface, inherit_interface_has_interface_object;
1118         if (this.base) {
1119             inherit_interface = this.base;
1120             inherit_interface_has_interface_object =
1121                 !this.array
1122                      .members[inherit_interface]
1123                      .has_extended_attribute("NoInterfaceObject");
1124         } else if (this.has_extended_attribute('ArrayClass')) {
1125             inherit_interface = 'Array';
1126             inherit_interface_has_interface_object = true;
1127         } else {
1128             inherit_interface = 'Object';
1129             inherit_interface_has_interface_object = true;
1130         }
1131         if (inherit_interface_has_interface_object) {
1132             assert_own_property(window, inherit_interface,
1133                                 'should inherit from ' + inherit_interface + ', but window has no such property');
1134             assert_own_property(window[inherit_interface], 'prototype',
1135                                 'should inherit from ' + inherit_interface + ', but that object has no "prototype" property');
1136             assert_equals(Object.getPrototypeOf(window[this.name].prototype),
1137                           window[inherit_interface].prototype,
1138                           'prototype of ' + this.name + '.prototype is not ' + inherit_interface + '.prototype');
1139         } else {
1140             // We can't test that we get the correct object, because this is the
1141             // only way to get our hands on it. We only test that its class
1142             // string, at least, is correct.
1143             assert_class_string(Object.getPrototypeOf(window[this.name].prototype),
1144                                 inherit_interface + 'Prototype',
1145                                 'Class name for prototype of ' + this.name +
1146                                 '.prototype is not "' + inherit_interface + 'Prototype"');
1147         }
1149         // "The class string of an interface prototype object is the
1150         // concatenation of the interface’s identifier and the string
1151         // “Prototype”."
1152         assert_class_string(window[this.name].prototype, this.name + "Prototype",
1153                             "class string of " + this.name + ".prototype");
1154         // String() should end up calling {}.toString if nothing defines a
1155         // stringifier.
1156         if (!this.has_stringifier()) {
1157             assert_equals(String(window[this.name].prototype), "[object " + this.name + "Prototype]",
1158                     "String(" + this.name + ".prototype)");
1159         }
1160     }.bind(this), this.name + " interface: existence and properties of interface prototype object");
1162     test(function()
1163     {
1164         assert_own_property(window, this.name,
1165                             "window does not have own property " + format_value(this.name));
1167         if (this.has_extended_attribute("Callback")) {
1168             assert_false("prototype" in window[this.name],
1169                          this.name + ' should not have a "prototype" property');
1170             return;
1171         }
1173         assert_own_property(window[this.name], "prototype",
1174                             'interface "' + this.name + '" does not have own property "prototype"');
1176         // "If the [NoInterfaceObject] extended attribute was not specified on
1177         // the interface, then the interface prototype object must also have a
1178         // property named “constructor” with attributes { [[Writable]]: true,
1179         // [[Enumerable]]: false, [[Configurable]]: true } whose value is a
1180         // reference to the interface object for the interface."
1181         assert_own_property(window[this.name].prototype, "constructor",
1182                             this.name + '.prototype does not have own property "constructor"');
1183         var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "constructor");
1184         assert_false("get" in desc, this.name + ".prototype.constructor has getter");
1185         assert_false("set" in desc, this.name + ".prototype.constructor has setter");
1186         assert_true(desc.writable, this.name + ".prototype.constructor is not writable");
1187         assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable");
1188         assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable");
1189         assert_equals(window[this.name].prototype.constructor, window[this.name],
1190                       this.name + '.prototype.constructor is not the same object as ' + this.name);
1191     }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property');
1194 //@}
1195 IdlInterface.prototype.test_member_const = function(member)
1196 //@{
1198     test(function()
1199     {
1200         assert_own_property(window, this.name,
1201                             "window does not have own property " + format_value(this.name));
1203         // "For each constant defined on an interface A, there must be
1204         // a corresponding property on the interface object, if it
1205         // exists."
1206         assert_own_property(window[this.name], member.name);
1207         // "The value of the property is that which is obtained by
1208         // converting the constant’s IDL value to an ECMAScript
1209         // value."
1210         assert_equals(window[this.name][member.name], constValue(member.value),
1211                       "property has wrong value");
1212         // "The property has attributes { [[Writable]]: false,
1213         // [[Enumerable]]: true, [[Configurable]]: false }."
1214         var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
1215         assert_false("get" in desc, "property has getter");
1216         assert_false("set" in desc, "property has setter");
1217         assert_false(desc.writable, "property is writable");
1218         assert_true(desc.enumerable, "property is not enumerable");
1219         assert_false(desc.configurable, "property is configurable");
1220     }.bind(this), this.name + " interface: constant " + member.name + " on interface object");
1221     // "In addition, a property with the same characteristics must
1222     // exist on the interface prototype object."
1223     test(function()
1224     {
1225         assert_own_property(window, this.name,
1226                             "window does not have own property " + format_value(this.name));
1228         if (this.has_extended_attribute("Callback")) {
1229             assert_false("prototype" in window[this.name],
1230                          this.name + ' should not have a "prototype" property');
1231             return;
1232         }
1234         assert_own_property(window[this.name], "prototype",
1235                             'interface "' + this.name + '" does not have own property "prototype"');
1237         assert_own_property(window[this.name].prototype, member.name);
1238         assert_equals(window[this.name].prototype[member.name], constValue(member.value),
1239                       "property has wrong value");
1240         var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
1241         assert_false("get" in desc, "property has getter");
1242         assert_false("set" in desc, "property has setter");
1243         assert_false(desc.writable, "property is writable");
1244         assert_true(desc.enumerable, "property is not enumerable");
1245         assert_false(desc.configurable, "property is configurable");
1246     }.bind(this), this.name + " interface: constant " + member.name + " on interface prototype object");
1250 //@}
1251 IdlInterface.prototype.test_member_attribute = function(member)
1252 //@{
1254     test(function()
1255     {
1256         assert_own_property(window, this.name,
1257                             "window does not have own property " + format_value(this.name));
1258         assert_own_property(window[this.name], "prototype",
1259                             'interface "' + this.name + '" does not have own property "prototype"');
1261         if (member["static"]) {
1262             assert_own_property(window[this.name], member.name,
1263                 "The interface object must have a property " +
1264                 format_value(member.name));
1265         }
1266         else
1267         {
1268             assert_true(member.name in window[this.name].prototype,
1269                 "The prototype object must have a property " +
1270                 format_value(member.name));
1272             if (!member.has_extended_attribute("LenientThis")) {
1273                 assert_throws(new TypeError(), function() {
1274                     window[this.name].prototype[member.name];
1275                 }.bind(this), "getting property on prototype object must throw TypeError");
1276             } else {
1277                 assert_equals(window[this.name].prototype[member.name], undefined,
1278                               "getting property on prototype object must return undefined");
1279             }
1280             do_interface_attribute_asserts(window[this.name].prototype, member);
1281         }
1282     }.bind(this), this.name + " interface: attribute " + member.name);
1285 //@}
1286 IdlInterface.prototype.test_member_operation = function(member)
1287 //@{
1289     test(function()
1290     {
1291         assert_own_property(window, this.name,
1292                             "window does not have own property " + format_value(this.name));
1294         if (this.has_extended_attribute("Callback")) {
1295             assert_false("prototype" in window[this.name],
1296                          this.name + ' should not have a "prototype" property');
1297             return;
1298         }
1300         assert_own_property(window[this.name], "prototype",
1301                             'interface "' + this.name + '" does not have own property "prototype"');
1303         // "For each unique identifier of an operation defined on the
1304         // interface, there must be a corresponding property on the
1305         // interface prototype object (if it is a regular operation) or
1306         // the interface object (if it is a static operation), unless
1307         // the effective overload set for that identifier and operation
1308         // and with an argument count of 0 (for the ECMAScript language
1309         // binding) has no entries."
1310         //
1311         var prototypeOrInterfaceObject;
1312         if (member["static"]) {
1313             assert_own_property(window[this.name], member.name,
1314                     "interface prototype object missing static operation");
1315             prototypeOrInterfaceObject = window[this.name];
1316         }
1317         else
1318         {
1319             assert_own_property(window[this.name].prototype, member.name,
1320                     "interface prototype object missing non-static operation");
1321             prototypeOrInterfaceObject = window[this.name].prototype;
1322         }
1324         var desc = Object.getOwnPropertyDescriptor(prototypeOrInterfaceObject, member.name);
1325         // "The property has attributes { [[Writable]]: true,
1326         // [[Enumerable]]: true, [[Configurable]]: true }."
1327         assert_false("get" in desc, "property has getter");
1328         assert_false("set" in desc, "property has setter");
1329         assert_true(desc.writable, "property is not writable");
1330         assert_true(desc.enumerable, "property is not enumerable");
1331         assert_true(desc.configurable, "property is not configurable");
1332         // "The value of the property is a Function object whose
1333         // behavior is as follows . . ."
1334         assert_equals(typeof prototypeOrInterfaceObject[member.name], "function",
1335                       "property must be a function");
1336         // "The value of the Function object’s “length” property is
1337         // a Number determined as follows:
1338         // ". . .
1339         // "Return the length of the shortest argument list of the
1340         // entries in S."
1341         //
1342         // TODO: Doesn't handle overloading or variadic arguments.
1343         assert_equals(prototypeOrInterfaceObject[member.name].length,
1344             member.arguments.filter(function(arg) {
1345                 return !arg.optional;
1346             }).length,
1347             "property has wrong .length");
1349         // Make some suitable arguments
1350         var args = member.arguments.map(function(arg) {
1351             return create_suitable_object(arg.idlType);
1352         });
1354         // "Let O be a value determined as follows:
1355         // ". . .
1356         // "Otherwise, throw a TypeError."
1357         // This should be hit if the operation is not static, there is
1358         // no [ImplicitThis] attribute, and the this value is null.
1359         //
1360         // TODO: We currently ignore the [ImplicitThis] case.
1361         if (!member["static"]) {
1362             assert_throws(new TypeError(), function() {
1363                 window[this.name].prototype[member.name].apply(null, args);
1364             }, "calling operation with this = null didn't throw TypeError");
1365         }
1367         // ". . . If O is not null and is also not a platform object
1368         // that implements interface I, throw a TypeError."
1369         //
1370         // TODO: Test a platform object that implements some other
1371         // interface.  (Have to be sure to get inheritance right.)
1372         assert_throws(new TypeError(), function() {
1373             window[this.name].prototype[member.name].apply({}, args);
1374         }, "calling operation with this = {} didn't throw TypeError");
1375     }.bind(this), this.name + " interface: operation " + member.name +
1376     "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
1377     ")");
1380 //@}
1381 IdlInterface.prototype.test_member_stringifier = function(member)
1382 //@{
1384     test(function()
1385     {
1386         assert_own_property(window, this.name,
1387                             "window does not have own property " + format_value(this.name));
1389         if (this.has_extended_attribute("Callback")) {
1390             assert_false("prototype" in window[this.name],
1391                          this.name + ' should not have a "prototype" property');
1392             return;
1393         }
1395         assert_own_property(window[this.name], "prototype",
1396                             'interface "' + this.name + '" does not have own property "prototype"');
1398         // ". . . the property exists on the interface prototype object."
1399         var interfacePrototypeObject = window[this.name].prototype;
1400         assert_own_property(window[this.name].prototype, "toString",
1401                 "interface prototype object missing non-static operation");
1403         var desc = Object.getOwnPropertyDescriptor(interfacePrototypeObject, "toString");
1404         // "The property has attributes { [[Writable]]: B,
1405         // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the
1406         // stringifier is unforgeable on the interface, and true otherwise."
1407         assert_false("get" in desc, "property has getter");
1408         assert_false("set" in desc, "property has setter");
1409         assert_true(desc.writable, "property is not writable");
1410         assert_true(desc.enumerable, "property is not enumerable");
1411         assert_true(desc.configurable, "property is not configurable");
1412         // "The value of the property is a Function object, which behaves as
1413         // follows . . ."
1414         assert_equals(typeof interfacePrototypeObject.toString, "function",
1415                       "property must be a function");
1416         // "The value of the Function object’s “length” property is the Number
1417         // value 0."
1418         assert_equals(interfacePrototypeObject.toString.length, 0,
1419             "property has wrong .length");
1421         // "Let O be the result of calling ToObject on the this value."
1422         assert_throws(new TypeError(), function() {
1423             window[this.name].prototype.toString.apply(null, []);
1424         }, "calling stringifier with this = null didn't throw TypeError");
1426         // "If O is not an object that implements the interface on which the
1427         // stringifier was declared, then throw a TypeError."
1428         //
1429         // TODO: Test a platform object that implements some other
1430         // interface.  (Have to be sure to get inheritance right.)
1431         assert_throws(new TypeError(), function() {
1432             window[this.name].prototype.toString.apply({}, []);
1433         }, "calling stringifier with this = {} didn't throw TypeError");
1434     }.bind(this), this.name + " interface: stringifier");
1437 //@}
1438 IdlInterface.prototype.test_members = function()
1439 //@{
1441     for (var i = 0; i < this.members.length; i++)
1442     {
1443         var member = this.members[i];
1444         if (member.untested) {
1445             continue;
1446         }
1448         switch (member.type) {
1449         case "const":
1450             this.test_member_const(member);
1451             break;
1453         case "attribute":
1454             // For unforgeable attributes, we do the checks in
1455             // test_interface_of instead.
1456             if (!member.has_extended_attribute("Unforgeable")) {
1457                 this.test_member_attribute(member);
1458             }
1459             break;
1461         case "operation":
1462             // TODO: Need to correctly handle multiple operations with the same
1463             // identifier.
1464             if (member.name) {
1465                 this.test_member_operation(member);
1466             } else if (member.stringifier) {
1467                 this.test_member_stringifier(member);
1468             }
1469             break;
1471         default:
1472             // TODO: check more member types.
1473             break;
1474         }
1475     }
1478 //@}
1479 IdlInterface.prototype.test_object = function(desc)
1480 //@{
1482     var obj, exception = null;
1483     try
1484     {
1485         obj = eval(desc);
1486     }
1487     catch(e)
1488     {
1489         exception = e;
1490     }
1492     // TODO: WebIDLParser doesn't currently support named legacycallers, so I'm
1493     // not sure what those would look like in the AST
1494     var expected_typeof = this.members.some(function(member)
1495     {
1496         return member.legacycaller
1497             || ("idlType" in member && member.idlType.legacycaller)
1498             || ("idlType" in member && typeof member.idlType == "object"
1499             && "idlType" in member.idlType && member.idlType.idlType == "legacycaller");
1500     }) ? "function" : "object";
1502     this.test_primary_interface_of(desc, obj, exception, expected_typeof);
1503     var current_interface = this;
1504     while (current_interface)
1505     {
1506         if (!(current_interface.name in this.array.members))
1507         {
1508             throw "Interface " + current_interface.name + " not found (inherited by " + this.name + ")";
1509         }
1510         if (current_interface.prevent_multiple_testing && current_interface.already_tested)
1511         {
1512             return;
1513         }
1514         current_interface.test_interface_of(desc, obj, exception, expected_typeof);
1515         current_interface = this.array.members[current_interface.base];
1516     }
1519 //@}
1520 IdlInterface.prototype.test_primary_interface_of = function(desc, obj, exception, expected_typeof)
1521 //@{
1523     // We can't easily test that its prototype is correct if there's no
1524     // interface object, or the object is from a different global environment
1525     // (not instanceof Object).  TODO: test in this case that its prototype at
1526     // least looks correct, even if we can't test that it's actually correct.
1527     if (!this.has_extended_attribute("NoInterfaceObject")
1528     && (typeof obj != expected_typeof || obj instanceof Object))
1529     {
1530         test(function()
1531         {
1532             assert_equals(exception, null, "Unexpected exception when evaluating object");
1533             assert_equals(typeof obj, expected_typeof, "wrong typeof object");
1534             assert_own_property(window, this.name,
1535                                 "window does not have own property " + format_value(this.name));
1536             assert_own_property(window[this.name], "prototype",
1537                                 'interface "' + this.name + '" does not have own property "prototype"');
1539             // "The value of the internal [[Prototype]] property of the
1540             // platform object is the interface prototype object of the primary
1541             // interface from the platform object’s associated global
1542             // environment."
1543             assert_equals(Object.getPrototypeOf(obj),
1544                           window[this.name].prototype,
1545                           desc + "'s prototype is not " + this.name + ".prototype");
1546         }.bind(this), this.name + " must be primary interface of " + desc);
1547     }
1549     // "The class string of a platform object that implements one or more
1550     // interfaces must be the identifier of the primary interface of the
1551     // platform object."
1552     test(function()
1553     {
1554         assert_equals(exception, null, "Unexpected exception when evaluating object");
1555         assert_equals(typeof obj, expected_typeof, "wrong typeof object");
1556         assert_class_string(obj, this.name, "class string of " + desc);
1557         if (!this.has_stringifier())
1558         {
1559             assert_equals(String(obj), "[object " + this.name + "]", "String(" + desc + ")");
1560         }
1561     }.bind(this), "Stringification of " + desc);
1564 //@}
1565 IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expected_typeof)
1566 //@{
1568     // TODO: Indexed and named properties, more checks on interface members
1569     this.already_tested = true;
1571     for (var i = 0; i < this.members.length; i++)
1572     {
1573         var member = this.members[i];
1574         if (member.has_extended_attribute("Unforgeable"))
1575         {
1576             test(function()
1577             {
1578                 assert_equals(exception, null, "Unexpected exception when evaluating object");
1579                 assert_equals(typeof obj, expected_typeof, "wrong typeof object");
1580                 do_interface_attribute_asserts(obj, member);
1581             }.bind(this), this.name + " interface: " + desc + ' must have own property "' + member.name + '"');
1582         }
1583         else if ((member.type == "const"
1584         || member.type == "attribute"
1585         || member.type == "operation")
1586         && member.name)
1587         {
1588             test(function()
1589             {
1590                 assert_equals(exception, null, "Unexpected exception when evaluating object");
1591                 assert_equals(typeof obj, expected_typeof, "wrong typeof object");
1592                 if (!member["static"]) {
1593                     assert_inherits(obj, member.name);
1594                     if (member.type == "const")
1595                     {
1596                         assert_equals(obj[member.name], constValue(member.value));
1597                     }
1598                     if (member.type == "attribute")
1599                     {
1600                         // Attributes are accessor properties, so they might
1601                         // legitimately throw an exception rather than returning
1602                         // anything.
1603                         var property, thrown = false;
1604                         try
1605                         {
1606                             property = obj[member.name];
1607                         }
1608                         catch (e)
1609                         {
1610                             thrown = true;
1611                         }
1612                         if (!thrown)
1613                         {
1614                             this.array.assert_type_is(property, member.idlType);
1615                         }
1616                     }
1617                     if (member.type == "operation")
1618                     {
1619                         assert_equals(typeof obj[member.name], "function");
1620                     }
1621                 }
1622             }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + member.name + '" with the proper type (' + i + ')');
1623         }
1624         // TODO: This is wrong if there are multiple operations with the same
1625         // identifier.
1626         // TODO: Test passing arguments of the wrong type.
1627         if (member.type == "operation" && member.name && member.arguments.length)
1628         {
1629             test(function()
1630             {
1631                 assert_equals(exception, null, "Unexpected exception when evaluating object");
1632                 assert_equals(typeof obj, expected_typeof, "wrong typeof object");
1633                 if (!member["static"]) {
1634                     assert_inherits(obj, member.name);
1635                 }
1636                 else
1637                 {
1638                     assert_false(member.name in obj);
1639                 }
1640                 var args = [];
1641                 for (var i = 0; i < member.arguments.length; i++)
1642                 {
1643                     if (member.arguments[i].optional)
1644                     {
1645                         break;
1646                     }
1647                     assert_throws(new TypeError(), function()
1648                     {
1649                         obj[member.name].apply(obj, args);
1650                     }.bind(this), "Called with " + i + " arguments");
1652                     args.push(create_suitable_object(member.arguments[i].idlType));
1653                 }
1654             }.bind(this), this.name + " interface: calling " + member.name +
1655             "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
1656             ") on " + desc + " with too few arguments must throw TypeError");
1657         }
1658     }
1661 //@}
1662 IdlInterface.prototype.has_stringifier = function()
1663 //@{
1665     if (this.members.some(function(member) { return member.stringifier; })) {
1666         return true;
1667     }
1668     if (this.base &&
1669         this.array.members[this.base].has_stringifier()) {
1670         return true;
1671     }
1672     return false;
1675 //@}
1676 function do_interface_attribute_asserts(obj, member)
1677 //@{
1679     // "For each attribute defined on the interface, there must exist a
1680     // corresponding property. If the attribute was declared with the
1681     // [Unforgeable] extended attribute, then the property exists on every
1682     // object that implements the interface.  Otherwise, it exists on the
1683     // interface’s interface prototype object."
1684     //
1685     // This is called by test_self() with the prototype as obj, and by
1686     // test_interface_of() with the object as obj.
1687     assert_own_property(obj, member.name);
1689     // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]:
1690     // true, [[Configurable]]: configurable }, where:
1691     // "configurable is false if the attribute was declared with the
1692     // [Unforgeable] extended attribute and true otherwise;
1693     // "G is the attribute getter, defined below; and
1694     // "S is the attribute setter, also defined below."
1695     var desc = Object.getOwnPropertyDescriptor(obj, member.name);
1696     assert_false("value" in desc, 'property descriptor has value but is supposed to be accessor');
1697     assert_false("writable" in desc, 'property descriptor has "writable" field but is supposed to be accessor');
1698     assert_true(desc.enumerable, "property is not enumerable");
1699     if (member.has_extended_attribute("Unforgeable"))
1700     {
1701         assert_false(desc.configurable, "[Unforgeable] property must not be configurable");
1702     }
1703     else
1704     {
1705         assert_true(desc.configurable, "property must be configurable");
1706     }
1708     // "The attribute getter is a Function object whose behavior when invoked
1709     // is as follows:
1710     // "...
1711     // "The value of the Function object’s “length” property is the Number
1712     // value 0."
1713     assert_equals(typeof desc.get, "function", "getter must be Function");
1714     assert_equals(desc.get.length, 0, "getter length must be 0");
1715     if (!member.has_extended_attribute("LenientThis")) {
1716         assert_throws(new TypeError(), function() {
1717             desc.get.call({});
1718         }.bind(this), "calling getter on wrong object type must throw TypeError");
1719     } else {
1720         assert_equals(desc.get.call({}), undefined,
1721                       "calling getter on wrong object type must return undefined");
1722     }
1724     // TODO: Test calling setter on the interface prototype (should throw
1725     // TypeError in most cases).
1726     //
1727     // "The attribute setter is undefined if the attribute is declared readonly
1728     // and has neither a [PutForwards] nor a [Replaceable] extended attribute
1729     // declared on it.  Otherwise, it is a Function object whose behavior when
1730     // invoked is as follows:
1731     // "...
1732     // "The value of the Function object’s “length” property is the Number
1733     // value 1."
1734     if (member.readonly
1735     && !member.has_extended_attribute("PutForwards")
1736     && !member.has_extended_attribute("Replaceable"))
1737     {
1738         assert_equals(desc.set, undefined, "setter must be undefined for readonly attributes");
1739     }
1740     else
1741     {
1742         assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes");
1743         assert_equals(desc.set.length, 1, "setter length must be 1");
1744         if (!member.has_extended_attribute("LenientThis")) {
1745             assert_throws(new TypeError(), function() {
1746                 desc.set.call({});
1747             }.bind(this), "calling setter on wrong object type must throw TypeError");
1748         } else {
1749             assert_equals(desc.set.call({}), undefined,
1750                           "calling setter on wrong object type must return undefined");
1751         }
1752     }
1754 //@}
1756 /// IdlInterfaceMember ///
1757 function IdlInterfaceMember(obj)
1758 //@{
1760     /**
1761      * obj is an object produced by the WebIDLParser.js "ifMember" production.
1762      * We just forward all properties to this object without modification,
1763      * except for special extAttrs handling.
1764      */
1765     for (var k in obj)
1766     {
1767         this[k] = obj[k];
1768     }
1769     if (!("extAttrs" in this))
1770     {
1771         this.extAttrs = [];
1772     }
1775 //@}
1776 IdlInterfaceMember.prototype = Object.create(IdlObject.prototype);
1778 /// Internal helper functions ///
1779 function create_suitable_object(type)
1780 //@{
1782     /**
1783      * type is an object produced by the WebIDLParser.js "type" production.  We
1784      * return a JavaScript value that matches the type, if we can figure out
1785      * how.
1786      */
1787     if (type.nullable)
1788     {
1789         return null;
1790     }
1791     switch (type.idlType)
1792     {
1793         case "any":
1794         case "boolean":
1795             return true;
1797         case "byte": case "octet": case "short": case "unsigned short":
1798         case "long": case "unsigned long": case "long long":
1799         case "unsigned long long": case "float": case "double":
1800         case "unrestricted float": case "unrestricted double":
1801             return 7;
1803         case "DOMString":
1804         case "ByteString":
1805         case "USVString":
1806             return "foo";
1808         case "object":
1809             return {a: "b"};
1811         case "Node":
1812             return document.createTextNode("abc");
1813     }
1814     return null;
1816 //@}
1818 /// IdlEnum ///
1819 // Used for IdlArray.prototype.assert_type_is
1820 function IdlEnum(obj)
1821 //@{
1823     /**
1824      * obj is an object produced by the WebIDLParser.js "dictionary"
1825      * production.
1826      */
1828     /** Self-explanatory. */
1829     this.name = obj.name;
1831     /** An array of values produced by the "enum" production. */
1832     this.values = obj.values;
1835 //@}
1837 IdlEnum.prototype = Object.create(IdlObject.prototype);
1839 /// IdlTypedef ///
1840 // Used for IdlArray.prototype.assert_type_is
1841 function IdlTypedef(obj)
1842 //@{
1844     /**
1845      * obj is an object produced by the WebIDLParser.js "typedef"
1846      * production.
1847      */
1849     /** Self-explanatory. */
1850     this.name = obj.name;
1852     /** An array of values produced by the "typedef" production. */
1853     this.values = obj.values;
1856 //@}
1858 IdlTypedef.prototype = Object.create(IdlObject.prototype);
1860 }());
1861 // vim: set expandtab shiftwidth=4 tabstop=4 foldmarker=@{,@} foldmethod=marker: