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