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