Upstream version 5.34.92.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / LayoutTests / resources / testharness.js
1 /*
2 Distributed under both the W3C Test Suite License [1] and the W3C
3 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
4 policies and contribution forms [3].
5
6 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
7 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
8 [3] http://www.w3.org/2004/10/27-testcases
9 */
10
11 /*
12  * == Introduction ==
13  *
14  * This file provides a framework for writing testcases. It is intended to
15  * provide a convenient API for making common assertions, and to work both
16  * for testing synchronous and asynchronous DOM features in a way that
17  * promotes clear, robust, tests.
18  *
19  * == Basic Usage ==
20  *
21  * To use this file, import the script and the testharnessreport script into
22  * the test document:
23  * <script src="/resources/testharness.js"></script>
24  * <script src="/resources/testharnessreport.js"></script>
25  *
26  * Within each file one may define one or more tests. Each test is atomic
27  * in the sense that a single test has a single result (pass/fail/timeout).
28  * Within each test one may have a number of asserts. The test fails at the
29  * first failing assert, and the remainder of the test is (typically) not run.
30  *
31  * If the file containing the tests is a HTML file with an element of id "log"
32  * this will be populated with a table containing the test results after all
33  * the tests have run.
34  *
35  * NOTE: By default tests must be created before the load event fires. For ways
36  *       to create tests after the load event, see "Determining when all tests are
37  *       complete", below
38  *
39  * == Synchronous Tests ==
40  *
41  * To create a synchronous test use the test() function:
42  *
43  * test(test_function, name, properties)
44  *
45  * test_function is a function that contains the code to test. For example a
46  * trivial passing test would be:
47  *
48  * test(function() {assert_true(true)}, "assert_true with true")
49  *
50  * The function passed in is run in the test() call.
51  *
52  * properties is an object that overrides default test properties. The recognised properties
53  * are:
54  *    timeout - the test timeout in ms
55  *
56  * e.g.
57  * test(test_function, "Sample test", {timeout:1000})
58  *
59  * would run test_function with a timeout of 1s.
60  *
61  * == Asynchronous Tests ==
62  *
63  * Testing asynchronous features is somewhat more complex since the result of
64  * a test may depend on one or more events or other callbacks. The API provided
65  * for testing these features is indended to be rather low-level but hopefully
66  * applicable to many situations.
67  *
68  * To create a test, one starts by getting a Test object using async_test:
69  *
70  * async_test(name, properties)
71  *
72  * e.g.
73  * var t = async_test("Simple async test")
74  *
75  * Assertions can be added to the test by calling the step method of the test
76  * object with a function containing the test assertions:
77  *
78  * t.step(function() {assert_true(true)});
79  *
80  * When all the steps are complete, the done() method must be called:
81  *
82  * t.done();
83  *
84  * The properties argument is identical to that for test().
85  *
86  * In many cases it is convenient to run a step in response to an event or a
87  * callback. A convenient method of doing this is through the step_func method
88  * which returns a function that, when called runs a test step. For example
89  *
90  * object.some_event = t.step_func(function(e) {assert_true(e.a)});
91  *
92  * == Making assertions ==
93  *
94  * Functions for making assertions start assert_
95  * The best way to get a list is to look in this file for functions names
96  * matching that pattern. The general signature is
97  *
98  * assert_something(actual, expected, description)
99  *
100  * although not all assertions precisely match this pattern e.g. assert_true
101  * only takes actual and description as arguments.
102  *
103  * The description parameter is used to present more useful error messages when
104  * a test fails
105  *
106  * NOTE: All asserts must be located in a test() or a step of an async_test().
107  *       asserts outside these places won't be detected correctly by the harness
108  *       and may cause a file to stop testing.
109  *
110  * == Setup ==
111  *
112  * Sometimes tests require non-trivial setup that may fail. For this purpose
113  * there is a setup() function, that may be called with one or two arguments.
114  * The two argument version is:
115  *
116  * setup(func, properties)
117  *
118  * The one argument versions may omit either argument.
119  * func is a function to be run synchronously. setup() becomes a no-op once
120  * any tests have returned results. Properties are global properties of the test
121  * harness. Currently recognised properties are:
122  *
123  * timeout - The time in ms after which the harness should stop waiting for
124  *           tests to complete (this is different to the per-test timeout
125  *           because async tests do not start their timer until .step is called)
126  *
127  * explicit_done - Wait for an explicit call to done() before declaring all tests
128  *                 complete (see below)
129  *
130  * output_document - The document to which results should be logged. By default this is
131  *                   the current document but could be an ancestor document in some cases
132  *                   e.g. a SVG test loaded in an HTML wrapper
133  *
134  * == Determining when all tests are complete ==
135  *
136  * By default the test harness will assume there are no more results to come
137  * when:
138  * 1) There are no Test objects that have been created but not completed
139  * 2) The load event on the document has fired
140  *
141  * This behaviour can be overridden by setting the explicit_done property to true
142  * in a call to setup(). If explicit_done is true, the test harness will not assume
143  * it is done until the global done() function is called. Once done() is called, the
144  * two conditions above apply like normal.
145  *
146  * == Generating tests ==
147  *
148  * NOTE: this functionality may be removed
149  *
150  * There are scenarios in which is is desirable to create a large number of
151  * (synchronous) tests that are internally similar but vary in the parameters
152  * used. To make this easier, the generate_tests function allows a single
153  * function to be called with each set of parameters in a list:
154  *
155  * generate_tests(test_function, parameter_lists)
156  *
157  * For example:
158  *
159  * generate_tests(assert_equals, [
160  *     ["Sum one and one", 1+1, 2],
161  *     ["Sum one and zero", 1+0, 1]
162  *     ])
163  *
164  * Is equivalent to:
165  *
166  * test(function() {assert_equals(1+1, 2)}, "Sum one and one")
167  * test(function() {assert_equals(1+0, 1)}, "Sum one and zero")
168  *
169  * Note that the first item in each parameter list corresponds to the name of
170  * the test.
171  *
172  * == Callback API ==
173  *
174  * The framework provides callbacks corresponding to 3 events:
175  *
176  * start - happens when the first Test is created
177  * result - happens when a test result is recieved
178  * complete - happens when all results are recieved
179  *
180  * The page defining the tests may add callbacks for these events by calling
181  * the following methods:
182  *
183  *   add_start_callback(callback) - callback called with no arguments
184  *   add_result_callback(callback) - callback called with a test argument
185  *   add_completion_callback(callback) - callback called with an array of tests
186  *                                       and an status object
187  *
188  * tests have the following properties:
189  *   status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and
190  *           NOTRUN properties on the test object
191  *   message: A message indicating the reason for failure. In the future this
192  *            will always be a string
193  *
194  *  The status object gives the overall status of the harness. It has the
195  *  following properties:
196  *    status: Can be compared to the OK, ERROR and TIMEOUT properties
197  *    message: An error message set when the status is ERROR
198  *
199  * == External API ==
200  *
201  * In order to collect the results of multiple pages containing tests, the test
202  * harness will, when loaded in a nested browsing context, attempt to call
203  * certain functions in each ancestor browsing context:
204  *
205  * start - start_callback
206  * result - result_callback
207  * complete - completion_callback
208  *
209  * These are given the same arguments as the corresponding internal callbacks
210  * described above.
211  *
212  * == List of assertions ==
213  *
214  * assert_true(actual, description)
215  *   asserts that /actual/ is strictly true
216  *
217  * assert_false(actual, description)
218  *   asserts that /actual/ is strictly false
219  *
220  * assert_equals(actual, expected, description)
221  *   asserts that /actual/ is the same value as /expected/
222  *
223  * assert_not_equals(actual, expected, description)
224  *   asserts that /actual/ is a different value to /expected/. Yes, this means
225  *   that "expected" is a misnomer
226  *
227  * assert_in_array(actual, expected, description)
228  *   asserts that /expected/ is an Array, and /actual/ is equal to one of the
229  *   members -- expected.indexOf(actual) != -1
230  *
231  * assert_array_equals(actual, expected, description)
232  *   asserts that /actual/ and /expected/ have the same length and the value of
233  *   each indexed property in /actual/ is the strictly equal to the corresponding
234  *   property value in /expected/
235  *
236  * assert_approx_equals(actual, expected, epsilon, description)
237  *   asserts that /actual/ is a number within +/- /epsilon/ of /expected/
238  *
239  * assert_regexp_match(actual, expected, description)
240  *   asserts that /actual/ matches the regexp /expected/
241  *
242  * assert_own_property(object, property_name, description)
243  *   assert that object has own property property_name
244  *
245  * assert_inherits(object, property_name, description)
246  *   assert that object does not have an own property named property_name
247  *   but that property_name is present in the prototype chain for object
248  *
249  * assert_idl_attribute(object, attribute_name, description)
250  *   assert that an object that is an instance of some interface has the
251  *   attribute attribute_name following the conditions specified by WebIDL
252  *
253  * assert_readonly(object, property_name, description)
254  *   assert that property property_name on object is readonly
255  *
256  * assert_throws(code, func, description)
257  *   code - a DOMException/RangeException code as a string, e.g. "HIERARCHY_REQUEST_ERR"
258  *   func - a function that should throw
259  *
260  *   assert that func throws a DOMException or RangeException (as appropriate)
261  *   with the given code.  If an object is passed for code instead of a string,
262  *   checks that the thrown exception has a property called "name" that matches
263  *   the property of code called "name".  Note, this function will probably be
264  *   rewritten sometime to make more sense.
265  *
266  * assert_unreached(description)
267  *   asserts if called. Used to ensure that some codepath is *not* taken e.g.
268  *   an event does not fire.
269  *
270  * assert_exists(object, property_name, description)
271  *   *** deprecated ***
272  *   asserts that object has an own property property_name
273  *
274  * assert_not_exists(object, property_name, description)
275  *   *** deprecated ***
276  *   assert that object does not have own property property_name
277  */
278
279 (function ()
280 {
281     var debug = false;
282     // default timeout is 5 seconds, test can override if needed
283     var settings = {
284       output:true,
285       timeout:5000,
286       test_timeout:2000
287     };
288
289     var xhtml_ns = "http://www.w3.org/1999/xhtml";
290
291     // script_prefix is used by Output.prototype.show_results() to figure out
292     // where to get testharness.css from.  It's enclosed in an extra closure to
293     // not pollute the library's namespace with variables like "src".
294     var script_prefix = null;
295     (function ()
296     {
297         var scripts = document.getElementsByTagName("script");
298         for (var i = 0; i < scripts.length; i++)
299         {
300             if (scripts[i].src)
301             {
302                 var src = scripts[i].src;
303             }
304             else if (scripts[i].href)
305             {
306                 //SVG case
307                 var src = scripts[i].href.baseVal;
308             }
309             if (src && src.slice(src.length - "testharness.js".length) === "testharness.js")
310             {
311                 script_prefix = src.slice(0, src.length - "testharness.js".length);
312                 break;
313             }
314         }
315     })();
316
317     /*
318      * API functions
319      */
320
321     var name_counter = 0;
322     function next_default_name()
323     {
324         //Don't use document.title to work around an Opera bug in XHTML documents
325         var prefix = document.getElementsByTagName("title").length > 0 ?
326                          document.getElementsByTagName("title")[0].firstChild.data :
327                          "Untitled";
328         var suffix = name_counter > 0 ? " " + name_counter : "";
329         name_counter++;
330         return prefix + suffix;
331     }
332
333     function test(func, name, properties)
334     {
335         var test_name = name ? name : next_default_name();
336         properties = properties ? properties : {};
337         var test_obj = new Test(test_name, properties);
338         test_obj.step(func);
339         if (test_obj.status === test_obj.NOTRUN) {
340             test_obj.done();
341         }
342     }
343
344     function async_test(name, properties)
345     {
346         var test_name = name ? name : next_default_name();
347         properties = properties ? properties : {};
348         var test_obj = new Test(test_name, properties);
349         return test_obj;
350     }
351
352     function setup(func_or_properties, maybe_properties)
353     {
354         var func = null;
355         var properties = {};
356         if (arguments.length === 2) {
357             func = func_or_properties;
358             properties = maybe_properties;
359         } else if (func_or_properties instanceof Function){
360             func = func_or_properties;
361         } else {
362             properties = func_or_properties;
363         }
364         tests.setup(func, properties);
365         output.setup(properties);
366     }
367
368     function done() {
369         tests.end_wait();
370     }
371
372     function generate_tests(func, args) {
373         forEach(args, function(x)
374                 {
375                     var name = x[0];
376                     test(function()
377                          {
378                              func.apply(this, x.slice(1));
379                          }, name);
380                 });
381     }
382
383     function on_event(object, event, callback)
384     {
385       object.addEventListener(event, callback, false);
386     }
387
388     expose(test, 'test');
389     expose(async_test, 'async_test');
390     expose(generate_tests, 'generate_tests');
391     expose(setup, 'setup');
392     expose(done, 'done');
393     expose(on_event, 'on_event');
394
395     /*
396      * Return a string truncated to the given length, with ... added at the end
397      * if it was longer.
398      */
399     function truncate(s, len)
400     {
401         if (s.length > len) {
402             return s.substring(0, len - 3) + "...";
403         }
404         return s;
405     }
406
407     function format_string(str) {
408         for (var i = 0; i < 32; i++) {
409             var replace = "\\";
410             switch (i) {
411             case 0: replace += "0"; break;
412             case 1: replace += "x01"; break;
413             case 2: replace += "x02"; break;
414             case 3: replace += "x03"; break;
415             case 4: replace += "x04"; break;
416             case 5: replace += "x05"; break;
417             case 6: replace += "x06"; break;
418             case 7: replace += "x07"; break;
419             case 8: replace += "b"; break;
420             case 9: replace += "t"; break;
421             case 10: replace += "n"; break;
422             case 11: replace += "v"; break;
423             case 12: replace += "f"; break;
424             case 13: replace += "r"; break;
425             case 14: replace += "x0e"; break;
426             case 15: replace += "x0f"; break;
427             case 16: replace += "x10"; break;
428             case 17: replace += "x11"; break;
429             case 18: replace += "x12"; break;
430             case 19: replace += "x13"; break;
431             case 20: replace += "x14"; break;
432             case 21: replace += "x15"; break;
433             case 22: replace += "x16"; break;
434             case 23: replace += "x17"; break;
435             case 24: replace += "x18"; break;
436             case 25: replace += "x19"; break;
437             case 26: replace += "x1a"; break;
438             case 27: replace += "x1b"; break;
439             case 28: replace += "x1c"; break;
440             case 29: replace += "x1d"; break;
441             case 30: replace += "x1e"; break;
442             case 31: replace += "x1f"; break;
443             }
444             str = str.replace(RegExp(String.fromCharCode(i), "g"), replace);
445         }
446         return str.replace(/"/g, '\\"')
447     }
448
449     /*
450      * Convert a value to a nice, human-readable string
451      */
452     function format_value(val)
453     {
454         if (Array.isArray(val))
455         {
456             return "[" + val.map(format_value).join(", ") + "]";
457         }
458
459         switch (typeof val)
460         {
461         case "string":
462             return '"' + format_string(val) + '"';
463         case "boolean":
464         case "undefined":
465             return String(val);
466         case "number":
467             // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
468             // special-case.
469             if (val === -0 && 1/val === -Infinity)
470             {
471                 return "-0";
472             }
473             return String(val);
474         case "object":
475             if (val === null)
476             {
477                 return "null";
478             }
479
480             // Special-case Node objects, since those come up a lot in my tests.  I
481             // ignore namespaces.  I use duck-typing instead of instanceof, because
482             // instanceof doesn't work if the node is from another window (like an
483             // iframe's contentWindow):
484             // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
485             if ("nodeType" in val
486             && "nodeName" in val
487             && "nodeValue" in val
488             && "childNodes" in val)
489             {
490                 switch (val.nodeType)
491                 {
492                 case Node.ELEMENT_NODE:
493                     var ret = "<" + val.tagName.toLowerCase();
494                     for (var i = 0; i < val.attributes.length; i++)
495                     {
496                         ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';
497                     }
498                     ret += ">" + val.innerHTML + "</" + val.tagName.toLowerCase() + ">";
499                     return "Element node " + truncate(ret, 60);
500                 case Node.TEXT_NODE:
501                     return 'Text node "' + truncate(val.data, 60) + '"';
502                 case Node.PROCESSING_INSTRUCTION_NODE:
503                     return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));
504                 case Node.COMMENT_NODE:
505                     return "Comment node <!--" + truncate(val.data, 60) + "-->";
506                 case Node.DOCUMENT_NODE:
507                     return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
508                 case Node.DOCUMENT_TYPE_NODE:
509                     return "DocumentType node";
510                 case Node.DOCUMENT_FRAGMENT_NODE:
511                     return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
512                 default:
513                     return "Node object of unknown type";
514                 }
515             }
516
517             // Fall through to default
518         default:
519             return typeof val + ' "' + truncate(String(val), 60) + '"';
520         }
521     }
522     expose(format_value, "format_value");
523
524     /*
525      * Assertions
526      */
527
528     function assert_true(actual, description)
529     {
530         assert(actual === true, "assert_true", description,
531                                 "expected true got ${actual}", {actual:actual});
532     };
533     expose(assert_true, "assert_true");
534
535     function assert_false(actual, description)
536     {
537         assert(actual === false, "assert_false", description,
538                                  "expected false got ${actual}", {actual:actual});
539     };
540     expose(assert_false, "assert_false");
541
542     function same_value(x, y) {
543         if (y !== y)
544         {
545             //NaN case
546             return x !== x;
547         }
548         else if (x === 0 && y === 0) {
549             //Distinguish +0 and -0
550             return 1/x === 1/y;
551         }
552         else
553         {
554             //typical case
555             return x === y;
556         }
557     }
558
559     function assert_equals(actual, expected, description)
560     {
561          /*
562           * Test if two primitives are equal or two objects
563           * are the same object
564           */
565         assert(same_value(actual, expected), "assert_equals", description,
566                                              "expected ${expected} but got ${actual}",
567                                              {expected:expected, actual:actual});
568     };
569     expose(assert_equals, "assert_equals");
570
571     function assert_not_equals(actual, expected, description)
572     {
573          /*
574           * Test if two primitives are unequal or two objects
575           * are different objects
576           */
577         assert(!same_value(actual, expected), "assert_not_equals", description,
578                                               "got disallowed value ${actual}",
579                                               {actual:actual});
580     };
581     expose(assert_not_equals, "assert_not_equals");
582
583     function assert_in_array(actual, expected, description)
584     {
585         assert(expected.indexOf(actual) != -1, "assert_in_array", description,
586                                                "value ${actual} not in array ${expected}",
587                                                {actual:actual, expected:expected});
588     }
589     expose(assert_in_array, "assert_in_array");
590
591     function assert_object_equals(actual, expected, description)
592     {
593          //This needs to be improved a great deal
594          function check_equal(expected, actual, stack)
595          {
596              stack.push(actual);
597
598              var p;
599              for (p in actual)
600              {
601                  assert(expected.hasOwnProperty(p), "assert_object_equals", description,
602                                                     "unexpected property ${p}", {p:p});
603
604                  if (typeof actual[p] === "object" && actual[p] !== null)
605                  {
606                      if (stack.indexOf(actual[p]) === -1)
607                      {
608                          check_equal(actual[p], expected[p], stack);
609                      }
610                  }
611                  else
612                  {
613                      assert(actual[p] === expected[p], "assert_object_equals", description,
614                                                        "property ${p} expected ${expected} got ${actual}",
615                                                        {p:p, expected:expected, actual:actual});
616                  }
617              }
618              for (p in expected)
619              {
620                  assert(actual.hasOwnProperty(p),
621                         "assert_object_equals", description,
622                         "expected property ${p} missing", {p:p});
623              }
624              stack.pop();
625          }
626          check_equal(actual, expected, []);
627     };
628     expose(assert_object_equals, "assert_object_equals");
629
630     function assert_array_equals(actual, expected, description)
631     {
632         assert(actual.length === expected.length,
633                "assert_array_equals", description,
634                "lengths differ, expected ${expected} got ${actual}",
635                {expected:expected.length, actual:actual.length});
636
637         for (var i=0; i < actual.length; i++)
638         {
639             assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
640                    "assert_array_equals", description,
641                    "property ${i}, property expected to be $expected but was $actual",
642                    {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
643                    actual:actual.hasOwnProperty(i) ? "present" : "missing"});
644             assert(expected[i] === actual[i],
645                    "assert_array_equals", description,
646                    "property ${i}, expected ${expected} but got ${actual}",
647                    {i:i, expected:expected[i], actual:actual[i]});
648         }
649     }
650     expose(assert_array_equals, "assert_array_equals");
651
652     function assert_approx_equals(actual, expected, epsilon, description)
653     {
654         /*
655          * Test if two primitive numbers are equal withing +/- epsilon
656          */
657         assert(typeof actual === "number",
658                "assert_approx_equals", description,
659                "expected a number but got a ${type_actual}",
660                {type_actual:typeof actual});
661
662         assert(Math.abs(actual - expected) <= epsilon,
663                "assert_approx_equals", description,
664                "expected ${expected} +/- ${epsilon} but got ${actual}",
665                {expected:expected, actual:actual, epsilon:epsilon});
666     };
667     expose(assert_approx_equals, "assert_approx_equals");
668
669     function assert_regexp_match(actual, expected, description) {
670         /*
671          * Test if a string (actual) matches a regexp (expected)
672          */
673         assert(expected.test(actual),
674                "assert_regexp_match", description,
675                "expected ${expected} but got ${actual}",
676                {expected:expected, actual:actual});
677     }
678     expose(assert_regexp_match, "assert_regexp_match");
679
680
681     function _assert_own_property(name) {
682         return function(object, property_name, description)
683         {
684             assert(object.hasOwnProperty(property_name),
685                    name, description,
686                    "expected property ${p} missing", {p:property_name});
687         };
688     }
689     expose(_assert_own_property("assert_exists"), "assert_exists");
690     expose(_assert_own_property("assert_own_property"), "assert_own_property");
691
692     function assert_not_exists(object, property_name, description)
693     {
694         assert(!object.hasOwnProperty(property_name),
695                "assert_not_exists", description,
696                "unexpected property ${p} found", {p:property_name});
697     };
698     expose(assert_not_exists, "assert_not_exists");
699
700     function _assert_inherits(name) {
701         return function (object, property_name, description)
702         {
703             assert(typeof object === "object",
704                    name, description,
705                    "provided value is not an object");
706
707             assert("hasOwnProperty" in object,
708                    name, description,
709                    "provided value is an object but has no hasOwnProperty method");
710
711             assert(!object.hasOwnProperty(property_name),
712                    name, description,
713                    "property ${p} found on object expected in prototype chain",
714                    {p:property_name});
715
716             assert(property_name in object,
717                    name, description,
718                    "property ${p} not found in prototype chain",
719                    {p:property_name});
720         };
721     }
722     expose(_assert_inherits("assert_inherits"), "assert_inherits");
723     expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
724
725     function assert_readonly(object, property_name, description)
726     {
727          var initial_value = object[property_name];
728          try {
729              //Note that this can have side effects in the case where
730              //the property has PutForwards
731              object[property_name] = initial_value + "a"; //XXX use some other value here?
732              assert(object[property_name] === initial_value,
733                     "assert_readonly", description,
734                     "changing property ${p} succeeded",
735                     {p:property_name});
736          }
737          finally
738          {
739              object[property_name] = initial_value;
740          }
741     };
742     expose(assert_readonly, "assert_readonly");
743
744     function assert_throws(code, func, description)
745     {
746         try
747         {
748             func.call(this);
749             assert(false, "assert_throws", description,
750                    "${func} did not throw", {func:func});
751         }
752         catch(e)
753         {
754             if (e instanceof AssertionError) {
755                 throw(e);
756             }
757             if (typeof code === "object")
758             {
759                 assert(typeof e == "object" && "name" in e && e.name == code.name,
760                        "assert_throws", description,
761                        "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
762                                     {func:func, actual:e, actual_name:e.name,
763                                      expected:code,
764                                      expected_name:code.name});
765                 return;
766             }
767             var required_props = {};
768             required_props.code = {
769                 INDEX_SIZE_ERR: 1,
770                 HIERARCHY_REQUEST_ERR: 3,
771                 WRONG_DOCUMENT_ERR: 4,
772                 INVALID_CHARACTER_ERR: 5,
773                 NO_MODIFICATION_ALLOWED_ERR: 7,
774                 NOT_FOUND_ERR: 8,
775                 NOT_SUPPORTED_ERR: 9,
776                 INVALID_STATE_ERR: 11,
777                 SYNTAX_ERR: 12,
778                 INVALID_MODIFICATION_ERR: 13,
779                 NAMESPACE_ERR: 14,
780                 INVALID_ACCESS_ERR: 15,
781                 TYPE_MISMATCH_ERR: 17,
782                 SECURITY_ERR: 18,
783                 NETWORK_ERR: 19,
784                 ABORT_ERR: 20,
785                 URL_MISMATCH_ERR: 21,
786                 QUOTA_EXCEEDED_ERR: 22,
787                 TIMEOUT_ERR: 23,
788                 INVALID_NODE_TYPE_ERR: 24,
789                 DATA_CLONE_ERR: 25,
790             }[code];
791             if (required_props.code === undefined)
792             {
793                 throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
794             }
795             required_props[code] = required_props.code;
796             //Uncomment this when the latest version of every browser
797             //actually implements the spec; otherwise it just creates
798             //zillions of failures.  Also do required_props.type.
799             //required_props.name = code;
800             //
801             //We'd like to test that e instanceof the appropriate interface,
802             //but we can't, because we don't know what window it was created
803             //in.  It might be an instanceof the appropriate interface on some
804             //unknown other window.  TODO: Work around this somehow?
805
806             assert(typeof e == "object",
807                    "assert_throws", description,
808                    "${func} threw ${e} with type ${type}, not an object",
809                    {func:func, e:e, type:typeof e});
810
811             for (var prop in required_props)
812             {
813                 assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],
814                        "assert_throws", description,
815                        "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
816                        {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
817             }
818         }
819     }
820     expose(assert_throws, "assert_throws");
821
822     function assert_unreached(description) {
823          assert(false, "assert_unreached", description,
824                 "Reached unreachable code");
825     }
826     expose(assert_unreached, "assert_unreached");
827
828     function Test(name, properties)
829     {
830         this.name = name;
831         this.status = this.NOTRUN;
832         this.timeout_id = null;
833         this.is_done = false;
834
835         this.timeout_length = properties.timeout ? properties.timeout : settings.test_timeout;
836
837         this.message = null;
838
839         var this_obj = this;
840         this.steps = [];
841
842         tests.push(this);
843     }
844
845     Test.prototype = {
846         PASS:0,
847         FAIL:1,
848         TIMEOUT:2,
849         NOTRUN:3
850     };
851
852
853     Test.prototype.step = function(func, this_obj)
854     {
855         //In case the test has already failed
856         if (this.status !== this.NOTRUN)
857         {
858           return;
859         }
860
861         tests.started = true;
862
863         if (this.timeout_id === null) {
864             this.set_timeout();
865         }
866
867         this.steps.push(func);
868
869         if (arguments.length === 1)
870         {
871             this_obj = this;
872         }
873
874         try
875         {
876             func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
877         }
878         catch(e)
879         {
880             //This can happen if something called synchronously invoked another
881             //step
882             if (this.status !== this.NOTRUN)
883             {
884                 return;
885             }
886             this.status = this.FAIL;
887             this.message = e.message;
888             if (typeof e.stack != "undefined" && typeof e.message == "string") {
889                 //Try to make it more informative for some exceptions, at least
890                 //in Gecko and WebKit.  This results in a stack dump instead of
891                 //just errors like "Cannot read property 'parentNode' of null"
892                 //or "root is null".  Makes it a lot longer, of course.
893                 this.message += "(stack: " + e.stack + ")";
894             }
895             this.done();
896             if (debug && e.constructor !== AssertionError) {
897                 throw e;
898             }
899         }
900     };
901
902     Test.prototype.step_func = function(func, this_obj)
903     {
904         var test_this = this;
905
906         if (arguments.length === 1)
907         {
908             this_obj = test_this;
909         }
910
911         return function()
912         {
913             test_this.step.apply(test_this, [func, this_obj].concat(
914                 Array.prototype.slice.call(arguments)));
915         };
916     };
917
918     Test.prototype.set_timeout = function()
919     {
920         var this_obj = this;
921         this.timeout_id = setTimeout(function()
922                                      {
923                                          this_obj.timeout();
924                                      }, this.timeout_length);
925     };
926
927     Test.prototype.timeout = function()
928     {
929         this.status = this.TIMEOUT;
930         this.timeout_id = null;
931         this.message = "Test timed out";
932         this.done();
933     };
934
935     Test.prototype.done = function()
936     {
937         if (this.is_done) {
938             return;
939         }
940         clearTimeout(this.timeout_id);
941         if (this.status === this.NOTRUN)
942         {
943             this.status = this.PASS;
944         }
945         this.is_done = true;
946         tests.result(this);
947     };
948
949
950     /*
951      * Harness
952      */
953
954     function TestsStatus()
955     {
956         this.status = null;
957         this.message = null;
958     }
959     TestsStatus.prototype = {
960         OK:0,
961         ERROR:1,
962         TIMEOUT:2
963     };
964
965     function Tests()
966     {
967         this.tests = [];
968         this.num_pending = 0;
969
970         this.phases = {
971             INITIAL:0,
972             SETUP:1,
973             HAVE_TESTS:2,
974             HAVE_RESULTS:3,
975             COMPLETE:4
976         };
977         this.phase = this.phases.INITIAL;
978
979         //All tests can't be done until the load event fires
980         this.all_loaded = false;
981         this.wait_for_finish = false;
982         this.processing_callbacks = false;
983
984         this.timeout_length = settings.timeout;
985         this.timeout_id = null;
986         this.set_timeout();
987
988         this.start_callbacks = [];
989         this.test_done_callbacks = [];
990         this.all_done_callbacks = [];
991
992         this.status = new TestsStatus();
993
994         var this_obj = this;
995
996         on_event(window, "load",
997                  function()
998                  {
999                      this_obj.all_loaded = true;
1000                      if (this_obj.all_done())
1001                      {
1002                          this_obj.complete();
1003                      }
1004                  });
1005         this.properties = {};
1006     }
1007
1008     Tests.prototype.setup = function(func, properties)
1009     {
1010         if (this.phase >= this.phases.HAVE_RESULTS)
1011         {
1012             return;
1013         }
1014         if (this.phase < this.phases.SETUP)
1015         {
1016             this.phase = this.phases.SETUP;
1017         }
1018
1019         for (var p in properties)
1020         {
1021             if (properties.hasOwnProperty(p))
1022             {
1023                 this.properties[p] = properties[p];
1024             }
1025         }
1026
1027         if (properties.timeout)
1028         {
1029             this.timeout_length = properties.timeout;
1030             this.set_timeout();
1031         }
1032         if (properties.explicit_done)
1033         {
1034             this.wait_for_finish = true;
1035         }
1036
1037         if (func)
1038         {
1039             try
1040             {
1041                 func();
1042             } catch(e)
1043             {
1044                 this.status.status = this.status.ERROR;
1045                 this.status.message = e;
1046             };
1047         }
1048     };
1049
1050     Tests.prototype.set_timeout = function()
1051     {
1052         var this_obj = this;
1053         clearTimeout(this.timeout_id);
1054         this.timeout_id = setTimeout(function() {
1055                                          this_obj.timeout();
1056                                      }, this.timeout_length);
1057     };
1058
1059     Tests.prototype.timeout = function() {
1060         this.status.status = this.status.TIMEOUT;
1061         this.complete();
1062     };
1063
1064     Tests.prototype.end_wait = function()
1065     {
1066         this.wait_for_finish = false;
1067         if (this.all_done()) {
1068             this.complete();
1069         }
1070     };
1071
1072     Tests.prototype.push = function(test)
1073     {
1074         if (this.phase < this.phases.HAVE_TESTS) {
1075             this.notify_start();
1076         }
1077         this.num_pending++;
1078         this.tests.push(test);
1079     };
1080
1081     Tests.prototype.all_done = function() {
1082         return (this.all_loaded && this.num_pending === 0 &&
1083                 !this.wait_for_finish && !this.processing_callbacks);
1084     };
1085
1086     Tests.prototype.start = function() {
1087         this.phase = this.phases.HAVE_TESTS;
1088         this.notify_start();
1089     };
1090
1091     Tests.prototype.notify_start = function() {
1092         var this_obj = this;
1093         forEach (this.start_callbacks,
1094                  function(callback)
1095                  {
1096                      callback(this_obj.properties);
1097                  });
1098         forEach(ancestor_windows(),
1099                 function(w)
1100                 {
1101                     if(w.start_callback)
1102                     {
1103                         try
1104                         {
1105                             w.start_callback(this_obj.properties);
1106                         }
1107                         catch(e)
1108                         {
1109                             if (debug)
1110                             {
1111                                 throw(e);
1112                             }
1113                         }
1114                     }
1115                 });
1116     };
1117
1118     Tests.prototype.result = function(test)
1119     {
1120         if (this.phase > this.phases.HAVE_RESULTS)
1121         {
1122             return;
1123         }
1124         this.phase = this.phases.HAVE_RESULTS;
1125         this.num_pending--;
1126         this.notify_result(test);
1127     };
1128
1129     Tests.prototype.notify_result = function(test) {
1130         var this_obj = this;
1131         this.processing_callbacks = true;
1132         forEach(this.test_done_callbacks,
1133                 function(callback)
1134                 {
1135                     callback(test, this_obj);
1136                 });
1137
1138         forEach(ancestor_windows(),
1139                 function(w)
1140                 {
1141                     if(w.result_callback)
1142                     {
1143                         try
1144                         {
1145                             w.result_callback(test);
1146                         }
1147                         catch(e)
1148                         {
1149                             if(debug) {
1150                                 throw e;
1151                             }
1152                         }
1153                     }
1154                 });
1155         this.processing_callbacks = false;
1156         if (this_obj.all_done())
1157         {
1158             this_obj.complete();
1159         }
1160     };
1161
1162     Tests.prototype.complete = function() {
1163         if (this.phase === this.phases.COMPLETE) {
1164             return;
1165         }
1166         this.phase = this.phases.COMPLETE;
1167         this.notify_complete();
1168     };
1169
1170     Tests.prototype.notify_complete = function()
1171     {
1172         clearTimeout(this.timeout_id);
1173         var this_obj = this;
1174         if (this.status.status === null)
1175         {
1176             this.status.status = this.status.OK;
1177         }
1178
1179         forEach (this.all_done_callbacks,
1180                  function(callback)
1181                  {
1182                      callback(this_obj.tests, this_obj.status);
1183                  });
1184
1185         forEach(ancestor_windows(),
1186                 function(w)
1187                 {
1188                     if(w.completion_callback)
1189                     {
1190                         try
1191                         {
1192                             w.completion_callback(this_obj.tests, this_obj.status);
1193                         }
1194                         catch(e)
1195                         {
1196                             if (debug)
1197                             {
1198                                 throw e;
1199                             }
1200                         }
1201                     }
1202                 });
1203     };
1204
1205     var tests = new Tests();
1206
1207     function add_start_callback(callback) {
1208         tests.start_callbacks.push(callback);
1209     }
1210
1211     function add_result_callback(callback)
1212     {
1213         tests.test_done_callbacks.push(callback);
1214     }
1215
1216     function add_completion_callback(callback)
1217     {
1218        tests.all_done_callbacks.push(callback);
1219     }
1220
1221     expose(add_start_callback, 'add_start_callback');
1222     expose(add_result_callback, 'add_result_callback');
1223     expose(add_completion_callback, 'add_completion_callback');
1224
1225     /*
1226      * Output listener
1227     */
1228
1229     function Output() {
1230       this.output_document = null;
1231       this.output_node = null;
1232       this.done_count = 0;
1233       this.enabled = settings.output;
1234       this.phase = this.INITIAL;
1235     }
1236
1237     Output.prototype.INITIAL = 0;
1238     Output.prototype.STARTED = 1;
1239     Output.prototype.HAVE_RESULTS = 2;
1240     Output.prototype.COMPLETE = 3;
1241
1242     Output.prototype.setup = function(properties) {
1243         if (this.phase > this.INITIAL) {
1244             return;
1245         }
1246
1247         //If output is disabled in testharnessreport.js the test shouldn't be
1248         //able to override that
1249         this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
1250                                         properties.output : settings.output);
1251     };
1252
1253     Output.prototype.init = function(properties)
1254     {
1255         if (this.phase >= this.STARTED) {
1256             return;
1257         }
1258         if (properties.output_document) {
1259             this.output_document = properties.output_document;
1260         } else {
1261             this.output_document = document;
1262         }
1263         this.phase = this.STARTED;
1264     };
1265
1266     Output.prototype.resolve_log = function()
1267     {
1268         if (!this.output_document) {
1269             return;
1270         }
1271         var node = this.output_document.getElementById("log");
1272         if (node) {
1273             this.output_node = node;
1274         }
1275     };
1276
1277     Output.prototype.show_status = function(test)
1278     {
1279         if (this.phase < this.STARTED)
1280         {
1281             this.init();
1282         }
1283         if (!this.enabled)
1284         {
1285             return;
1286         }
1287         if (this.phase < this.HAVE_RESULTS)
1288         {
1289             this.resolve_log();
1290             this.phase = this.HAVE_RESULTS;
1291         }
1292         this.done_count++;
1293         if (this.output_node)
1294         {
1295             if (this.done_count < 100
1296             || (this.done_count < 1000 && this.done_count % 100 == 0)
1297             || this.done_count % 1000 == 0) {
1298                 this.output_node.textContent = "Running, "
1299                     + this.done_count + " complete, "
1300                     + tests.num_pending + " remain";
1301             }
1302         }
1303     };
1304
1305     Output.prototype.show_results = function (tests, harness_status)
1306     {
1307         if (this.phase >= this.COMPLETE) {
1308             return;
1309         }
1310         if (!this.enabled)
1311         {
1312             return;
1313         }
1314         if (!this.output_node) {
1315             this.resolve_log();
1316         }
1317         this.phase = this.COMPLETE;
1318
1319         var log = this.output_node;
1320         if (!log)
1321         {
1322             return;
1323         }
1324         var output_document = this.output_document;
1325
1326         while (log.lastChild)
1327         {
1328             log.removeChild(log.lastChild);
1329         }
1330
1331         if (script_prefix != null) {
1332             var stylesheet = output_document.createElementNS(xhtml_ns, "link");
1333             stylesheet.setAttribute("rel", "stylesheet");
1334             stylesheet.setAttribute("href", script_prefix + "testharness.css");
1335             var heads = output_document.getElementsByTagName("head");
1336             if (heads.length) {
1337                 heads[0].appendChild(stylesheet);
1338             }
1339         }
1340
1341         var status_text = {};
1342         status_text[Test.prototype.PASS] = "Pass";
1343         status_text[Test.prototype.FAIL] = "Fail";
1344         status_text[Test.prototype.TIMEOUT] = "Timeout";
1345         status_text[Test.prototype.NOTRUN] = "Not Run";
1346
1347         var status_number = {};
1348         forEach(tests, function(test) {
1349                     var status = status_text[test.status];
1350                     if (status_number.hasOwnProperty(status))
1351                     {
1352                         status_number[status] += 1;
1353                     } else {
1354                         status_number[status] = 1;
1355                     }
1356                 });
1357
1358         function status_class(status)
1359         {
1360             return status.replace(/\s/g, '').toLowerCase();
1361         }
1362
1363         var summary_template = ["section", {"id":"summary"},
1364                                 ["h2", {}, "Summary"],
1365                                 ["p", {}, "Found ${num_tests} tests"],
1366                                 function(vars) {
1367                                     var rv = [["div", {}]];
1368                                     var i=0;
1369                                     while (status_text.hasOwnProperty(i)) {
1370                                         if (status_number.hasOwnProperty(status_text[i])) {
1371                                             var status = status_text[i];
1372                                             rv[0].push(["div", {"class":status_class(status)},
1373                                                         ["label", {},
1374                                                          ["input", {type:"checkbox", checked:"checked"}],
1375                                                          status_number[status] + " " + status]]);
1376                                         }
1377                                         i++;
1378                                     }
1379                                     return rv;
1380                                 }];
1381
1382         log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
1383
1384         forEach(output_document.querySelectorAll("section#summary label"),
1385                 function(element)
1386                 {
1387                     on_event(element, "click",
1388                              function(e)
1389                              {
1390                                  if (output_document.getElementById("results") === null)
1391                                  {
1392                                      e.preventDefault();
1393                                      return;
1394                                  }
1395                                  var result_class = element.parentNode.getAttribute("class");
1396                                  var style_element = output_document.querySelector("style#hide-" + result_class);
1397                                  var input_element = element.querySelector("input");
1398                                  if (!style_element && !input_element.checked) {
1399                                      style_element = output_document.createElementNS(xhtml_ns, "style");
1400                                      style_element.id = "hide-" + result_class;
1401                                      style_element.innerHTML = "table#results > tbody > tr."+result_class+"{display:none}";
1402                                      output_document.body.appendChild(style_element);
1403                                  } else if (style_element && input_element.checked) {
1404                                      style_element.parentNode.removeChild(style_element);
1405                                  }
1406                              });
1407                 });
1408
1409         // This use of innerHTML plus manual escaping is not recommended in
1410         // general, but is necessary here for performance.  Using textContent
1411         // on each individual <td> adds tens of seconds of execution time for
1412         // large test suites (tens of thousands of tests).
1413         function escape_html(s)
1414         {
1415             return s.replace(/\&/g, "&amp;")
1416                 .replace(/</g, "&lt;")
1417                 .replace(/"/g, "&quot;")
1418                 .replace(/'/g, "&#39;");
1419         }
1420
1421         log.appendChild(document.createElement("section"));
1422         var html = "<h2>Details</h2><table id='results'>"
1423             + "<thead><tr><th>Result</th><th>Test Name</th><th>Message</th></tr></thead>"
1424             + "<tbody>";
1425         for (var i = 0; i < tests.length; i++) {
1426             html += '<tr class="'
1427                 + escape_html(status_class(status_text[tests[i].status]))
1428                 + '"><td>'
1429                 + escape_html(status_text[tests[i].status])
1430                 + "</td><td>"
1431                 + escape_html(format_string(tests[i].name))
1432                 + "</td><td>"
1433                 + escape_html(tests[i].message ? format_string(tests[i].message) : " ")
1434                 + "</td></tr>";
1435         }
1436         log.lastChild.innerHTML = html + "</tbody></table>";
1437     };
1438
1439     var output = new Output();
1440     add_start_callback(function (properties) {output.init(properties);});
1441     add_result_callback(function (test) {output.show_status(tests);});
1442     add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);});
1443
1444     /*
1445      * Template code
1446      *
1447      * A template is just a javascript structure. An element is represented as:
1448      *
1449      * [tag_name, {attr_name:attr_value}, child1, child2]
1450      *
1451      * the children can either be strings (which act like text nodes), other templates or
1452      * functions (see below)
1453      *
1454      * A text node is represented as
1455      *
1456      * ["{text}", value]
1457      *
1458      * String values have a simple substitution syntax; ${foo} represents a variable foo.
1459      *
1460      * It is possible to embed logic in templates by using a function in a place where a
1461      * node would usually go. The function must either return part of a template or null.
1462      *
1463      * In cases where a set of nodes are required as output rather than a single node
1464      * with children it is possible to just use a list
1465      * [node1, node2, node3]
1466      *
1467      * Usage:
1468      *
1469      * render(template, substitutions) - take a template and an object mapping
1470      * variable names to parameters and return either a DOM node or a list of DOM nodes
1471      *
1472      * substitute(template, substitutions) - take a template and variable mapping object,
1473      * make the variable substitutions and return the substituted template
1474      *
1475      */
1476
1477     function is_single_node(template)
1478     {
1479         return typeof template[0] === "string";
1480     }
1481
1482     function substitute(template, substitutions)
1483     {
1484         if (typeof template === "function") {
1485             var replacement = template(substitutions);
1486             if (replacement)
1487             {
1488                 var rv = substitute(replacement, substitutions);
1489                 return rv;
1490             }
1491             else
1492             {
1493                 return null;
1494             }
1495         }
1496         else if (is_single_node(template))
1497         {
1498             return substitute_single(template, substitutions);
1499         }
1500         else
1501         {
1502             return filter(map(template, function(x) {
1503                                   return substitute(x, substitutions);
1504                               }), function(x) {return x !== null;});
1505         }
1506     }
1507
1508     function substitute_single(template, substitutions)
1509     {
1510         var substitution_re = /\${([^ }]*)}/g;
1511
1512         function do_substitution(input) {
1513             var components = input.split(substitution_re);
1514             var rv = [];
1515             for (var i=0; i<components.length; i+=2)
1516             {
1517                 rv.push(components[i]);
1518                 if (components[i+1])
1519                 {
1520                     rv.push(String(substitutions[components[i+1]]));
1521                 }
1522             }
1523             return rv;
1524         }
1525
1526         var rv = [];
1527         rv.push(do_substitution(String(template[0])).join(""));
1528
1529         if (template[0] === "{text}") {
1530             substitute_children(template.slice(1), rv);
1531         } else {
1532             substitute_attrs(template[1], rv);
1533             substitute_children(template.slice(2), rv);
1534         }
1535
1536         function substitute_attrs(attrs, rv)
1537         {
1538             rv[1] = {};
1539             for (var name in template[1])
1540             {
1541                 if (attrs.hasOwnProperty(name))
1542                 {
1543                     var new_name = do_substitution(name).join("");
1544                     var new_value = do_substitution(attrs[name]).join("");
1545                     rv[1][new_name] = new_value;
1546                 };
1547             }
1548         }
1549
1550         function substitute_children(children, rv)
1551         {
1552             for (var i=0; i<children.length; i++)
1553             {
1554                 if (children[i] instanceof Object) {
1555                     var replacement = substitute(children[i], substitutions);
1556                     if (replacement !== null)
1557                     {
1558                         if (is_single_node(replacement))
1559                         {
1560                             rv.push(replacement);
1561                         }
1562                         else
1563                         {
1564                             extend(rv, replacement);
1565                         }
1566                     }
1567                 }
1568                 else
1569                 {
1570                     extend(rv, do_substitution(String(children[i])));
1571                 }
1572             }
1573             return rv;
1574         }
1575
1576         return rv;
1577     }
1578
1579  function make_dom_single(template, doc)
1580  {
1581      var output_document = doc || document;
1582      if (template[0] === "{text}")
1583      {
1584          var element = output_document.createTextNode("");
1585          for (var i=1; i<template.length; i++)
1586          {
1587              element.data += template[i];
1588          }
1589      }
1590      else
1591      {
1592          var element = output_document.createElementNS(xhtml_ns, template[0]);
1593          for (var name in template[1]) {
1594              if (template[1].hasOwnProperty(name))
1595              {
1596                  element.setAttribute(name, template[1][name]);
1597              }
1598          }
1599          for (var i=2; i<template.length; i++)
1600          {
1601              if (template[i] instanceof Object)
1602              {
1603                  var sub_element = make_dom(template[i]);
1604                  element.appendChild(sub_element);
1605              }
1606              else
1607              {
1608                  var text_node = output_document.createTextNode(template[i]);
1609                  element.appendChild(text_node);
1610              }
1611          }
1612      }
1613
1614      return element;
1615  }
1616
1617
1618
1619  function make_dom(template, substitutions, output_document)
1620     {
1621         if (is_single_node(template))
1622         {
1623             return make_dom_single(template, output_document);
1624         }
1625         else
1626         {
1627             return map(template, function(x) {
1628                            return make_dom_single(x, output_document);
1629                        });
1630         }
1631     }
1632
1633  function render(template, substitutions, output_document)
1634     {
1635         return make_dom(substitute(template, substitutions), output_document);
1636     }
1637     expose(render, "template.render");
1638
1639     /*
1640      * Utility funcions
1641      */
1642     function assert(expected_true, function_name, description, error, substitutions)
1643     {
1644         if (expected_true !== true)
1645         {
1646             throw new AssertionError(make_message(function_name, description,
1647                                                   error, substitutions));
1648         }
1649     }
1650
1651     function AssertionError(message)
1652     {
1653         this.message = message;
1654     }
1655
1656     function make_message(function_name, description, error, substitutions)
1657     {
1658         for (var p in substitutions) {
1659             if (substitutions.hasOwnProperty(p)) {
1660                 substitutions[p] = format_value(substitutions[p]);
1661             }
1662         }
1663         var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
1664                                    merge({function_name:function_name,
1665                                           description:(description?description + " ":"")},
1666                                           substitutions));
1667         return node_form.slice(1).join("");
1668     }
1669
1670     function filter(array, callable, thisObj) {
1671         var rv = [];
1672         for (var i=0; i<array.length; i++)
1673         {
1674             if (array.hasOwnProperty(i))
1675             {
1676                 var pass = callable.call(thisObj, array[i], i, array);
1677                 if (pass) {
1678                     rv.push(array[i]);
1679                 }
1680             }
1681         }
1682         return rv;
1683     }
1684
1685     function map(array, callable, thisObj)
1686     {
1687         var rv = [];
1688         rv.length = array.length;
1689         for (var i=0; i<array.length; i++)
1690         {
1691             if (array.hasOwnProperty(i))
1692             {
1693                 rv[i] = callable.call(thisObj, array[i], i, array);
1694             }
1695         }
1696         return rv;
1697     }
1698
1699     function extend(array, items)
1700     {
1701         Array.prototype.push.apply(array, items);
1702     }
1703
1704     function forEach (array, callback, thisObj)
1705     {
1706         for (var i=0; i<array.length; i++)
1707         {
1708             if (array.hasOwnProperty(i))
1709             {
1710                 callback.call(thisObj, array[i], i, array);
1711             }
1712         }
1713     }
1714
1715     function merge(a,b)
1716     {
1717         var rv = {};
1718         var p;
1719         for (p in a)
1720         {
1721             rv[p] = a[p];
1722         }
1723         for (p in b) {
1724             rv[p] = b[p];
1725         }
1726         return rv;
1727     }
1728
1729     function expose(object, name)
1730     {
1731         var components = name.split(".");
1732         var target = window;
1733         for (var i=0; i<components.length - 1; i++)
1734         {
1735             if (!(components[i] in target))
1736             {
1737                 target[components[i]] = {};
1738             }
1739             target = target[components[i]];
1740         }
1741         target[components[components.length - 1]] = object;
1742     }
1743
1744  function ancestor_windows() {
1745      //Get the windows [self ... top] as an array
1746      if ("result_cache" in ancestor_windows)
1747      {
1748          return ancestor_windows.result_cache;
1749      }
1750      var rv = [self];
1751      var w = self;
1752      while (w != w.parent)
1753      {
1754          w = w.parent;
1755          rv.push(w);
1756      }
1757      ancestor_windows.result_cache = rv;
1758      return rv;
1759  }
1760
1761 })();
1762 // vim: set expandtab shiftwidth=4 tabstop=4: