dom/imptests/idlharness.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial