moving test directory and to a new tests directory
authorArt Barstow <art.barstow@nokia.com>
Tue, 17 Jan 2012 09:47:15 -0500
changeset 148 22c2e3d43b31
parent 147 6ce387d53cf0
child 149 068cf38fa150
moving test directory and to a new tests directory
test/testharness.css
test/testharness.js
test/touchevents/single-touch.html
tests/testharness.css
tests/testharness.js
tests/touchevents/single-touch.html
--- a/test/testharness.css	Mon Jan 09 13:00:42 2012 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-html {
-    font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;
-}
-
-section#summary {
-    margin-bottom:1em;
-}
-
-table#results {
-    border-collapse:collapse;
-}
-
-table#results th {
-    padding:0;
-    padding-bottom:0.5em;
-    border-bottom:medium solid black;
-}
-
-table#results td {
-    padding:1em;
-    padding-bottom:0.5em;
-    border-bottom:thin solid black;
-}
-
-td.pass {
-    color:green;
-}
-
-td.fail {
-    color:red;
-}
-
-td.timeout {
-    color:red;
-}
-
-td.notrun {
-    color:blue;
-}
-
-.pass .fail .timeout .notrun {
-    font-variant:small-caps;
-}
-
-table#results span {
-    display:block;
-}
-
-table#results span.expected {
-    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
-    white-space:pre;
-}
-
-table#results span.actual {
-    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
-    white-space:pre;
-}
-
--- a/test/testharness.js	Mon Jan 09 13:00:42 2012 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1500 +0,0 @@
-/*
-Distributed under both the W3C Test Suite License [1] and the W3C
-3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
-policies and contribution forms [3].
-
-[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
-[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
-[3] http://www.w3.org/2004/10/27-testcases
-*/
-
-/*
- * == Introduction ==
- *
- * This file provides a framework for writing testcases. It is intended to
- * provide a convenient API for making common assertions, and to work both
- * for testing synchronous and asynchronous DOM features in a way that
- * promotes clear, robust, tests.
- *
- * == Basic Usage ==
- *
- * To use this file, import the script into the test document:
- * <script src="http://w3c-test.org/resources/testharness.js"></script>
- *
- * Within each file one may define one or more tests. Each test is atomic
- * in the sense that a single test has a single result (pass/fail/timeout).
- * Within each test one may have a number of asserts. The test fails at the
- * first failing assert, and the remainder of the test is (typically) not run.
- *
- * If the file containing the tests is a HTML file with an element of id "log"
- * this will be populated with a table containing the test results after all
- * the tests have run.
- *
- * == Synchronous Tests ==
- *
- * To create a synchronous test use the test() function:
- *
- * test(test_function, name, properties)
- *
- * test_function is a function that contains the code to test. For example a
- * trivial passing test would be:
- *
- * test(function() {assert_true(true)}, "assert_true with true")
- *
- * The function passed in is run in the test() call.
- *
- * properties is an object that overrides default test properties. The recognised properties
- * are:
- *    timeout - the test timeout in ms
- *
- * e.g.
- * test(test_function, "Sample test", {timeout:1000})
- *
- * would run test_function with a timeout of 1s.
- *
- * == Asynchronous Tests ==
- *
- * Testing asynchronous features is somewhat more complex since the result of
- * a test may depend on one or more events or other callbacks. The API provided
- * for testing these features is indended to be rather low-level but hopefully
- * applicable to many situations.
- *
- * To create a test, one starts by getting a Test object using async_test:
- *
- * async_test(name, properties)
- *
- * e.g.
- * var t = async_test("Simple async test")
- *
- * Assertions can be added to the test by calling the step method of the test
- * object with a function containing the test assertions:
- *
- * t.step(function() {assert_true(true)});
- *
- * When all the steps are complete, the done() method must be called:
- *
- * t.done();
- *
- * The properties argument is identical to that for test().
- *
- * == Making assertions ==
- *
- * Functions for making assertions start assert_
- * The best way to get a list is to look in this file for functions names
- * matching that pattern. The general signature is
- *
- * assert_something(actual, expected, description)
- *
- * although not all assertions precisely match this pattern e.g. assert_true
- * only takes actual and description as arguments.
- *
- * The description parameter is used to present more useful error messages when
- * a test fails
- *
- * == Setup ==
- *
- * Sometimes tests require non-trivial setup that may fail. For this purpose
- * there is a setup() function, that may be called with one or two arguments.
- * The two argument version is:
- *
- * setup(func, properties)
- *
- * The one argument versions may omit either argument.
- * func is a function to be run synchronously. setup() becomes a no-op once
- * any tests have returned results. Properties are global properties of the test
- * harness. Currently recognised properties are:
- *
- * timeout - The time in ms after which the harness should stop waiting for
- *           tests to complete (this is different to the per-test timeout
- *           because async tests do not start their timer until .step is called)
- *
- * explicit_done - Wait for an explicit call to done() before declaring all tests
- *                 complete (see below)
- *
- * == Determining when all tests are complete ==
- *
- * By default the test harness will assume there are no more results to come
- * when:
- * 1) There are no Test objects that have been created but not completed
- * 2) The load event on the document has fired
- *
- * This behaviour can be overridden by setting the explicit_done property to true
- * in a call to setup(). If explicit_done is true, the test harness will not assume
- * it is done until the global done() function is called. Once done() is called, the
- * two conditions above apply like normal.
- *
- * == Generating tests ==
- *
- * NOTE: this functionality may be removed
- *
- * There are scenarios in which is is desirable to create a large number of
- * (synchronous) tests that are internally similar but vary in the parameters
- * used. To make this easier, the generate_tests function allows a single
- * function to be called with each set of parameters in a list:
- *
- * generate_tests(test_function, parameter_lists)
- *
- * For example:
- *
- * generate_tests(assert_equals, [
- *     ["Sum one and one", 1+1, 2],
- *     ["Sum one and zero", 1+0, 1]
- *     ])
- *
- * Is equivalent to:
- *
- * test(function() {assert_equals(1+1, 2)}, "Sum one and one")
- * test(function() {assert_equals(1+0, 1)}, "Sum one and zero")
- *
- * Note that the first item in each parameter list corresponds to the name of
- * the test.
- *
- * == Callback API ==
- *
- * The framework provides callbacks corresponding to 3 events:
- *
- * start - happens when the first Test is created
- * result - happens when a test result is recieved
- * complete - happens when all results are recieved
- *
- * The page defining the tests may add callbacks for these events by calling
- * the following methods:
- *
- *   add_start_callback(callback) - callback called with no arguments
- *   add_result_callback(callback) - callback called with a test argument
- *   add_completion_callback(callback) - callback called with an array of tests
- *                                       and an status object
- *
- * tests have the following properties:
- *   status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and
- *           NOTRUN properties on the test object
- *   message: A message indicating the reason for failure. In the future this
- *            will always be a string
- *
- *  The status object gives the overall status of the harness. It has the
- *  following properties:
- *    status: Can be compared to the OK, ERROR and TIMEOUT properties
- *    message: An error message set when the status is ERROR
- *
- * == External API ==
- *
- * In order to collect the results of multiple pages containing tests, the test
- * harness will, when loaded in a nested browsing context, attempt to call
- * certain functions in the top level browsing context:
- *
- * start - top.start_callback
- * result - top.result_callback
- * complete - top.completion_callback
- *
- * These are given the same arguments as the corresponding internal callbacks
- * described above.
- *
- * == List of assertions ==
- *
- * assert_true(actual, description)
- *   asserts that /actual/ is strictly true
- *
- * assert_false(actual, description)
- *   asserts that /actual/ is strictly false
- *
- * assert_equals(actual, expected, description)
- *   asserts that /actual/ is strictly equal to /expected/
- *
- * assert_array_equals(actual, expected, description)
- *   asserts that /actual/ and /expected/ have the same length and the value of
- *   each indexed property in /actual/ is the strictly equal to the corresponding
- *   property value in /expected/
- *
- * assert_regexp_match(actual, expected, description)
- *   asserts that /actual/ matches the regexp /expected/
- *
- * assert_exists(object, property_name, description)
- *   asserts that object has an own property property_name
- *
- * assert_not_exists(object, property_name, description)
- *   assert that object does not have own property property_name
- *
- * assert_inherits(object, property_name, description)
- *   assert that object does not have an own property named property_name
- *   but that property_name is present in the prototype chain for object
- *
- * assert_readonly(object, property_name, description)
- *   assert that property property_name on object is readonly
- *
- * assert_throws(code_or_object, func, description)
- *   code_or_object - either a DOM error code as a string e.g. "NAMESPACE_ERR"
- *                    or an object that should be thrown
- *   func - a function that should throw
- *
- *   assert that func throws either a DOMException with error code
- *   code_or_object (if code_or_object is a string) or that it throws an object
- *   code_or_object
- *
- * assert_unreached(description)
- *   asserts if called. Used to ensure that some codepath is *not* taken e.g.
- *   an event does not fire.
- *
- */
-
-(function ()
-{
-    var debug = false;
-    // default timeout is 5 seconds, test can override if needed
-    var default_timeout = 5000;
-    var default_test_timeout = 2000;
-
-    /*
-    * API functions
-    */
-
-    var name_counter = 0;
-    function next_default_name()
-    {
-        //Don't use document.title to work around an Opera bug in XHTML documents
-        var prefix = document.getElementsByTagName("title").length > 0 ?
-                         document.getElementsByTagName("title")[0].firstChild.data :
-                         "Untitled";
-        var suffix = name_counter > 0 ? " " + name_counter : "";
-        name_counter++;
-        return prefix + suffix;
-    }
-
-    function test(func, name, properties)
-    {
-        var test_name = name ? name : next_default_name();
-        properties = properties ? properties : {};
-        var test_obj = new Test(test_name, properties);
-        test_obj.step(func);
-        if (test_obj.status === test_obj.NOTRUN) {
-            test_obj.done();
-        }
-    }
-
-    function async_test(name, properties)
-    {
-        var test_name = name ? name : next_default_name();
-        properties = properties ? properties : {};
-        var test_obj = new Test(test_name, properties);
-        return test_obj;
-    }
-
-    function setup(func_or_properties, properties_or_func)
-    {
-        var func = null;
-        var properties = {};
-        if (arguments.length === 2) {
-            func = func_or_properties;
-            properties = properties_or_func;
-        } else if (func_or_properties instanceof Function){
-            func = func_or_properties;
-        } else {
-            properties = func_or_properties;
-
-        }
-        tests.setup(func, properties);
-    }
-
-    function done() {
-        tests.end_wait();
-    }
-
-    function generate_tests(func, args) {
-        forEach(args, function(x)
-                {
-                    var name = x[0];
-                    test(function()
-                         {
-                             func.apply(this, x.slice(1));
-                         }, name);
-                });
-    }
-
-    function on_event(object, event, callback)
-    {
-      object.addEventListener(event, callback, false);
-    }
-
-    expose(test, 'test');
-    expose(async_test, 'async_test');
-    expose(generate_tests, 'generate_tests');
-    expose(setup, 'setup');
-    expose(done, 'done');
-    expose(on_event, 'on_event');
-
-    /*
-    * Convert a value to a nice, human-readable string
-    */
-    function format_value(val)
-    {
-        if (val === null)
-        {
-            // typeof is object, so the switch isn't useful
-            return "null";
-        }
-        // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
-        // special-case.
-        if (val === -0 && 1/val === -Infinity)
-        {
-            return "-0";
-        }
-        // Special-case Node objects, since those come up a lot in my tests.  I
-        // ignore namespaces.
-        if (typeof val == "object" && val instanceof Node)
-        {
-            switch (val.nodeType)
-            {
-            case Node.ELEMENT_NODE:
-                var ret = "Element node <";
-                if (val.namespaceURI == "http://www.w3.org/1999/xhtml" || val.namespaceURI === null)
-                {
-                    ret += val.tagName.toLowerCase();
-                }
-                else
-                {
-                    ret += val.tagName;
-                }
-                for (var i = 0; i < val.attributes.length; i++)
-                {
-                    ret += " " + val.attributes[i].name + "=" + format_value(val.attributes[i].value);
-                }
-                ret += ">";
-                return ret;
-            case Node.TEXT_NODE:
-                return "Text node with data " + format_value(val.data) + " and parent " + format_value(val.parentNode);
-            case Node.PROCESSING_INSTRUCTION_NODE:
-                return "ProcessingInstruction node with target " + format_value(val.target) + " and data " + format_value(val.data);
-            case Node.COMMENT_NODE:
-                return "Comment node with data " + format_value(val.data);
-            case Node.DOCUMENT_NODE:
-                return "Document node";
-            case Node.DOCUMENT_TYPE_NODE:
-                return "DocumentType node";
-            case Node.DOCUMENT_FRAGMENT_NODE:
-                return "DocumentFragment node";
-            default:
-                return "Node object of unknown type";
-            }
-        }
-        switch (typeof val)
-        {
-        case "string":
-            for (var i = 0; i < 32; i++)
-            {
-                var replace = "\\";
-                switch (i) {
-                case 0: replace += "0"; break;
-                case 1: replace += "x01"; break;
-                case 2: replace += "x02"; break;
-                case 3: replace += "x03"; break;
-                case 4: replace += "x04"; break;
-                case 5: replace += "x05"; break;
-                case 6: replace += "x06"; break;
-                case 7: replace += "x07"; break;
-                case 8: replace += "b"; break;
-                case 9: replace += "t"; break;
-                case 10: replace += "n"; break;
-                case 11: replace += "v"; break;
-                case 12: replace += "f"; break;
-                case 13: replace += "r"; break;
-                case 14: replace += "x0e"; break;
-                case 15: replace += "x0f"; break;
-                case 16: replace += "x10"; break;
-                case 17: replace += "x11"; break;
-                case 18: replace += "x12"; break;
-                case 19: replace += "x13"; break;
-                case 20: replace += "x14"; break;
-                case 21: replace += "x15"; break;
-                case 22: replace += "x16"; break;
-                case 23: replace += "x17"; break;
-                case 24: replace += "x18"; break;
-                case 25: replace += "x19"; break;
-                case 26: replace += "x1a"; break;
-                case 27: replace += "x1b"; break;
-                case 28: replace += "x1c"; break;
-                case 29: replace += "x1d"; break;
-                case 30: replace += "x1e"; break;
-                case 31: replace += "x1f"; break;
-                }
-                val = val.replace(String.fromCharCode(i), replace);
-            }
-            return '"' + val.replace('"', '\\"') + '"';
-        case "boolean":
-        case "undefined":
-        case "number":
-            return String(val);
-        default:
-            return typeof val + ' "' + val + '"';
-        }
-    }
-
-    /*
-    * Assertions
-    */
-
-    function assert_true(actual, description)
-    {
-        var message = make_message("assert_true", description,
-                                   "expected true got ${actual}", {actual:String(actual)});
-        assert(actual === true, message);
-    };
-    expose(assert_true, "assert_true");
-
-    function assert_false(actual, description)
-    {
-        var message = make_message("assert_false", description,
-                                   "expected false got ${actual}", {actual:String(actual)});
-        assert(actual === false, message);
-    };
-    expose(assert_false, "assert_false");
-
-    function assert_equals(actual, expected, description)
-    {
-         /*
-          * Test if two primitives are equal or two objects
-          * are the same object
-          */
-         var message = make_message("assert_equals", description,
-                                    "expected ${expected} but got ${actual}",
-                                    {expected:format_value(expected), actual:format_value(actual)});
-         if (expected !== expected)
-         {
-             //NaN case
-             assert(actual !== actual, message);
-         }
-         else
-         {
-             //typical case
-             assert(actual === expected, message);
-         }
-    };
-    expose(assert_equals, "assert_equals");
-
-    function assert_object_equals(actual, expected, description)
-    {
-         //This needs to be improved a great deal
-         function check_equal(expected, actual, stack)
-         {
-             stack.push(actual);
-
-             for (p in actual)
-             {
-                 var message = make_message(
-                     "assert_object_equals", description,
-                     "unexpected property ${p}", {p:p});
-
-                 assert(expected.hasOwnProperty(p), message);
-
-                 if (typeof actual[p] === "object" && actual[p] !== null)
-                 {
-                     if (stack.indexOf(actual[p]) === -1)
-                     {
-                         check_equal(actual[p], expected[p], stack);
-                     }
-                 }
-                 else
-                 {
-                     message = make_message(
-                         "assert_object_equals", description,
-                         "property ${p} expected ${expected} got ${actual}",
-                         {p:p, expected:expected, actual:actual});
-
-                     assert(actual[p] === expected[p], message);
-                 }
-             }
-             for (p in expected)
-             {
-                 var message = make_message(
-                     "assert_object_equals", description,
-                     "expected property ${p} missing", {p:p});
-
-                 assert(actual.hasOwnProperty(p), message);
-             }
-             stack.pop();
-         }
-         check_equal(actual, expected, []);
-    };
-    expose(assert_object_equals, "assert_object_equals");
-
-    function assert_array_equals(actual, expected, description)
-    {
-        var message = make_message(
-            "assert_array_equals", description,
-            "lengths differ, expected ${expected} got ${actual}",
-            {expected:expected.length, actual:actual.length});
-
-        assert(actual.length === expected.length, message);
-
-        for (var i=0; i < actual.length; i++)
-        {
-            message = make_message(
-                "assert_array_equals", description,
-                "property ${i}, property expected to be $expected but was $actual",
-                {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
-                 actual:actual.hasOwnProperty(i) ? "present" : "missing"});
-            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), message);
-            message = make_message(
-                          "assert_array_equals", description,
-                          "property ${i}, expected ${expected} but got ${actual}",
-                          {i:i, expected:expected[i], actual:actual[i]});
-            assert(expected[i] === actual[i], message);
-        }
-    }
-    expose(assert_array_equals, "assert_array_equals");
-
-    function assert_regexp_match(actual, expected, description) {
-        /*
-         * Test if a string (actual) matches a regexp (expected)
-         */
-        var message = make_message("assert_regexp_match", description,
-                                   "expected ${expected} but got ${actual}",
-                                   {expected:String(expected), actual:String(actual)});
-        assert(expected.test(actual), message);
-    }
-    expose(assert_regexp_match, "assert_regexp_match");
-
-    function assert_exists(object, property_name, description)
-    {
-         var message = make_message(
-             "assert_exists", description,
-             "expected property ${p} missing", {p:property_name});
-
-         assert(object.hasOwnProperty(property_name), message);
-    };
-    expose(assert_exists, "assert_exists");
-
-    function assert_not_exists(object, property_name, description)
-    {
-         var message = make_message(
-             "assert_not_exists", description,
-             "unexpected property ${p} found", {p:property_name});
-
-         assert(!object.hasOwnProperty(property_name), message);
-    };
-    expose(assert_not_exists, "assert_not_exists");
-
-  function assert_inherits(object, property_name, description)
-    {
-         var message = make_message(
-             "assert_inherits", description,
-             "property ${p} found on object expected in prototype chain",
-           {p:property_name});
-         assert(!object.hasOwnProperty(property_name), message);
-
-         message = make_message(
-             "assert_inherits", description,
-             "property ${p} not found in prototype chain",
-           {p:property_name});
-         assert(property_name in object, message);
-    };
-    expose(assert_inherits, "assert_inherits");
-
-    function assert_readonly(object, property_name, description)
-    {
-         var initial_value = object[property_name];
-         try {
-             var message = make_message(
-                 "assert_readonly", description,
-                 "deleting property ${p} succeeded", {p:property_name});
-             assert(delete object[property_name] === false, message);
-             assert(object[property_name] === initial_value, message);
-             //Note that this can have side effects in the case where
-             //the property has PutForwards
-             object[property_name] = initial_value + "a"; //XXX use some other value here?
-             message = make_message("assert_readonly", description,
-                                    "changing property ${p} succeeded",
-                                    {p:property_name});
-             assert(object[property_name] === initial_value, message);
-         }
-         finally
-         {
-             object[property_name] = initial_value;
-         }
-    };
-    expose(assert_readonly, "assert_readonly");
-
-    function assert_throws(code_or_object, func, description)
-    {
-        try
-        {
-            func.call(this);
-            assert(false, make_message("assert_throws", description,
-                                      "${func} did not throw", {func:String(func)}));
-        }
-        catch(e)
-        {
-            if (e instanceof AssertionError) {
-                throw(e);
-            }
-            if (typeof code_or_object === "string")
-            {
-                assert(e[code_or_object] !== undefined &&
-                       e.code === e[code_or_object],
-                       make_message("assert_throws", description,
-                           [["{text}", "${func} threw with"] ,
-                            function()
-                            {
-                                var actual_name;
-                                for (var p in DOMException)
-                                {
-                                    if (e.code === DOMException[p])
-                                    {
-                                        actual_name = p;
-                                        break;
-                                    }
-                                }
-                                if (actual_name)
-                                {
-                                    return ["{text}", " code " + actual_name + " (${actual_number})"];
-                                }
-                                else
-                                {
-                                    return ["{text}", " error number ${actual_number}"];
-                                }
-                            },
-                            ["{text}"," expected ${expected}"],
-                            function()
-                            {
-                                return e[code_or_object] ?
-                                    ["{text}", " (${expected_number})"] : null;
-                            }
-                           ],
-                                    {func:String(func), actual_number:e.code,
-                                     expected:String(code_or_object),
-                                     expected_number:e[code_or_object]}));
-                assert(e instanceof DOMException,
-                      make_message("assert_throws", description,
-                                   "thrown exception ${exception} was not a DOMException",
-                                  {exception:String(e)}));
-            }
-            else
-            {
-                assert(e instanceof Object && "name" in e && e.name == code_or_object.name,
-                       make_message("assert_throws", description,
-                           "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
-                                    {func:String(func), actual:String(e), actual_name:e.name,
-                                     expected:String(code_or_object),
-                                     expected_name:code_or_object.name}));
-            }
-        }
-    }
-    expose(assert_throws, "assert_throws");
-
-    function assert_unreached(description) {
-         var message = make_message("assert_unreached", description,
-                                    "Reached unreachable code");
-
-         assert(false, message);
-    }
-    expose(assert_unreached, "assert_unreached");
-
-    function Test(name, properties)
-    {
-        this.name = name;
-        this.status = this.NOTRUN;
-        this.timeout_id = null;
-        this.is_done = false;
-
-        this.timeout_length = properties.timeout ? properties.timeout : default_test_timeout;
-
-        this.message = null;
-
-        var this_obj = this;
-        this.steps = [];
-
-        tests.push(this);
-    }
-
-    Test.prototype = {
-        PASS:0,
-        FAIL:1,
-        TIMEOUT:2,
-        NOTRUN:3
-    };
-
-
-    Test.prototype.step = function(func, this_obj)
-    {
-        //In case the test has already failed
-        if (this.status !== this.NOTRUN)
-        {
-          return;
-        }
-
-        tests.started = true;
-
-        if (this.timeout_id === null) {
-            this.set_timeout();
-        }
-
-        this.steps.push(func);
-
-        try
-        {
-            func.apply(this_obj);
-        }
-        catch(e)
-        {
-            //This can happen if something called synchronously invoked another
-            //step
-            if (this.status !== this.NOTRUN)
-            {
-                return;
-            }
-            this.status = this.FAIL;
-            this.message = e.message;
-            this.done();
-            if (debug && e.constructor !== AssertionError) {
-                throw e;
-            }
-        }
-    };
-
-    Test.prototype.set_timeout = function()
-    {
-        var this_obj = this;
-        this.timeout_id = setTimeout(function()
-                                     {
-                                         this_obj.timeout();
-                                     }, this.timeout_length);
-    };
-
-    Test.prototype.timeout = function()
-    {
-        this.status = this.TIMEOUT;
-        this.timeout_id = null;
-        this.message = "Test timed out";
-        this.done();
-    };
-
-    Test.prototype.done = function()
-    {
-        if (this.is_done) {
-            return;
-        }
-        clearTimeout(this.timeout_id);
-        if (this.status === this.NOTRUN)
-        {
-            this.status = this.PASS;
-        }
-        this.is_done = true;
-        tests.result(this);
-    };
-
-
-   /*
-    * Harness
-    */
-
-    function TestsStatus()
-    {
-        this.status = null;
-        this.message = null;
-    }
-    TestsStatus.prototype = {
-        OK:0,
-        ERROR:1,
-        TIMEOUT:2
-    };
-
-    function Tests()
-    {
-        this.tests = [];
-        this.num_pending = 0;
-
-        this.phases = {
-            INITIAL:0,
-            SETUP:1,
-            HAVE_TESTS:2,
-            HAVE_RESULTS:3,
-            COMPLETE:4
-        };
-        this.phase = this.phases.INITIAL;
-
-        //All tests can't be done until the load event fires
-        this.all_loaded = false;
-        this.wait_for_finish = false;
-        this.processing_callbacks = false;
-
-        this.timeout_length = default_timeout;
-        this.timeout_id = null;
-        this.set_timeout();
-
-        this.start_callbacks = [];
-        this.test_done_callbacks = [];
-        this.all_done_callbacks = [];
-
-        this.status = new TestsStatus();
-
-        var this_obj = this;
-
-        on_event(window, "load",
-                 function()
-                 {
-                     this_obj.all_loaded = true;
-                     if (this_obj.all_done())
-                     {
-                         this_obj.complete();
-                     }
-                 });
-    }
-
-    Tests.prototype.setup = function(func, properties)
-    {
-        if (this.phase >= this.phases.HAVE_RESULTS) {
-            return;
-        }
-        if (this.phase < this.phases.SETUP) {
-            this.phase = this.phases.SETUP;
-        }
-
-        if (properties.timeout)
-        {
-            this.timeout_length = properties.timeout;
-            this.set_timeout();
-        }
-        if (properties.explicit_done)
-        {
-            this.wait_for_finish = true;
-        }
-
-        if (func)
-        {
-            try
-            {
-                func();
-            } catch(e)
-            {
-                this.status.status = this.status.ERROR;
-                this.status.message = e;
-            };
-        }
-    };
-
-    Tests.prototype.set_timeout = function()
-    {
-        var this_obj = this;
-        clearTimeout(this.timeout_id);
-        this.timeout_id = setTimeout(function() {
-                                         this_obj.timeout();
-                                     }, this.timeout_length);
-    };
-
-    Tests.prototype.timeout = function() {
-        this.status.status = this.status.TIMEOUT;
-        this.complete();
-    };
-
-    Tests.prototype.end_wait = function()
-    {
-        this.wait_for_finish = false;
-        if (this.all_done()) {
-            this.complete();
-        }
-    };
-
-    Tests.prototype.push = function(test)
-    {
-        if (this.phase < this.phases.HAVE_TESTS) {
-            this.notify_start();
-        }
-        this.num_pending++;
-        this.tests.push(test);
-    };
-
-    Tests.prototype.all_done = function() {
-        return (this.all_loaded && this.num_pending === 0 &&
-                !this.wait_for_finish && !this.processing_callbacks);
-    };
-
-    Tests.prototype.start = function() {
-        this.phase = this.phases.HAVE_TESTS;
-        this.notify_start();
-    };
-
-    Tests.prototype.notify_start = function() {
-        var this_obj = this;
-        forEach (this.start_callbacks,
-                 function(callback)
-                 {
-                     callback(this_obj);
-                 });
-        if(top !== window && top.start_callback)
-        {
-            try
-            {
-                top.start_callback.call(this_obj);
-            }
-            catch(e)
-            {
-                if (debug)
-                {
-                    throw(e);
-                }
-            }
-        }
-    };
-
-    Tests.prototype.result = function(test)
-    {
-        if (this.phase > this.phases.HAVE_RESULTS)
-        {
-            return;
-        }
-        this.phase = this.phases.HAVE_RESULTS;
-        this.num_pending--;
-        this.notify_result(test);
-    };
-
-    Tests.prototype.notify_result = function(test) {
-        var this_obj = this;
-        this.processing_callbacks = true;
-        forEach(this.test_done_callbacks,
-                function(callback)
-                {
-                    callback(test, this_obj);
-                });
-
-        if(top !== window && top.result_callback)
-        {
-            try
-            {
-                top.result_callback.call(this_obj, test);
-            }
-            catch(e)
-            {
-                if(debug) {
-                    throw e;
-                }
-            }
-        }
-        this.processing_callbacks = false;
-        if (this.all_done())
-        {
-            this.complete();
-        }
-
-    };
-
-    Tests.prototype.complete = function() {
-        if (this.phase === this.phases.COMPLETE) {
-            return;
-        }
-        this.phase = this.phases.COMPLETE;
-        this.notify_complete();
-    };
-
-    Tests.prototype.notify_complete = function()
-    {
-        clearTimeout(this.timeout_id);
-        var this_obj = this;
-        if (this.status.status === null)
-        {
-            this.status.status = this.status.OK;
-        }
-
-        forEach (this.all_done_callbacks,
-                 function(callback)
-                 {
-                     callback(this_obj.tests, this_obj.status);
-                 });
-        if(top !== window && top.completion_callback)
-        {
-            try
-            {
-                top.completion_callback(this_obj.tests, this.status);
-            }
-            catch(e)
-            {
-                if (debug)
-                {
-                    throw e;
-                }
-            }
-
-        }
-    };
-
-    var tests = new Tests();
-    add_completion_callback(output_results);
-
-
-    function add_start_callback(callback) {
-        tests.start_callbacks.push(callback);
-    }
-
-    function add_result_callback(callback)
-    {
-        tests.test_done_callbacks.push(callback);
-    }
-
-    function add_completion_callback(callback)
-    {
-       tests.all_done_callbacks.push(callback);
-    }
-
-    expose(add_start_callback, 'add_start_callback');
-    expose(add_result_callback, 'add_result_callback');
-    expose(add_completion_callback, 'add_completion_callback');
-
-    /*
-     * Output listener
-    */
-
-    (function show_status() {
-        var done_count = 0;
-         function on_done(test, tests) {
-             var log = document.getElementById("log");
-             done_count++;
-             if (log)
-             {
-                 if (log.lastChild) {
-                     log.removeChild(log.lastChild);
-                 }
-                 var nodes = render([["{text}", "Running, ${done} complete"],
-                                 function() {
-                                     if (tests.all_done) {
-                                         return ["{text}", " ${pending} remain"];
-                                     } else {
-                                         return null;
-                                     }
-                                 }
-                                    ], {done:done_count,
-                                        pending:tests.num_pending});
-                 forEach(nodes, function(node) {
-                             log.appendChild(node);
-                         });
-                 log.normalize();
-             }
-         }
-         if (document.getElementById("log"))
-         {
-             add_result_callback(on_done);
-         }
-     })();
-
-    function output_results(tests, harness_status)
-    {
-        var log = document.getElementById("log");
-        if (!log)
-        {
-            return;
-        }
-        while (log.lastChild)
-        {
-            log.removeChild(log.lastChild);
-        }
-        var prefix = null;
-        var scripts = document.getElementsByTagName("script");
-        for (var i=0; i<scripts.length; i++)
-        {
-            var src = scripts[i].src;
-            if (src.slice(src.length - "testharness.js".length) === "testharness.js")
-            {
-                prefix = src.slice(0, src.length - "testharness.js".length);
-                break;
-            }
-        }
-        if (prefix != null) {
-            var stylesheet = document.createElement("link");
-            stylesheet.setAttribute("rel", "stylesheet");
-            stylesheet.setAttribute("href", prefix + "testharness.css");
-            var heads = document.getElementsByTagName("head");
-            if (heads) {
-                heads[0].appendChild(stylesheet);
-            }
-        }
-
-        var status_text = {};
-        status_text[Test.prototype.PASS] = "Pass";
-        status_text[Test.prototype.FAIL] = "Fail";
-        status_text[Test.prototype.TIMEOUT] = "Timeout";
-        status_text[Test.prototype.NOTRUN] = "Not Run";
-
-        var status_number = {};
-        forEach(tests, function(test) {
-                    var status = status_text[test.status];
-                    if (status_number.hasOwnProperty(status))
-                    {
-                        status_number[status] += 1;
-                    } else {
-                        status_number[status] = 1;
-                    }
-                });
-
-        function status_class(status)
-        {
-            return status.replace(/\s/g, '').toLowerCase();
-        }
-
-        var summary_template = ["section", {"id":"summary"},
-                                ["h2", {}, "Summary"],
-                                ["p", {}, "Found ${num_tests} tests"],
-                                function(vars) {
-                                    var rv = [["div", {}]];
-                                    var i=0;
-                                    while (status_text.hasOwnProperty(i)) {
-                                        if (status_number.hasOwnProperty(status_text[i])) {
-                                            var status = status_text[i];
-                                            rv[0].push(["div", {"class":status_class(status)},
-                                                        ["input", {type:"checkbox", checked:"checked"}],
-                                                       status_number[status] + " " + status]);
-                                        }
-                                        i++;
-                                    }
-                                    return rv;
-                                }];
-
-        log.appendChild(render(summary_template, {num_tests:tests.length}));
-
-        forEach(document.querySelectorAll("section#summary input"),
-                function(element)
-                {
-                    on_event(element, "click",
-                             function(e)
-                             {
-                                 if (document.getElementById("results") === null)
-                                 {
-                                     e.preventDefault();
-                                     return;
-                                 }
-                                 var result_class = element.parentNode.getAttribute("class");
-                                 var checked = element.checked;
-                                 forEach(document.querySelectorAll("table#results > tbody > tr > td."+result_class),
-                                         function(cell)
-                                         {
-                                             cell.parentNode.style.display = checked ? "" : "None";
-                                         });
-                             });
-                });
-
-        var template = ["section", {},
-                        ["h2", {}, "Details"],
-                        ["table", {"id":"results"},
-                        ["tr", {},
-                         ["th", {}, "Result"],
-                         ["th", {}, "Test Name"],
-                         ["th", {}, "Message"]
-                        ],
-                        ["tbody", {},
-                        function(vars) {
-                            var rv = map(vars.tests, function(test) {
-                                             var status = status_text[test.status];
-                                             return  ["tr", {},
-                                                      ["td", {"class":status_class(status)}, status],
-                                                      ["td", {}, test.name],
-                                                      ["td", {}, test.message ? test.message : " "]
-                                                     ];
-                                         });
-                            return rv;
-                        }]
-                       ]];
-
-        log.appendChild(render(template, {tests:tests}));
-
-    }
-
-
-    /*
-     * Template code
-     *
-     * A template is just a javascript structure. An element is represented as:
-     *
-     * [tag_name, {attr_name:attr_value}, child1, child2]
-     *
-     * the children can either be strings (which act like text nodes), other templates or
-     * functions (see below)
-     *
-     * A text node is represented as
-     *
-     * ["{text}", value]
-     *
-     * String values have a simple substitution syntax; ${foo} represents a variable foo.
-     *
-     * It is possible to embed logic in templates by using a function in a place where a
-     * node would usually go. The function must either return part of a template or null.
-     *
-     * In cases where a set of nodes are required as output rather than a single node
-     * with children it is possible to just use a list
-     * [node1, node2, node3]
-     *
-     * Usage:
-     *
-     * render(template, substitutions) - take a template and an object mapping
-     * variable names to parameters and return either a DOM node or a list of DOM nodes
-     *
-     * substitute(template, substitutions) - take a template and variable mapping object,
-     * make the variable substitutions and return the substituted template
-     *
-     */
-
-    function is_single_node(template)
-    {
-        return typeof template[0] === "string";
-    }
-
-    function substitute(template, substitutions)
-    {
-        if (typeof template === "function") {
-            var replacement = template(substitutions);
-            if (replacement)
-            {
-                var rv = substitute(replacement, substitutions);
-                return rv;
-            }
-            else
-            {
-                return null;
-            }
-        }
-        else if (is_single_node(template))
-        {
-            return substitute_single(template, substitutions);
-        }
-        else
-        {
-            return filter(map(template, function(x) {
-                                  return substitute(x, substitutions);
-                              }), function(x) {return x !== null;});
-        }
-    }
-    expose(substitute, "template.substitute");
-
-    function substitute_single(template, substitutions)
-    {
-        var substitution_re = /\${([^ }]*)}/g;
-
-        function do_substitution(input) {
-            var components = input.split(substitution_re);
-            var rv = [];
-            for (var i=0; i<components.length; i+=2)
-            {
-                rv.push(components[i]);
-                if (components[i+1])
-                {
-                    rv.push(String(substitutions[components[i+1]]));
-                }
-            }
-            return rv;
-        }
-
-        var rv = [];
-        rv.push(do_substitution(String(template[0])).join(""));
-
-        if (template[0] === "{text}") {
-            substitute_children(template.slice(1), rv);
-        } else {
-            substitute_attrs(template[1], rv);
-            substitute_children(template.slice(2), rv);
-        }
-
-        function substitute_attrs(attrs, rv)
-        {
-            rv[1] = {};
-            for (var name in template[1])
-            {
-                if (attrs.hasOwnProperty(name))
-                {
-                    var new_name = do_substitution(name).join("");
-                    var new_value = do_substitution(attrs[name]).join("");
-                    rv[1][new_name] = new_value;
-                };
-            }
-        }
-
-        function substitute_children(children, rv)
-        {
-            for (var i=0; i<children.length; i++)
-            {
-                if (children[i] instanceof Object) {
-                    var replacement = substitute(children[i], substitutions);
-                    if (replacement !== null)
-                    {
-                        if (is_single_node(replacement))
-                        {
-                            rv.push(replacement);
-                        }
-                        else
-                        {
-                            extend(rv, replacement);
-                        }
-                    }
-                }
-                else
-                {
-                    extend(rv, do_substitution(String(children[i])));
-                }
-            }
-            return rv;
-        }
-
-        return rv;
-    }
-
-    function make_dom_single(template)
-    {
-        if (template[0] === "{text}")
-        {
-            var element = document.createTextNode("");
-            for (var i=1; i<template.length; i++)
-            {
-                element.data += template[i];
-            }
-        }
-        else
-        {
-            var element = document.createElement(template[0]);
-            for (var name in template[1]) {
-                if (template[1].hasOwnProperty(name))
-                {
-                    element.setAttribute(name, template[1][name]);
-                }
-            }
-            for (var i=2; i<template.length; i++)
-            {
-                if (template[i] instanceof Object)
-                {
-                    var sub_element = make_dom(template[i]);
-                    element.appendChild(sub_element);
-                }
-                else
-                {
-                    var text_node = document.createTextNode(template[i]);
-                    element.appendChild(text_node);
-                }
-            }
-        }
-
-        return element;
-    }
-
-
-
-    function make_dom(template, substitutions)
-    {
-        if (is_single_node(template))
-        {
-            return make_dom_single(template);
-        }
-        else
-        {
-            return map(template, function(x) {
-                           return make_dom_single(x);
-                       });
-        }
-    }
-
-    function render(template, substitutions)
-    {
-        return make_dom(substitute(template, substitutions));
-    }
-    expose(render, "template.render");
-
-    /*
-     * Utility funcions
-     */
-    function assert(expected_true, message)
-    {
-        if (expected_true !== true)
-        {
-            throw new AssertionError(message);
-        }
-    }
-
-    function AssertionError(message)
-    {
-        this.message = message;
-    }
-
-    function make_message(function_name, description, error, substitutions)
-    {
-        var message = substitute([["span", {"class":"assert"}, "${function_name}:"],
-                                  function()
-                                  {
-                                      if (description) {
-                                          return ["span", {"class":"description"}, description];
-                                      } else {
-                                          return null;
-                                      }
-                                  },
-                                  ["div", {"class":"error"}, error]
-                                 ], merge({function_name:function_name},
-                                         substitutions));
-
-        return message;
-    }
-
-    function filter(array, callable, thisObj) {
-        var rv = [];
-        for (var i=0; i<array.length; i++)
-        {
-            if (array.hasOwnProperty(i))
-            {
-                var pass = callable.call(thisObj, array[i], i, array);
-                if (pass) {
-                    rv.push(array[i]);
-                }
-            }
-        }
-        return rv;
-    }
-
-    function map(array, callable, thisObj)
-    {
-        var rv = [];
-        rv.length = array.length;
-        for (var i=0; i<array.length; i++)
-        {
-            if (array.hasOwnProperty(i))
-            {
-                rv[i] = callable.call(thisObj, array[i], i, array);
-            }
-        }
-        return rv;
-    }
-
-    function extend(array, items)
-    {
-        Array.prototype.push.apply(array, items);
-    }
-
-    function forEach (array, callback, thisObj)
-    {
-        for (var i=0; i<array.length; i++)
-        {
-            if (array.hasOwnProperty(i))
-            {
-                callback.call(thisObj, array[i], i, array);
-            }
-        }
-    }
-
-    function merge(a,b)
-    {
-        var rv = {};
-        var p;
-        for (p in a)
-        {
-            rv[p] = a[p];
-        }
-        for (p in b) {
-            rv[p] = b[p];
-        }
-        return rv;
-    }
-
-    function expose(object, name)
-    {
-        var components = name.split(".");
-        var target = window;
-        for (var i=0; i<components.length - 1; i++)
-        {
-            if (!(components[i] in target))
-            {
-                target[components[i]] = {};
-            }
-            target = target[components[i]];
-        }
-        target[components[components.length - 1]] = object;
-    }
-
-})();
--- a/test/touchevents/single-touch.html	Mon Jan 09 13:00:42 2012 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Touch Events: Single-touch tests</title>
-  <meta name="viewport" content="width=device-width">
-  <script src="../testharness.js"></script>
-  <script>
-    setup({explicit_done: true});
-
-    function run() {
-      var target0 = document.getElementById("target0");
-      var target1 = document.getElementById("target1");
-
-      var test_touchstart = async_test("touchstart event received");
-      var test_touchmove = async_test("touchmove event received");
-      var test_touchend = async_test("touchend event received");
-      var test_mousedown = async_test("Interaction with mouse events");
-
-      var touchstart_received = false;
-      var touchmove_received = false;
-      var touchend_received = false;
-      var invalid_touchmove_received = false;
-      var touchstart_identifier;
-
-      on_event(target0, "touchstart", function onTouchStart(ev) {
-        ev.preventDefault();
-
-        touchstart_received = true;
-        test_touchstart.step(function() {
-          assert_false(touchmove_received, "touchstart precedes touchmove");
-          assert_false(touchend_received, "touchstart precedes touchend");
-        });
-        test_touchstart.done();
-        test_mousedown.done(); // If we got here, then the mouse event test is not needed.
-
-        test(function() {
-          assert_true(ev.target instanceof Element, "target must be an Element.");
-          assert_equals(ev.relatedTarget, null, "relatedTarget must be null for touchstart event.");
-        }, "touchstart TouchEvent target attributes are correct.");
-
-        test(function() {
-          var attrs = ["altKey", "metaKey", "ctrlKey", "shiftKey"];
-          for (var i = 0; i < attrs.length; i++) {
-            assert_true(attrs[i] in ev);
-          }
-        }, "touchstart TouchEvent modifier key attributes are present.");
-
-        test(function() {
-          assert_equals(ev.touches.length, 1, "One touch point.");
-          assert_equals(ev.changedTouches.length, 1, "One changed touch point.");
-          assert_equals(ev.targetTouches.length, 1, "One target touch point.");
-        }, "touchstart TouchList lengths are correct.");
-
-        var t = ev.touches[0];
-        var ct = ev.changedTouches[0];
-        var tt = ev.targetTouches[0];
-
-        test(function() {
-          assert_true(ev instanceof TouchEvent, "Event is a TouchEvent.");
-          assert_true(ev.touches instanceof TouchList, "touches is a TouchList");
-          assert_true(t instanceof Touch, "touches[0] is a Touch");
-        }, "Interface names are correct.");
-
-        touchstart_identifier = t.identifier;
-        test(function() {
-          assert_equals(ct.identifier, touchstart_identifier, "changedTouches identifier matches.");
-          assert_equals(tt.identifier, touchstart_identifier, "targetTouches identifier matches.");
-        }, "Touch identifiers are consistent.");
-
-        test(function() {
-          assert_equals(ev.touches.identifiedTouch(touchstart_identifier), t, "touches.identifiedTouch is correct.");
-        }, "identifiedTouch");
-
-        test(function() {
-          assert_true(t.rotationAngle >= 0, "rotationAngle must be >= 0.");
-          assert_true(t.rotationAngle < 90, "rotationAngle must be > 90.");
-
-          assert_true(t.radiusX > 0, "radiusX is positive.");
-          assert_true(t.radiusY > 0, "radiusY is positive.");
-
-          assert_true(t.force >= 0, "force >= 0.");
-          assert_true(t.force <= 1, "force <= 1.");
-        }, "Touch attribute values are valid");
-
-        test(function() {
-          var attrs = ["screenX", "screenY", "clientX", "clientY",
-                       "pageX", "pageY"];
-          for (var i = 0; i < attrs.length; i++) {
-            assert_true(attrs[i] in t);
-          }
-        }, "Touch location attributes are present.");
-
-      });
-
-      on_event(target0, "touchmove", function onTouchMove(ev) {
-        ev.preventDefault();
-
-        if (touchmove_received)
-          return;
-        touchmove_received = true;
-
-        test_touchmove.step(function() {
-          assert_true(touchstart_received, "touchend follows touchstart");
-          assert_false(touchend_received, "touchmove precedes touchend");
-        });
-        test_touchmove.done();
-
-        test(function() {
-          assert_equals(ev.touches.length, 1, "One touch point.");
-          assert_equals(ev.changedTouches.length, 1, "One changed touch point.");
-          assert_equals(ev.targetTouches.length, 1, "One target touch point.");
-        }, "touchmove TouchList lengths are correct.");
-
-        test(function() {
-          assert_equals(ev.touches[0].identifier, touchstart_identifier, "Touch identifier matches.");
-          assert_equals(ev.changedTouches[0].identifier, touchstart_identifier, "Changed touch identifier matches.");
-          assert_equals(ev.targetTouches[0].identifier, touchstart_identifier, "Target touch identifier matches.");
-        }, "touchmove identifier matches touchstart identifier.");
-      });
-
-      on_event(target1, "touchmove", function onTouchMove(ev) {
-        invalid_touchmove_received = true;
-      });
-
-      on_event(window, "touchend", function onTouchStart(ev) {
-        touchend_received = true;
-
-        test_touchend.step(function() {
-          assert_equals(ev.target, target0, "touchend is dispatched to the original target");
-          assert_true(touchstart_received, "touchend follows touchstart");
-          assert_true(touchmove_received, "touchend follows touchmove");
-          assert_false(invalid_touchmove_received, "touchmove dispatched to correct target");
-        });
-        test_touchend.done();
-
-        test(function() {
-          var attrs = ["altKey", "metaKey", "ctrlKey", "shiftKey"];
-          for (var i = 0; i < attrs.length; i++) {
-            assert_true(attrs[i] in ev);
-          }
-        }, "touchend TouchEvent modifier key attributes are present.");
-
-        test(function() {
-          assert_equals(ev.touches.length, 0, "Zero touch points.");
-          assert_equals(ev.changedTouches.length, 1, "One changed touch point.");
-          assert_equals(ev.targetTouches.length, 0, "Zero target touch points.");
-        }, "touchend TouchList lengths are correct.");
-
-        var t = ev.changedTouches[0];
-
-        test(function() {
-          assert_equals(t.identifier, touchstart_identifier, "changedTouches identifier matches.");
-        }, "touchend identifier matches.");
-
-        test(function() {
-          assert_true(t.rotationAngle >= 0, "rotationAngle must be >= 0.");
-          assert_true(t.rotationAngle < 90, "rotationAngle must be > 90.");
-
-          assert_true(t.radiusX > 0, "radiusX is positive.");
-          assert_true(t.radiusY > 0, "radiusY is positive.");
-
-          assert_true(t.force >= 0, "force >= 0.");
-          assert_true(t.force <= 1, "force <= 1.");
-        }, "Touch attribute values are valid");
-
-        test(function() {
-          var attrs = ["screenX", "screenY", "clientX", "clientY",
-                       "pageX", "pageY"];
-          for (var i = 0; i < attrs.length; i++) {
-            assert_true(attrs[i] in t);
-          }
-        }, "Touch location attributes are present.");
-
-        done();
-      });
-
-
-      on_event(target0, "mousedown", function onMouseDown(ev) {
-        test_mousedown.step(function() {
-          assert_true(touchstart_received,
-            "The touchstart event must be dispatched before any mouse " +
-            "events. (If this fails, it might mean that the user agent does " +
-            "not implement W3C touch events at all.)"
-          );
-        });
-        test_mousedown.done();
-
-        if (!touchstart_received) {
-          // Abort the tests.  If touch events are not supported, then most of
-          // the other event handlers will never be called, and the test will
-          // time out with misleading results.
-          done();
-        }
-      });
-    }
-  </script>
-  <style>
-    div {
-      margin: 0em;
-      padding: 2em;
-    }
-    #target0 {
-      background: yellow;
-      border: 1px solid orange;
-    }
-    #target1 {
-      background: lightblue;
-      border: 1px solid blue;
-    }
-  </style>
-</head>
-<body onload="run()">
-  <h1>Touch Events: single-touch tests</h1>
-  <div id="target0">
-    Touch this box with one finger (or other pointing device)...
-  </div>
-  <div id="target1">
-    ...then drag to this box and lift your finger.
-  </div>
-  <div id="log"></div>
-</body>
-</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testharness.css	Tue Jan 17 09:47:15 2012 -0500
@@ -0,0 +1,58 @@
+html {
+    font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;
+}
+
+section#summary {
+    margin-bottom:1em;
+}
+
+table#results {
+    border-collapse:collapse;
+}
+
+table#results th {
+    padding:0;
+    padding-bottom:0.5em;
+    border-bottom:medium solid black;
+}
+
+table#results td {
+    padding:1em;
+    padding-bottom:0.5em;
+    border-bottom:thin solid black;
+}
+
+td.pass {
+    color:green;
+}
+
+td.fail {
+    color:red;
+}
+
+td.timeout {
+    color:red;
+}
+
+td.notrun {
+    color:blue;
+}
+
+.pass .fail .timeout .notrun {
+    font-variant:small-caps;
+}
+
+table#results span {
+    display:block;
+}
+
+table#results span.expected {
+    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
+    white-space:pre;
+}
+
+table#results span.actual {
+    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
+    white-space:pre;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testharness.js	Tue Jan 17 09:47:15 2012 -0500
@@ -0,0 +1,1500 @@
+/*
+Distributed under both the W3C Test Suite License [1] and the W3C
+3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
+policies and contribution forms [3].
+
+[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
+[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
+[3] http://www.w3.org/2004/10/27-testcases
+*/
+
+/*
+ * == Introduction ==
+ *
+ * This file provides a framework for writing testcases. It is intended to
+ * provide a convenient API for making common assertions, and to work both
+ * for testing synchronous and asynchronous DOM features in a way that
+ * promotes clear, robust, tests.
+ *
+ * == Basic Usage ==
+ *
+ * To use this file, import the script into the test document:
+ * <script src="http://w3c-test.org/resources/testharness.js"></script>
+ *
+ * Within each file one may define one or more tests. Each test is atomic
+ * in the sense that a single test has a single result (pass/fail/timeout).
+ * Within each test one may have a number of asserts. The test fails at the
+ * first failing assert, and the remainder of the test is (typically) not run.
+ *
+ * If the file containing the tests is a HTML file with an element of id "log"
+ * this will be populated with a table containing the test results after all
+ * the tests have run.
+ *
+ * == Synchronous Tests ==
+ *
+ * To create a synchronous test use the test() function:
+ *
+ * test(test_function, name, properties)
+ *
+ * test_function is a function that contains the code to test. For example a
+ * trivial passing test would be:
+ *
+ * test(function() {assert_true(true)}, "assert_true with true")
+ *
+ * The function passed in is run in the test() call.
+ *
+ * properties is an object that overrides default test properties. The recognised properties
+ * are:
+ *    timeout - the test timeout in ms
+ *
+ * e.g.
+ * test(test_function, "Sample test", {timeout:1000})
+ *
+ * would run test_function with a timeout of 1s.
+ *
+ * == Asynchronous Tests ==
+ *
+ * Testing asynchronous features is somewhat more complex since the result of
+ * a test may depend on one or more events or other callbacks. The API provided
+ * for testing these features is indended to be rather low-level but hopefully
+ * applicable to many situations.
+ *
+ * To create a test, one starts by getting a Test object using async_test:
+ *
+ * async_test(name, properties)
+ *
+ * e.g.
+ * var t = async_test("Simple async test")
+ *
+ * Assertions can be added to the test by calling the step method of the test
+ * object with a function containing the test assertions:
+ *
+ * t.step(function() {assert_true(true)});
+ *
+ * When all the steps are complete, the done() method must be called:
+ *
+ * t.done();
+ *
+ * The properties argument is identical to that for test().
+ *
+ * == Making assertions ==
+ *
+ * Functions for making assertions start assert_
+ * The best way to get a list is to look in this file for functions names
+ * matching that pattern. The general signature is
+ *
+ * assert_something(actual, expected, description)
+ *
+ * although not all assertions precisely match this pattern e.g. assert_true
+ * only takes actual and description as arguments.
+ *
+ * The description parameter is used to present more useful error messages when
+ * a test fails
+ *
+ * == Setup ==
+ *
+ * Sometimes tests require non-trivial setup that may fail. For this purpose
+ * there is a setup() function, that may be called with one or two arguments.
+ * The two argument version is:
+ *
+ * setup(func, properties)
+ *
+ * The one argument versions may omit either argument.
+ * func is a function to be run synchronously. setup() becomes a no-op once
+ * any tests have returned results. Properties are global properties of the test
+ * harness. Currently recognised properties are:
+ *
+ * timeout - The time in ms after which the harness should stop waiting for
+ *           tests to complete (this is different to the per-test timeout
+ *           because async tests do not start their timer until .step is called)
+ *
+ * explicit_done - Wait for an explicit call to done() before declaring all tests
+ *                 complete (see below)
+ *
+ * == Determining when all tests are complete ==
+ *
+ * By default the test harness will assume there are no more results to come
+ * when:
+ * 1) There are no Test objects that have been created but not completed
+ * 2) The load event on the document has fired
+ *
+ * This behaviour can be overridden by setting the explicit_done property to true
+ * in a call to setup(). If explicit_done is true, the test harness will not assume
+ * it is done until the global done() function is called. Once done() is called, the
+ * two conditions above apply like normal.
+ *
+ * == Generating tests ==
+ *
+ * NOTE: this functionality may be removed
+ *
+ * There are scenarios in which is is desirable to create a large number of
+ * (synchronous) tests that are internally similar but vary in the parameters
+ * used. To make this easier, the generate_tests function allows a single
+ * function to be called with each set of parameters in a list:
+ *
+ * generate_tests(test_function, parameter_lists)
+ *
+ * For example:
+ *
+ * generate_tests(assert_equals, [
+ *     ["Sum one and one", 1+1, 2],
+ *     ["Sum one and zero", 1+0, 1]
+ *     ])
+ *
+ * Is equivalent to:
+ *
+ * test(function() {assert_equals(1+1, 2)}, "Sum one and one")
+ * test(function() {assert_equals(1+0, 1)}, "Sum one and zero")
+ *
+ * Note that the first item in each parameter list corresponds to the name of
+ * the test.
+ *
+ * == Callback API ==
+ *
+ * The framework provides callbacks corresponding to 3 events:
+ *
+ * start - happens when the first Test is created
+ * result - happens when a test result is recieved
+ * complete - happens when all results are recieved
+ *
+ * The page defining the tests may add callbacks for these events by calling
+ * the following methods:
+ *
+ *   add_start_callback(callback) - callback called with no arguments
+ *   add_result_callback(callback) - callback called with a test argument
+ *   add_completion_callback(callback) - callback called with an array of tests
+ *                                       and an status object
+ *
+ * tests have the following properties:
+ *   status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and
+ *           NOTRUN properties on the test object
+ *   message: A message indicating the reason for failure. In the future this
+ *            will always be a string
+ *
+ *  The status object gives the overall status of the harness. It has the
+ *  following properties:
+ *    status: Can be compared to the OK, ERROR and TIMEOUT properties
+ *    message: An error message set when the status is ERROR
+ *
+ * == External API ==
+ *
+ * In order to collect the results of multiple pages containing tests, the test
+ * harness will, when loaded in a nested browsing context, attempt to call
+ * certain functions in the top level browsing context:
+ *
+ * start - top.start_callback
+ * result - top.result_callback
+ * complete - top.completion_callback
+ *
+ * These are given the same arguments as the corresponding internal callbacks
+ * described above.
+ *
+ * == List of assertions ==
+ *
+ * assert_true(actual, description)
+ *   asserts that /actual/ is strictly true
+ *
+ * assert_false(actual, description)
+ *   asserts that /actual/ is strictly false
+ *
+ * assert_equals(actual, expected, description)
+ *   asserts that /actual/ is strictly equal to /expected/
+ *
+ * assert_array_equals(actual, expected, description)
+ *   asserts that /actual/ and /expected/ have the same length and the value of
+ *   each indexed property in /actual/ is the strictly equal to the corresponding
+ *   property value in /expected/
+ *
+ * assert_regexp_match(actual, expected, description)
+ *   asserts that /actual/ matches the regexp /expected/
+ *
+ * assert_exists(object, property_name, description)
+ *   asserts that object has an own property property_name
+ *
+ * assert_not_exists(object, property_name, description)
+ *   assert that object does not have own property property_name
+ *
+ * assert_inherits(object, property_name, description)
+ *   assert that object does not have an own property named property_name
+ *   but that property_name is present in the prototype chain for object
+ *
+ * assert_readonly(object, property_name, description)
+ *   assert that property property_name on object is readonly
+ *
+ * assert_throws(code_or_object, func, description)
+ *   code_or_object - either a DOM error code as a string e.g. "NAMESPACE_ERR"
+ *                    or an object that should be thrown
+ *   func - a function that should throw
+ *
+ *   assert that func throws either a DOMException with error code
+ *   code_or_object (if code_or_object is a string) or that it throws an object
+ *   code_or_object
+ *
+ * assert_unreached(description)
+ *   asserts if called. Used to ensure that some codepath is *not* taken e.g.
+ *   an event does not fire.
+ *
+ */
+
+(function ()
+{
+    var debug = false;
+    // default timeout is 5 seconds, test can override if needed
+    var default_timeout = 5000;
+    var default_test_timeout = 2000;
+
+    /*
+    * API functions
+    */
+
+    var name_counter = 0;
+    function next_default_name()
+    {
+        //Don't use document.title to work around an Opera bug in XHTML documents
+        var prefix = document.getElementsByTagName("title").length > 0 ?
+                         document.getElementsByTagName("title")[0].firstChild.data :
+                         "Untitled";
+        var suffix = name_counter > 0 ? " " + name_counter : "";
+        name_counter++;
+        return prefix + suffix;
+    }
+
+    function test(func, name, properties)
+    {
+        var test_name = name ? name : next_default_name();
+        properties = properties ? properties : {};
+        var test_obj = new Test(test_name, properties);
+        test_obj.step(func);
+        if (test_obj.status === test_obj.NOTRUN) {
+            test_obj.done();
+        }
+    }
+
+    function async_test(name, properties)
+    {
+        var test_name = name ? name : next_default_name();
+        properties = properties ? properties : {};
+        var test_obj = new Test(test_name, properties);
+        return test_obj;
+    }
+
+    function setup(func_or_properties, properties_or_func)
+    {
+        var func = null;
+        var properties = {};
+        if (arguments.length === 2) {
+            func = func_or_properties;
+            properties = properties_or_func;
+        } else if (func_or_properties instanceof Function){
+            func = func_or_properties;
+        } else {
+            properties = func_or_properties;
+
+        }
+        tests.setup(func, properties);
+    }
+
+    function done() {
+        tests.end_wait();
+    }
+
+    function generate_tests(func, args) {
+        forEach(args, function(x)
+                {
+                    var name = x[0];
+                    test(function()
+                         {
+                             func.apply(this, x.slice(1));
+                         }, name);
+                });
+    }
+
+    function on_event(object, event, callback)
+    {
+      object.addEventListener(event, callback, false);
+    }
+
+    expose(test, 'test');
+    expose(async_test, 'async_test');
+    expose(generate_tests, 'generate_tests');
+    expose(setup, 'setup');
+    expose(done, 'done');
+    expose(on_event, 'on_event');
+
+    /*
+    * Convert a value to a nice, human-readable string
+    */
+    function format_value(val)
+    {
+        if (val === null)
+        {
+            // typeof is object, so the switch isn't useful
+            return "null";
+        }
+        // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
+        // special-case.
+        if (val === -0 && 1/val === -Infinity)
+        {
+            return "-0";
+        }
+        // Special-case Node objects, since those come up a lot in my tests.  I
+        // ignore namespaces.
+        if (typeof val == "object" && val instanceof Node)
+        {
+            switch (val.nodeType)
+            {
+            case Node.ELEMENT_NODE:
+                var ret = "Element node <";
+                if (val.namespaceURI == "http://www.w3.org/1999/xhtml" || val.namespaceURI === null)
+                {
+                    ret += val.tagName.toLowerCase();
+                }
+                else
+                {
+                    ret += val.tagName;
+                }
+                for (var i = 0; i < val.attributes.length; i++)
+                {
+                    ret += " " + val.attributes[i].name + "=" + format_value(val.attributes[i].value);
+                }
+                ret += ">";
+                return ret;
+            case Node.TEXT_NODE:
+                return "Text node with data " + format_value(val.data) + " and parent " + format_value(val.parentNode);
+            case Node.PROCESSING_INSTRUCTION_NODE:
+                return "ProcessingInstruction node with target " + format_value(val.target) + " and data " + format_value(val.data);
+            case Node.COMMENT_NODE:
+                return "Comment node with data " + format_value(val.data);
+            case Node.DOCUMENT_NODE:
+                return "Document node";
+            case Node.DOCUMENT_TYPE_NODE:
+                return "DocumentType node";
+            case Node.DOCUMENT_FRAGMENT_NODE:
+                return "DocumentFragment node";
+            default:
+                return "Node object of unknown type";
+            }
+        }
+        switch (typeof val)
+        {
+        case "string":
+            for (var i = 0; i < 32; i++)
+            {
+                var replace = "\\";
+                switch (i) {
+                case 0: replace += "0"; break;
+                case 1: replace += "x01"; break;
+                case 2: replace += "x02"; break;
+                case 3: replace += "x03"; break;
+                case 4: replace += "x04"; break;
+                case 5: replace += "x05"; break;
+                case 6: replace += "x06"; break;
+                case 7: replace += "x07"; break;
+                case 8: replace += "b"; break;
+                case 9: replace += "t"; break;
+                case 10: replace += "n"; break;
+                case 11: replace += "v"; break;
+                case 12: replace += "f"; break;
+                case 13: replace += "r"; break;
+                case 14: replace += "x0e"; break;
+                case 15: replace += "x0f"; break;
+                case 16: replace += "x10"; break;
+                case 17: replace += "x11"; break;
+                case 18: replace += "x12"; break;
+                case 19: replace += "x13"; break;
+                case 20: replace += "x14"; break;
+                case 21: replace += "x15"; break;
+                case 22: replace += "x16"; break;
+                case 23: replace += "x17"; break;
+                case 24: replace += "x18"; break;
+                case 25: replace += "x19"; break;
+                case 26: replace += "x1a"; break;
+                case 27: replace += "x1b"; break;
+                case 28: replace += "x1c"; break;
+                case 29: replace += "x1d"; break;
+                case 30: replace += "x1e"; break;
+                case 31: replace += "x1f"; break;
+                }
+                val = val.replace(String.fromCharCode(i), replace);
+            }
+            return '"' + val.replace('"', '\\"') + '"';
+        case "boolean":
+        case "undefined":
+        case "number":
+            return String(val);
+        default:
+            return typeof val + ' "' + val + '"';
+        }
+    }
+
+    /*
+    * Assertions
+    */
+
+    function assert_true(actual, description)
+    {
+        var message = make_message("assert_true", description,
+                                   "expected true got ${actual}", {actual:String(actual)});
+        assert(actual === true, message);
+    };
+    expose(assert_true, "assert_true");
+
+    function assert_false(actual, description)
+    {
+        var message = make_message("assert_false", description,
+                                   "expected false got ${actual}", {actual:String(actual)});
+        assert(actual === false, message);
+    };
+    expose(assert_false, "assert_false");
+
+    function assert_equals(actual, expected, description)
+    {
+         /*
+          * Test if two primitives are equal or two objects
+          * are the same object
+          */
+         var message = make_message("assert_equals", description,
+                                    "expected ${expected} but got ${actual}",
+                                    {expected:format_value(expected), actual:format_value(actual)});
+         if (expected !== expected)
+         {
+             //NaN case
+             assert(actual !== actual, message);
+         }
+         else
+         {
+             //typical case
+             assert(actual === expected, message);
+         }
+    };
+    expose(assert_equals, "assert_equals");
+
+    function assert_object_equals(actual, expected, description)
+    {
+         //This needs to be improved a great deal
+         function check_equal(expected, actual, stack)
+         {
+             stack.push(actual);
+
+             for (p in actual)
+             {
+                 var message = make_message(
+                     "assert_object_equals", description,
+                     "unexpected property ${p}", {p:p});
+
+                 assert(expected.hasOwnProperty(p), message);
+
+                 if (typeof actual[p] === "object" && actual[p] !== null)
+                 {
+                     if (stack.indexOf(actual[p]) === -1)
+                     {
+                         check_equal(actual[p], expected[p], stack);
+                     }
+                 }
+                 else
+                 {
+                     message = make_message(
+                         "assert_object_equals", description,
+                         "property ${p} expected ${expected} got ${actual}",
+                         {p:p, expected:expected, actual:actual});
+
+                     assert(actual[p] === expected[p], message);
+                 }
+             }
+             for (p in expected)
+             {
+                 var message = make_message(
+                     "assert_object_equals", description,
+                     "expected property ${p} missing", {p:p});
+
+                 assert(actual.hasOwnProperty(p), message);
+             }
+             stack.pop();
+         }
+         check_equal(actual, expected, []);
+    };
+    expose(assert_object_equals, "assert_object_equals");
+
+    function assert_array_equals(actual, expected, description)
+    {
+        var message = make_message(
+            "assert_array_equals", description,
+            "lengths differ, expected ${expected} got ${actual}",
+            {expected:expected.length, actual:actual.length});
+
+        assert(actual.length === expected.length, message);
+
+        for (var i=0; i < actual.length; i++)
+        {
+            message = make_message(
+                "assert_array_equals", description,
+                "property ${i}, property expected to be $expected but was $actual",
+                {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
+                 actual:actual.hasOwnProperty(i) ? "present" : "missing"});
+            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), message);
+            message = make_message(
+                          "assert_array_equals", description,
+                          "property ${i}, expected ${expected} but got ${actual}",
+                          {i:i, expected:expected[i], actual:actual[i]});
+            assert(expected[i] === actual[i], message);
+        }
+    }
+    expose(assert_array_equals, "assert_array_equals");
+
+    function assert_regexp_match(actual, expected, description) {
+        /*
+         * Test if a string (actual) matches a regexp (expected)
+         */
+        var message = make_message("assert_regexp_match", description,
+                                   "expected ${expected} but got ${actual}",
+                                   {expected:String(expected), actual:String(actual)});
+        assert(expected.test(actual), message);
+    }
+    expose(assert_regexp_match, "assert_regexp_match");
+
+    function assert_exists(object, property_name, description)
+    {
+         var message = make_message(
+             "assert_exists", description,
+             "expected property ${p} missing", {p:property_name});
+
+         assert(object.hasOwnProperty(property_name), message);
+    };
+    expose(assert_exists, "assert_exists");
+
+    function assert_not_exists(object, property_name, description)
+    {
+         var message = make_message(
+             "assert_not_exists", description,
+             "unexpected property ${p} found", {p:property_name});
+
+         assert(!object.hasOwnProperty(property_name), message);
+    };
+    expose(assert_not_exists, "assert_not_exists");
+
+  function assert_inherits(object, property_name, description)
+    {
+         var message = make_message(
+             "assert_inherits", description,
+             "property ${p} found on object expected in prototype chain",
+           {p:property_name});
+         assert(!object.hasOwnProperty(property_name), message);
+
+         message = make_message(
+             "assert_inherits", description,
+             "property ${p} not found in prototype chain",
+           {p:property_name});
+         assert(property_name in object, message);
+    };
+    expose(assert_inherits, "assert_inherits");
+
+    function assert_readonly(object, property_name, description)
+    {
+         var initial_value = object[property_name];
+         try {
+             var message = make_message(
+                 "assert_readonly", description,
+                 "deleting property ${p} succeeded", {p:property_name});
+             assert(delete object[property_name] === false, message);
+             assert(object[property_name] === initial_value, message);
+             //Note that this can have side effects in the case where
+             //the property has PutForwards
+             object[property_name] = initial_value + "a"; //XXX use some other value here?
+             message = make_message("assert_readonly", description,
+                                    "changing property ${p} succeeded",
+                                    {p:property_name});
+             assert(object[property_name] === initial_value, message);
+         }
+         finally
+         {
+             object[property_name] = initial_value;
+         }
+    };
+    expose(assert_readonly, "assert_readonly");
+
+    function assert_throws(code_or_object, func, description)
+    {
+        try
+        {
+            func.call(this);
+            assert(false, make_message("assert_throws", description,
+                                      "${func} did not throw", {func:String(func)}));
+        }
+        catch(e)
+        {
+            if (e instanceof AssertionError) {
+                throw(e);
+            }
+            if (typeof code_or_object === "string")
+            {
+                assert(e[code_or_object] !== undefined &&
+                       e.code === e[code_or_object],
+                       make_message("assert_throws", description,
+                           [["{text}", "${func} threw with"] ,
+                            function()
+                            {
+                                var actual_name;
+                                for (var p in DOMException)
+                                {
+                                    if (e.code === DOMException[p])
+                                    {
+                                        actual_name = p;
+                                        break;
+                                    }
+                                }
+                                if (actual_name)
+                                {
+                                    return ["{text}", " code " + actual_name + " (${actual_number})"];
+                                }
+                                else
+                                {
+                                    return ["{text}", " error number ${actual_number}"];
+                                }
+                            },
+                            ["{text}"," expected ${expected}"],
+                            function()
+                            {
+                                return e[code_or_object] ?
+                                    ["{text}", " (${expected_number})"] : null;
+                            }
+                           ],
+                                    {func:String(func), actual_number:e.code,
+                                     expected:String(code_or_object),
+                                     expected_number:e[code_or_object]}));
+                assert(e instanceof DOMException,
+                      make_message("assert_throws", description,
+                                   "thrown exception ${exception} was not a DOMException",
+                                  {exception:String(e)}));
+            }
+            else
+            {
+                assert(e instanceof Object && "name" in e && e.name == code_or_object.name,
+                       make_message("assert_throws", description,
+                           "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
+                                    {func:String(func), actual:String(e), actual_name:e.name,
+                                     expected:String(code_or_object),
+                                     expected_name:code_or_object.name}));
+            }
+        }
+    }
+    expose(assert_throws, "assert_throws");
+
+    function assert_unreached(description) {
+         var message = make_message("assert_unreached", description,
+                                    "Reached unreachable code");
+
+         assert(false, message);
+    }
+    expose(assert_unreached, "assert_unreached");
+
+    function Test(name, properties)
+    {
+        this.name = name;
+        this.status = this.NOTRUN;
+        this.timeout_id = null;
+        this.is_done = false;
+
+        this.timeout_length = properties.timeout ? properties.timeout : default_test_timeout;
+
+        this.message = null;
+
+        var this_obj = this;
+        this.steps = [];
+
+        tests.push(this);
+    }
+
+    Test.prototype = {
+        PASS:0,
+        FAIL:1,
+        TIMEOUT:2,
+        NOTRUN:3
+    };
+
+
+    Test.prototype.step = function(func, this_obj)
+    {
+        //In case the test has already failed
+        if (this.status !== this.NOTRUN)
+        {
+          return;
+        }
+
+        tests.started = true;
+
+        if (this.timeout_id === null) {
+            this.set_timeout();
+        }
+
+        this.steps.push(func);
+
+        try
+        {
+            func.apply(this_obj);
+        }
+        catch(e)
+        {
+            //This can happen if something called synchronously invoked another
+            //step
+            if (this.status !== this.NOTRUN)
+            {
+                return;
+            }
+            this.status = this.FAIL;
+            this.message = e.message;
+            this.done();
+            if (debug && e.constructor !== AssertionError) {
+                throw e;
+            }
+        }
+    };
+
+    Test.prototype.set_timeout = function()
+    {
+        var this_obj = this;
+        this.timeout_id = setTimeout(function()
+                                     {
+                                         this_obj.timeout();
+                                     }, this.timeout_length);
+    };
+
+    Test.prototype.timeout = function()
+    {
+        this.status = this.TIMEOUT;
+        this.timeout_id = null;
+        this.message = "Test timed out";
+        this.done();
+    };
+
+    Test.prototype.done = function()
+    {
+        if (this.is_done) {
+            return;
+        }
+        clearTimeout(this.timeout_id);
+        if (this.status === this.NOTRUN)
+        {
+            this.status = this.PASS;
+        }
+        this.is_done = true;
+        tests.result(this);
+    };
+
+
+   /*
+    * Harness
+    */
+
+    function TestsStatus()
+    {
+        this.status = null;
+        this.message = null;
+    }
+    TestsStatus.prototype = {
+        OK:0,
+        ERROR:1,
+        TIMEOUT:2
+    };
+
+    function Tests()
+    {
+        this.tests = [];
+        this.num_pending = 0;
+
+        this.phases = {
+            INITIAL:0,
+            SETUP:1,
+            HAVE_TESTS:2,
+            HAVE_RESULTS:3,
+            COMPLETE:4
+        };
+        this.phase = this.phases.INITIAL;
+
+        //All tests can't be done until the load event fires
+        this.all_loaded = false;
+        this.wait_for_finish = false;
+        this.processing_callbacks = false;
+
+        this.timeout_length = default_timeout;
+        this.timeout_id = null;
+        this.set_timeout();
+
+        this.start_callbacks = [];
+        this.test_done_callbacks = [];
+        this.all_done_callbacks = [];
+
+        this.status = new TestsStatus();
+
+        var this_obj = this;
+
+        on_event(window, "load",
+                 function()
+                 {
+                     this_obj.all_loaded = true;
+                     if (this_obj.all_done())
+                     {
+                         this_obj.complete();
+                     }
+                 });
+    }
+
+    Tests.prototype.setup = function(func, properties)
+    {
+        if (this.phase >= this.phases.HAVE_RESULTS) {
+            return;
+        }
+        if (this.phase < this.phases.SETUP) {
+            this.phase = this.phases.SETUP;
+        }
+
+        if (properties.timeout)
+        {
+            this.timeout_length = properties.timeout;
+            this.set_timeout();
+        }
+        if (properties.explicit_done)
+        {
+            this.wait_for_finish = true;
+        }
+
+        if (func)
+        {
+            try
+            {
+                func();
+            } catch(e)
+            {
+                this.status.status = this.status.ERROR;
+                this.status.message = e;
+            };
+        }
+    };
+
+    Tests.prototype.set_timeout = function()
+    {
+        var this_obj = this;
+        clearTimeout(this.timeout_id);
+        this.timeout_id = setTimeout(function() {
+                                         this_obj.timeout();
+                                     }, this.timeout_length);
+    };
+
+    Tests.prototype.timeout = function() {
+        this.status.status = this.status.TIMEOUT;
+        this.complete();
+    };
+
+    Tests.prototype.end_wait = function()
+    {
+        this.wait_for_finish = false;
+        if (this.all_done()) {
+            this.complete();
+        }
+    };
+
+    Tests.prototype.push = function(test)
+    {
+        if (this.phase < this.phases.HAVE_TESTS) {
+            this.notify_start();
+        }
+        this.num_pending++;
+        this.tests.push(test);
+    };
+
+    Tests.prototype.all_done = function() {
+        return (this.all_loaded && this.num_pending === 0 &&
+                !this.wait_for_finish && !this.processing_callbacks);
+    };
+
+    Tests.prototype.start = function() {
+        this.phase = this.phases.HAVE_TESTS;
+        this.notify_start();
+    };
+
+    Tests.prototype.notify_start = function() {
+        var this_obj = this;
+        forEach (this.start_callbacks,
+                 function(callback)
+                 {
+                     callback(this_obj);
+                 });
+        if(top !== window && top.start_callback)
+        {
+            try
+            {
+                top.start_callback.call(this_obj);
+            }
+            catch(e)
+            {
+                if (debug)
+                {
+                    throw(e);
+                }
+            }
+        }
+    };
+
+    Tests.prototype.result = function(test)
+    {
+        if (this.phase > this.phases.HAVE_RESULTS)
+        {
+            return;
+        }
+        this.phase = this.phases.HAVE_RESULTS;
+        this.num_pending--;
+        this.notify_result(test);
+    };
+
+    Tests.prototype.notify_result = function(test) {
+        var this_obj = this;
+        this.processing_callbacks = true;
+        forEach(this.test_done_callbacks,
+                function(callback)
+                {
+                    callback(test, this_obj);
+                });
+
+        if(top !== window && top.result_callback)
+        {
+            try
+            {
+                top.result_callback.call(this_obj, test);
+            }
+            catch(e)
+            {
+                if(debug) {
+                    throw e;
+                }
+            }
+        }
+        this.processing_callbacks = false;
+        if (this.all_done())
+        {
+            this.complete();
+        }
+
+    };
+
+    Tests.prototype.complete = function() {
+        if (this.phase === this.phases.COMPLETE) {
+            return;
+        }
+        this.phase = this.phases.COMPLETE;
+        this.notify_complete();
+    };
+
+    Tests.prototype.notify_complete = function()
+    {
+        clearTimeout(this.timeout_id);
+        var this_obj = this;
+        if (this.status.status === null)
+        {
+            this.status.status = this.status.OK;
+        }
+
+        forEach (this.all_done_callbacks,
+                 function(callback)
+                 {
+                     callback(this_obj.tests, this_obj.status);
+                 });
+        if(top !== window && top.completion_callback)
+        {
+            try
+            {
+                top.completion_callback(this_obj.tests, this.status);
+            }
+            catch(e)
+            {
+                if (debug)
+                {
+                    throw e;
+                }
+            }
+
+        }
+    };
+
+    var tests = new Tests();
+    add_completion_callback(output_results);
+
+
+    function add_start_callback(callback) {
+        tests.start_callbacks.push(callback);
+    }
+
+    function add_result_callback(callback)
+    {
+        tests.test_done_callbacks.push(callback);
+    }
+
+    function add_completion_callback(callback)
+    {
+       tests.all_done_callbacks.push(callback);
+    }
+
+    expose(add_start_callback, 'add_start_callback');
+    expose(add_result_callback, 'add_result_callback');
+    expose(add_completion_callback, 'add_completion_callback');
+
+    /*
+     * Output listener
+    */
+
+    (function show_status() {
+        var done_count = 0;
+         function on_done(test, tests) {
+             var log = document.getElementById("log");
+             done_count++;
+             if (log)
+             {
+                 if (log.lastChild) {
+                     log.removeChild(log.lastChild);
+                 }
+                 var nodes = render([["{text}", "Running, ${done} complete"],
+                                 function() {
+                                     if (tests.all_done) {
+                                         return ["{text}", " ${pending} remain"];
+                                     } else {
+                                         return null;
+                                     }
+                                 }
+                                    ], {done:done_count,
+                                        pending:tests.num_pending});
+                 forEach(nodes, function(node) {
+                             log.appendChild(node);
+                         });
+                 log.normalize();
+             }
+         }
+         if (document.getElementById("log"))
+         {
+             add_result_callback(on_done);
+         }
+     })();
+
+    function output_results(tests, harness_status)
+    {
+        var log = document.getElementById("log");
+        if (!log)
+        {
+            return;
+        }
+        while (log.lastChild)
+        {
+            log.removeChild(log.lastChild);
+        }
+        var prefix = null;
+        var scripts = document.getElementsByTagName("script");
+        for (var i=0; i<scripts.length; i++)
+        {
+            var src = scripts[i].src;
+            if (src.slice(src.length - "testharness.js".length) === "testharness.js")
+            {
+                prefix = src.slice(0, src.length - "testharness.js".length);
+                break;
+            }
+        }
+        if (prefix != null) {
+            var stylesheet = document.createElement("link");
+            stylesheet.setAttribute("rel", "stylesheet");
+            stylesheet.setAttribute("href", prefix + "testharness.css");
+            var heads = document.getElementsByTagName("head");
+            if (heads) {
+                heads[0].appendChild(stylesheet);
+            }
+        }
+
+        var status_text = {};
+        status_text[Test.prototype.PASS] = "Pass";
+        status_text[Test.prototype.FAIL] = "Fail";
+        status_text[Test.prototype.TIMEOUT] = "Timeout";
+        status_text[Test.prototype.NOTRUN] = "Not Run";
+
+        var status_number = {};
+        forEach(tests, function(test) {
+                    var status = status_text[test.status];
+                    if (status_number.hasOwnProperty(status))
+                    {
+                        status_number[status] += 1;
+                    } else {
+                        status_number[status] = 1;
+                    }
+                });
+
+        function status_class(status)
+        {
+            return status.replace(/\s/g, '').toLowerCase();
+        }
+
+        var summary_template = ["section", {"id":"summary"},
+                                ["h2", {}, "Summary"],
+                                ["p", {}, "Found ${num_tests} tests"],
+                                function(vars) {
+                                    var rv = [["div", {}]];
+                                    var i=0;
+                                    while (status_text.hasOwnProperty(i)) {
+                                        if (status_number.hasOwnProperty(status_text[i])) {
+                                            var status = status_text[i];
+                                            rv[0].push(["div", {"class":status_class(status)},
+                                                        ["input", {type:"checkbox", checked:"checked"}],
+                                                       status_number[status] + " " + status]);
+                                        }
+                                        i++;
+                                    }
+                                    return rv;
+                                }];
+
+        log.appendChild(render(summary_template, {num_tests:tests.length}));
+
+        forEach(document.querySelectorAll("section#summary input"),
+                function(element)
+                {
+                    on_event(element, "click",
+                             function(e)
+                             {
+                                 if (document.getElementById("results") === null)
+                                 {
+                                     e.preventDefault();
+                                     return;
+                                 }
+                                 var result_class = element.parentNode.getAttribute("class");
+                                 var checked = element.checked;
+                                 forEach(document.querySelectorAll("table#results > tbody > tr > td."+result_class),
+                                         function(cell)
+                                         {
+                                             cell.parentNode.style.display = checked ? "" : "None";
+                                         });
+                             });
+                });
+
+        var template = ["section", {},
+                        ["h2", {}, "Details"],
+                        ["table", {"id":"results"},
+                        ["tr", {},
+                         ["th", {}, "Result"],
+                         ["th", {}, "Test Name"],
+                         ["th", {}, "Message"]
+                        ],
+                        ["tbody", {},
+                        function(vars) {
+                            var rv = map(vars.tests, function(test) {
+                                             var status = status_text[test.status];
+                                             return  ["tr", {},
+                                                      ["td", {"class":status_class(status)}, status],
+                                                      ["td", {}, test.name],
+                                                      ["td", {}, test.message ? test.message : " "]
+                                                     ];
+                                         });
+                            return rv;
+                        }]
+                       ]];
+
+        log.appendChild(render(template, {tests:tests}));
+
+    }
+
+
+    /*
+     * Template code
+     *
+     * A template is just a javascript structure. An element is represented as:
+     *
+     * [tag_name, {attr_name:attr_value}, child1, child2]
+     *
+     * the children can either be strings (which act like text nodes), other templates or
+     * functions (see below)
+     *
+     * A text node is represented as
+     *
+     * ["{text}", value]
+     *
+     * String values have a simple substitution syntax; ${foo} represents a variable foo.
+     *
+     * It is possible to embed logic in templates by using a function in a place where a
+     * node would usually go. The function must either return part of a template or null.
+     *
+     * In cases where a set of nodes are required as output rather than a single node
+     * with children it is possible to just use a list
+     * [node1, node2, node3]
+     *
+     * Usage:
+     *
+     * render(template, substitutions) - take a template and an object mapping
+     * variable names to parameters and return either a DOM node or a list of DOM nodes
+     *
+     * substitute(template, substitutions) - take a template and variable mapping object,
+     * make the variable substitutions and return the substituted template
+     *
+     */
+
+    function is_single_node(template)
+    {
+        return typeof template[0] === "string";
+    }
+
+    function substitute(template, substitutions)
+    {
+        if (typeof template === "function") {
+            var replacement = template(substitutions);
+            if (replacement)
+            {
+                var rv = substitute(replacement, substitutions);
+                return rv;
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else if (is_single_node(template))
+        {
+            return substitute_single(template, substitutions);
+        }
+        else
+        {
+            return filter(map(template, function(x) {
+                                  return substitute(x, substitutions);
+                              }), function(x) {return x !== null;});
+        }
+    }
+    expose(substitute, "template.substitute");
+
+    function substitute_single(template, substitutions)
+    {
+        var substitution_re = /\${([^ }]*)}/g;
+
+        function do_substitution(input) {
+            var components = input.split(substitution_re);
+            var rv = [];
+            for (var i=0; i<components.length; i+=2)
+            {
+                rv.push(components[i]);
+                if (components[i+1])
+                {
+                    rv.push(String(substitutions[components[i+1]]));
+                }
+            }
+            return rv;
+        }
+
+        var rv = [];
+        rv.push(do_substitution(String(template[0])).join(""));
+
+        if (template[0] === "{text}") {
+            substitute_children(template.slice(1), rv);
+        } else {
+            substitute_attrs(template[1], rv);
+            substitute_children(template.slice(2), rv);
+        }
+
+        function substitute_attrs(attrs, rv)
+        {
+            rv[1] = {};
+            for (var name in template[1])
+            {
+                if (attrs.hasOwnProperty(name))
+                {
+                    var new_name = do_substitution(name).join("");
+                    var new_value = do_substitution(attrs[name]).join("");
+                    rv[1][new_name] = new_value;
+                };
+            }
+        }
+
+        function substitute_children(children, rv)
+        {
+            for (var i=0; i<children.length; i++)
+            {
+                if (children[i] instanceof Object) {
+                    var replacement = substitute(children[i], substitutions);
+                    if (replacement !== null)
+                    {
+                        if (is_single_node(replacement))
+                        {
+                            rv.push(replacement);
+                        }
+                        else
+                        {
+                            extend(rv, replacement);
+                        }
+                    }
+                }
+                else
+                {
+                    extend(rv, do_substitution(String(children[i])));
+                }
+            }
+            return rv;
+        }
+
+        return rv;
+    }
+
+    function make_dom_single(template)
+    {
+        if (template[0] === "{text}")
+        {
+            var element = document.createTextNode("");
+            for (var i=1; i<template.length; i++)
+            {
+                element.data += template[i];
+            }
+        }
+        else
+        {
+            var element = document.createElement(template[0]);
+            for (var name in template[1]) {
+                if (template[1].hasOwnProperty(name))
+                {
+                    element.setAttribute(name, template[1][name]);
+                }
+            }
+            for (var i=2; i<template.length; i++)
+            {
+                if (template[i] instanceof Object)
+                {
+                    var sub_element = make_dom(template[i]);
+                    element.appendChild(sub_element);
+                }
+                else
+                {
+                    var text_node = document.createTextNode(template[i]);
+                    element.appendChild(text_node);
+                }
+            }
+        }
+
+        return element;
+    }
+
+
+
+    function make_dom(template, substitutions)
+    {
+        if (is_single_node(template))
+        {
+            return make_dom_single(template);
+        }
+        else
+        {
+            return map(template, function(x) {
+                           return make_dom_single(x);
+                       });
+        }
+    }
+
+    function render(template, substitutions)
+    {
+        return make_dom(substitute(template, substitutions));
+    }
+    expose(render, "template.render");
+
+    /*
+     * Utility funcions
+     */
+    function assert(expected_true, message)
+    {
+        if (expected_true !== true)
+        {
+            throw new AssertionError(message);
+        }
+    }
+
+    function AssertionError(message)
+    {
+        this.message = message;
+    }
+
+    function make_message(function_name, description, error, substitutions)
+    {
+        var message = substitute([["span", {"class":"assert"}, "${function_name}:"],
+                                  function()
+                                  {
+                                      if (description) {
+                                          return ["span", {"class":"description"}, description];
+                                      } else {
+                                          return null;
+                                      }
+                                  },
+                                  ["div", {"class":"error"}, error]
+                                 ], merge({function_name:function_name},
+                                         substitutions));
+
+        return message;
+    }
+
+    function filter(array, callable, thisObj) {
+        var rv = [];
+        for (var i=0; i<array.length; i++)
+        {
+            if (array.hasOwnProperty(i))
+            {
+                var pass = callable.call(thisObj, array[i], i, array);
+                if (pass) {
+                    rv.push(array[i]);
+                }
+            }
+        }
+        return rv;
+    }
+
+    function map(array, callable, thisObj)
+    {
+        var rv = [];
+        rv.length = array.length;
+        for (var i=0; i<array.length; i++)
+        {
+            if (array.hasOwnProperty(i))
+            {
+                rv[i] = callable.call(thisObj, array[i], i, array);
+            }
+        }
+        return rv;
+    }
+
+    function extend(array, items)
+    {
+        Array.prototype.push.apply(array, items);
+    }
+
+    function forEach (array, callback, thisObj)
+    {
+        for (var i=0; i<array.length; i++)
+        {
+            if (array.hasOwnProperty(i))
+            {
+                callback.call(thisObj, array[i], i, array);
+            }
+        }
+    }
+
+    function merge(a,b)
+    {
+        var rv = {};
+        var p;
+        for (p in a)
+        {
+            rv[p] = a[p];
+        }
+        for (p in b) {
+            rv[p] = b[p];
+        }
+        return rv;
+    }
+
+    function expose(object, name)
+    {
+        var components = name.split(".");
+        var target = window;
+        for (var i=0; i<components.length - 1; i++)
+        {
+            if (!(components[i] in target))
+            {
+                target[components[i]] = {};
+            }
+            target = target[components[i]];
+        }
+        target[components[components.length - 1]] = object;
+    }
+
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/touchevents/single-touch.html	Tue Jan 17 09:47:15 2012 -0500
@@ -0,0 +1,222 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Touch Events: Single-touch tests</title>
+  <meta name="viewport" content="width=device-width">
+  <script src="../testharness.js"></script>
+  <script>
+    setup({explicit_done: true});
+
+    function run() {
+      var target0 = document.getElementById("target0");
+      var target1 = document.getElementById("target1");
+
+      var test_touchstart = async_test("touchstart event received");
+      var test_touchmove = async_test("touchmove event received");
+      var test_touchend = async_test("touchend event received");
+      var test_mousedown = async_test("Interaction with mouse events");
+
+      var touchstart_received = false;
+      var touchmove_received = false;
+      var touchend_received = false;
+      var invalid_touchmove_received = false;
+      var touchstart_identifier;
+
+      on_event(target0, "touchstart", function onTouchStart(ev) {
+        ev.preventDefault();
+
+        touchstart_received = true;
+        test_touchstart.step(function() {
+          assert_false(touchmove_received, "touchstart precedes touchmove");
+          assert_false(touchend_received, "touchstart precedes touchend");
+        });
+        test_touchstart.done();
+        test_mousedown.done(); // If we got here, then the mouse event test is not needed.
+
+        test(function() {
+          assert_true(ev.target instanceof Element, "target must be an Element.");
+          assert_equals(ev.relatedTarget, null, "relatedTarget must be null for touchstart event.");
+        }, "touchstart TouchEvent target attributes are correct.");
+
+        test(function() {
+          var attrs = ["altKey", "metaKey", "ctrlKey", "shiftKey"];
+          for (var i = 0; i < attrs.length; i++) {
+            assert_true(attrs[i] in ev);
+          }
+        }, "touchstart TouchEvent modifier key attributes are present.");
+
+        test(function() {
+          assert_equals(ev.touches.length, 1, "One touch point.");
+          assert_equals(ev.changedTouches.length, 1, "One changed touch point.");
+          assert_equals(ev.targetTouches.length, 1, "One target touch point.");
+        }, "touchstart TouchList lengths are correct.");
+
+        var t = ev.touches[0];
+        var ct = ev.changedTouches[0];
+        var tt = ev.targetTouches[0];
+
+        test(function() {
+          assert_true(ev instanceof TouchEvent, "Event is a TouchEvent.");
+          assert_true(ev.touches instanceof TouchList, "touches is a TouchList");
+          assert_true(t instanceof Touch, "touches[0] is a Touch");
+        }, "Interface names are correct.");
+
+        touchstart_identifier = t.identifier;
+        test(function() {
+          assert_equals(ct.identifier, touchstart_identifier, "changedTouches identifier matches.");
+          assert_equals(tt.identifier, touchstart_identifier, "targetTouches identifier matches.");
+        }, "Touch identifiers are consistent.");
+
+        test(function() {
+          assert_equals(ev.touches.identifiedTouch(touchstart_identifier), t, "touches.identifiedTouch is correct.");
+        }, "identifiedTouch");
+
+        test(function() {
+          assert_true(t.rotationAngle >= 0, "rotationAngle must be >= 0.");
+          assert_true(t.rotationAngle < 90, "rotationAngle must be > 90.");
+
+          assert_true(t.radiusX > 0, "radiusX is positive.");
+          assert_true(t.radiusY > 0, "radiusY is positive.");
+
+          assert_true(t.force >= 0, "force >= 0.");
+          assert_true(t.force <= 1, "force <= 1.");
+        }, "Touch attribute values are valid");
+
+        test(function() {
+          var attrs = ["screenX", "screenY", "clientX", "clientY",
+                       "pageX", "pageY"];
+          for (var i = 0; i < attrs.length; i++) {
+            assert_true(attrs[i] in t);
+          }
+        }, "Touch location attributes are present.");
+
+      });
+
+      on_event(target0, "touchmove", function onTouchMove(ev) {
+        ev.preventDefault();
+
+        if (touchmove_received)
+          return;
+        touchmove_received = true;
+
+        test_touchmove.step(function() {
+          assert_true(touchstart_received, "touchend follows touchstart");
+          assert_false(touchend_received, "touchmove precedes touchend");
+        });
+        test_touchmove.done();
+
+        test(function() {
+          assert_equals(ev.touches.length, 1, "One touch point.");
+          assert_equals(ev.changedTouches.length, 1, "One changed touch point.");
+          assert_equals(ev.targetTouches.length, 1, "One target touch point.");
+        }, "touchmove TouchList lengths are correct.");
+
+        test(function() {
+          assert_equals(ev.touches[0].identifier, touchstart_identifier, "Touch identifier matches.");
+          assert_equals(ev.changedTouches[0].identifier, touchstart_identifier, "Changed touch identifier matches.");
+          assert_equals(ev.targetTouches[0].identifier, touchstart_identifier, "Target touch identifier matches.");
+        }, "touchmove identifier matches touchstart identifier.");
+      });
+
+      on_event(target1, "touchmove", function onTouchMove(ev) {
+        invalid_touchmove_received = true;
+      });
+
+      on_event(window, "touchend", function onTouchStart(ev) {
+        touchend_received = true;
+
+        test_touchend.step(function() {
+          assert_equals(ev.target, target0, "touchend is dispatched to the original target");
+          assert_true(touchstart_received, "touchend follows touchstart");
+          assert_true(touchmove_received, "touchend follows touchmove");
+          assert_false(invalid_touchmove_received, "touchmove dispatched to correct target");
+        });
+        test_touchend.done();
+
+        test(function() {
+          var attrs = ["altKey", "metaKey", "ctrlKey", "shiftKey"];
+          for (var i = 0; i < attrs.length; i++) {
+            assert_true(attrs[i] in ev);
+          }
+        }, "touchend TouchEvent modifier key attributes are present.");
+
+        test(function() {
+          assert_equals(ev.touches.length, 0, "Zero touch points.");
+          assert_equals(ev.changedTouches.length, 1, "One changed touch point.");
+          assert_equals(ev.targetTouches.length, 0, "Zero target touch points.");
+        }, "touchend TouchList lengths are correct.");
+
+        var t = ev.changedTouches[0];
+
+        test(function() {
+          assert_equals(t.identifier, touchstart_identifier, "changedTouches identifier matches.");
+        }, "touchend identifier matches.");
+
+        test(function() {
+          assert_true(t.rotationAngle >= 0, "rotationAngle must be >= 0.");
+          assert_true(t.rotationAngle < 90, "rotationAngle must be > 90.");
+
+          assert_true(t.radiusX > 0, "radiusX is positive.");
+          assert_true(t.radiusY > 0, "radiusY is positive.");
+
+          assert_true(t.force >= 0, "force >= 0.");
+          assert_true(t.force <= 1, "force <= 1.");
+        }, "Touch attribute values are valid");
+
+        test(function() {
+          var attrs = ["screenX", "screenY", "clientX", "clientY",
+                       "pageX", "pageY"];
+          for (var i = 0; i < attrs.length; i++) {
+            assert_true(attrs[i] in t);
+          }
+        }, "Touch location attributes are present.");
+
+        done();
+      });
+
+
+      on_event(target0, "mousedown", function onMouseDown(ev) {
+        test_mousedown.step(function() {
+          assert_true(touchstart_received,
+            "The touchstart event must be dispatched before any mouse " +
+            "events. (If this fails, it might mean that the user agent does " +
+            "not implement W3C touch events at all.)"
+          );
+        });
+        test_mousedown.done();
+
+        if (!touchstart_received) {
+          // Abort the tests.  If touch events are not supported, then most of
+          // the other event handlers will never be called, and the test will
+          // time out with misleading results.
+          done();
+        }
+      });
+    }
+  </script>
+  <style>
+    div {
+      margin: 0em;
+      padding: 2em;
+    }
+    #target0 {
+      background: yellow;
+      border: 1px solid orange;
+    }
+    #target1 {
+      background: lightblue;
+      border: 1px solid blue;
+    }
+  </style>
+</head>
+<body onload="run()">
+  <h1>Touch Events: single-touch tests</h1>
+  <div id="target0">
+    Touch this box with one finger (or other pointing device)...
+  </div>
+  <div id="target1">
+    ...then drag to this box and lift your finger.
+  </div>
+  <div id="log"></div>
+</body>
+</html>