├── .github └── CODEOWNERS ├── .gitignore ├── .npmignore ├── README.md ├── bower.json ├── gulpfile.js ├── package-lock.json ├── package.json ├── playground.html ├── testharness.css ├── testharness.js ├── testharnessreport.js ├── url.js ├── urltestgenerator.html ├── urltestharness.html ├── urltestparser.js ├── urltestrunner.html └── urltests.txt /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @azakus 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | url.min.js* 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 🚨 Moved to [`webcomponents/polyfills/packages/url`][1] 🚨 2 | 3 | The [`webcomponents/template`][2] repo has been migrated to [`packages/url`][1] folder of the [`webcomponents/polyfills`][3] 🚝 *monorepo*. 4 | 5 | We are *actively* working on migrating open Issues and PRs to the new repo. New Issues and PRs should be filed at [`webcomponents/polyfills`][3]. 6 | 7 | [1]: https://github.com/webcomponents/polyfills/tree/master/packages/url 8 | [2]: https://github.com/webcomponents/url 9 | [3]: https://github.com/webcomponents/polyfills 10 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "url", 3 | "main": "url.js", 4 | "homepage": "https://github.com/Polymer/URL", 5 | "authors": [ 6 | "https://github.com/Polymer/URL/graphs/contributors" 7 | ], 8 | "description": "URL parser in JavaScript", 9 | "keywords": [ 10 | "url", 11 | "urlsearchparams", 12 | "whatwg", 13 | "w3c" 14 | ], 15 | "license": "https://github.com/Polymer/URL#license", 16 | "ignore": [ 17 | "*test*", 18 | "playground.html" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 'use strict'; 11 | 12 | const compilerPackage = require('google-closure-compiler'); 13 | const gulp = require('gulp'); 14 | const sourcemaps = require('gulp-sourcemaps'); 15 | const closureCompiler = compilerPackage.gulp(); 16 | 17 | gulp.task('default', () => { 18 | return gulp.src('./url.js', {base: './'}) 19 | .pipe(sourcemaps.init()) 20 | .pipe(closureCompiler({ 21 | compilation_level: 'ADVANCED', 22 | warning_level: 'VERBOSE', 23 | language_in: 'ECMASCRIPT6_STRICT', 24 | language_out: 'ECMASCRIPT5_STRICT', 25 | dependency_mode: 'STRICT', 26 | entry_point: ['/url'], 27 | js_output_file: 'url.min.js', 28 | output_wrapper: '(function(){\n%output%\n}).call(self);', 29 | assume_function_wrapper: true, 30 | new_type_inf: true, 31 | rewrite_polyfills: false 32 | })) 33 | .pipe(sourcemaps.write('/')) 34 | .pipe(gulp.dest('./')); 35 | }); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webcomponents/url", 3 | "version": "0.7.3", 4 | "main": "url.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/webcomponents/URL.git" 8 | }, 9 | "license": "SAX-PD", 10 | "description": "URL parser in JavaScript", 11 | "bugs": { 12 | "url": "https://github.com/webcomponents/URL/issues" 13 | }, 14 | "homepage": "https://github.com/webcomponents/URL#readme", 15 | "scripts": { 16 | "build": "gulp", 17 | "prepack": "npm run build" 18 | }, 19 | "author": "Erik Arvidsson ", 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "devDependencies": { 24 | "google-closure-compiler": "^20180910.0.0", 25 | "gulp": "^4.0.0", 26 | "gulp-sourcemaps": "^2.6.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /playground.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
  4 | 
194 | 


--------------------------------------------------------------------------------
/testharness.css:
--------------------------------------------------------------------------------
 1 | html {
 2 |     font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;
 3 | }
 4 | 
 5 | #log .warning,
 6 | #log .warning a {
 7 |   color: black;
 8 |   background: yellow;
 9 | }
10 | 
11 | #log .error,
12 | #log .error a {
13 |   color: white;
14 |   background: red;
15 | }
16 | 
17 | #log pre {
18 |   border: 1px solid black;
19 |   padding: 1em;
20 | }
21 | 
22 | section#summary {
23 |     margin-bottom:1em;
24 | }
25 | 
26 | table#results {
27 |     border-collapse:collapse;
28 |     table-layout:fixed;
29 |     width:100%;
30 | }
31 | 
32 | table#results th:first-child,
33 | table#results td:first-child {
34 |     width:4em;
35 | }
36 | 
37 | table#results th:last-child,
38 | table#results td:last-child {
39 |     width:50%;
40 | }
41 | 
42 | table#results.assertions th:last-child,
43 | table#results.assertions td:last-child {
44 |     width:35%;
45 | }
46 | 
47 | table#results th {
48 |     padding:0;
49 |     padding-bottom:0.5em;
50 |     border-bottom:medium solid black;
51 | }
52 | 
53 | table#results td {
54 |     padding:1em;
55 |     padding-bottom:0.5em;
56 |     border-bottom:thin solid black;
57 | }
58 | 
59 | tr.pass > td:first-child {
60 |     color:green;
61 | }
62 | 
63 | tr.fail > td:first-child {
64 |     color:red;
65 | }
66 | 
67 | tr.timeout > td:first-child {
68 |     color:red;
69 | }
70 | 
71 | tr.notrun > td:first-child {
72 |     color:blue;
73 | }
74 | 
75 | .pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child {
76 |     font-variant:small-caps;
77 | }
78 | 
79 | table#results span {
80 |     display:block;
81 | }
82 | 
83 | table#results span.expected {
84 |     font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
85 |     white-space:pre;
86 | }
87 | 
88 | table#results span.actual {
89 |     font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
90 |     white-space:pre;
91 | }
92 | 


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


--------------------------------------------------------------------------------
/urltestgenerator.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
11 | 
12 | 
13 | 
89 | -------------------------------------------------------------------------------- /urltestharness.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 60 | -------------------------------------------------------------------------------- /urltestparser.js: -------------------------------------------------------------------------------- 1 | function URLTestParser(input) { 2 | var relativeSchemes = ["ftp", "file", "gopher", "http", "https", "ws", "wss"], 3 | tokenMap = { "\\": "\\", n: "\n", r: "\r", s: " ", t: "\t", f: "\f" } 4 | resultMap = { s: "scheme", u: "username", pass: "password", h: "host", port: "port", p: "path", q: "query", f: "fragment", o: "origin" }, 5 | results = [] 6 | function Test() { 7 | this.input = "" 8 | this.base = "" 9 | this.scheme = "" 10 | this.username = "" 11 | this.password = null 12 | this.host = "" 13 | this.port = "" 14 | this.path = "" 15 | this.query = "" 16 | this.fragment = "" 17 | this.origin = "" 18 | Object.defineProperties(this, { 19 | "href": { get: function() { return !this.scheme ? this.input : this.protocol + (relativeSchemes.indexOf(this.scheme) != -1 ? "//" + (("" != this.username || null != this.password) ? this.username + (null != this.password ? ":" + this.password : "") + "@" : "") + this.host : "") + (this.port ? ":" + this.port : "") + this.path + this.query + this.fragment } }, 20 | "protocol": { get: function() { return this.scheme + ":" } }, 21 | "search": { get: function() { return "?" == this.query ? "" : this.query } }, 22 | "hash": { get: function() { return "#" == this.fragment ? "" : this.fragment } } 23 | }) 24 | } 25 | function normalize(input) { 26 | var output = "" 27 | for(var i = 0, l = input.length; i < l; i++) { 28 | var c = input[i] 29 | if(c == "\\") { 30 | var nextC = input[++i] 31 | if(tokenMap.hasOwnProperty(nextC)) { 32 | output += tokenMap[nextC] 33 | } else if(nextC == "u") { 34 | output += String.fromCharCode(parseInt(input[++i] + input[++i] + input[++i] + input[++i], 16)) 35 | } else { 36 | throw new Error("Input is invalid.") 37 | } 38 | } else { 39 | output += c 40 | } 41 | } 42 | return output 43 | } 44 | var lines = input.split("\n") 45 | for(var i = 0, l = lines.length; i < l; i++) { 46 | var line = lines[i] 47 | if(line === "" || line.indexOf("#", 0) === 0) { 48 | continue 49 | } 50 | var pieces = line.split(" "), 51 | result = new Test() 52 | result.input = normalize(pieces.shift()) 53 | var base = pieces.shift() 54 | if(base === "" || base === undefined) { 55 | result.base = results[results.length - 1].base 56 | } else { 57 | result.base = normalize(base) 58 | } 59 | for(var ii = 0, ll = pieces.length; ii < ll; ii++) { 60 | var piece = pieces[ii] 61 | if(piece.indexOf("#", 0) === 0) { 62 | continue 63 | } 64 | var subpieces = piece.split(":"), 65 | token = subpieces.shift() 66 | value = subpieces.join(":") 67 | result[resultMap[token]] = normalize(value) 68 | } 69 | results.push(result) 70 | } 71 | return results 72 | } 73 | -------------------------------------------------------------------------------- /urltestrunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
61 | 


--------------------------------------------------------------------------------
/urltests.txt:
--------------------------------------------------------------------------------
  1 | # FORMAT NOT DOCUMENTED YET (parser is urltestparser.js)
  2 | 
  3 | # Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/segments.js
  4 | http://example\t.\norg http://example.org/foo/bar s:http h:example.org p:/
  5 | http://user:pass@foo:21/bar;par?b#c  s:http u:user pass:pass h:foo port:21 p:/bar;par q:?b f:#c
  6 | http:foo.com  s:http h:example.org p:/foo/foo.com
  7 | \t\s\s\s:foo.com\s\s\s\n  s:http h:example.org p:/foo/:foo.com
  8 | \sfoo.com\s\s  s:http h:example.org p:/foo/foo.com
  9 | a:\t\sfoo.com  s:a p:%20foo.com
 10 | http://f:21/\sb\s?\sd\s#\se\s  s:http h:f port:21 p:/%20b%20 q:?%20d%20 f:#\se
 11 | http://f:/c  s:http h:f p:/c
 12 | http://f:0/c  s:http h:f port:0 p:/c
 13 | http://f:00000000000000/c  s:http h:f port:0 p:/c
 14 | http://f:00000000000000000000080/c  s:http h:f p:/c
 15 | http://f:b/c
 16 | http://f:\s/c
 17 | http://f:\n/c  s:http h:f p:/c
 18 | http://f:fifty-two/c
 19 | http://f:999999/c  s:http h:f port:999999 p:/c
 20 | http://f:\s21\s/\sb\s?\sd\s#\se\s
 21 |   s:http h:example.org p:/foo/bar
 22 | \s\s\t  s:http h:example.org p:/foo/bar
 23 | :foo.com/  s:http h:example.org p:/foo/:foo.com/
 24 | :foo.com\\  s:http h:example.org p:/foo/:foo.com/
 25 | :  s:http h:example.org p:/foo/:
 26 | :a  s:http h:example.org p:/foo/:a
 27 | :/  s:http h:example.org p:/foo/:/
 28 | :\\  s:http h:example.org p:/foo/:/
 29 | :#  s:http h:example.org p:/foo/: f:#
 30 | #  s:http h:example.org p:/foo/bar f:#
 31 | #/  s:http h:example.org p:/foo/bar f:#/
 32 | #\\  s:http h:example.org p:/foo/bar f:#\\
 33 | #;?  s:http h:example.org p:/foo/bar f:#;?
 34 | ?  s:http h:example.org p:/foo/bar q:?
 35 | /  s:http h:example.org p:/
 36 | :23  s:http h:example.org p:/foo/:23
 37 | /:23  s:http h:example.org p:/:23
 38 | ::  s:http h:example.org p:/foo/::
 39 | ::23  s:http h:example.org p:/foo/::23
 40 | foo://  s:foo p://
 41 | http://a:b@c:29/d  s:http u:a pass:b h:c port:29 p:/d
 42 | http::@c:29  s:http h:example.org p:/foo/:@c:29
 43 | http://&a:foo(b]c@d:2/  s:http u:&a pass:foo(b]c h:d port:2 p:/
 44 | http://::@c@d:2  s:http pass::c%40 h:d port:2 p:/
 45 | http://foo.com:b@d/  s:http u:foo.com pass:b h:d p:/
 46 | http://foo.com/\\@  s:http h:foo.com p://@
 47 | http:\\\\foo.com\\  s:http h:foo.com p:/
 48 | http:\\\\a\\b:c\\d@foo.com\\  s:http h:a p:/b:c/d@foo.com/
 49 | foo:/  s:foo p:/
 50 | foo:/bar.com/  s:foo p:/bar.com/
 51 | foo://///////  s:foo p://///////
 52 | foo://///////bar.com/  s:foo p://///////bar.com/
 53 | foo:////://///  s:foo p:////://///
 54 | c:/foo  s:c p:/foo
 55 | //foo/bar  s:http h:foo p:/bar
 56 | http://foo/path;a??e#f#g  s:http h:foo p:/path;a q:??e f:#f#g
 57 | http://foo/abcd?efgh?ijkl  s:http h:foo p:/abcd q:?efgh?ijkl
 58 | http://foo/abcd#foo?bar  s:http h:foo p:/abcd f:#foo?bar
 59 | [61:24:74]:98  s:http h:example.org p:/foo/[61:24:74]:98
 60 | http:[61:27]/:foo  s:http h:example.org p:/foo/[61:27]/:foo
 61 | http://[1::2]:3:4
 62 | http://2001::1
 63 | http://2001::1]
 64 | http://2001::1]:80
 65 | http://[2001::1]  s:http h:[2001::1] p:/
 66 | http://[2001::1]:80  s:http h:[2001::1] p:/
 67 | http:/example.com/  s:http h:example.org p:/example.com/
 68 | ftp:/example.com/  s:ftp h:example.com p:/
 69 | https:/example.com/  s:https h:example.com p:/
 70 | madeupscheme:/example.com/  s:madeupscheme p:/example.com/
 71 | file:/example.com/  s:file p:/example.com/
 72 | ftps:/example.com/  s:ftps p:/example.com/
 73 | gopher:/example.com/  s:gopher h:example.com p:/
 74 | ws:/example.com/  s:ws h:example.com p:/
 75 | wss:/example.com/  s:wss h:example.com p:/
 76 | data:/example.com/  s:data p:/example.com/
 77 | javascript:/example.com/  s:javascript p:/example.com/
 78 | mailto:/example.com/  s:mailto p:/example.com/
 79 | http:example.com/  s:http h:example.org p:/foo/example.com/
 80 | ftp:example.com/  s:ftp h:example.com p:/
 81 | https:example.com/  s:https h:example.com p:/
 82 | madeupscheme:example.com/  s:madeupscheme p:example.com/
 83 | ftps:example.com/  s:ftps p:example.com/
 84 | gopher:example.com/  s:gopher h:example.com p:/
 85 | ws:example.com/  s:ws h:example.com p:/
 86 | wss:example.com/  s:wss h:example.com p:/
 87 | data:example.com/  s:data p:example.com/
 88 | javascript:example.com/  s:javascript p:example.com/
 89 | mailto:example.com/  s:mailto p:example.com/
 90 | /a/b/c  s:http h:example.org p:/a/b/c
 91 | /a/\s/c  s:http h:example.org p:/a/%20/c
 92 | /a%2fc  s:http h:example.org p:/a%2fc
 93 | /a/%2f/c  s:http h:example.org p:/a/%2f/c
 94 | #\u03B2  s:http h:example.org p:/foo/bar f:#\u03B2
 95 | data:text/html,test#test  s:data p:text/html,test f:#test
 96 | 
 97 | # Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html
 98 | file:c:\\foo\\bar.html file:///tmp/mock/path s:file p:/c:/foo/bar.html
 99 | \s\sFile:c|////foo\\bar.html  s:file p:/c:////foo/bar.html
