Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / LayoutTests / resources / testharness.js
1 /*
2 Distributed under both the W3C Test Suite License [1] and the W3C
3 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
4 policies and contribution forms [3].
5
6 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
7 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
8 [3] http://www.w3.org/2004/10/27-testcases
9 */
10
11 /*
12  * == Introduction ==
13  *
14  * This file provides a framework for writing testcases. It is intended to
15  * provide a convenient API for making common assertions, and to work both
16  * for testing synchronous and asynchronous DOM features in a way that
17  * promotes clear, robust, tests.
18  *
19  * == Basic Usage ==
20  *
21  * To use this file, import the script and the testharnessreport script into
22  * the test document:
23  * <script src="/resources/testharness.js"></script>
24  * <script src="/resources/testharnessreport.js"></script>
25  *
26  * Within each file one may define one or more tests. Each test is atomic
27  * in the sense that a single test has a single result (pass/fail/timeout).
28  * Within each test one may have a number of asserts. The test fails at the
29  * first failing assert, and the remainder of the test is (typically) not run.
30  *
31  * If the file containing the tests is a HTML file with an element of id "log"
32  * this will be populated with a table containing the test results after all
33  * the tests have run.
34  *
35  * NOTE: By default tests must be created before the load event fires. For ways
36  *       to create tests after the load event, see "Determining when all tests
37  *       are complete", below
38  *
39  * == Synchronous Tests ==
40  *
41  * To create a synchronous test use the test() function:
42  *
43  * test(test_function, name, properties)
44  *
45  * test_function is a function that contains the code to test. For example a
46  * trivial passing test would be:
47  *
48  * test(function() {assert_true(true)}, "assert_true with true")
49  *
50  * The function passed in is run in the test() call.
51  *
52  * properties is an object that overrides default test properties. The
53  * recognised properties are:
54  *    timeout - the test timeout in ms
55  *
56  * e.g.
57  * test(test_function, "Sample test", {timeout:1000})
58  *
59  * would run test_function with a timeout of 1s.
60  *
61  * Additionally, test-specific metadata can be passed in the properties. These
62  * are used when the individual test has different metadata from that stored
63  * in the <head>.
64  * The recognized metadata properties are:
65  *
66  *    help - The url of the part of the specification being tested
67  *
68  *    assert - A human readable description of what the test is attempting
69  *             to prove
70  *
71  *    author - Name and contact information for the author of the test in the
72  *             format: "Name <email_addr>" or "Name http://contact/url"
73  *
74  * == Asynchronous Tests ==
75  *
76  * Testing asynchronous features is somewhat more complex since the result of
77  * a test may depend on one or more events or other callbacks. The API provided
78  * for testing these features is indended to be rather low-level but hopefully
79  * applicable to many situations.
80  *
81  * To create a test, one starts by getting a Test object using async_test:
82  *
83  * async_test(name, properties)
84  *
85  * e.g.
86  * var t = async_test("Simple async test")
87  *
88  * Assertions can be added to the test by calling the step method of the test
89  * object with a function containing the test assertions:
90  *
91  * t.step(function() {assert_true(true)});
92  *
93  * When all the steps are complete, the done() method must be called:
94  *
95  * t.done();
96  *
97  * As a convenience, async_test can also takes a function as first argument.
98  * This function is called with the test object as both its `this` object and
99  * first argument. The above example can be rewritten as:
100  *
101  * async_test(function(t) {
102  *     object.some_event = function() {
103  *         t.step(function (){assert_true(true); t.done();});
104  *     };
105  * }, "Simple async test");
106  *
107  * which avoids cluttering the global scope with references to async
108  * tests instances.
109  *
110  * The properties argument is identical to that for test().
111  *
112  * In many cases it is convenient to run a step in response to an event or a
113  * callback. A convenient method of doing this is through the step_func method
114  * which returns a function that, when called runs a test step. For example
115  *
116  * object.some_event = t.step_func(function(e) {assert_true(e.a)});
117  *
118  * == Making assertions ==
119  *
120  * Functions for making assertions start assert_
121  * The best way to get a list is to look in this file for functions names
122  * matching that pattern. The general signature is
123  *
124  * assert_something(actual, expected, description)
125  *
126  * although not all assertions precisely match this pattern e.g. assert_true
127  * only takes actual and description as arguments.
128  *
129  * The description parameter is used to present more useful error messages when
130  * a test fails
131  *
132  * NOTE: All asserts must be located in a test() or a step of an async_test().
133  *       asserts outside these places won't be detected correctly by the harness
134  *       and may cause a file to stop testing.
135  *
136  * == Setup ==
137  *
138  * Sometimes tests require non-trivial setup that may fail. For this purpose
139  * there is a setup() function, that may be called with one or two arguments.
140  * The two argument version is:
141  *
142  * setup(func, properties)
143  *
144  * The one argument versions may omit either argument.
145  * func is a function to be run synchronously. setup() becomes a no-op once
146  * any tests have returned results. Properties are global properties of the test
147  * harness. Currently recognised properties are:
148  *
149  * timeout - The time in ms after which the harness should stop waiting for
150  *           tests to complete (this is different to the per-test timeout
151  *           because async tests do not start their timer until .step is called)
152  *
153  * explicit_done - Wait for an explicit call to done() before declaring all
154  *                 tests complete (see below)
155  *
156  * output_document - The document to which results should be logged. By default
157  *                   this is the current document but could be an ancestor
158  *                   document in some cases e.g. a SVG test loaded in an HTML
159  *                   wrapper
160  *
161  * explicit_timeout - disable file timeout; only stop waiting for results
162  *                    when the timeout() function is called (typically for
163  *                    use when integrating with some existing test framework
164  *                    that has its own timeout mechanism).
165  *
166  * == Determining when all tests are complete ==
167  *
168  * By default the test harness will assume there are no more results to come
169  * when:
170  * 1) There are no Test objects that have been created but not completed
171  * 2) The load event on the document has fired
172  *
173  * This behaviour can be overridden by setting the explicit_done property to
174  * true in a call to setup(). If explicit_done is true, the test harness will
175  * not assume it is done until the global done() function is called. Once done()
176  * is called, the two conditions above apply like normal.
177  *
178  * == Generating tests ==
179  *
180  * NOTE: this functionality may be removed
181  *
182  * There are scenarios in which is is desirable to create a large number of
183  * (synchronous) tests that are internally similar but vary in the parameters
184  * used. To make this easier, the generate_tests function allows a single
185  * function to be called with each set of parameters in a list:
186  *
187  * generate_tests(test_function, parameter_lists, properties)
188  *
189  * For example:
190  *
191  * generate_tests(assert_equals, [
192  *     ["Sum one and one", 1+1, 2],
193  *     ["Sum one and zero", 1+0, 1]
194  *     ])
195  *
196  * Is equivalent to:
197  *
198  * test(function() {assert_equals(1+1, 2)}, "Sum one and one")
199  * test(function() {assert_equals(1+0, 1)}, "Sum one and zero")
200  *
201  * Note that the first item in each parameter list corresponds to the name of
202  * the test.
203  *
204  * The properties argument is identical to that for test(). This may be a
205  * single object (used for all generated tests) or an array.
206  *
207  * == Callback API ==
208  *
209  * The framework provides callbacks corresponding to 3 events:
210  *
211  * start - happens when the first Test is created
212  * result - happens when a test result is recieved
213  * complete - happens when all results are recieved
214  *
215  * The page defining the tests may add callbacks for these events by calling
216  * the following methods:
217  *
218  *   add_start_callback(callback) - callback called with no arguments
219  *   add_result_callback(callback) - callback called with a test argument
220  *   add_completion_callback(callback) - callback called with an array of tests
221  *                                       and an status object
222  *
223  * tests have the following properties:
224  *   status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and
225  *           NOTRUN properties on the test object
226  *   message: A message indicating the reason for failure. In the future this
227  *            will always be a string
228  *
229  *  The status object gives the overall status of the harness. It has the
230  *  following properties:
231  *    status: Can be compared to the OK, ERROR and TIMEOUT properties
232  *    message: An error message set when the status is ERROR
233  *
234  * == External API ==
235  *
236  * In order to collect the results of multiple pages containing tests, the test
237  * harness will, when loaded in a nested browsing context, attempt to call
238  * certain functions in each ancestor and opener browsing context:
239  *
240  * start - start_callback
241  * result - result_callback
242  * complete - completion_callback
243  *
244  * These are given the same arguments as the corresponding internal callbacks
245  * described above.
246  *
247  * == External API through cross-document messaging ==
248  *
249  * Where supported, the test harness will also send messages using
250  * cross-document messaging to each ancestor and opener browsing context. Since
251  * it uses the wildcard keyword (*), cross-origin communication is enabled and
252  * script on different origins can collect the results.
253  *
254  * This API follows similar conventions as those described above only slightly
255  * modified to accommodate message event API. Each message is sent by the harness
256  * is passed a single vanilla object, available as the `data` property of the
257  * event object. These objects are structures as follows:
258  *
259  * start - { type: "start" }
260  * result - { type: "result", test: Test }
261  * complete - { type: "complete", tests: [Test, ...], status: TestsStatus }
262  *
263  * == List of assertions ==
264  *
265  * assert_true(actual, description)
266  *   asserts that /actual/ is strictly true
267  *
268  * assert_false(actual, description)
269  *   asserts that /actual/ is strictly false
270  *
271  * assert_equals(actual, expected, description)
272  *   asserts that /actual/ is the same value as /expected/
273  *
274  * assert_not_equals(actual, expected, description)
275  *   asserts that /actual/ is a different value to /expected/. Yes, this means
276  *   that "expected" is a misnomer
277  *
278  * assert_in_array(actual, expected, description)
279  *   asserts that /expected/ is an Array, and /actual/ is equal to one of the
280  *   members -- expected.indexOf(actual) != -1
281  *
282  * assert_array_equals(actual, expected, description)
283  *   asserts that /actual/ and /expected/ have the same length and the value of
284  *   each indexed property in /actual/ is the strictly equal to the corresponding
285  *   property value in /expected/
286  *
287  * assert_approx_equals(actual, expected, epsilon, description)
288  *   asserts that /actual/ is a number within +/- /epsilon/ of /expected/
289  *
290  * assert_less_than(actual, expected, description)
291  *   asserts that /actual/ is a number less than /expected/
292  *
293  * assert_greater_than(actual, expected, description)
294  *   asserts that /actual/ is a number greater than /expected/
295  *
296  * assert_less_than_equal(actual, expected, description)
297  *   asserts that /actual/ is a number less than or equal to /expected/
298  *
299  * assert_greater_than_equal(actual, expected, description)
300  *   asserts that /actual/ is a number greater than or equal to /expected/
301  *
302  * assert_regexp_match(actual, expected, description)
303  *   asserts that /actual/ matches the regexp /expected/
304  *
305  * assert_class_string(object, class_name, description)
306  *   asserts that the class string of /object/ as returned in
307  *   Object.prototype.toString is equal to /class_name/.
308  *
309  * assert_own_property(object, property_name, description)
310  *   assert that object has own property property_name
311  *
312  * assert_inherits(object, property_name, description)
313  *   assert that object does not have an own property named property_name
314  *   but that property_name is present in the prototype chain for object
315  *
316  * assert_idl_attribute(object, attribute_name, description)
317  *   assert that an object that is an instance of some interface has the
318  *   attribute attribute_name following the conditions specified by WebIDL
319  *
320  * assert_readonly(object, property_name, description)
321  *   assert that property property_name on object is readonly
322  *
323  * assert_throws(code, func, description)
324  *   code - the expected exception:
325  *     o string: the thrown exception must be a DOMException with the given
326  *               name, e.g., "TimeoutError" (for compatibility with existing
327  *               tests, a constant is also supported, e.g., "TIMEOUT_ERR")
328  *     o object: the thrown exception must have a property called "name" that
329  *               matches code.name
330  *     o null:   allow any exception (in general, one of the options above
331  *               should be used)
332  *   func - a function that should throw
333  *
334  * assert_unreached(description)
335  *   asserts if called. Used to ensure that some codepath is *not* taken e.g.
336  *   an event does not fire.
337  *
338  * assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N)
339  *   asserts that one assert_func(actual, expected_array_N, extra_arg1, ..., extra_arg_N)
340  *   is true for some expected_array_N in expected_array. This only works for assert_func
341  *   with signature assert_func(actual, expected, args_1, ..., args_N). Note that tests
342  *   with multiple allowed pass conditions are bad practice unless the spec specifically
343  *   allows multiple behaviours. Test authors should not use this method simply to hide
344  *   UA bugs.
345  *
346  * assert_exists(object, property_name, description)
347  *   *** deprecated ***
348  *   asserts that object has an own property property_name
349  *
350  * assert_not_exists(object, property_name, description)
351  *   *** deprecated ***
352  *   assert that object does not have own property property_name
353  */
354
355 (function ()
356 {
357     var debug = false;
358     // default timeout is 5 seconds, test can override if needed
359     var settings = {
360       output:true,
361       timeout:5000,
362       test_timeout:2000
363     };
364
365     var xhtml_ns = "http://www.w3.org/1999/xhtml";
366
367     // script_prefix is used by Output.prototype.show_results() to figure out
368     // where to get testharness.css from.  It's enclosed in an extra closure to
369     // not pollute the library's namespace with variables like "src".
370     var script_prefix = null;
371     (function ()
372     {
373         var scripts = document.getElementsByTagName("script");
374         for (var i = 0; i < scripts.length; i++)
375         {
376             if (scripts[i].src)
377             {
378                 var src = scripts[i].src;
379             }
380             else if (scripts[i].href)
381             {
382                 //SVG case
383                 var src = scripts[i].href.baseVal;
384             }
385             if (src && src.slice(src.length - "testharness.js".length) === "testharness.js")
386             {
387                 script_prefix = src.slice(0, src.length - "testharness.js".length);
388                 break;
389             }
390         }
391     })();
392
393     /*
394      * API functions
395      */
396
397     var name_counter = 0;
398     function next_default_name()
399     {
400         //Don't use document.title to work around an Opera bug in XHTML documents
401         var prefix = document.getElementsByTagName("title").length > 0 ?
402                          document.getElementsByTagName("title")[0].textContent :
403                          "Untitled";
404         var suffix = name_counter > 0 ? " " + name_counter : "";
405         name_counter++;
406         return prefix + suffix;
407     }
408
409     function test(func, name, properties)
410     {
411         var test_name = name ? name : next_default_name();
412         properties = properties ? properties : {};
413         var test_obj = new Test(test_name, properties);
414         test_obj.step(func);
415         if (test_obj.status === test_obj.NOTRUN) {
416             test_obj.done();
417         }
418     }
419
420     function async_test(func, name, properties)
421     {
422         if (typeof func !== "function") {
423             properties = name;
424             name = func;
425             func = null;
426         }
427         var test_name = name ? name : next_default_name();
428         properties = properties ? properties : {};
429         var test_obj = new Test(test_name, properties);
430         if (func) {
431             test_obj.step(func, test_obj, test_obj);
432         }
433         return test_obj;
434     }
435
436     function setup(func_or_properties, maybe_properties)
437     {
438         var func = null;
439         var properties = {};
440         if (arguments.length === 2) {
441             func = func_or_properties;
442             properties = maybe_properties;
443         } else if (func_or_properties instanceof Function){
444             func = func_or_properties;
445         } else {
446             properties = func_or_properties;
447         }
448         tests.setup(func, properties);
449         output.setup(properties);
450     }
451
452     function done() {
453         tests.end_wait();
454     }
455
456     function generate_tests(func, args, properties) {
457         forEach(args, function(x, i)
458                 {
459                     var name = x[0];
460                     test(function()
461                          {
462                              func.apply(this, x.slice(1));
463                          },
464                          name,
465                          Array.isArray(properties) ? properties[i] : properties);
466                 });
467     }
468
469     function on_event(object, event, callback)
470     {
471       object.addEventListener(event, callback, false);
472     }
473
474     expose(test, 'test');
475     expose(async_test, 'async_test');
476     expose(generate_tests, 'generate_tests');
477     expose(setup, 'setup');
478     expose(done, 'done');
479     expose(on_event, 'on_event');
480
481     /*
482      * Return a string truncated to the given length, with ... added at the end
483      * if it was longer.
484      */
485     function truncate(s, len)
486     {
487         if (s.length > len) {
488             return s.substring(0, len - 3) + "...";
489         }
490         return s;
491     }
492
493     /*
494      * Convert a value to a nice, human-readable string
495      */
496     function format_value(val, seen)
497     {
498         if (!seen) {
499             seen = [];
500         }
501         if (typeof val === "object" && val !== null)
502         {
503             if (seen.indexOf(val) >= 0)
504             {
505                 return "[...]";
506             }
507             seen.push(val);
508         }
509         if (Array.isArray(val))
510         {
511             return "[" + val.map(function(x) {return format_value(x, seen)}).join(", ") + "]";
512         }
513
514         switch (typeof val)
515         {
516         case "string":
517             val = val.replace("\\", "\\\\");
518             for (var i = 0; i < 32; i++)
519             {
520                 var replace = "\\";
521                 switch (i) {
522                 case 0: replace += "0"; break;
523                 case 1: replace += "x01"; break;
524                 case 2: replace += "x02"; break;
525                 case 3: replace += "x03"; break;
526                 case 4: replace += "x04"; break;
527                 case 5: replace += "x05"; break;
528                 case 6: replace += "x06"; break;
529                 case 7: replace += "x07"; break;
530                 case 8: replace += "b"; break;
531                 case 9: replace += "t"; break;
532                 case 10: replace += "n"; break;
533                 case 11: replace += "v"; break;
534                 case 12: replace += "f"; break;
535                 case 13: replace += "r"; break;
536                 case 14: replace += "x0e"; break;
537                 case 15: replace += "x0f"; break;
538                 case 16: replace += "x10"; break;
539                 case 17: replace += "x11"; break;
540                 case 18: replace += "x12"; break;
541                 case 19: replace += "x13"; break;
542                 case 20: replace += "x14"; break;
543                 case 21: replace += "x15"; break;
544                 case 22: replace += "x16"; break;
545                 case 23: replace += "x17"; break;
546                 case 24: replace += "x18"; break;
547                 case 25: replace += "x19"; break;
548                 case 26: replace += "x1a"; break;
549                 case 27: replace += "x1b"; break;
550                 case 28: replace += "x1c"; break;
551                 case 29: replace += "x1d"; break;
552                 case 30: replace += "x1e"; break;
553                 case 31: replace += "x1f"; break;
554                 }
555                 val = val.replace(RegExp(String.fromCharCode(i), "g"), replace);
556             }
557             return '"' + val.replace(/"/g, '\\"') + '"';
558         case "boolean":
559         case "undefined":
560             return String(val);
561         case "number":
562             // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
563             // special-case.
564             if (val === -0 && 1/val === -Infinity)
565             {
566                 return "-0";
567             }
568             return String(val);
569         case "object":
570             if (val === null)
571             {
572                 return "null";
573             }
574
575             // Special-case Node objects, since those come up a lot in my tests.  I
576             // ignore namespaces.  I use duck-typing instead of instanceof, because
577             // instanceof doesn't work if the node is from another window (like an
578             // iframe's contentWindow):
579             // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
580             if ("nodeType" in val
581             && "nodeName" in val
582             && "nodeValue" in val
583             && "childNodes" in val)
584             {
585                 switch (val.nodeType)
586                 {
587                 case Node.ELEMENT_NODE:
588                     var ret = "<" + val.tagName.toLowerCase();
589                     for (var i = 0; i < val.attributes.length; i++)
590                     {
591                         ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';
592                     }
593                     ret += ">" + val.innerHTML + "</" + val.tagName.toLowerCase() + ">";
594                     return "Element node " + truncate(ret, 60);
595                 case Node.TEXT_NODE:
596                     return 'Text node "' + truncate(val.data, 60) + '"';
597                 case Node.PROCESSING_INSTRUCTION_NODE:
598                     return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));
599                 case Node.COMMENT_NODE:
600                     return "Comment node <!--" + truncate(val.data, 60) + "-->";
601                 case Node.DOCUMENT_NODE:
602                     return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
603                 case Node.DOCUMENT_TYPE_NODE:
604                     return "DocumentType node";
605                 case Node.DOCUMENT_FRAGMENT_NODE:
606                     return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
607                 default:
608                     return "Node object of unknown type";
609                 }
610             }
611
612             // Fall through to default
613         default:
614             return typeof val + ' "' + truncate(String(val), 60) + '"';
615         }
616     }
617     expose(format_value, "format_value");
618
619     /*
620      * Assertions
621      */
622
623     function assert_true(actual, description)
624     {
625         assert(actual === true, "assert_true", description,
626                                 "expected true got ${actual}", {actual:actual});
627     };
628     expose(assert_true, "assert_true");
629
630     function assert_false(actual, description)
631     {
632         assert(actual === false, "assert_false", description,
633                                  "expected false got ${actual}", {actual:actual});
634     };
635     expose(assert_false, "assert_false");
636
637     function same_value(x, y) {
638         if (y !== y)
639         {
640             //NaN case
641             return x !== x;
642         }
643         else if (x === 0 && y === 0) {
644             //Distinguish +0 and -0
645             return 1/x === 1/y;
646         }
647         else
648         {
649             //typical case
650             return x === y;
651         }
652     }
653
654     function assert_equals(actual, expected, description)
655     {
656          /*
657           * Test if two primitives are equal or two objects
658           * are the same object
659           */
660         if (typeof actual != typeof expected)
661         {
662             assert(false, "assert_equals", description,
663                           "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",
664                           {expected:expected, actual:actual});
665             return;
666         }
667         assert(same_value(actual, expected), "assert_equals", description,
668                                              "expected ${expected} but got ${actual}",
669                                              {expected:expected, actual:actual});
670     };
671     expose(assert_equals, "assert_equals");
672
673     function assert_not_equals(actual, expected, description)
674     {
675          /*
676           * Test if two primitives are unequal or two objects
677           * are different objects
678           */
679         assert(!same_value(actual, expected), "assert_not_equals", description,
680                                               "got disallowed value ${actual}",
681                                               {actual:actual});
682     };
683     expose(assert_not_equals, "assert_not_equals");
684
685     function assert_in_array(actual, expected, description)
686     {
687         assert(expected.indexOf(actual) != -1, "assert_in_array", description,
688                                                "value ${actual} not in array ${expected}",
689                                                {actual:actual, expected:expected});
690     }
691     expose(assert_in_array, "assert_in_array");
692
693     function assert_object_equals(actual, expected, description)
694     {
695          //This needs to be improved a great deal
696          function check_equal(actual, expected, stack)
697          {
698              stack.push(actual);
699
700              var p;
701              for (p in actual)
702              {
703                  assert(expected.hasOwnProperty(p), "assert_object_equals", description,
704                                                     "unexpected property ${p}", {p:p});
705
706                  if (typeof actual[p] === "object" && actual[p] !== null)
707                  {
708                      if (stack.indexOf(actual[p]) === -1)
709                      {
710                          check_equal(actual[p], expected[p], stack);
711                      }
712                  }
713                  else
714                  {
715                      assert(same_value(actual[p], expected[p]), "assert_object_equals", description,
716                                                        "property ${p} expected ${expected} got ${actual}",
717                                                        {p:p, expected:expected, actual:actual});
718                  }
719              }
720              for (p in expected)
721              {
722                  assert(actual.hasOwnProperty(p),
723                         "assert_object_equals", description,
724                         "expected property ${p} missing", {p:p});
725              }
726              stack.pop();
727          }
728          check_equal(actual, expected, []);
729     };
730     expose(assert_object_equals, "assert_object_equals");
731
732     function assert_array_equals(actual, expected, description)
733     {
734         assert(actual.length === expected.length,
735                "assert_array_equals", description,
736                "lengths differ, expected ${expected} got ${actual}",
737                {expected:expected.length, actual:actual.length});
738
739         for (var i=0; i < actual.length; i++)
740         {
741             assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
742                    "assert_array_equals", description,
743                    "property ${i}, property expected to be $expected but was $actual",
744                    {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
745                    actual:actual.hasOwnProperty(i) ? "present" : "missing"});
746             assert(same_value(expected[i], actual[i]),
747                    "assert_array_equals", description,
748                    "property ${i}, expected ${expected} but got ${actual}",
749                    {i:i, expected:expected[i], actual:actual[i]});
750         }
751     }
752     expose(assert_array_equals, "assert_array_equals");
753
754     function assert_approx_equals(actual, expected, epsilon, description)
755     {
756         /*
757          * Test if two primitive numbers are equal withing +/- epsilon
758          */
759         assert(typeof actual === "number",
760                "assert_approx_equals", description,
761                "expected a number but got a ${type_actual}",
762                {type_actual:typeof actual});
763
764         assert(Math.abs(actual - expected) <= epsilon,
765                "assert_approx_equals", description,
766                "expected ${expected} +/- ${epsilon} but got ${actual}",
767                {expected:expected, actual:actual, epsilon:epsilon});
768     };
769     expose(assert_approx_equals, "assert_approx_equals");
770
771     function assert_less_than(actual, expected, description)
772     {
773         /*
774          * Test if a primitive number is less than another
775          */
776         assert(typeof actual === "number",
777                "assert_less_than", description,
778                "expected a number but got a ${type_actual}",
779                {type_actual:typeof actual});
780
781         assert(actual < expected,
782                "assert_less_than", description,
783                "expected a number less than ${expected} but got ${actual}",
784                {expected:expected, actual:actual});
785     };
786     expose(assert_less_than, "assert_less_than");
787
788     function assert_greater_than(actual, expected, description)
789     {
790         /*
791          * Test if a primitive number is greater than another
792          */
793         assert(typeof actual === "number",
794                "assert_greater_than", description,
795                "expected a number but got a ${type_actual}",
796                {type_actual:typeof actual});
797
798         assert(actual > expected,
799                "assert_greater_than", description,
800                "expected a number greater than ${expected} but got ${actual}",
801                {expected:expected, actual:actual});
802     };
803     expose(assert_greater_than, "assert_greater_than");
804
805     function assert_less_than_equal(actual, expected, description)
806     {
807         /*
808          * Test if a primitive number is less than or equal to another
809          */
810         assert(typeof actual === "number",
811                "assert_less_than_equal", description,
812                "expected a number but got a ${type_actual}",
813                {type_actual:typeof actual});
814
815         assert(actual <= expected,
816                "assert_less_than", description,
817                "expected a number less than or equal to ${expected} but got ${actual}",
818                {expected:expected, actual:actual});
819     };
820     expose(assert_less_than_equal, "assert_less_than_equal");
821
822     function assert_greater_than_equal(actual, expected, description)
823     {
824         /*
825          * Test if a primitive number is greater than or equal to another
826          */
827         assert(typeof actual === "number",
828                "assert_greater_than_equal", description,
829                "expected a number but got a ${type_actual}",
830                {type_actual:typeof actual});
831
832         assert(actual >= expected,
833                "assert_greater_than_equal", description,
834                "expected a number greater than or equal to ${expected} but got ${actual}",
835                {expected:expected, actual:actual});
836     };
837     expose(assert_greater_than_equal, "assert_greater_than_equal");
838
839     function assert_regexp_match(actual, expected, description) {
840         /*
841          * Test if a string (actual) matches a regexp (expected)
842          */
843         assert(expected.test(actual),
844                "assert_regexp_match", description,
845                "expected ${expected} but got ${actual}",
846                {expected:expected, actual:actual});
847     }
848     expose(assert_regexp_match, "assert_regexp_match");
849
850     function assert_class_string(object, class_string, description) {
851         assert_equals({}.toString.call(object), "[object " + class_string + "]",
852                       description);
853     }
854     expose(assert_class_string, "assert_class_string");
855
856
857     function _assert_own_property(name) {
858         return function(object, property_name, description)
859         {
860             assert(object.hasOwnProperty(property_name),
861                    name, description,
862                    "expected property ${p} missing", {p:property_name});
863         };
864     }
865     expose(_assert_own_property("assert_exists"), "assert_exists");
866     expose(_assert_own_property("assert_own_property"), "assert_own_property");
867
868     function assert_not_exists(object, property_name, description)
869     {
870         assert(!object.hasOwnProperty(property_name),
871                "assert_not_exists", description,
872                "unexpected property ${p} found", {p:property_name});
873     };
874     expose(assert_not_exists, "assert_not_exists");
875
876     function _assert_inherits(name) {
877         return function (object, property_name, description)
878         {
879             assert(typeof object === "object",
880                    name, description,
881                    "provided value is not an object");
882
883             assert("hasOwnProperty" in object,
884                    name, description,
885                    "provided value is an object but has no hasOwnProperty method");
886
887             assert(!object.hasOwnProperty(property_name),
888                    name, description,
889                    "property ${p} found on object expected in prototype chain",
890                    {p:property_name});
891
892             assert(property_name in object,
893                    name, description,
894                    "property ${p} not found in prototype chain",
895                    {p:property_name});
896         };
897     }
898     expose(_assert_inherits("assert_inherits"), "assert_inherits");
899     expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
900
901     function assert_readonly(object, property_name, description)
902     {
903          var initial_value = object[property_name];
904          try {
905              //Note that this can have side effects in the case where
906              //the property has PutForwards
907              object[property_name] = initial_value + "a"; //XXX use some other value here?
908              assert(same_value(object[property_name], initial_value),
909                     "assert_readonly", description,
910                     "changing property ${p} succeeded",
911                     {p:property_name});
912          }
913          finally
914          {
915              object[property_name] = initial_value;
916          }
917     };
918     expose(assert_readonly, "assert_readonly");
919
920     function assert_throws(code, func, description)
921     {
922         try
923         {
924             func.call(this);
925             assert(false, "assert_throws", description,
926                    "${func} did not throw", {func:func});
927         }
928         catch(e)
929         {
930             if (e instanceof AssertionError) {
931                 throw(e);
932             }
933             if (code === null)
934             {
935                 return;
936             }
937             if (typeof code === "object")
938             {
939                 assert(typeof e == "object" && "name" in e && e.name == code.name,
940                        "assert_throws", description,
941                        "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
942                                     {func:func, actual:e, actual_name:e.name,
943                                      expected:code,
944                                      expected_name:code.name});
945                 return;
946             }
947
948             var code_name_map = {
949                 INDEX_SIZE_ERR: 'IndexSizeError',
950                 HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
951                 WRONG_DOCUMENT_ERR: 'WrongDocumentError',
952                 INVALID_CHARACTER_ERR: 'InvalidCharacterError',
953                 NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
954                 NOT_FOUND_ERR: 'NotFoundError',
955                 NOT_SUPPORTED_ERR: 'NotSupportedError',
956                 INVALID_STATE_ERR: 'InvalidStateError',
957                 SYNTAX_ERR: 'SyntaxError',
958                 INVALID_MODIFICATION_ERR: 'InvalidModificationError',
959                 NAMESPACE_ERR: 'NamespaceError',
960                 INVALID_ACCESS_ERR: 'InvalidAccessError',
961                 TYPE_MISMATCH_ERR: 'TypeMismatchError',
962                 SECURITY_ERR: 'SecurityError',
963                 NETWORK_ERR: 'NetworkError',
964                 ABORT_ERR: 'AbortError',
965                 URL_MISMATCH_ERR: 'URLMismatchError',
966                 QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
967                 TIMEOUT_ERR: 'TimeoutError',
968                 INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
969                 DATA_CLONE_ERR: 'DataCloneError'
970             };
971
972             var name = code in code_name_map ? code_name_map[code] : code;
973
974             var name_code_map = {
975                 IndexSizeError: 1,
976                 HierarchyRequestError: 3,
977                 WrongDocumentError: 4,
978                 InvalidCharacterError: 5,
979                 NoModificationAllowedError: 7,
980                 NotFoundError: 8,
981                 NotSupportedError: 9,
982                 InvalidStateError: 11,
983                 SyntaxError: 12,
984                 InvalidModificationError: 13,
985                 NamespaceError: 14,
986                 InvalidAccessError: 15,
987                 TypeMismatchError: 17,
988                 SecurityError: 18,
989                 NetworkError: 19,
990                 AbortError: 20,
991                 URLMismatchError: 21,
992                 QuotaExceededError: 22,
993                 TimeoutError: 23,
994                 InvalidNodeTypeError: 24,
995                 DataCloneError: 25,
996
997                 UnknownError: 0,
998                 ConstraintError: 0,
999                 DataError: 0,
1000                 TransactionInactiveError: 0,
1001                 ReadOnlyError: 0,
1002                 VersionError: 0
1003             };
1004
1005             if (!(name in name_code_map))
1006             {
1007                 throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
1008             }
1009
1010             var required_props = { code: name_code_map[name] };
1011
1012             if (required_props.code === 0
1013             || ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException"))
1014             {
1015                 // New style exception: also test the name property.
1016                 required_props.name = name;
1017             }
1018
1019             //We'd like to test that e instanceof the appropriate interface,
1020             //but we can't, because we don't know what window it was created
1021             //in.  It might be an instanceof the appropriate interface on some
1022             //unknown other window.  TODO: Work around this somehow?
1023
1024             assert(typeof e == "object",
1025                    "assert_throws", description,
1026                    "${func} threw ${e} with type ${type}, not an object",
1027                    {func:func, e:e, type:typeof e});
1028
1029             for (var prop in required_props)
1030             {
1031                 assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],
1032                        "assert_throws", description,
1033                        "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
1034                        {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
1035             }
1036         }
1037     }
1038     expose(assert_throws, "assert_throws");
1039
1040     function assert_unreached(description) {
1041          assert(false, "assert_unreached", description,
1042                 "Reached unreachable code");
1043     }
1044     expose(assert_unreached, "assert_unreached");
1045
1046     function assert_any(assert_func, actual, expected_array)
1047     {
1048         var args = [].slice.call(arguments, 3)
1049         var errors = []
1050         var passed = false;
1051         forEach(expected_array,
1052                 function(expected)
1053                 {
1054                     try {
1055                         assert_func.apply(this, [actual, expected].concat(args))
1056                         passed = true;
1057                     } catch(e) {
1058                         errors.push(e.message);
1059                     }
1060                 });
1061         if (!passed) {
1062             throw new AssertionError(errors.join("\n\n"));
1063         }
1064     }
1065     expose(assert_any, "assert_any");
1066
1067     function Test(name, properties)
1068     {
1069         this.name = name;
1070         this.status = this.NOTRUN;
1071         this.timeout_id = null;
1072         this.is_done = false;
1073
1074         this.properties = properties;
1075         this.timeout_length = properties.timeout ? properties.timeout : settings.test_timeout;
1076
1077         this.message = null;
1078
1079         var this_obj = this;
1080         this.steps = [];
1081
1082         tests.push(this);
1083     }
1084
1085     Test.statuses = {
1086         PASS:0,
1087         FAIL:1,
1088         TIMEOUT:2,
1089         NOTRUN:3
1090     };
1091
1092     Test.prototype = merge({}, Test.statuses);
1093
1094     Test.prototype.structured_clone = function()
1095     {
1096         if(!this._structured_clone)
1097         {
1098             var msg = this.message;
1099             msg = msg ? String(msg) : msg;
1100             this._structured_clone = merge({
1101                 name:String(this.name),
1102                 status:this.status,
1103                 message:msg
1104             }, Test.statuses);
1105         }
1106         return this._structured_clone;
1107     };
1108
1109     Test.prototype.step = function(func, this_obj)
1110     {
1111         //In case the test has already failed
1112         if (this.status !== this.NOTRUN)
1113         {
1114           return;
1115         }
1116
1117         tests.started = true;
1118
1119         if (this.timeout_id === null) {
1120             this.set_timeout();
1121         }
1122
1123         this.steps.push(func);
1124
1125         if (arguments.length === 1)
1126         {
1127             this_obj = this;
1128         }
1129
1130         try
1131         {
1132             return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
1133         }
1134         catch(e)
1135         {
1136             //This can happen if something called synchronously invoked another
1137             //step
1138             if (this.status !== this.NOTRUN)
1139             {
1140                 return;
1141             }
1142             this.status = this.FAIL;
1143             this.message = (typeof e === "object" && e !== null) ? e.message : e;
1144             if (typeof e.stack != "undefined" && typeof e.message == "string") {
1145                 //Try to make it more informative for some exceptions, at least
1146                 //in Gecko and WebKit.  This results in a stack dump instead of
1147                 //just errors like "Cannot read property 'parentNode' of null"
1148                 //or "root is null".  Makes it a lot longer, of course.
1149                 this.message += "(stack: " + e.stack + ")";
1150             }
1151             this.done();
1152             if (debug && e.constructor !== AssertionError) {
1153                 throw e;
1154             }
1155         }
1156     };
1157
1158     Test.prototype.step_func = function(func, this_obj)
1159     {
1160         var test_this = this;
1161
1162         if (arguments.length === 1)
1163         {
1164             this_obj = test_this;
1165         }
1166
1167         return function()
1168         {
1169             test_this.step.apply(test_this, [func, this_obj].concat(
1170                 Array.prototype.slice.call(arguments)));
1171         };
1172     };
1173
1174     Test.prototype.step_func_done = function(func, this_obj)
1175     {
1176         var test_this = this;
1177
1178         if (arguments.length === 1)
1179         {
1180             this_obj = test_this;
1181         }
1182
1183         return function()
1184         {
1185             test_this.step.apply(test_this, [func, this_obj].concat(
1186                 Array.prototype.slice.call(arguments)));
1187             test_this.done();
1188         };
1189     };
1190
1191     Test.prototype.set_timeout = function()
1192     {
1193         var this_obj = this;
1194         this.timeout_id = setTimeout(function()
1195                                      {
1196                                          this_obj.timeout();
1197                                      }, this.timeout_length);
1198     };
1199
1200     Test.prototype.timeout = function()
1201     {
1202         this.status = this.TIMEOUT;
1203         this.timeout_id = null;
1204         this.message = "Test timed out";
1205         this.done();
1206     };
1207
1208     Test.prototype.done = function()
1209     {
1210         if (this.is_done) {
1211             return;
1212         }
1213         clearTimeout(this.timeout_id);
1214         if (this.status === this.NOTRUN)
1215         {
1216             this.status = this.PASS;
1217         }
1218         this.is_done = true;
1219         tests.result(this);
1220     };
1221
1222
1223     /*
1224      * Harness
1225      */
1226
1227     function TestsStatus()
1228     {
1229         this.status = null;
1230         this.message = null;
1231     }
1232
1233     TestsStatus.statuses = {
1234         OK:0,
1235         ERROR:1,
1236         TIMEOUT:2
1237     };
1238
1239     TestsStatus.prototype = merge({}, TestsStatus.statuses);
1240
1241     TestsStatus.prototype.structured_clone = function()
1242     {
1243         if(!this._structured_clone)
1244         {
1245             var msg = this.message;
1246             msg = msg ? String(msg) : msg;
1247             this._structured_clone = merge({
1248                 status:this.status,
1249                 message:msg
1250             }, TestsStatus.statuses);
1251         }
1252         return this._structured_clone;
1253     };
1254
1255     function Tests()
1256     {
1257         this.tests = [];
1258         this.num_pending = 0;
1259
1260         this.phases = {
1261             INITIAL:0,
1262             SETUP:1,
1263             HAVE_TESTS:2,
1264             HAVE_RESULTS:3,
1265             COMPLETE:4
1266         };
1267         this.phase = this.phases.INITIAL;
1268
1269         this.properties = {};
1270
1271         //All tests can't be done until the load event fires
1272         this.all_loaded = false;
1273         this.wait_for_finish = false;
1274         this.processing_callbacks = false;
1275
1276         this.timeout_length = settings.timeout;
1277         this.timeout_id = null;
1278
1279         this.start_callbacks = [];
1280         this.test_done_callbacks = [];
1281         this.all_done_callbacks = [];
1282
1283         this.status = new TestsStatus();
1284
1285         var this_obj = this;
1286
1287         on_event(window, "load",
1288                  function()
1289                  {
1290                      this_obj.all_loaded = true;
1291                      if (this_obj.all_done())
1292                      {
1293                          this_obj.complete();
1294                      }
1295                  });
1296
1297         this.set_timeout();
1298     }
1299
1300     Tests.prototype.setup = function(func, properties)
1301     {
1302         if (this.phase >= this.phases.HAVE_RESULTS)
1303         {
1304             return;
1305         }
1306         if (this.phase < this.phases.SETUP)
1307         {
1308             this.phase = this.phases.SETUP;
1309         }
1310
1311         for (var p in properties)
1312         {
1313             if (properties.hasOwnProperty(p))
1314             {
1315                 this.properties[p] = properties[p];
1316             }
1317         }
1318
1319         if (properties.timeout)
1320         {
1321             this.timeout_length = properties.timeout;
1322         }
1323         if (properties.explicit_done)
1324         {
1325             this.wait_for_finish = true;
1326         }
1327         if (properties.explicit_timeout) {
1328             this.timeout_length = null;
1329         }
1330
1331         if (func)
1332         {
1333             try
1334             {
1335                 func();
1336             } catch(e)
1337             {
1338                 this.status.status = this.status.ERROR;
1339                 this.status.message = e;
1340             };
1341         }
1342         this.set_timeout();
1343     };
1344
1345     Tests.prototype.set_timeout = function()
1346     {
1347         var this_obj = this;
1348         clearTimeout(this.timeout_id);
1349         if (this.timeout_length !== null)
1350         {
1351             this.timeout_id = setTimeout(function() {
1352                                              this_obj.timeout();
1353                                          }, this.timeout_length);
1354         }
1355     };
1356
1357     Tests.prototype.timeout = function() {
1358         this.status.status = this.status.TIMEOUT;
1359         this.complete();
1360     };
1361
1362     Tests.prototype.end_wait = function()
1363     {
1364         this.wait_for_finish = false;
1365         if (this.all_done()) {
1366             this.complete();
1367         }
1368     };
1369
1370     Tests.prototype.push = function(test)
1371     {
1372         if (this.phase < this.phases.HAVE_TESTS) {
1373             this.start();
1374         }
1375         this.num_pending++;
1376         this.tests.push(test);
1377     };
1378
1379     Tests.prototype.all_done = function() {
1380         return (this.all_loaded && this.num_pending === 0 &&
1381                 !this.wait_for_finish && !this.processing_callbacks);
1382     };
1383
1384     Tests.prototype.start = function() {
1385         this.phase = this.phases.HAVE_TESTS;
1386         this.notify_start();
1387     };
1388
1389     Tests.prototype.notify_start = function() {
1390         var this_obj = this;
1391         forEach (this.start_callbacks,
1392                  function(callback)
1393                  {
1394                      callback(this_obj.properties);
1395                  });
1396         forEach_windows(
1397                 function(w, is_same_origin)
1398                 {
1399                     if(is_same_origin && w.start_callback)
1400                     {
1401                         try
1402                         {
1403                             w.start_callback(this_obj.properties);
1404                         }
1405                         catch(e)
1406                         {
1407                             if (debug)
1408                             {
1409                                 throw(e);
1410                             }
1411                         }
1412                     }
1413                     if (supports_post_message(w) && w !== self)
1414                     {
1415                         w.postMessage({
1416                             type: "start",
1417                             properties: this_obj.properties
1418                         }, "*");
1419                     }
1420                 });
1421     };
1422
1423     Tests.prototype.result = function(test)
1424     {
1425         if (this.phase > this.phases.HAVE_RESULTS)
1426         {
1427             return;
1428         }
1429         this.phase = this.phases.HAVE_RESULTS;
1430         this.num_pending--;
1431         this.notify_result(test);
1432     };
1433
1434     Tests.prototype.notify_result = function(test) {
1435         var this_obj = this;
1436         this.processing_callbacks = true;
1437         forEach(this.test_done_callbacks,
1438                 function(callback)
1439                 {
1440                     callback(test, this_obj);
1441                 });
1442
1443         forEach_windows(
1444                 function(w, is_same_origin)
1445                 {
1446                     if(is_same_origin && w.result_callback)
1447                     {
1448                         try
1449                         {
1450                             w.result_callback(test);
1451                         }
1452                         catch(e)
1453                         {
1454                             if(debug) {
1455                                 throw e;
1456                             }
1457                         }
1458                     }
1459                     if (supports_post_message(w) && w !== self)
1460                     {
1461                         w.postMessage({
1462                             type: "result",
1463                             test: test.structured_clone()
1464                         }, "*");
1465                     }
1466                 });
1467         this.processing_callbacks = false;
1468         if (this_obj.all_done())
1469         {
1470             this_obj.complete();
1471         }
1472     };
1473
1474     Tests.prototype.complete = function() {
1475         if (this.phase === this.phases.COMPLETE) {
1476             return;
1477         }
1478         this.phase = this.phases.COMPLETE;
1479         var this_obj = this;
1480         this.tests.forEach(
1481             function(x)
1482             {
1483                 if(x.status === x.NOTRUN)
1484                 {
1485                     this_obj.notify_result(x);
1486                 }
1487             }
1488         );
1489         this.notify_complete();
1490     };
1491
1492     Tests.prototype.notify_complete = function()
1493     {
1494         clearTimeout(this.timeout_id);
1495         var this_obj = this;
1496         var tests = map(this_obj.tests,
1497                         function(test)
1498                         {
1499                             return test.structured_clone();
1500                         });
1501         if (this.status.status === null)
1502         {
1503             this.status.status = this.status.OK;
1504         }
1505
1506         forEach (this.all_done_callbacks,
1507                  function(callback)
1508                  {
1509                      callback(this_obj.tests, this_obj.status);
1510                  });
1511
1512         forEach_windows(
1513                 function(w, is_same_origin)
1514                 {
1515                     if(is_same_origin && w.completion_callback)
1516                     {
1517                         try
1518                         {
1519                             w.completion_callback(this_obj.tests, this_obj.status);
1520                         }
1521                         catch(e)
1522                         {
1523                             if (debug)
1524                             {
1525                                 throw e;
1526                             }
1527                         }
1528                     }
1529                     if (supports_post_message(w) && w !== self)
1530                     {
1531                         w.postMessage({
1532                             type: "complete",
1533                             tests: tests,
1534                             status: this_obj.status.structured_clone()
1535                         }, "*");
1536                     }
1537                 });
1538     };
1539
1540     var tests = new Tests();
1541
1542     function timeout() {
1543         if (tests.timeout_length === null)
1544         {
1545             tests.timeout();
1546         }
1547     }
1548     expose(timeout, 'timeout');
1549
1550     function add_start_callback(callback) {
1551         tests.start_callbacks.push(callback);
1552     }
1553
1554     function add_result_callback(callback)
1555     {
1556         tests.test_done_callbacks.push(callback);
1557     }
1558
1559     function add_completion_callback(callback)
1560     {
1561        tests.all_done_callbacks.push(callback);
1562     }
1563
1564     expose(add_start_callback, 'add_start_callback');
1565     expose(add_result_callback, 'add_result_callback');
1566     expose(add_completion_callback, 'add_completion_callback');
1567
1568     /*
1569      * Output listener
1570     */
1571
1572     function Output() {
1573       this.output_document = document;
1574       this.output_node = null;
1575       this.done_count = 0;
1576       this.enabled = settings.output;
1577       this.phase = this.INITIAL;
1578     }
1579
1580     Output.prototype.INITIAL = 0;
1581     Output.prototype.STARTED = 1;
1582     Output.prototype.HAVE_RESULTS = 2;
1583     Output.prototype.COMPLETE = 3;
1584
1585     Output.prototype.setup = function(properties) {
1586         if (this.phase > this.INITIAL) {
1587             return;
1588         }
1589
1590         //If output is disabled in testharnessreport.js the test shouldn't be
1591         //able to override that
1592         this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
1593                                         properties.output : settings.output);
1594     };
1595
1596     Output.prototype.init = function(properties)
1597     {
1598         if (this.phase >= this.STARTED) {
1599             return;
1600         }
1601         if (properties.output_document) {
1602             this.output_document = properties.output_document;
1603         } else {
1604             this.output_document = document;
1605         }
1606         this.phase = this.STARTED;
1607     };
1608
1609     Output.prototype.resolve_log = function()
1610     {
1611         var output_document;
1612         if (typeof this.output_document === "function")
1613         {
1614             output_document = this.output_document.apply(undefined);
1615         } else
1616         {
1617             output_document = this.output_document;
1618         }
1619         if (!output_document)
1620         {
1621             return;
1622         }
1623         var node = output_document.getElementById("log");
1624         if (node)
1625         {
1626             this.output_document = output_document;
1627             this.output_node = node;
1628         }
1629     };
1630
1631     Output.prototype.show_status = function(test)
1632     {
1633         if (this.phase < this.STARTED)
1634         {
1635             this.init();
1636         }
1637         if (!this.enabled)
1638         {
1639             return;
1640         }
1641         if (this.phase < this.HAVE_RESULTS)
1642         {
1643             this.resolve_log();
1644             this.phase = this.HAVE_RESULTS;
1645         }
1646         this.done_count++;
1647         if (this.output_node)
1648         {
1649             if (this.done_count < 100
1650             || (this.done_count < 1000 && this.done_count % 100 == 0)
1651             || this.done_count % 1000 == 0) {
1652                 this.output_node.textContent = "Running, "
1653                     + this.done_count + " complete, "
1654                     + tests.num_pending + " remain";
1655             }
1656         }
1657     };
1658
1659     Output.prototype.show_results = function (tests, harness_status)
1660     {
1661         if (this.phase >= this.COMPLETE) {
1662             return;
1663         }
1664         if (!this.enabled)
1665         {
1666             return;
1667         }
1668         if (!this.output_node) {
1669             this.resolve_log();
1670         }
1671         this.phase = this.COMPLETE;
1672
1673         var log = this.output_node;
1674         if (!log)
1675         {
1676             return;
1677         }
1678         var output_document = this.output_document;
1679
1680         while (log.lastChild)
1681         {
1682             log.removeChild(log.lastChild);
1683         }
1684
1685         if (script_prefix != null) {
1686             var stylesheet = output_document.createElementNS(xhtml_ns, "link");
1687             stylesheet.setAttribute("rel", "stylesheet");
1688             stylesheet.setAttribute("href", script_prefix + "testharness.css");
1689             var heads = output_document.getElementsByTagName("head");
1690             if (heads.length) {
1691                 heads[0].appendChild(stylesheet);
1692             }
1693         }
1694
1695         var status_text = {};
1696         status_text[Test.prototype.PASS] = "Pass";
1697         status_text[Test.prototype.FAIL] = "Fail";
1698         status_text[Test.prototype.TIMEOUT] = "Timeout";
1699         status_text[Test.prototype.NOTRUN] = "Not Run";
1700
1701         var status_number = {};
1702         forEach(tests, function(test) {
1703                     var status = status_text[test.status];
1704                     if (status_number.hasOwnProperty(status))
1705                     {
1706                         status_number[status] += 1;
1707                     } else {
1708                         status_number[status] = 1;
1709                     }
1710                 });
1711
1712         function status_class(status)
1713         {
1714             return status.replace(/\s/g, '').toLowerCase();
1715         }
1716
1717         var summary_template = ["section", {"id":"summary"},
1718                                 ["h2", {}, "Summary"],
1719                                 ["p", {}, "Found ${num_tests} tests"],
1720                                 function(vars) {
1721                                     var rv = [["div", {}]];
1722                                     var i=0;
1723                                     while (status_text.hasOwnProperty(i)) {
1724                                         if (status_number.hasOwnProperty(status_text[i])) {
1725                                             var status = status_text[i];
1726                                             rv[0].push(["div", {"class":status_class(status)},
1727                                                         ["label", {},
1728                                                          ["input", {type:"checkbox", checked:"checked"}],
1729                                                          status_number[status] + " " + status]]);
1730                                         }
1731                                         i++;
1732                                     }
1733                                     return rv;
1734                                 }];
1735
1736         log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
1737
1738         forEach(output_document.querySelectorAll("section#summary label"),
1739                 function(element)
1740                 {
1741                     on_event(element, "click",
1742                              function(e)
1743                              {
1744                                  if (output_document.getElementById("results") === null)
1745                                  {
1746                                      e.preventDefault();
1747                                      return;
1748                                  }
1749                                  var result_class = element.parentNode.getAttribute("class");
1750                                  var style_element = output_document.querySelector("style#hide-" + result_class);
1751                                  var input_element = element.querySelector("input");
1752                                  if (!style_element && !input_element.checked) {
1753                                      style_element = output_document.createElementNS(xhtml_ns, "style");
1754                                      style_element.id = "hide-" + result_class;
1755                                      style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
1756                                      output_document.body.appendChild(style_element);
1757                                  } else if (style_element && input_element.checked) {
1758                                      style_element.parentNode.removeChild(style_element);
1759                                  }
1760                              });
1761                 });
1762
1763         // This use of innerHTML plus manual escaping is not recommended in
1764         // general, but is necessary here for performance.  Using textContent
1765         // on each individual <td> adds tens of seconds of execution time for
1766         // large test suites (tens of thousands of tests).
1767         function escape_html(s)
1768         {
1769             return s.replace(/\&/g, "&amp;")
1770                 .replace(/</g, "&lt;")
1771                 .replace(/"/g, "&quot;")
1772                 .replace(/'/g, "&#39;");
1773         }
1774
1775         function has_assertions()
1776         {
1777             for (var i = 0; i < tests.length; i++) {
1778                 if (tests[i].properties.hasOwnProperty("assert")) {
1779                     return true;
1780                 }
1781             }
1782             return false;
1783         }
1784
1785         function get_assertion(test)
1786         {
1787             if (test.properties.hasOwnProperty("assert")) {
1788                 if (Array.isArray(test.properties.assert)) {
1789                     return test.properties.assert.join(' ');
1790                 }
1791                 return test.properties.assert;
1792             }
1793             return '';
1794         }
1795
1796         log.appendChild(document.createElementNS(xhtml_ns, "section"));
1797         var assertions = has_assertions();
1798         var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">"
1799             + "<thead><tr><th>Result</th><th>Test Name</th>"
1800             + (assertions ? "<th>Assertion</th>" : "")
1801             + "<th>Message</th></tr></thead>"
1802             + "<tbody>";
1803         for (var i = 0; i < tests.length; i++) {
1804             html += '<tr class="'
1805                 + escape_html(status_class(status_text[tests[i].status]))
1806                 + '"><td>'
1807                 + escape_html(status_text[tests[i].status])
1808                 + "</td><td>"
1809                 + escape_html(tests[i].name)
1810                 + "</td><td>"
1811                 + (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "")
1812                 + escape_html(tests[i].message ? tests[i].message : " ")
1813                 + "</td></tr>";
1814         }
1815         html += "</tbody></table>";
1816         try {
1817             log.lastChild.innerHTML = html;
1818         } catch (e) {
1819             log.appendChild(document.createElementNS(xhtml_ns, "p"))
1820                .textContent = "Setting innerHTML for the log threw an exception.";
1821             log.appendChild(document.createElementNS(xhtml_ns, "pre"))
1822                .textContent = html;
1823         }
1824     };
1825
1826     var output = new Output();
1827     add_start_callback(function (properties) {output.init(properties);});
1828     add_result_callback(function (test) {output.show_status(tests);});
1829     add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);});
1830
1831     /*
1832      * Template code
1833      *
1834      * A template is just a javascript structure. An element is represented as:
1835      *
1836      * [tag_name, {attr_name:attr_value}, child1, child2]
1837      *
1838      * the children can either be strings (which act like text nodes), other templates or
1839      * functions (see below)
1840      *
1841      * A text node is represented as
1842      *
1843      * ["{text}", value]
1844      *
1845      * String values have a simple substitution syntax; ${foo} represents a variable foo.
1846      *
1847      * It is possible to embed logic in templates by using a function in a place where a
1848      * node would usually go. The function must either return part of a template or null.
1849      *
1850      * In cases where a set of nodes are required as output rather than a single node
1851      * with children it is possible to just use a list
1852      * [node1, node2, node3]
1853      *
1854      * Usage:
1855      *
1856      * render(template, substitutions) - take a template and an object mapping
1857      * variable names to parameters and return either a DOM node or a list of DOM nodes
1858      *
1859      * substitute(template, substitutions) - take a template and variable mapping object,
1860      * make the variable substitutions and return the substituted template
1861      *
1862      */
1863
1864     function is_single_node(template)
1865     {
1866         return typeof template[0] === "string";
1867     }
1868
1869     function substitute(template, substitutions)
1870     {
1871         if (typeof template === "function") {
1872             var replacement = template(substitutions);
1873             if (replacement)
1874             {
1875                 var rv = substitute(replacement, substitutions);
1876                 return rv;
1877             }
1878             else
1879             {
1880                 return null;
1881             }
1882         }
1883         else if (is_single_node(template))
1884         {
1885             return substitute_single(template, substitutions);
1886         }
1887         else
1888         {
1889             return filter(map(template, function(x) {
1890                                   return substitute(x, substitutions);
1891                               }), function(x) {return x !== null;});
1892         }
1893     }
1894
1895     function substitute_single(template, substitutions)
1896     {
1897         var substitution_re = /\${([^ }]*)}/g;
1898
1899         function do_substitution(input) {
1900             var components = input.split(substitution_re);
1901             var rv = [];
1902             for (var i=0; i<components.length; i+=2)
1903             {
1904                 rv.push(components[i]);
1905                 if (components[i+1])
1906                 {
1907                     rv.push(String(substitutions[components[i+1]]));
1908                 }
1909             }
1910             return rv;
1911         }
1912
1913         var rv = [];
1914         rv.push(do_substitution(String(template[0])).join(""));
1915
1916         if (template[0] === "{text}") {
1917             substitute_children(template.slice(1), rv);
1918         } else {
1919             substitute_attrs(template[1], rv);
1920             substitute_children(template.slice(2), rv);
1921         }
1922
1923         function substitute_attrs(attrs, rv)
1924         {
1925             rv[1] = {};
1926             for (var name in template[1])
1927             {
1928                 if (attrs.hasOwnProperty(name))
1929                 {
1930                     var new_name = do_substitution(name).join("");
1931                     var new_value = do_substitution(attrs[name]).join("");
1932                     rv[1][new_name] = new_value;
1933                 };
1934             }
1935         }
1936
1937         function substitute_children(children, rv)
1938         {
1939             for (var i=0; i<children.length; i++)
1940             {
1941                 if (children[i] instanceof Object) {
1942                     var replacement = substitute(children[i], substitutions);
1943                     if (replacement !== null)
1944                     {
1945                         if (is_single_node(replacement))
1946                         {
1947                             rv.push(replacement);
1948                         }
1949                         else
1950                         {
1951                             extend(rv, replacement);
1952                         }
1953                     }
1954                 }
1955                 else
1956                 {
1957                     extend(rv, do_substitution(String(children[i])));
1958                 }
1959             }
1960             return rv;
1961         }
1962
1963         return rv;
1964     }
1965
1966  function make_dom_single(template, doc)
1967  {
1968      var output_document = doc || document;
1969      if (template[0] === "{text}")
1970      {
1971          var element = output_document.createTextNode("");
1972          for (var i=1; i<template.length; i++)
1973          {
1974              element.data += template[i];
1975          }
1976      }
1977      else
1978      {
1979          var element = output_document.createElementNS(xhtml_ns, template[0]);
1980          for (var name in template[1]) {
1981              if (template[1].hasOwnProperty(name))
1982              {
1983                  element.setAttribute(name, template[1][name]);
1984              }
1985          }
1986          for (var i=2; i<template.length; i++)
1987          {
1988              if (template[i] instanceof Object)
1989              {
1990                  var sub_element = make_dom(template[i]);
1991                  element.appendChild(sub_element);
1992              }
1993              else
1994              {
1995                  var text_node = output_document.createTextNode(template[i]);
1996                  element.appendChild(text_node);
1997              }
1998          }
1999      }
2000
2001      return element;
2002  }
2003
2004
2005
2006  function make_dom(template, substitutions, output_document)
2007     {
2008         if (is_single_node(template))
2009         {
2010             return make_dom_single(template, output_document);
2011         }
2012         else
2013         {
2014             return map(template, function(x) {
2015                            return make_dom_single(x, output_document);
2016                        });
2017         }
2018     }
2019
2020  function render(template, substitutions, output_document)
2021     {
2022         return make_dom(substitute(template, substitutions), output_document);
2023     }
2024
2025     /*
2026      * Utility funcions
2027      */
2028     function assert(expected_true, function_name, description, error, substitutions)
2029     {
2030         if (expected_true !== true)
2031         {
2032             throw new AssertionError(make_message(function_name, description,
2033                                                   error, substitutions));
2034         }
2035     }
2036
2037     function AssertionError(message)
2038     {
2039         this.message = message;
2040     }
2041
2042     function make_message(function_name, description, error, substitutions)
2043     {
2044         for (var p in substitutions) {
2045             if (substitutions.hasOwnProperty(p)) {
2046                 substitutions[p] = format_value(substitutions[p]);
2047             }
2048         }
2049         var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
2050                                    merge({function_name:function_name,
2051                                           description:(description?description + " ":"")},
2052                                           substitutions));
2053         return node_form.slice(1).join("");
2054     }
2055
2056     function filter(array, callable, thisObj) {
2057         var rv = [];
2058         for (var i=0; i<array.length; i++)
2059         {
2060             if (array.hasOwnProperty(i))
2061             {
2062                 var pass = callable.call(thisObj, array[i], i, array);
2063                 if (pass) {
2064                     rv.push(array[i]);
2065                 }
2066             }
2067         }
2068         return rv;
2069     }
2070
2071     function map(array, callable, thisObj)
2072     {
2073         var rv = [];
2074         rv.length = array.length;
2075         for (var i=0; i<array.length; i++)
2076         {
2077             if (array.hasOwnProperty(i))
2078             {
2079                 rv[i] = callable.call(thisObj, array[i], i, array);
2080             }
2081         }
2082         return rv;
2083     }
2084
2085     function extend(array, items)
2086     {
2087         Array.prototype.push.apply(array, items);
2088     }
2089
2090     function forEach (array, callback, thisObj)
2091     {
2092         for (var i=0; i<array.length; i++)
2093         {
2094             if (array.hasOwnProperty(i))
2095             {
2096                 callback.call(thisObj, array[i], i, array);
2097             }
2098         }
2099     }
2100
2101     function merge(a,b)
2102     {
2103         var rv = {};
2104         var p;
2105         for (p in a)
2106         {
2107             rv[p] = a[p];
2108         }
2109         for (p in b) {
2110             rv[p] = b[p];
2111         }
2112         return rv;
2113     }
2114
2115     function expose(object, name)
2116     {
2117         var components = name.split(".");
2118         var target = window;
2119         for (var i=0; i<components.length - 1; i++)
2120         {
2121             if (!(components[i] in target))
2122             {
2123                 target[components[i]] = {};
2124             }
2125             target = target[components[i]];
2126         }
2127         target[components[components.length - 1]] = object;
2128     }
2129
2130     function forEach_windows(callback) {
2131         // Iterate of the the windows [self ... top, opener]. The callback is passed
2132         // two objects, the first one is the windows object itself, the second one
2133         // is a boolean indicating whether or not its on the same origin as the
2134         // current window.
2135         var cache = forEach_windows.result_cache;
2136         if (!cache) {
2137             cache = [[self, true]];
2138             var w = self;
2139             var i = 0;
2140             var so;
2141             var origins = location.ancestorOrigins;
2142             while (w != w.parent)
2143             {
2144                 w = w.parent;
2145                 // In WebKit, calls to parent windows' properties that aren't on the same
2146                 // origin cause an error message to be displayed in the error console but
2147                 // don't throw an exception. This is a deviation from the current HTML5
2148                 // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
2149                 // The problem with WebKit's behavior is that it pollutes the error console
2150                 // with error messages that can't be caught.
2151                 //
2152                 // This issue can be mitigated by relying on the (for now) proprietary
2153                 // `location.ancestorOrigins` property which returns an ordered list of
2154                 // the origins of enclosing windows. See:
2155                 // http://trac.webkit.org/changeset/113945.
2156                 if(origins) {
2157                     so = (location.origin == origins[i]);
2158                 }
2159                 else
2160                 {
2161                     so = is_same_origin(w);
2162                 }
2163                 cache.push([w, so]);
2164                 i++;
2165             }
2166             w = window.opener;
2167             if(w)
2168             {
2169                 // window.opener isn't included in the `location.ancestorOrigins` prop.
2170                 // We'll just have to deal with a simple check and an error msg on WebKit
2171                 // browsers in this case.
2172                 cache.push([w, is_same_origin(w)]);
2173             }
2174             forEach_windows.result_cache = cache;
2175         }
2176
2177         forEach(cache,
2178                 function(a)
2179                 {
2180                     callback.apply(null, a);
2181                 });
2182     }
2183
2184     function is_same_origin(w) {
2185         try {
2186             'random_prop' in w;
2187             return true;
2188         } catch(e) {
2189             return false;
2190         }
2191     }
2192
2193     function supports_post_message(w)
2194     {
2195         var supports;
2196         var type;
2197         // Given IE  implements postMessage across nested iframes but not across
2198         // windows or tabs, you can't infer cross-origin communication from the presence
2199         // of postMessage on the current window object only.
2200         //
2201         // Touching the postMessage prop on a window can throw if the window is
2202         // not from the same origin AND post message is not supported in that
2203         // browser. So just doing an existence test here won't do, you also need
2204         // to wrap it in a try..cacth block.
2205         try
2206         {
2207             type = typeof w.postMessage;
2208             if (type === "function")
2209             {
2210                 supports = true;
2211             }
2212             // IE8 supports postMessage, but implements it as a host object which
2213             // returns "object" as its `typeof`.
2214             else if (type === "object")
2215             {
2216                 supports = true;
2217             }
2218             // This is the case where postMessage isn't supported AND accessing a
2219             // window property across origins does NOT throw (e.g. old Safari browser).
2220             else
2221             {
2222                 supports = false;
2223             }
2224         }
2225         catch(e) {
2226             // This is the case where postMessage isn't supported AND accessing a
2227             // window property across origins throws (e.g. old Firefox browser).
2228             supports = false;
2229         }
2230         return supports;
2231     }
2232 })();
2233 // vim: set expandtab shiftwidth=4 tabstop=4: