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