100 | C|/foo/bar  s:file p:/C:/foo/bar
101 | /C|\\foo\\bar  s:file p:/C:/foo/bar
102 | //C|/foo/bar  s:file p:/C:/foo/bar
103 | //server/file  s:file h:server p:/file
104 | \\\\server\\file  s:file h:server p:/file
105 | /\\server/file  s:file h:server p:/file
106 | file:///foo/bar.txt  s:file p:/foo/bar.txt
107 | file:///home/me  s:file p:/home/me
108 | file:c:\\foo\\bar.html  s:file p:/c:/foo/bar.html
109 | //  s:file p:/
110 | ///  s:file p:/
111 | ///test  s:file p:/test
112 | file://test  s:file h:test p:/
113 | file://localhost  s:file h:localhost p:/
114 | file://localhost/  s:file h:localhost p:/
115 | file://localhost/test  s:file h:localhost p:/test
116 | test  s:file p:/tmp/mock/test
117 | file:test  s:file p:/tmp/mock/test
118 | 
119 | # Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js
120 | http://example.com/././foo about:blank s:http h:example.com p:/foo
121 | http://example.com/./.foo  s:http h:example.com p:/.foo
122 | http://example.com/foo/.  s:http h:example.com p:/foo/
123 | http://example.com/foo/./  s:http h:example.com p:/foo/
124 | http://example.com/foo/bar/..  s:http h:example.com p:/foo/
125 | http://example.com/foo/bar/../  s:http h:example.com p:/foo/
126 | http://example.com/foo/..bar  s:http h:example.com p:/foo/..bar
127 | http://example.com/foo/bar/../ton  s:http h:example.com p:/foo/ton
128 | http://example.com/foo/bar/../ton/../../a  s:http h:example.com p:/a
129 | http://example.com/foo/../../..  s:http h:example.com p:/
130 | http://example.com/foo/../../../ton  s:http h:example.com p:/ton
131 | http://example.com/foo/%2e  s:http h:example.com p:/foo/
132 | http://example.com/foo/%2e%2  s:http h:example.com p:/foo/%2e%2
133 | http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar  s:http h:example.com p:/%2e.bar
134 | http://example.com////../..  s:http h:example.com p://
135 | http://example.com/foo/bar//../..  s:http h:example.com p:/foo/
136 | http://example.com/foo/bar//..  s:http h:example.com p:/foo/bar/
137 | http://example.com/foo/bar/..  s:http h:example.com p:/foo/
138 | http://example.com/foo  s:http h:example.com p:/foo
139 | http://example.com/%20foo  s:http h:example.com p:/%20foo
140 | http://example.com/foo%  s:http h:example.com p:/foo%
141 | http://example.com/foo%2  s:http h:example.com p:/foo%2
142 | http://example.com/foo%2zbar  s:http h:example.com p:/foo%2zbar
143 | http://example.com/foo%2\u00C2\u00A9zbar  s:http h:example.com p:/foo%2%C3%82%C2%A9zbar
144 | http://example.com/foo%41%7a  s:http h:example.com p:/foo%41%7a
145 | http://example.com/foo\t\u0091%91  s:http h:example.com p:/foo%C2%91%91
146 | http://example.com/foo%00%51  s:http h:example.com p:/foo%00%51
147 | http://example.com/(%28:%3A%29)  s:http h:example.com p:/(%28:%3A%29)
148 | http://example.com/%3A%3a%3C%3c  s:http h:example.com p:/%3A%3a%3C%3c
149 | http://example.com/foo\tbar  s:http h:example.com p:/foobar
150 | http://example.com\\\\foo\\\\bar  s:http h:example.com p://foo//bar
151 | http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd  s:http h:example.com p:/%7Ffp3%3Eju%3Dduvgw%3Dd
152 | http://example.com/@asdf%40  s:http h:example.com p:/@asdf%40
153 | http://example.com/\u4F60\u597D\u4F60\u597D  s:http h:example.com p:/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD
154 | http://example.com/\u2025/foo  s:http h:example.com p:/%E2%80%A5/foo
155 | http://example.com/\uFEFF/foo  s:http h:example.com p:/%EF%BB%BF/foo
156 | http://example.com/\u202E/foo/\u202D/bar  s:http h:example.com p:/%E2%80%AE/foo/%E2%80%AD/bar
157 | 
158 | # Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js
159 | http://www.google.com/foo?bar=baz# about:blank s:http h:www.google.com p:/foo q:?bar=baz f:# o:http://www.google.com
160 | http://www.google.com/foo?bar=baz#\s\u00BB  s:http h:www.google.com p:/foo q:?bar=baz f:#\s\u00BB o:http://www.google.com
161 | http://[www.google.com]/  s:http h:[www.google.com] p:/ o:http://[www.google.com]
162 | http://www.google.com  s:http h:www.google.com p:/
163 | http://192.0x00A80001  s:http h:192.0x00a80001 p:/
164 | http://www/foo%2Ehtml  s:http h:www p:/foo%2Ehtml
165 | http://www/foo/%2E/html  s:http h:www p:/foo/html
166 | http://user:pass@/
167 | http://%25DOMAIN:foobar@foodomain.com/  s:http u:%25DOMAIN pass:foobar h:foodomain.com p:/
168 | http:\\\\www.google.com\\foo  s:http h:www.google.com p:/foo o:http://www.google.com
169 | http://foo:80/  s:http h:foo p:/ o:http://foo:80
170 | http://foo:81/  s:http h:foo port:81 p:/ o:http://foo:81
171 | httpa://foo:80/  s:httpa p://foo:80/ o:httpa://foo:80
172 | http://foo:-80/
173 | https://foo:443/  s:https h:foo p:/ o:https://foo:443
174 | https://foo:80/  s:https h:foo port:80 p:/ o:http://foo:80
175 | ftp://foo:21/  s:ftp h:foo p:/ o:ftp://foo:21
176 | ftp://foo:80/  s:ftp h:foo port:80 p:/ o:ftp://foo:80
177 | gopher://foo:70/  s:gopher h:foo p:/ o:gopher://foo:70
178 | gopher://foo:443/  s:gopher h:foo port:443 p:/ o:gopher://foo:443
179 | ws://foo:80/  s:ws h:foo p:/ o:ws://foo:80
180 | ws://foo:81/  s:ws h:foo port:81 p:/ o:ws://foo:81
181 | ws://foo:443/  s:ws h:foo port:443 p:/ o:ws://foo:443
182 | ws://foo:815/  s:ws h:foo port:815 p:/ o:ws://goo:815
183 | wss://foo:80/  s:wss h:foo port:80 p:/ o:wss://foo:80
184 | wss://foo:81/  s:wss h:foo port:81 p:/ o:wss://foo:81
185 | wss://foo:443/  s:wss h:foo p:/ o:wss://foo:443
186 | wss://foo:815/  s:wss h:foo port:815 p:/ o:wss://foo:815
187 | http:/example.com/  s:http h:example.com p:/ o:http://example.com
188 | ftp:/example.com/  s:ftp h:example.com p:/ o:ftp:/example.com
189 | https:/example.com/  s:https h:example.com p:/ o:https://example.com
190 | madeupscheme:/example.com/  s:madeupscheme p:/example.com/ o:madeupscheme://example.com
191 | file:/example.com/  s:file p:/example.com/
192 | ftps:/example.com/  s:ftps p:/example.com/ o:ftps://example.com
193 | gopher:/example.com/  s:gopher h:example.com p:/ o:gopher://example.com
194 | ws:/example.com/  s:ws h:example.com p:/ o:ws://example.com
195 | wss:/example.com/  s:wss h:example.com p:/ o:wss://example.com
196 | data:/example.com/  s:data p:/example.com/
197 | javascript:/example.com/  s:javascript p:/example.com/
198 | mailto:/example.com/  s:mailto p:/example.com/
199 | http:example.com/  s:http h:example.com p:/ o:http://example.com
200 | ftp:example.com/  s:ftp h:example.com p:/ o:ftp://example.com
201 | https:example.com/  s:https h:example.com p:/ o:https://example.com
202 | madeupscheme:example.com/  s:madeupscheme p:example.com/ o:madeupscheme://example.com
203 | ftps:example.com/  s:ftps p:example.com/ o:ftps://example.com
204 | gopher:example.com/  s:gopher h:example.com p:/ o:gopher://example.com
205 | ws:example.com/  s:ws h:example.com p:/ o:ws://example.com
206 | wss:example.com/  s:wss h:example.com p:/ o:wss://example.com
207 | data:example.com/  s:data p:example.com/
208 | javascript:example.com/  s:javascript p:example.com/
209 | mailto:example.com/  s:mailto p:example.com/
210 | 
211 | # Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/segments-userinfo-vs-host.html
212 | http:@www.example.com about:blank s:http h:www.example.com p:/ o:http://www.example.com
213 | http:/@www.example.com  s:http h:www.example.com p:/ o:http://www.example.com
214 | http://@www.example.com  s:http h:www.example.com p:/ o:http://www.example.com
215 | http:a:b@www.example.com  s:http u:a pass:b h:www.example.com p:/ o:http://www.example.com
216 | http:/a:b@www.example.com  s:http u:a pass:b h:www.example.com p:/ o:http://www.example.com
217 | http://a:b@www.example.com  s:http u:a pass:b h:www.example.com p:/ o:http://www.example.com
218 | http://@pple.com  s:http h:pple.com p:/ o:http://ppl.com
219 | http::b@www.example.com  s:http pass:b h:www.example.com p:/ o:http://www.example.com
220 | http:/:b@www.example.com  s:http pass:b h:www.example.com p:/ o:http://www.example.com
221 | http://:b@www.example.com  s:http pass:b h:www.example.com p:/ o:http://www.example.com
222 | http:/:@/www.example.com
223 | http://user@/www.example.com
224 | http:@/www.example.com
225 | http:/@/www.example.com
226 | http://@/www.example.com
227 | https:@/www.example.com
228 | http:a:b@/www.example.com
229 | http:/a:b@/www.example.com
230 | http://a:b@/www.example.com
231 | http::@/www.example.com
232 | http:a:@www.example.com  s:http u:a pass: h:www.example.com p:/ o:http://www.example.com
233 | http:/a:@www.example.com  s:http u:a pass: h:www.example.com p:/ o:http://www.example.com
234 | http://a:@www.example.com  s:http u:a pass: h:www.example.com p:/ o:http://www.example.com
235 | http://www.@pple.com  s:http u:www. h:pple.com p:/ o:http://ppl.com
236 | http:@:www.example.com
237 | http:/@:www.example.com
238 | http://@:www.example.com
239 | http://:@www.example.com  s:http pass: h:www.example.com p:/ o:http://www.example.com
240 | 
241 | # Issue #13
242 | /some/path http://user:pass@example.org:21/smth s:http u:user pass:pass h:example.org port:21 p:/some/path
243 | 
244 | 


--------------------------------------------------------------------------------