Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(112)

Side by Side Diff: third_party/WebKit/LayoutTests/imported/web-platform-tests/resources/testharness.js

Issue 2004463002: Move remaining files from web-platform-tests/ and update remaining paths. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update paths in webkitpy/layout_tests/servers/. Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*global self*/
2 /*jshint latedef: nofunc*/
3 /*
4 Distributed under both the W3C Test Suite License [1] and the W3C
5 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
6 policies and contribution forms [3].
7
8 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
9 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
10 [3] http://www.w3.org/2004/10/27-testcases
11 */
12
13 /* Documentation is in docs/api.md */
14
15 (function ()
16 {
17 var debug = false;
18 // default timeout is 10 seconds, test can override if needed
19 var settings = {
20 output:true,
21 harness_timeout:{
22 "normal":10000,
23 "long":60000
24 },
25 test_timeout:null,
26 message_events: ["start", "test_state", "result", "completion"]
27 };
28
29 var xhtml_ns = "http://www.w3.org/1999/xhtml";
30
31 /*
32 * TestEnvironment is an abstraction for the environment in which the test
33 * harness is used. Each implementation of a test environment has to provide
34 * the following interface:
35 *
36 * interface TestEnvironment {
37 * // Invoked after the global 'tests' object has been created and it's
38 * // safe to call add_*_callback() to register event handlers.
39 * void on_tests_ready();
40 *
41 * // Invoked after setup() has been called to notify the test environment
42 * // of changes to the test harness properties.
43 * void on_new_harness_properties(object properties);
44 *
45 * // Should return a new unique default test name.
46 * DOMString next_default_test_name();
47 *
48 * // Should return the test harness timeout duration in milliseconds.
49 * float test_timeout();
50 *
51 * // Should return the global scope object.
52 * object global_scope();
53 * };
54 */
55
56 /*
57 * A test environment with a DOM. The global object is 'window'. By default
58 * test results are displayed in a table. Any parent windows receive
59 * callbacks or messages via postMessage() when test events occur. See
60 * apisample11.html and apisample12.html.
61 */
62 function WindowTestEnvironment() {
63 this.name_counter = 0;
64 this.window_cache = null;
65 this.output_handler = null;
66 this.all_loaded = false;
67 var this_obj = this;
68 this.message_events = [];
69
70 this.message_functions = {
71 start: [add_start_callback, remove_start_callback,
72 function (properties) {
73 this_obj._dispatch("start_callback", [properties],
74 {type: "start", properties: propertie s});
75 }],
76
77 test_state: [add_test_state_callback, remove_test_state_callback,
78 function(test) {
79 this_obj._dispatch("test_state_callback", [test],
80 {type: "test_state",
81 test: test.structured_clone()}) ;
82 }],
83 result: [add_result_callback, remove_result_callback,
84 function (test) {
85 this_obj.output_handler.show_status();
86 this_obj._dispatch("result_callback", [test],
87 {type: "result",
88 test: test.structured_clone()});
89 }],
90 completion: [add_completion_callback, remove_completion_callback,
91 function (tests, harness_status) {
92 var cloned_tests = map(tests, function(test) {
93 return test.structured_clone();
94 });
95 this_obj._dispatch("completion_callback", [tests, h arness_status],
96 {type: "complete",
97 tests: cloned_tests,
98 status: harness_status.structur ed_clone()});
99 }]
100 }
101
102 on_event(window, 'load', function() {
103 this_obj.all_loaded = true;
104 });
105 }
106
107 WindowTestEnvironment.prototype._dispatch = function(selector, callback_args , message_arg) {
108 this._forEach_windows(
109 function(w, same_origin) {
110 if (same_origin) {
111 try {
112 var has_selector = selector in w;
113 } catch(e) {
114 // If document.domain was set at some point same_ori gin can be
115 // wrong and the above will fail.
116 has_selector = false;
117 }
118 if (has_selector) {
119 try {
120 w[selector].apply(undefined, callback_args);
121 } catch (e) {
122 if (debug) {
123 throw e;
124 }
125 }
126 }
127 }
128 if (supports_post_message(w) && w !== self) {
129 w.postMessage(message_arg, "*");
130 }
131 });
132 };
133
134 WindowTestEnvironment.prototype._forEach_windows = function(callback) {
135 // Iterate of the the windows [self ... top, opener]. The callback is pa ssed
136 // two objects, the first one is the windows object itself, the second o ne
137 // is a boolean indicating whether or not its on the same origin as the
138 // current window.
139 var cache = this.window_cache;
140 if (!cache) {
141 cache = [[self, true]];
142 var w = self;
143 var i = 0;
144 var so;
145 var origins = location.ancestorOrigins;
146 while (w != w.parent) {
147 w = w.parent;
148 // In WebKit, calls to parent windows' properties that aren't on the same
149 // origin cause an error message to be displayed in the error co nsole but
150 // don't throw an exception. This is a deviation from the curren t HTML5
151 // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
152 // The problem with WebKit's behavior is that it pollutes the er ror console
153 // with error messages that can't be caught.
154 //
155 // This issue can be mitigated by relying on the (for now) propr ietary
156 // `location.ancestorOrigins` property which returns an ordered list of
157 // the origins of enclosing windows. See:
158 // http://trac.webkit.org/changeset/113945.
159 if (origins) {
160 so = (location.origin == origins[i]);
161 } else {
162 so = is_same_origin(w);
163 }
164 cache.push([w, so]);
165 i++;
166 }
167 w = window.opener;
168 if (w) {
169 // window.opener isn't included in the `location.ancestorOrigins ` prop.
170 // We'll just have to deal with a simple check and an error msg on WebKit
171 // browsers in this case.
172 cache.push([w, is_same_origin(w)]);
173 }
174 this.window_cache = cache;
175 }
176
177 forEach(cache,
178 function(a) {
179 callback.apply(null, a);
180 });
181 };
182
183 WindowTestEnvironment.prototype.on_tests_ready = function() {
184 var output = new Output();
185 this.output_handler = output;
186
187 var this_obj = this;
188
189 add_start_callback(function (properties) {
190 this_obj.output_handler.init(properties);
191 });
192
193 add_test_state_callback(function(test) {
194 this_obj.output_handler.show_status();
195 });
196
197 add_result_callback(function (test) {
198 this_obj.output_handler.show_status();
199 });
200
201 add_completion_callback(function (tests, harness_status) {
202 this_obj.output_handler.show_results(tests, harness_status);
203 });
204 this.setup_messages(settings.message_events);
205 };
206
207 WindowTestEnvironment.prototype.setup_messages = function(new_events) {
208 var this_obj = this;
209 forEach(settings.message_events, function(x) {
210 var current_dispatch = this_obj.message_events.indexOf(x) !== -1;
211 var new_dispatch = new_events.indexOf(x) !== -1;
212 if (!current_dispatch && new_dispatch) {
213 this_obj.message_functions[x][0](this_obj.message_functions[x][2 ]);
214 } else if (current_dispatch && !new_dispatch) {
215 this_obj.message_functions[x][1](this_obj.message_functions[x][2 ]);
216 }
217 });
218 this.message_events = new_events;
219 }
220
221 WindowTestEnvironment.prototype.next_default_test_name = function() {
222 //Don't use document.title to work around an Opera bug in XHTML document s
223 var title = document.getElementsByTagName("title")[0];
224 var prefix = (title && title.firstChild && title.firstChild.data) || "Un titled";
225 var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
226 this.name_counter++;
227 return prefix + suffix;
228 };
229
230 WindowTestEnvironment.prototype.on_new_harness_properties = function(propert ies) {
231 this.output_handler.setup(properties);
232 if (properties.hasOwnProperty("message_events")) {
233 this.setup_messages(properties.message_events);
234 }
235 };
236
237 WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
238 on_event(window, 'load', callback);
239 };
240
241 WindowTestEnvironment.prototype.test_timeout = function() {
242 var metas = document.getElementsByTagName("meta");
243 for (var i = 0; i < metas.length; i++) {
244 if (metas[i].name == "timeout") {
245 if (metas[i].content == "long") {
246 return settings.harness_timeout.long;
247 }
248 break;
249 }
250 }
251 return settings.harness_timeout.normal;
252 };
253
254 WindowTestEnvironment.prototype.global_scope = function() {
255 return window;
256 };
257
258 /*
259 * Base TestEnvironment implementation for a generic web worker.
260 *
261 * Workers accumulate test results. One or more clients can connect and
262 * retrieve results from a worker at any time.
263 *
264 * WorkerTestEnvironment supports communicating with a client via a
265 * MessagePort. The mechanism for determining the appropriate MessagePort
266 * for communicating with a client depends on the type of worker and is
267 * implemented by the various specializations of WorkerTestEnvironment
268 * below.
269 *
270 * A client document using testharness can use fetch_tests_from_worker() to
271 * retrieve results from a worker. See apisample16.html.
272 */
273 function WorkerTestEnvironment() {
274 this.name_counter = 0;
275 this.all_loaded = true;
276 this.message_list = [];
277 this.message_ports = [];
278 }
279
280 WorkerTestEnvironment.prototype._dispatch = function(message) {
281 this.message_list.push(message);
282 for (var i = 0; i < this.message_ports.length; ++i)
283 {
284 this.message_ports[i].postMessage(message);
285 }
286 };
287
288 // The only requirement is that port has a postMessage() method. It doesn't
289 // have to be an instance of a MessagePort, and often isn't.
290 WorkerTestEnvironment.prototype._add_message_port = function(port) {
291 this.message_ports.push(port);
292 for (var i = 0; i < this.message_list.length; ++i)
293 {
294 port.postMessage(this.message_list[i]);
295 }
296 };
297
298 WorkerTestEnvironment.prototype.next_default_test_name = function() {
299 var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
300 this.name_counter++;
301 return "Untitled" + suffix;
302 };
303
304 WorkerTestEnvironment.prototype.on_new_harness_properties = function() {};
305
306 WorkerTestEnvironment.prototype.on_tests_ready = function() {
307 var this_obj = this;
308 add_start_callback(
309 function(properties) {
310 this_obj._dispatch({
311 type: "start",
312 properties: properties,
313 });
314 });
315 add_test_state_callback(
316 function(test) {
317 this_obj._dispatch({
318 type: "test_state",
319 test: test.structured_clone()
320 });
321 });
322 add_result_callback(
323 function(test) {
324 this_obj._dispatch({
325 type: "result",
326 test: test.structured_clone()
327 });
328 });
329 add_completion_callback(
330 function(tests, harness_status) {
331 this_obj._dispatch({
332 type: "complete",
333 tests: map(tests,
334 function(test) {
335 return test.structured_clone();
336 }),
337 status: harness_status.structured_clone()
338 });
339 });
340 };
341
342 WorkerTestEnvironment.prototype.add_on_loaded_callback = function() {};
343
344 WorkerTestEnvironment.prototype.test_timeout = function() {
345 // Tests running in a worker don't have a default timeout. I.e. all
346 // worker tests behave as if settings.explicit_timeout is true.
347 return null;
348 };
349
350 WorkerTestEnvironment.prototype.global_scope = function() {
351 return self;
352 };
353
354 /*
355 * Dedicated web workers.
356 * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobal scope
357 *
358 * This class is used as the test_environment when testharness is running
359 * inside a dedicated worker.
360 */
361 function DedicatedWorkerTestEnvironment() {
362 WorkerTestEnvironment.call(this);
363 // self is an instance of DedicatedWorkerGlobalScope which exposes
364 // a postMessage() method for communicating via the message channel
365 // established when the worker is created.
366 this._add_message_port(self);
367 }
368 DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironme nt.prototype);
369
370 DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() {
371 WorkerTestEnvironment.prototype.on_tests_ready.call(this);
372 // In the absence of an onload notification, we a require dedicated
373 // workers to explicitly signal when the tests are done.
374 tests.wait_for_finish = true;
375 };
376
377 /*
378 * Shared web workers.
379 * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalsco pe
380 *
381 * This class is used as the test_environment when testharness is running
382 * inside a shared web worker.
383 */
384 function SharedWorkerTestEnvironment() {
385 WorkerTestEnvironment.call(this);
386 var this_obj = this;
387 // Shared workers receive message ports via the 'onconnect' event for
388 // each connection.
389 self.addEventListener("connect",
390 function(message_event) {
391 this_obj._add_message_port(message_event.source);
392 });
393 }
394 SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment. prototype);
395
396 SharedWorkerTestEnvironment.prototype.on_tests_ready = function() {
397 WorkerTestEnvironment.prototype.on_tests_ready.call(this);
398 // In the absence of an onload notification, we a require shared
399 // workers to explicitly signal when the tests are done.
400 tests.wait_for_finish = true;
401 };
402
403 /*
404 * Service workers.
405 * http://www.w3.org/TR/service-workers/
406 *
407 * This class is used as the test_environment when testharness is running
408 * inside a service worker.
409 */
410 function ServiceWorkerTestEnvironment() {
411 WorkerTestEnvironment.call(this);
412 this.all_loaded = false;
413 this.on_loaded_callback = null;
414 var this_obj = this;
415 self.addEventListener("message",
416 function(event) {
417 if (event.data.type && event.data.type === "connect") {
418 if (event.ports && event.ports[0]) {
419 // If a MessageChannel was passed, then use it to
420 // send results back to the main window. This
421 // allows the tests to work even if the browser
422 // does not fully support MessageEvent.source in
423 // ServiceWorkers yet.
424 this_obj._add_message_port(event.ports[0]);
425 event.ports[0].start();
426 } else {
427 // If there is no MessageChannel, then attempt to
428 // use the MessageEvent.source to send results
429 // back to the main window.
430 this_obj._add_message_port(event.source);
431 }
432 }
433 });
434
435 // The oninstall event is received after the service worker script and
436 // all imported scripts have been fetched and executed. It's the
437 // equivalent of an onload event for a document. All tests should have
438 // been added by the time this event is received, thus it's not
439 // necessary to wait until the onactivate event.
440 on_event(self, "install",
441 function(event) {
442 this_obj.all_loaded = true;
443 if (this_obj.on_loaded_callback) {
444 this_obj.on_loaded_callback();
445 }
446 });
447 }
448 ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment .prototype);
449
450 ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(cal lback) {
451 if (this.all_loaded) {
452 callback();
453 } else {
454 this.on_loaded_callback = callback;
455 }
456 };
457
458 function create_test_environment() {
459 if ('document' in self) {
460 return new WindowTestEnvironment();
461 }
462 if ('DedicatedWorkerGlobalScope' in self &&
463 self instanceof DedicatedWorkerGlobalScope) {
464 return new DedicatedWorkerTestEnvironment();
465 }
466 if ('SharedWorkerGlobalScope' in self &&
467 self instanceof SharedWorkerGlobalScope) {
468 return new SharedWorkerTestEnvironment();
469 }
470 if ('ServiceWorkerGlobalScope' in self &&
471 self instanceof ServiceWorkerGlobalScope) {
472 return new ServiceWorkerTestEnvironment();
473 }
474 throw new Error("Unsupported test environment");
475 }
476
477 var test_environment = create_test_environment();
478
479 function is_shared_worker(worker) {
480 return 'SharedWorker' in self && worker instanceof SharedWorker;
481 }
482
483 function is_service_worker(worker) {
484 return 'ServiceWorker' in self && worker instanceof ServiceWorker;
485 }
486
487 /*
488 * API functions
489 */
490
491 function test(func, name, properties)
492 {
493 var test_name = name ? name : test_environment.next_default_test_name();
494 properties = properties ? properties : {};
495 var test_obj = new Test(test_name, properties);
496 test_obj.step(func, test_obj, test_obj);
497 if (test_obj.phase === test_obj.phases.STARTED) {
498 test_obj.done();
499 }
500 }
501
502 function async_test(func, name, properties)
503 {
504 if (typeof func !== "function") {
505 properties = name;
506 name = func;
507 func = null;
508 }
509 var test_name = name ? name : test_environment.next_default_test_name();
510 properties = properties ? properties : {};
511 var test_obj = new Test(test_name, properties);
512 if (func) {
513 test_obj.step(func, test_obj, test_obj);
514 }
515 return test_obj;
516 }
517
518 function promise_test(func, name, properties) {
519 var test = async_test(name, properties);
520 // If there is no promise tests queue make one.
521 test.step(function() {
522 if (!tests.promise_tests) {
523 tests.promise_tests = Promise.resolve();
524 }
525 });
526 tests.promise_tests = tests.promise_tests.then(function() {
527 return Promise.resolve(test.step(func, test, test))
528 .then(
529 function() {
530 test.done();
531 })
532 .catch(test.step_func(
533 function(value) {
534 if (value instanceof AssertionError) {
535 throw value;
536 }
537 assert(false, "promise_test", null,
538 "Unhandled rejection with value: ${value}", {valu e:value});
539 }));
540 });
541 }
542
543 function promise_rejects(test, expected, promise, description) {
544 return promise.then(test.unreached_func("Should have rejected: " + descr iption)).catch(function(e) {
545 assert_throws(expected, function() { throw e }, description);
546 });
547 }
548
549 /**
550 * This constructor helper allows DOM events to be handled using Promises,
551 * which can make it a lot easier to test a very specific series of events,
552 * including ensuring that unexpected events are not fired at any point.
553 */
554 function EventWatcher(test, watchedNode, eventTypes)
555 {
556 if (typeof eventTypes == 'string') {
557 eventTypes = [eventTypes];
558 }
559
560 var waitingFor = null;
561
562 var eventHandler = test.step_func(function(evt) {
563 assert_true(!!waitingFor,
564 'Not expecting event, but got ' + evt.type + ' event');
565 assert_equals(evt.type, waitingFor.types[0],
566 'Expected ' + waitingFor.types[0] + ' event, but got ' +
567 evt.type + ' event instead');
568 if (waitingFor.types.length > 1) {
569 // Pop first event from array
570 waitingFor.types.shift();
571 return;
572 }
573 // We need to null out waitingFor before calling the resolve functio n
574 // since the Promise's resolve handlers may call wait_for() which wi ll
575 // need to set waitingFor.
576 var resolveFunc = waitingFor.resolve;
577 waitingFor = null;
578 resolveFunc(evt);
579 });
580
581 for (var i = 0; i < eventTypes.length; i++) {
582 watchedNode.addEventListener(eventTypes[i], eventHandler);
583 }
584
585 /**
586 * Returns a Promise that will resolve after the specified event or
587 * series of events has occured.
588 */
589 this.wait_for = function(types) {
590 if (waitingFor) {
591 return Promise.reject('Already waiting for an event or events');
592 }
593 if (typeof types == 'string') {
594 types = [types];
595 }
596 return new Promise(function(resolve, reject) {
597 waitingFor = {
598 types: types,
599 resolve: resolve,
600 reject: reject
601 };
602 });
603 };
604
605 function stop_watching() {
606 for (var i = 0; i < eventTypes.length; i++) {
607 watchedNode.removeEventListener(eventTypes[i], eventHandler);
608 }
609 };
610
611 test.add_cleanup(stop_watching);
612
613 return this;
614 }
615 expose(EventWatcher, 'EventWatcher');
616
617 function setup(func_or_properties, maybe_properties)
618 {
619 var func = null;
620 var properties = {};
621 if (arguments.length === 2) {
622 func = func_or_properties;
623 properties = maybe_properties;
624 } else if (func_or_properties instanceof Function) {
625 func = func_or_properties;
626 } else {
627 properties = func_or_properties;
628 }
629 tests.setup(func, properties);
630 test_environment.on_new_harness_properties(properties);
631 }
632
633 function done() {
634 if (tests.tests.length === 0) {
635 tests.set_file_is_test();
636 }
637 if (tests.file_is_test) {
638 tests.tests[0].done();
639 }
640 tests.end_wait();
641 }
642
643 function generate_tests(func, args, properties) {
644 forEach(args, function(x, i)
645 {
646 var name = x[0];
647 test(function()
648 {
649 func.apply(this, x.slice(1));
650 },
651 name,
652 Array.isArray(properties) ? properties[i] : properties) ;
653 });
654 }
655
656 function on_event(object, event, callback)
657 {
658 object.addEventListener(event, callback, false);
659 }
660
661 function step_timeout(f, t) {
662 var outer_this = this;
663 var args = Array.prototype.slice.call(arguments, 2);
664 return setTimeout(function() {
665 f.apply(outer_this, args);
666 }, t * tests.timeout_multiplier);
667 }
668
669 expose(test, 'test');
670 expose(async_test, 'async_test');
671 expose(promise_test, 'promise_test');
672 expose(promise_rejects, 'promise_rejects');
673 expose(generate_tests, 'generate_tests');
674 expose(setup, 'setup');
675 expose(done, 'done');
676 expose(on_event, 'on_event');
677 expose(step_timeout, 'step_timeout');
678
679 /*
680 * Return a string truncated to the given length, with ... added at the end
681 * if it was longer.
682 */
683 function truncate(s, len)
684 {
685 if (s.length > len) {
686 return s.substring(0, len - 3) + "...";
687 }
688 return s;
689 }
690
691 /*
692 * Return true if object is probably a Node object.
693 */
694 function is_node(object)
695 {
696 // I use duck-typing instead of instanceof, because
697 // instanceof doesn't work if the node is from another window (like an
698 // iframe's contentWindow):
699 // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
700 try {
701 var has_node_properties = ("nodeType" in object &&
702 "nodeName" in object &&
703 "nodeValue" in object &&
704 "childNodes" in object);
705 } catch (e) {
706 // We're probably cross-origin, which means we aren't a node
707 return false;
708 }
709
710 if (has_node_properties) {
711 try {
712 object.nodeType;
713 } catch (e) {
714 // The object is probably Node.prototype or another prototype
715 // object that inherits from it, and not a Node instance.
716 return false;
717 }
718 return true;
719 }
720 return false;
721 }
722
723 var replacements = {
724 "0": "0",
725 "1": "x01",
726 "2": "x02",
727 "3": "x03",
728 "4": "x04",
729 "5": "x05",
730 "6": "x06",
731 "7": "x07",
732 "8": "b",
733 "9": "t",
734 "10": "n",
735 "11": "v",
736 "12": "f",
737 "13": "r",
738 "14": "x0e",
739 "15": "x0f",
740 "16": "x10",
741 "17": "x11",
742 "18": "x12",
743 "19": "x13",
744 "20": "x14",
745 "21": "x15",
746 "22": "x16",
747 "23": "x17",
748 "24": "x18",
749 "25": "x19",
750 "26": "x1a",
751 "27": "x1b",
752 "28": "x1c",
753 "29": "x1d",
754 "30": "x1e",
755 "31": "x1f",
756 "0xfffd": "ufffd",
757 "0xfffe": "ufffe",
758 "0xffff": "uffff",
759 };
760
761 /*
762 * Convert a value to a nice, human-readable string
763 */
764 function format_value(val, seen)
765 {
766 if (!seen) {
767 seen = [];
768 }
769 if (typeof val === "object" && val !== null) {
770 if (seen.indexOf(val) >= 0) {
771 return "[...]";
772 }
773 seen.push(val);
774 }
775 if (Array.isArray(val)) {
776 return "[" + val.map(function(x) {return format_value(x, seen);}).jo in(", ") + "]";
777 }
778
779 switch (typeof val) {
780 case "string":
781 val = val.replace("\\", "\\\\");
782 for (var p in replacements) {
783 var replace = "\\" + replacements[p];
784 val = val.replace(RegExp(String.fromCharCode(p), "g"), replace);
785 }
786 return '"' + val.replace(/"/g, '\\"') + '"';
787 case "boolean":
788 case "undefined":
789 return String(val);
790 case "number":
791 // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
792 // special-case.
793 if (val === -0 && 1/val === -Infinity) {
794 return "-0";
795 }
796 return String(val);
797 case "object":
798 if (val === null) {
799 return "null";
800 }
801
802 // Special-case Node objects, since those come up a lot in my tests. I
803 // ignore namespaces.
804 if (is_node(val)) {
805 switch (val.nodeType) {
806 case Node.ELEMENT_NODE:
807 var ret = "<" + val.localName;
808 for (var i = 0; i < val.attributes.length; i++) {
809 ret += " " + val.attributes[i].name + '="' + val.attribu tes[i].value + '"';
810 }
811 ret += ">" + val.innerHTML + "</" + val.localName + ">";
812 return "Element node " + truncate(ret, 60);
813 case Node.TEXT_NODE:
814 return 'Text node "' + truncate(val.data, 60) + '"';
815 case Node.PROCESSING_INSTRUCTION_NODE:
816 return "ProcessingInstruction node with target " + format_va lue(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 6 0));
817 case Node.COMMENT_NODE:
818 return "Comment node <!--" + truncate(val.data, 60) + "-->";
819 case Node.DOCUMENT_NODE:
820 return "Document node with " + val.childNodes.length + (val. childNodes.length == 1 ? " child" : " children");
821 case Node.DOCUMENT_TYPE_NODE:
822 return "DocumentType node";
823 case Node.DOCUMENT_FRAGMENT_NODE:
824 return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
825 default:
826 return "Node object of unknown type";
827 }
828 }
829
830 /* falls through */
831 default:
832 try {
833 return typeof val + ' "' + truncate(String(val), 60) + '"';
834 } catch(e) {
835 return ("[stringifying object threw " + String(e) +
836 " with type " + String(typeof e) + "]");
837 }
838 }
839 }
840 expose(format_value, "format_value");
841
842 /*
843 * Assertions
844 */
845
846 function assert_true(actual, description)
847 {
848 assert(actual === true, "assert_true", description,
849 "expected true got ${actual}", {actual:actual});
850 }
851 expose(assert_true, "assert_true");
852
853 function assert_false(actual, description)
854 {
855 assert(actual === false, "assert_false", description,
856 "expected false got ${actual}", {actual:actual} );
857 }
858 expose(assert_false, "assert_false");
859
860 function same_value(x, y) {
861 if (y !== y) {
862 //NaN case
863 return x !== x;
864 }
865 if (x === 0 && y === 0) {
866 //Distinguish +0 and -0
867 return 1/x === 1/y;
868 }
869 return x === y;
870 }
871
872 function assert_equals(actual, expected, description)
873 {
874 /*
875 * Test if two primitives are equal or two objects
876 * are the same object
877 */
878 if (typeof actual != typeof expected) {
879 assert(false, "assert_equals", description,
880 "expected (" + typeof expected + ") ${expected} but go t (" + typeof actual + ") ${actual}",
881 {expected:expected, actual:actual});
882 return;
883 }
884 assert(same_value(actual, expected), "assert_equals", description,
885 "expected ${expected} but got ${act ual}",
886 {expected:expected, actual:actual}) ;
887 }
888 expose(assert_equals, "assert_equals");
889
890 function assert_not_equals(actual, expected, description)
891 {
892 /*
893 * Test if two primitives are unequal or two objects
894 * are different objects
895 */
896 assert(!same_value(actual, expected), "assert_not_equals", description,
897 "got disallowed value ${actual}",
898 {actual:actual});
899 }
900 expose(assert_not_equals, "assert_not_equals");
901
902 function assert_in_array(actual, expected, description)
903 {
904 assert(expected.indexOf(actual) != -1, "assert_in_array", description,
905 "value ${actual} not in array ${e xpected}",
906 {actual:actual, expected:expected });
907 }
908 expose(assert_in_array, "assert_in_array");
909
910 function assert_object_equals(actual, expected, description)
911 {
912 //This needs to be improved a great deal
913 function check_equal(actual, expected, stack)
914 {
915 stack.push(actual);
916
917 var p;
918 for (p in actual) {
919 assert(expected.hasOwnProperty(p), "assert_object_equals", desc ription,
920 "unexpected property ${p}", {p:p});
921
922 if (typeof actual[p] === "object" && actual[p] !== null) {
923 if (stack.indexOf(actual[p]) === -1) {
924 check_equal(actual[p], expected[p], stack);
925 }
926 } else {
927 assert(same_value(actual[p], expected[p]), "assert_object_e quals", description,
928 "property ${p} expected $ {expected} got ${actual}",
929 {p:p, expected:expected, actual:actual});
930 }
931 }
932 for (p in expected) {
933 assert(actual.hasOwnProperty(p),
934 "assert_object_equals", description,
935 "expected property ${p} missing", {p:p});
936 }
937 stack.pop();
938 }
939 check_equal(actual, expected, []);
940 }
941 expose(assert_object_equals, "assert_object_equals");
942
943 function assert_array_equals(actual, expected, description)
944 {
945 assert(actual.length === expected.length,
946 "assert_array_equals", description,
947 "lengths differ, expected ${expected} got ${actual}",
948 {expected:expected.length, actual:actual.length});
949
950 for (var i = 0; i < actual.length; i++) {
951 assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
952 "assert_array_equals", description,
953 "property ${i}, property expected to be ${expected} but was $ {actual}",
954 {i:i, expected:expected.hasOwnProperty(i) ? "present" : "miss ing",
955 actual:actual.hasOwnProperty(i) ? "present" : "missing"});
956 assert(same_value(expected[i], actual[i]),
957 "assert_array_equals", description,
958 "property ${i}, expected ${expected} but got ${actual}",
959 {i:i, expected:expected[i], actual:actual[i]});
960 }
961 }
962 expose(assert_array_equals, "assert_array_equals");
963
964 function assert_approx_equals(actual, expected, epsilon, description)
965 {
966 /*
967 * Test if two primitive numbers are equal withing +/- epsilon
968 */
969 assert(typeof actual === "number",
970 "assert_approx_equals", description,
971 "expected a number but got a ${type_actual}",
972 {type_actual:typeof actual});
973
974 assert(Math.abs(actual - expected) <= epsilon,
975 "assert_approx_equals", description,
976 "expected ${expected} +/- ${epsilon} but got ${actual}",
977 {expected:expected, actual:actual, epsilon:epsilon});
978 }
979 expose(assert_approx_equals, "assert_approx_equals");
980
981 function assert_less_than(actual, expected, description)
982 {
983 /*
984 * Test if a primitive number is less than another
985 */
986 assert(typeof actual === "number",
987 "assert_less_than", description,
988 "expected a number but got a ${type_actual}",
989 {type_actual:typeof actual});
990
991 assert(actual < expected,
992 "assert_less_than", description,
993 "expected a number less than ${expected} but got ${actual}",
994 {expected:expected, actual:actual});
995 }
996 expose(assert_less_than, "assert_less_than");
997
998 function assert_greater_than(actual, expected, description)
999 {
1000 /*
1001 * Test if a primitive number is greater than another
1002 */
1003 assert(typeof actual === "number",
1004 "assert_greater_than", description,
1005 "expected a number but got a ${type_actual}",
1006 {type_actual:typeof actual});
1007
1008 assert(actual > expected,
1009 "assert_greater_than", description,
1010 "expected a number greater than ${expected} but got ${actual}",
1011 {expected:expected, actual:actual});
1012 }
1013 expose(assert_greater_than, "assert_greater_than");
1014
1015 function assert_between_exclusive(actual, lower, upper, description)
1016 {
1017 /*
1018 * Test if a primitive number is between two others
1019 */
1020 assert(typeof actual === "number",
1021 "assert_between_exclusive", description,
1022 "expected a number but got a ${type_actual}",
1023 {type_actual:typeof actual});
1024
1025 assert(actual > lower && actual < upper,
1026 "assert_between_exclusive", description,
1027 "expected a number greater than ${lower} " +
1028 "and less than ${upper} but got ${actual}",
1029 {lower:lower, upper:upper, actual:actual});
1030 }
1031 expose(assert_between_exclusive, "assert_between_exclusive");
1032
1033 function assert_less_than_equal(actual, expected, description)
1034 {
1035 /*
1036 * Test if a primitive number is less than or equal to another
1037 */
1038 assert(typeof actual === "number",
1039 "assert_less_than_equal", description,
1040 "expected a number but got a ${type_actual}",
1041 {type_actual:typeof actual});
1042
1043 assert(actual <= expected,
1044 "assert_less_than_equal", description,
1045 "expected a number less than or equal to ${expected} but got ${ac tual}",
1046 {expected:expected, actual:actual});
1047 }
1048 expose(assert_less_than_equal, "assert_less_than_equal");
1049
1050 function assert_greater_than_equal(actual, expected, description)
1051 {
1052 /*
1053 * Test if a primitive number is greater than or equal to another
1054 */
1055 assert(typeof actual === "number",
1056 "assert_greater_than_equal", description,
1057 "expected a number but got a ${type_actual}",
1058 {type_actual:typeof actual});
1059
1060 assert(actual >= expected,
1061 "assert_greater_than_equal", description,
1062 "expected a number greater than or equal to ${expected} but got $ {actual}",
1063 {expected:expected, actual:actual});
1064 }
1065 expose(assert_greater_than_equal, "assert_greater_than_equal");
1066
1067 function assert_between_inclusive(actual, lower, upper, description)
1068 {
1069 /*
1070 * Test if a primitive number is between to two others or equal to eithe r of them
1071 */
1072 assert(typeof actual === "number",
1073 "assert_between_inclusive", description,
1074 "expected a number but got a ${type_actual}",
1075 {type_actual:typeof actual});
1076
1077 assert(actual >= lower && actual <= upper,
1078 "assert_between_inclusive", description,
1079 "expected a number greater than or equal to ${lower} " +
1080 "and less than or equal to ${upper} but got ${actual}",
1081 {lower:lower, upper:upper, actual:actual});
1082 }
1083 expose(assert_between_inclusive, "assert_between_inclusive");
1084
1085 function assert_regexp_match(actual, expected, description) {
1086 /*
1087 * Test if a string (actual) matches a regexp (expected)
1088 */
1089 assert(expected.test(actual),
1090 "assert_regexp_match", description,
1091 "expected ${expected} but got ${actual}",
1092 {expected:expected, actual:actual});
1093 }
1094 expose(assert_regexp_match, "assert_regexp_match");
1095
1096 function assert_class_string(object, class_string, description) {
1097 assert_equals({}.toString.call(object), "[object " + class_string + "]",
1098 description);
1099 }
1100 expose(assert_class_string, "assert_class_string");
1101
1102
1103 function _assert_own_property(name) {
1104 return function(object, property_name, description)
1105 {
1106 assert(object.hasOwnProperty(property_name),
1107 name, description,
1108 "expected property ${p} missing", {p:property_name});
1109 };
1110 }
1111 expose(_assert_own_property("assert_exists"), "assert_exists");
1112 expose(_assert_own_property("assert_own_property"), "assert_own_property");
1113
1114 function assert_not_exists(object, property_name, description)
1115 {
1116 assert(!object.hasOwnProperty(property_name),
1117 "assert_not_exists", description,
1118 "unexpected property ${p} found", {p:property_name});
1119 }
1120 expose(assert_not_exists, "assert_not_exists");
1121
1122 function _assert_inherits(name) {
1123 return function (object, property_name, description)
1124 {
1125 assert(typeof object === "object" || typeof object === "function",
1126 name, description,
1127 "provided value is not an object");
1128
1129 assert("hasOwnProperty" in object,
1130 name, description,
1131 "provided value is an object but has no hasOwnProperty method ");
1132
1133 assert(!object.hasOwnProperty(property_name),
1134 name, description,
1135 "property ${p} found on object expected in prototype chain",
1136 {p:property_name});
1137
1138 assert(property_name in object,
1139 name, description,
1140 "property ${p} not found in prototype chain",
1141 {p:property_name});
1142 };
1143 }
1144 expose(_assert_inherits("assert_inherits"), "assert_inherits");
1145 expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
1146
1147 function assert_readonly(object, property_name, description)
1148 {
1149 var initial_value = object[property_name];
1150 try {
1151 //Note that this can have side effects in the case where
1152 //the property has PutForwards
1153 object[property_name] = initial_value + "a"; //XXX use some other v alue here?
1154 assert(same_value(object[property_name], initial_value),
1155 "assert_readonly", description,
1156 "changing property ${p} succeeded",
1157 {p:property_name});
1158 } finally {
1159 object[property_name] = initial_value;
1160 }
1161 }
1162 expose(assert_readonly, "assert_readonly");
1163
1164 function assert_throws(code, func, description)
1165 {
1166 try {
1167 func.call(this);
1168 assert(false, "assert_throws", description,
1169 "${func} did not throw", {func:func});
1170 } catch (e) {
1171 if (e instanceof AssertionError) {
1172 throw e;
1173 }
1174 if (code === null) {
1175 return;
1176 }
1177 if (typeof code === "object") {
1178 assert(typeof e == "object" && "name" in e && e.name == code.nam e,
1179 "assert_throws", description,
1180 "${func} threw ${actual} (${actual_name}) expected ${expe cted} (${expected_name})",
1181 {func:func, actual:e, actual_name:e.name,
1182 expected:code,
1183 expected_name:code.name});
1184 return;
1185 }
1186
1187 var code_name_map = {
1188 INDEX_SIZE_ERR: 'IndexSizeError',
1189 HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
1190 WRONG_DOCUMENT_ERR: 'WrongDocumentError',
1191 INVALID_CHARACTER_ERR: 'InvalidCharacterError',
1192 NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
1193 NOT_FOUND_ERR: 'NotFoundError',
1194 NOT_SUPPORTED_ERR: 'NotSupportedError',
1195 INUSE_ATTRIBUTE_ERR: 'InUseAttributeError',
1196 INVALID_STATE_ERR: 'InvalidStateError',
1197 SYNTAX_ERR: 'SyntaxError',
1198 INVALID_MODIFICATION_ERR: 'InvalidModificationError',
1199 NAMESPACE_ERR: 'NamespaceError',
1200 INVALID_ACCESS_ERR: 'InvalidAccessError',
1201 TYPE_MISMATCH_ERR: 'TypeMismatchError',
1202 SECURITY_ERR: 'SecurityError',
1203 NETWORK_ERR: 'NetworkError',
1204 ABORT_ERR: 'AbortError',
1205 URL_MISMATCH_ERR: 'URLMismatchError',
1206 QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
1207 TIMEOUT_ERR: 'TimeoutError',
1208 INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
1209 DATA_CLONE_ERR: 'DataCloneError'
1210 };
1211
1212 var name = code in code_name_map ? code_name_map[code] : code;
1213
1214 var name_code_map = {
1215 IndexSizeError: 1,
1216 HierarchyRequestError: 3,
1217 WrongDocumentError: 4,
1218 InvalidCharacterError: 5,
1219 NoModificationAllowedError: 7,
1220 NotFoundError: 8,
1221 NotSupportedError: 9,
1222 InUseAttributeError: 10,
1223 InvalidStateError: 11,
1224 SyntaxError: 12,
1225 InvalidModificationError: 13,
1226 NamespaceError: 14,
1227 InvalidAccessError: 15,
1228 TypeMismatchError: 17,
1229 SecurityError: 18,
1230 NetworkError: 19,
1231 AbortError: 20,
1232 URLMismatchError: 21,
1233 QuotaExceededError: 22,
1234 TimeoutError: 23,
1235 InvalidNodeTypeError: 24,
1236 DataCloneError: 25,
1237
1238 EncodingError: 0,
1239 NotReadableError: 0,
1240 UnknownError: 0,
1241 ConstraintError: 0,
1242 DataError: 0,
1243 TransactionInactiveError: 0,
1244 ReadOnlyError: 0,
1245 VersionError: 0,
1246 OperationError: 0,
1247 };
1248
1249 if (!(name in name_code_map)) {
1250 throw new AssertionError('Test bug: unrecognized DOMException co de "' + code + '" passed to assert_throws()');
1251 }
1252
1253 var required_props = { code: name_code_map[name] };
1254
1255 if (required_props.code === 0 ||
1256 (typeof e == "object" &&
1257 "name" in e &&
1258 e.name !== e.name.toUpperCase() &&
1259 e.name !== "DOMException")) {
1260 // New style exception: also test the name property.
1261 required_props.name = name;
1262 }
1263
1264 //We'd like to test that e instanceof the appropriate interface,
1265 //but we can't, because we don't know what window it was created
1266 //in. It might be an instanceof the appropriate interface on some
1267 //unknown other window. TODO: Work around this somehow?
1268
1269 assert(typeof e == "object",
1270 "assert_throws", description,
1271 "${func} threw ${e} with type ${type}, not an object",
1272 {func:func, e:e, type:typeof e});
1273
1274 for (var prop in required_props) {
1275 assert(typeof e == "object" && prop in e && e[prop] == required_ props[prop],
1276 "assert_throws", description,
1277 "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
1278 {func:func, e:e, prop:prop, actual:e[prop], expected:requ ired_props[prop]});
1279 }
1280 }
1281 }
1282 expose(assert_throws, "assert_throws");
1283
1284 function assert_unreached(description) {
1285 assert(false, "assert_unreached", description,
1286 "Reached unreachable code");
1287 }
1288 expose(assert_unreached, "assert_unreached");
1289
1290 function assert_any(assert_func, actual, expected_array)
1291 {
1292 var args = [].slice.call(arguments, 3);
1293 var errors = [];
1294 var passed = false;
1295 forEach(expected_array,
1296 function(expected)
1297 {
1298 try {
1299 assert_func.apply(this, [actual, expected].concat(args)) ;
1300 passed = true;
1301 } catch (e) {
1302 errors.push(e.message);
1303 }
1304 });
1305 if (!passed) {
1306 throw new AssertionError(errors.join("\n\n"));
1307 }
1308 }
1309 expose(assert_any, "assert_any");
1310
1311 function Test(name, properties)
1312 {
1313 if (tests.file_is_test && tests.tests.length) {
1314 throw new Error("Tried to create a test with file_is_test");
1315 }
1316 this.name = name;
1317
1318 this.phase = this.phases.INITIAL;
1319
1320 this.status = this.NOTRUN;
1321 this.timeout_id = null;
1322 this.index = null;
1323
1324 this.properties = properties;
1325 var timeout = properties.timeout ? properties.timeout : settings.test_ti meout;
1326 if (timeout !== null) {
1327 this.timeout_length = timeout * tests.timeout_multiplier;
1328 } else {
1329 this.timeout_length = null;
1330 }
1331
1332 this.message = null;
1333 this.stack = null;
1334
1335 this.steps = [];
1336
1337 this.cleanup_callbacks = [];
1338
1339 tests.push(this);
1340 }
1341
1342 Test.statuses = {
1343 PASS:0,
1344 FAIL:1,
1345 TIMEOUT:2,
1346 NOTRUN:3
1347 };
1348
1349 Test.prototype = merge({}, Test.statuses);
1350
1351 Test.prototype.phases = {
1352 INITIAL:0,
1353 STARTED:1,
1354 HAS_RESULT:2,
1355 COMPLETE:3
1356 };
1357
1358 Test.prototype.structured_clone = function()
1359 {
1360 if (!this._structured_clone) {
1361 var msg = this.message;
1362 msg = msg ? String(msg) : msg;
1363 this._structured_clone = merge({
1364 name:String(this.name),
1365 properties:merge({}, this.properties),
1366 }, Test.statuses);
1367 }
1368 this._structured_clone.status = this.status;
1369 this._structured_clone.message = this.message;
1370 this._structured_clone.stack = this.stack;
1371 this._structured_clone.index = this.index;
1372 return this._structured_clone;
1373 };
1374
1375 Test.prototype.step = function(func, this_obj)
1376 {
1377 if (this.phase > this.phases.STARTED) {
1378 return;
1379 }
1380 this.phase = this.phases.STARTED;
1381 //If we don't get a result before the harness times out that will be a t est timout
1382 this.set_status(this.TIMEOUT, "Test timed out");
1383
1384 tests.started = true;
1385 tests.notify_test_state(this);
1386
1387 if (this.timeout_id === null) {
1388 this.set_timeout();
1389 }
1390
1391 this.steps.push(func);
1392
1393 if (arguments.length === 1) {
1394 this_obj = this;
1395 }
1396
1397 try {
1398 return func.apply(this_obj, Array.prototype.slice.call(arguments, 2) );
1399 } catch (e) {
1400 if (this.phase >= this.phases.HAS_RESULT) {
1401 return;
1402 }
1403 var message = String((typeof e === "object" && e !== null) ? e.messa ge : e);
1404 var stack = e.stack ? e.stack : null;
1405
1406 this.set_status(this.FAIL, message, stack);
1407 this.phase = this.phases.HAS_RESULT;
1408 this.done();
1409 }
1410 };
1411
1412 Test.prototype.step_func = function(func, this_obj)
1413 {
1414 var test_this = this;
1415
1416 if (arguments.length === 1) {
1417 this_obj = test_this;
1418 }
1419
1420 return function()
1421 {
1422 return test_this.step.apply(test_this, [func, this_obj].concat(
1423 Array.prototype.slice.call(arguments)));
1424 };
1425 };
1426
1427 Test.prototype.step_func_done = function(func, this_obj)
1428 {
1429 var test_this = this;
1430
1431 if (arguments.length === 1) {
1432 this_obj = test_this;
1433 }
1434
1435 return function()
1436 {
1437 if (func) {
1438 test_this.step.apply(test_this, [func, this_obj].concat(
1439 Array.prototype.slice.call(arguments)));
1440 }
1441 test_this.done();
1442 };
1443 };
1444
1445 Test.prototype.unreached_func = function(description)
1446 {
1447 return this.step_func(function() {
1448 assert_unreached(description);
1449 });
1450 };
1451
1452 Test.prototype.step_timeout = function(f, timeout) {
1453 var test_this = this;
1454 var args = Array.prototype.slice.call(arguments, 2);
1455 return setTimeout(this.step_func(function() {
1456 return f.apply(test_this, args);
1457 }), timeout * tests.timeout_multiplier);
1458 }
1459
1460 Test.prototype.add_cleanup = function(callback) {
1461 this.cleanup_callbacks.push(callback);
1462 };
1463
1464 Test.prototype.force_timeout = function() {
1465 this.set_status(this.TIMEOUT);
1466 this.phase = this.phases.HAS_RESULT;
1467 };
1468
1469 Test.prototype.set_timeout = function()
1470 {
1471 if (this.timeout_length !== null) {
1472 var this_obj = this;
1473 this.timeout_id = setTimeout(function()
1474 {
1475 this_obj.timeout();
1476 }, this.timeout_length);
1477 }
1478 };
1479
1480 Test.prototype.set_status = function(status, message, stack)
1481 {
1482 this.status = status;
1483 this.message = message;
1484 this.stack = stack ? stack : null;
1485 };
1486
1487 Test.prototype.timeout = function()
1488 {
1489 this.timeout_id = null;
1490 this.set_status(this.TIMEOUT, "Test timed out");
1491 this.phase = this.phases.HAS_RESULT;
1492 this.done();
1493 };
1494
1495 Test.prototype.done = function()
1496 {
1497 if (this.phase == this.phases.COMPLETE) {
1498 return;
1499 }
1500
1501 if (this.phase <= this.phases.STARTED) {
1502 this.set_status(this.PASS, null);
1503 }
1504
1505 this.phase = this.phases.COMPLETE;
1506
1507 clearTimeout(this.timeout_id);
1508 tests.result(this);
1509 this.cleanup();
1510 };
1511
1512 Test.prototype.cleanup = function() {
1513 forEach(this.cleanup_callbacks,
1514 function(cleanup_callback) {
1515 cleanup_callback();
1516 });
1517 };
1518
1519 /*
1520 * A RemoteTest object mirrors a Test object on a remote worker. The
1521 * associated RemoteWorker updates the RemoteTest object in response to
1522 * received events. In turn, the RemoteTest object replicates these events
1523 * on the local document. This allows listeners (test result reporting
1524 * etc..) to transparently handle local and remote events.
1525 */
1526 function RemoteTest(clone) {
1527 var this_obj = this;
1528 Object.keys(clone).forEach(
1529 function(key) {
1530 this_obj[key] = clone[key];
1531 });
1532 this.index = null;
1533 this.phase = this.phases.INITIAL;
1534 this.update_state_from(clone);
1535 tests.push(this);
1536 }
1537
1538 RemoteTest.prototype.structured_clone = function() {
1539 var clone = {};
1540 Object.keys(this).forEach(
1541 (function(key) {
1542 if (typeof(this[key]) === "object") {
1543 clone[key] = merge({}, this[key]);
1544 } else {
1545 clone[key] = this[key];
1546 }
1547 }).bind(this));
1548 clone.phases = merge({}, this.phases);
1549 return clone;
1550 };
1551
1552 RemoteTest.prototype.cleanup = function() {};
1553 RemoteTest.prototype.phases = Test.prototype.phases;
1554 RemoteTest.prototype.update_state_from = function(clone) {
1555 this.status = clone.status;
1556 this.message = clone.message;
1557 this.stack = clone.stack;
1558 if (this.phase === this.phases.INITIAL) {
1559 this.phase = this.phases.STARTED;
1560 }
1561 };
1562 RemoteTest.prototype.done = function() {
1563 this.phase = this.phases.COMPLETE;
1564 }
1565
1566 /*
1567 * A RemoteWorker listens for test events from a worker. These events are
1568 * then used to construct and maintain RemoteTest objects that mirror the
1569 * tests running on the remote worker.
1570 */
1571 function RemoteWorker(worker) {
1572 this.running = true;
1573 this.tests = new Array();
1574
1575 var this_obj = this;
1576 worker.onerror = function(error) { this_obj.worker_error(error); };
1577
1578 var message_port;
1579
1580 if (is_service_worker(worker)) {
1581 if (window.MessageChannel) {
1582 // The ServiceWorker's implicit MessagePort is currently not
1583 // reliably accessible from the ServiceWorkerGlobalScope due to
1584 // Blink setting MessageEvent.source to null for messages sent
1585 // via ServiceWorker.postMessage(). Until that's resolved,
1586 // create an explicit MessageChannel and pass one end to the
1587 // worker.
1588 var message_channel = new MessageChannel();
1589 message_port = message_channel.port1;
1590 message_port.start();
1591 worker.postMessage({type: "connect"}, [message_channel.port2]);
1592 } else {
1593 // If MessageChannel is not available, then try the
1594 // ServiceWorker.postMessage() approach using MessageEvent.sourc e
1595 // on the other end.
1596 message_port = navigator.serviceWorker;
1597 worker.postMessage({type: "connect"});
1598 }
1599 } else if (is_shared_worker(worker)) {
1600 message_port = worker.port;
1601 } else {
1602 message_port = worker;
1603 }
1604
1605 // Keeping a reference to the worker until worker_done() is seen
1606 // prevents the Worker object and its MessageChannel from going away
1607 // before all the messages are dispatched.
1608 this.worker = worker;
1609
1610 message_port.onmessage =
1611 function(message) {
1612 if (this_obj.running && (message.data.type in this_obj.message_h andlers)) {
1613 this_obj.message_handlers[message.data.type].call(this_obj, message.data);
1614 }
1615 };
1616 }
1617
1618 RemoteWorker.prototype.worker_error = function(error) {
1619 var message = error.message || String(error);
1620 var filename = (error.filename ? " " + error.filename: "");
1621 // FIXME: Display worker error states separately from main document
1622 // error state.
1623 this.worker_done({
1624 status: {
1625 status: tests.status.ERROR,
1626 message: "Error in worker" + filename + ": " + message,
1627 stack: error.stack
1628 }
1629 });
1630 error.preventDefault();
1631 };
1632
1633 RemoteWorker.prototype.test_state = function(data) {
1634 var remote_test = this.tests[data.test.index];
1635 if (!remote_test) {
1636 remote_test = new RemoteTest(data.test);
1637 this.tests[data.test.index] = remote_test;
1638 }
1639 remote_test.update_state_from(data.test);
1640 tests.notify_test_state(remote_test);
1641 };
1642
1643 RemoteWorker.prototype.test_done = function(data) {
1644 var remote_test = this.tests[data.test.index];
1645 remote_test.update_state_from(data.test);
1646 remote_test.done();
1647 tests.result(remote_test);
1648 };
1649
1650 RemoteWorker.prototype.worker_done = function(data) {
1651 if (tests.status.status === null &&
1652 data.status.status !== data.status.OK) {
1653 tests.status.status = data.status.status;
1654 tests.status.message = data.status.message;
1655 tests.status.stack = data.status.stack;
1656 }
1657 this.running = false;
1658 this.worker = null;
1659 if (tests.all_done()) {
1660 tests.complete();
1661 }
1662 };
1663
1664 RemoteWorker.prototype.message_handlers = {
1665 test_state: RemoteWorker.prototype.test_state,
1666 result: RemoteWorker.prototype.test_done,
1667 complete: RemoteWorker.prototype.worker_done
1668 };
1669
1670 /*
1671 * Harness
1672 */
1673
1674 function TestsStatus()
1675 {
1676 this.status = null;
1677 this.message = null;
1678 this.stack = null;
1679 }
1680
1681 TestsStatus.statuses = {
1682 OK:0,
1683 ERROR:1,
1684 TIMEOUT:2
1685 };
1686
1687 TestsStatus.prototype = merge({}, TestsStatus.statuses);
1688
1689 TestsStatus.prototype.structured_clone = function()
1690 {
1691 if (!this._structured_clone) {
1692 var msg = this.message;
1693 msg = msg ? String(msg) : msg;
1694 this._structured_clone = merge({
1695 status:this.status,
1696 message:msg,
1697 stack:this.stack
1698 }, TestsStatus.statuses);
1699 }
1700 return this._structured_clone;
1701 };
1702
1703 function Tests()
1704 {
1705 this.tests = [];
1706 this.num_pending = 0;
1707
1708 this.phases = {
1709 INITIAL:0,
1710 SETUP:1,
1711 HAVE_TESTS:2,
1712 HAVE_RESULTS:3,
1713 COMPLETE:4
1714 };
1715 this.phase = this.phases.INITIAL;
1716
1717 this.properties = {};
1718
1719 this.wait_for_finish = false;
1720 this.processing_callbacks = false;
1721
1722 this.allow_uncaught_exception = false;
1723
1724 this.file_is_test = false;
1725
1726 this.timeout_multiplier = 1;
1727 this.timeout_length = test_environment.test_timeout();
1728 this.timeout_id = null;
1729
1730 this.start_callbacks = [];
1731 this.test_state_callbacks = [];
1732 this.test_done_callbacks = [];
1733 this.all_done_callbacks = [];
1734
1735 this.pending_workers = [];
1736
1737 this.status = new TestsStatus();
1738
1739 var this_obj = this;
1740
1741 test_environment.add_on_loaded_callback(function() {
1742 if (this_obj.all_done()) {
1743 this_obj.complete();
1744 }
1745 });
1746
1747 this.set_timeout();
1748 }
1749
1750 Tests.prototype.setup = function(func, properties)
1751 {
1752 if (this.phase >= this.phases.HAVE_RESULTS) {
1753 return;
1754 }
1755
1756 if (this.phase < this.phases.SETUP) {
1757 this.phase = this.phases.SETUP;
1758 }
1759
1760 this.properties = properties;
1761
1762 for (var p in properties) {
1763 if (properties.hasOwnProperty(p)) {
1764 var value = properties[p];
1765 if (p == "allow_uncaught_exception") {
1766 this.allow_uncaught_exception = value;
1767 } else if (p == "explicit_done" && value) {
1768 this.wait_for_finish = true;
1769 } else if (p == "explicit_timeout" && value) {
1770 this.timeout_length = null;
1771 if (this.timeout_id)
1772 {
1773 clearTimeout(this.timeout_id);
1774 }
1775 } else if (p == "timeout_multiplier") {
1776 this.timeout_multiplier = value;
1777 }
1778 }
1779 }
1780
1781 if (func) {
1782 try {
1783 func();
1784 } catch (e) {
1785 this.status.status = this.status.ERROR;
1786 this.status.message = String(e);
1787 this.status.stack = e.stack ? e.stack : null;
1788 }
1789 }
1790 this.set_timeout();
1791 };
1792
1793 Tests.prototype.set_file_is_test = function() {
1794 if (this.tests.length > 0) {
1795 throw new Error("Tried to set file as test after creating a test");
1796 }
1797 this.wait_for_finish = true;
1798 this.file_is_test = true;
1799 // Create the test, which will add it to the list of tests
1800 async_test();
1801 };
1802
1803 Tests.prototype.set_timeout = function() {
1804 var this_obj = this;
1805 clearTimeout(this.timeout_id);
1806 if (this.timeout_length !== null) {
1807 this.timeout_id = setTimeout(function() {
1808 this_obj.timeout();
1809 }, this.timeout_length);
1810 }
1811 };
1812
1813 Tests.prototype.timeout = function() {
1814 if (this.status.status === null) {
1815 this.status.status = this.status.TIMEOUT;
1816 }
1817 this.complete();
1818 };
1819
1820 Tests.prototype.end_wait = function()
1821 {
1822 this.wait_for_finish = false;
1823 if (this.all_done()) {
1824 this.complete();
1825 }
1826 };
1827
1828 Tests.prototype.push = function(test)
1829 {
1830 if (this.phase < this.phases.HAVE_TESTS) {
1831 this.start();
1832 }
1833 this.num_pending++;
1834 test.index = this.tests.push(test);
1835 this.notify_test_state(test);
1836 };
1837
1838 Tests.prototype.notify_test_state = function(test) {
1839 var this_obj = this;
1840 forEach(this.test_state_callbacks,
1841 function(callback) {
1842 callback(test, this_obj);
1843 });
1844 };
1845
1846 Tests.prototype.all_done = function() {
1847 return (this.tests.length > 0 && test_environment.all_loaded &&
1848 this.num_pending === 0 && !this.wait_for_finish &&
1849 !this.processing_callbacks &&
1850 !this.pending_workers.some(function(w) { return w.running; }));
1851 };
1852
1853 Tests.prototype.start = function() {
1854 this.phase = this.phases.HAVE_TESTS;
1855 this.notify_start();
1856 };
1857
1858 Tests.prototype.notify_start = function() {
1859 var this_obj = this;
1860 forEach (this.start_callbacks,
1861 function(callback)
1862 {
1863 callback(this_obj.properties);
1864 });
1865 };
1866
1867 Tests.prototype.result = function(test)
1868 {
1869 if (this.phase > this.phases.HAVE_RESULTS) {
1870 return;
1871 }
1872 this.phase = this.phases.HAVE_RESULTS;
1873 this.num_pending--;
1874 this.notify_result(test);
1875 };
1876
1877 Tests.prototype.notify_result = function(test) {
1878 var this_obj = this;
1879 this.processing_callbacks = true;
1880 forEach(this.test_done_callbacks,
1881 function(callback)
1882 {
1883 callback(test, this_obj);
1884 });
1885 this.processing_callbacks = false;
1886 if (this_obj.all_done()) {
1887 this_obj.complete();
1888 }
1889 };
1890
1891 Tests.prototype.complete = function() {
1892 if (this.phase === this.phases.COMPLETE) {
1893 return;
1894 }
1895 this.phase = this.phases.COMPLETE;
1896 var this_obj = this;
1897 this.tests.forEach(
1898 function(x)
1899 {
1900 if (x.phase < x.phases.COMPLETE) {
1901 this_obj.notify_result(x);
1902 x.cleanup();
1903 x.phase = x.phases.COMPLETE;
1904 }
1905 }
1906 );
1907 this.notify_complete();
1908 };
1909
1910 Tests.prototype.notify_complete = function() {
1911 var this_obj = this;
1912 if (this.status.status === null) {
1913 this.status.status = this.status.OK;
1914 }
1915
1916 forEach (this.all_done_callbacks,
1917 function(callback)
1918 {
1919 callback(this_obj.tests, this_obj.status);
1920 });
1921 };
1922
1923 Tests.prototype.fetch_tests_from_worker = function(worker) {
1924 if (this.phase >= this.phases.COMPLETE) {
1925 return;
1926 }
1927
1928 this.pending_workers.push(new RemoteWorker(worker));
1929 };
1930
1931 function fetch_tests_from_worker(port) {
1932 tests.fetch_tests_from_worker(port);
1933 }
1934 expose(fetch_tests_from_worker, 'fetch_tests_from_worker');
1935
1936 function timeout() {
1937 if (tests.timeout_length === null) {
1938 tests.timeout();
1939 }
1940 }
1941 expose(timeout, 'timeout');
1942
1943 function add_start_callback(callback) {
1944 tests.start_callbacks.push(callback);
1945 }
1946
1947 function add_test_state_callback(callback) {
1948 tests.test_state_callbacks.push(callback);
1949 }
1950
1951 function add_result_callback(callback) {
1952 tests.test_done_callbacks.push(callback);
1953 }
1954
1955 function add_completion_callback(callback) {
1956 tests.all_done_callbacks.push(callback);
1957 }
1958
1959 expose(add_start_callback, 'add_start_callback');
1960 expose(add_test_state_callback, 'add_test_state_callback');
1961 expose(add_result_callback, 'add_result_callback');
1962 expose(add_completion_callback, 'add_completion_callback');
1963
1964 function remove(array, item) {
1965 var index = array.indexOf(item);
1966 if (index > -1) {
1967 array.splice(index, 1);
1968 }
1969 }
1970
1971 function remove_start_callback(callback) {
1972 remove(tests.start_callbacks, callback);
1973 }
1974
1975 function remove_test_state_callback(callback) {
1976 remove(tests.test_state_callbacks, callback);
1977 }
1978
1979 function remove_result_callback(callback) {
1980 remove(tests.test_done_callbacks, callback);
1981 }
1982
1983 function remove_completion_callback(callback) {
1984 remove(tests.all_done_callbacks, callback);
1985 }
1986
1987 /*
1988 * Output listener
1989 */
1990
1991 function Output() {
1992 this.output_document = document;
1993 this.output_node = null;
1994 this.enabled = settings.output;
1995 this.phase = this.INITIAL;
1996 }
1997
1998 Output.prototype.INITIAL = 0;
1999 Output.prototype.STARTED = 1;
2000 Output.prototype.HAVE_RESULTS = 2;
2001 Output.prototype.COMPLETE = 3;
2002
2003 Output.prototype.setup = function(properties) {
2004 if (this.phase > this.INITIAL) {
2005 return;
2006 }
2007
2008 //If output is disabled in testharnessreport.js the test shouldn't be
2009 //able to override that
2010 this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
2011 properties.output : settings.output);
2012 };
2013
2014 Output.prototype.init = function(properties) {
2015 if (this.phase >= this.STARTED) {
2016 return;
2017 }
2018 if (properties.output_document) {
2019 this.output_document = properties.output_document;
2020 } else {
2021 this.output_document = document;
2022 }
2023 this.phase = this.STARTED;
2024 };
2025
2026 Output.prototype.resolve_log = function() {
2027 var output_document;
2028 if (typeof this.output_document === "function") {
2029 output_document = this.output_document.apply(undefined);
2030 } else {
2031 output_document = this.output_document;
2032 }
2033 if (!output_document) {
2034 return;
2035 }
2036 var node = output_document.getElementById("log");
2037 if (!node) {
2038 if (!document.body || document.readyState == "loading") {
2039 return;
2040 }
2041 node = output_document.createElement("div");
2042 node.id = "log";
2043 output_document.body.appendChild(node);
2044 }
2045 this.output_document = output_document;
2046 this.output_node = node;
2047 };
2048
2049 Output.prototype.show_status = function() {
2050 if (this.phase < this.STARTED) {
2051 this.init();
2052 }
2053 if (!this.enabled) {
2054 return;
2055 }
2056 if (this.phase < this.HAVE_RESULTS) {
2057 this.resolve_log();
2058 this.phase = this.HAVE_RESULTS;
2059 }
2060 var done_count = tests.tests.length - tests.num_pending;
2061 if (this.output_node) {
2062 if (done_count < 100 ||
2063 (done_count < 1000 && done_count % 100 === 0) ||
2064 done_count % 1000 === 0) {
2065 this.output_node.textContent = "Running, " +
2066 done_count + " complete, " +
2067 tests.num_pending + " remain";
2068 }
2069 }
2070 };
2071
2072 Output.prototype.show_results = function (tests, harness_status) {
2073 if (this.phase >= this.COMPLETE) {
2074 return;
2075 }
2076 if (!this.enabled) {
2077 return;
2078 }
2079 if (!this.output_node) {
2080 this.resolve_log();
2081 }
2082 this.phase = this.COMPLETE;
2083
2084 var log = this.output_node;
2085 if (!log) {
2086 return;
2087 }
2088 var output_document = this.output_document;
2089
2090 while (log.lastChild) {
2091 log.removeChild(log.lastChild);
2092 }
2093
2094 var harness_url = get_harness_url();
2095 if (harness_url !== null) {
2096 var stylesheet = output_document.createElementNS(xhtml_ns, "link");
2097 stylesheet.setAttribute("rel", "stylesheet");
2098 stylesheet.setAttribute("href", harness_url + "testharness.css");
2099 var heads = output_document.getElementsByTagName("head");
2100 if (heads.length) {
2101 heads[0].appendChild(stylesheet);
2102 }
2103 }
2104
2105 var status_text_harness = {};
2106 status_text_harness[harness_status.OK] = "OK";
2107 status_text_harness[harness_status.ERROR] = "Error";
2108 status_text_harness[harness_status.TIMEOUT] = "Timeout";
2109
2110 var status_text = {};
2111 status_text[Test.prototype.PASS] = "Pass";
2112 status_text[Test.prototype.FAIL] = "Fail";
2113 status_text[Test.prototype.TIMEOUT] = "Timeout";
2114 status_text[Test.prototype.NOTRUN] = "Not Run";
2115
2116 var status_number = {};
2117 forEach(tests,
2118 function(test) {
2119 var status = status_text[test.status];
2120 if (status_number.hasOwnProperty(status)) {
2121 status_number[status] += 1;
2122 } else {
2123 status_number[status] = 1;
2124 }
2125 });
2126
2127 function status_class(status)
2128 {
2129 return status.replace(/\s/g, '').toLowerCase();
2130 }
2131
2132 var summary_template = ["section", {"id":"summary"},
2133 ["h2", {}, "Summary"],
2134 function()
2135 {
2136
2137 var status = status_text_harness[harness_sta tus.status];
2138 var rv = [["section", {},
2139 ["p", {},
2140 "Harness status: ",
2141 ["span", {"class":status_class(s tatus)},
2142 status
2143 ],
2144 ]
2145 ]];
2146
2147 if (harness_status.status === harness_status .ERROR) {
2148 rv[0].push(["pre", {}, harness_status.me ssage]);
2149 if (harness_status.stack) {
2150 rv[0].push(["pre", {}, harness_statu s.stack]);
2151 }
2152 }
2153 return rv;
2154 },
2155 ["p", {}, "Found ${num_tests} tests"],
2156 function() {
2157 var rv = [["div", {}]];
2158 var i = 0;
2159 while (status_text.hasOwnProperty(i)) {
2160 if (status_number.hasOwnProperty(status_ text[i])) {
2161 var status = status_text[i];
2162 rv[0].push(["div", {"class":status_c lass(status)},
2163 ["label", {},
2164 ["input", {type:"checkb ox", checked:"checked"}],
2165 status_number[status] + " " + status]]);
2166 }
2167 i++;
2168 }
2169 return rv;
2170 },
2171 ];
2172
2173 log.appendChild(render(summary_template, {num_tests:tests.length}, outpu t_document));
2174
2175 forEach(output_document.querySelectorAll("section#summary label"),
2176 function(element)
2177 {
2178 on_event(element, "click",
2179 function(e)
2180 {
2181 if (output_document.getElementById("results") = == null) {
2182 e.preventDefault();
2183 return;
2184 }
2185 var result_class = element.parentNode.getAttrib ute("class");
2186 var style_element = output_document.querySelect or("style#hide-" + result_class);
2187 var input_element = element.querySelector("inpu t");
2188 if (!style_element && !input_element.checked) {
2189 style_element = output_document.createEleme ntNS(xhtml_ns, "style");
2190 style_element.id = "hide-" + result_class;
2191 style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
2192 output_document.body.appendChild(style_elem ent);
2193 } else if (style_element && input_element.check ed) {
2194 style_element.parentNode.removeChild(style_ element);
2195 }
2196 });
2197 });
2198
2199 // This use of innerHTML plus manual escaping is not recommended in
2200 // general, but is necessary here for performance. Using textContent
2201 // on each individual <td> adds tens of seconds of execution time for
2202 // large test suites (tens of thousands of tests).
2203 function escape_html(s)
2204 {
2205 return s.replace(/\&/g, "&amp;")
2206 .replace(/</g, "&lt;")
2207 .replace(/"/g, "&quot;")
2208 .replace(/'/g, "&#39;");
2209 }
2210
2211 function has_assertions()
2212 {
2213 for (var i = 0; i < tests.length; i++) {
2214 if (tests[i].properties.hasOwnProperty("assert")) {
2215 return true;
2216 }
2217 }
2218 return false;
2219 }
2220
2221 function get_assertion(test)
2222 {
2223 if (test.properties.hasOwnProperty("assert")) {
2224 if (Array.isArray(test.properties.assert)) {
2225 return test.properties.assert.join(' ');
2226 }
2227 return test.properties.assert;
2228 }
2229 return '';
2230 }
2231
2232 log.appendChild(document.createElementNS(xhtml_ns, "section"));
2233 var assertions = has_assertions();
2234 var html = "<h2>Details</h2><table id='results' " + (assertions ? "class ='assertions'" : "" ) + ">" +
2235 "<thead><tr><th>Result</th><th>Test Name</th>" +
2236 (assertions ? "<th>Assertion</th>" : "") +
2237 "<th>Message</th></tr></thead>" +
2238 "<tbody>";
2239 for (var i = 0; i < tests.length; i++) {
2240 html += '<tr class="' +
2241 escape_html(status_class(status_text[tests[i].status])) +
2242 '"><td>' +
2243 escape_html(status_text[tests[i].status]) +
2244 "</td><td>" +
2245 escape_html(tests[i].name) +
2246 "</td><td>" +
2247 (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") +
2248 escape_html(tests[i].message ? tests[i].message : " ") +
2249 (tests[i].stack ? "<pre>" +
2250 escape_html(tests[i].stack) +
2251 "</pre>": "") +
2252 "</td></tr>";
2253 }
2254 html += "</tbody></table>";
2255 try {
2256 log.lastChild.innerHTML = html;
2257 } catch (e) {
2258 log.appendChild(document.createElementNS(xhtml_ns, "p"))
2259 .textContent = "Setting innerHTML for the log threw an exception. ";
2260 log.appendChild(document.createElementNS(xhtml_ns, "pre"))
2261 .textContent = html;
2262 }
2263 };
2264
2265 /*
2266 * Template code
2267 *
2268 * A template is just a javascript structure. An element is represented as:
2269 *
2270 * [tag_name, {attr_name:attr_value}, child1, child2]
2271 *
2272 * the children can either be strings (which act like text nodes), other tem plates or
2273 * functions (see below)
2274 *
2275 * A text node is represented as
2276 *
2277 * ["{text}", value]
2278 *
2279 * String values have a simple substitution syntax; ${foo} represents a vari able foo.
2280 *
2281 * It is possible to embed logic in templates by using a function in a place where a
2282 * node would usually go. The function must either return part of a template or null.
2283 *
2284 * In cases where a set of nodes are required as output rather than a single node
2285 * with children it is possible to just use a list
2286 * [node1, node2, node3]
2287 *
2288 * Usage:
2289 *
2290 * render(template, substitutions) - take a template and an object mapping
2291 * variable names to parameters and return either a DOM node or a list of DO M nodes
2292 *
2293 * substitute(template, substitutions) - take a template and variable mappin g object,
2294 * make the variable substitutions and return the substituted template
2295 *
2296 */
2297
2298 function is_single_node(template)
2299 {
2300 return typeof template[0] === "string";
2301 }
2302
2303 function substitute(template, substitutions)
2304 {
2305 if (typeof template === "function") {
2306 var replacement = template(substitutions);
2307 if (!replacement) {
2308 return null;
2309 }
2310
2311 return substitute(replacement, substitutions);
2312 }
2313
2314 if (is_single_node(template)) {
2315 return substitute_single(template, substitutions);
2316 }
2317
2318 return filter(map(template, function(x) {
2319 return substitute(x, substitutions);
2320 }), function(x) {return x !== null;});
2321 }
2322
2323 function substitute_single(template, substitutions)
2324 {
2325 var substitution_re = /\$\{([^ }]*)\}/g;
2326
2327 function do_substitution(input) {
2328 var components = input.split(substitution_re);
2329 var rv = [];
2330 for (var i = 0; i < components.length; i += 2) {
2331 rv.push(components[i]);
2332 if (components[i + 1]) {
2333 rv.push(String(substitutions[components[i + 1]]));
2334 }
2335 }
2336 return rv;
2337 }
2338
2339 function substitute_attrs(attrs, rv)
2340 {
2341 rv[1] = {};
2342 for (var name in template[1]) {
2343 if (attrs.hasOwnProperty(name)) {
2344 var new_name = do_substitution(name).join("");
2345 var new_value = do_substitution(attrs[name]).join("");
2346 rv[1][new_name] = new_value;
2347 }
2348 }
2349 }
2350
2351 function substitute_children(children, rv)
2352 {
2353 for (var i = 0; i < children.length; i++) {
2354 if (children[i] instanceof Object) {
2355 var replacement = substitute(children[i], substitutions);
2356 if (replacement !== null) {
2357 if (is_single_node(replacement)) {
2358 rv.push(replacement);
2359 } else {
2360 extend(rv, replacement);
2361 }
2362 }
2363 } else {
2364 extend(rv, do_substitution(String(children[i])));
2365 }
2366 }
2367 return rv;
2368 }
2369
2370 var rv = [];
2371 rv.push(do_substitution(String(template[0])).join(""));
2372
2373 if (template[0] === "{text}") {
2374 substitute_children(template.slice(1), rv);
2375 } else {
2376 substitute_attrs(template[1], rv);
2377 substitute_children(template.slice(2), rv);
2378 }
2379
2380 return rv;
2381 }
2382
2383 function make_dom_single(template, doc)
2384 {
2385 var output_document = doc || document;
2386 var element;
2387 if (template[0] === "{text}") {
2388 element = output_document.createTextNode("");
2389 for (var i = 1; i < template.length; i++) {
2390 element.data += template[i];
2391 }
2392 } else {
2393 element = output_document.createElementNS(xhtml_ns, template[0]);
2394 for (var name in template[1]) {
2395 if (template[1].hasOwnProperty(name)) {
2396 element.setAttribute(name, template[1][name]);
2397 }
2398 }
2399 for (var i = 2; i < template.length; i++) {
2400 if (template[i] instanceof Object) {
2401 var sub_element = make_dom(template[i]);
2402 element.appendChild(sub_element);
2403 } else {
2404 var text_node = output_document.createTextNode(template[i]);
2405 element.appendChild(text_node);
2406 }
2407 }
2408 }
2409
2410 return element;
2411 }
2412
2413 function make_dom(template, substitutions, output_document)
2414 {
2415 if (is_single_node(template)) {
2416 return make_dom_single(template, output_document);
2417 }
2418
2419 return map(template, function(x) {
2420 return make_dom_single(x, output_document);
2421 });
2422 }
2423
2424 function render(template, substitutions, output_document)
2425 {
2426 return make_dom(substitute(template, substitutions), output_document);
2427 }
2428
2429 /*
2430 * Utility funcions
2431 */
2432 function assert(expected_true, function_name, description, error, substituti ons)
2433 {
2434 if (tests.tests.length === 0) {
2435 tests.set_file_is_test();
2436 }
2437 if (expected_true !== true) {
2438 var msg = make_message(function_name, description,
2439 error, substitutions);
2440 throw new AssertionError(msg);
2441 }
2442 }
2443
2444 function AssertionError(message)
2445 {
2446 this.message = message;
2447 this.stack = this.get_stack();
2448 }
2449
2450 AssertionError.prototype = Object.create(Error.prototype);
2451
2452 AssertionError.prototype.get_stack = function() {
2453 var stack = new Error().stack;
2454 // IE11 does not initialize 'Error.stack' until the object is thrown.
2455 if (!stack) {
2456 try {
2457 throw new Error();
2458 } catch (e) {
2459 stack = e.stack;
2460 }
2461 }
2462
2463 var lines = stack.split("\n");
2464
2465 // Create a pattern to match stack frames originating within testharness .js. These include the
2466 // script URL, followed by the line/col (e.g., '/resources/testharness.j s:120:21').
2467 var re = new RegExp((get_script_url() || "\\btestharness.js") + ":\\d+:\ \d+");
2468
2469 // Some browsers include a preamble that specifies the type of the error object. Skip this by
2470 // advancing until we find the first stack frame originating from testha rness.js.
2471 var i = 0;
2472 while (!re.test(lines[i]) && i < lines.length) {
2473 i++;
2474 }
2475
2476 // Then skip the top frames originating from testharness.js to begin the stack at the test code.
2477 while (re.test(lines[i]) && i < lines.length) {
2478 i++;
2479 }
2480
2481 // Paranoid check that we didn't skip all frames. If so, return the ori ginal stack unmodified.
2482 if (i >= lines.length) {
2483 return stack;
2484 }
2485
2486 return lines.slice(i).join("\n");
2487 }
2488
2489 function make_message(function_name, description, error, substitutions)
2490 {
2491 for (var p in substitutions) {
2492 if (substitutions.hasOwnProperty(p)) {
2493 substitutions[p] = format_value(substitutions[p]);
2494 }
2495 }
2496 var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
2497 merge({function_name:function_name,
2498 description:(description?description + " ":"")},
2499 substitutions));
2500 return node_form.slice(1).join("");
2501 }
2502
2503 function filter(array, callable, thisObj) {
2504 var rv = [];
2505 for (var i = 0; i < array.length; i++) {
2506 if (array.hasOwnProperty(i)) {
2507 var pass = callable.call(thisObj, array[i], i, array);
2508 if (pass) {
2509 rv.push(array[i]);
2510 }
2511 }
2512 }
2513 return rv;
2514 }
2515
2516 function map(array, callable, thisObj)
2517 {
2518 var rv = [];
2519 rv.length = array.length;
2520 for (var i = 0; i < array.length; i++) {
2521 if (array.hasOwnProperty(i)) {
2522 rv[i] = callable.call(thisObj, array[i], i, array);
2523 }
2524 }
2525 return rv;
2526 }
2527
2528 function extend(array, items)
2529 {
2530 Array.prototype.push.apply(array, items);
2531 }
2532
2533 function forEach(array, callback, thisObj)
2534 {
2535 for (var i = 0; i < array.length; i++) {
2536 if (array.hasOwnProperty(i)) {
2537 callback.call(thisObj, array[i], i, array);
2538 }
2539 }
2540 }
2541
2542 function merge(a,b)
2543 {
2544 var rv = {};
2545 var p;
2546 for (p in a) {
2547 rv[p] = a[p];
2548 }
2549 for (p in b) {
2550 rv[p] = b[p];
2551 }
2552 return rv;
2553 }
2554
2555 function expose(object, name)
2556 {
2557 var components = name.split(".");
2558 var target = test_environment.global_scope();
2559 for (var i = 0; i < components.length - 1; i++) {
2560 if (!(components[i] in target)) {
2561 target[components[i]] = {};
2562 }
2563 target = target[components[i]];
2564 }
2565 target[components[components.length - 1]] = object;
2566 }
2567
2568 function is_same_origin(w) {
2569 try {
2570 'random_prop' in w;
2571 return true;
2572 } catch (e) {
2573 return false;
2574 }
2575 }
2576
2577 /** Returns the 'src' URL of the first <script> tag in the page to include t he file 'testharness.js'. */
2578 function get_script_url()
2579 {
2580 if (!('document' in self)) {
2581 return undefined;
2582 }
2583
2584 var scripts = document.getElementsByTagName("script");
2585 for (var i = 0; i < scripts.length; i++) {
2586 var src;
2587 if (scripts[i].src) {
2588 src = scripts[i].src;
2589 } else if (scripts[i].href) {
2590 //SVG case
2591 src = scripts[i].href.baseVal;
2592 }
2593
2594 var matches = src && src.match(/^(.*\/|)testharness\.js$/);
2595 if (matches) {
2596 return src;
2597 }
2598 }
2599 return undefined;
2600 }
2601
2602 /** Returns the URL path at which the files for testharness.js are assumed t o reside (e.g., '/resources/').
2603 The path is derived from inspecting the 'src' of the <script> tag that i ncluded 'testharness.js'. */
2604 function get_harness_url()
2605 {
2606 var script_url = get_script_url();
2607
2608 // Exclude the 'testharness.js' file from the returned path, but '+ 1' t o include the trailing slash.
2609 return script_url ? script_url.slice(0, script_url.lastIndexOf('/') + 1) : undefined;
2610 }
2611
2612 function supports_post_message(w)
2613 {
2614 var supports;
2615 var type;
2616 // Given IE implements postMessage across nested iframes but not across
2617 // windows or tabs, you can't infer cross-origin communication from the presence
2618 // of postMessage on the current window object only.
2619 //
2620 // Touching the postMessage prop on a window can throw if the window is
2621 // not from the same origin AND post message is not supported in that
2622 // browser. So just doing an existence test here won't do, you also need
2623 // to wrap it in a try..cacth block.
2624 try {
2625 type = typeof w.postMessage;
2626 if (type === "function") {
2627 supports = true;
2628 }
2629
2630 // IE8 supports postMessage, but implements it as a host object whic h
2631 // returns "object" as its `typeof`.
2632 else if (type === "object") {
2633 supports = true;
2634 }
2635
2636 // This is the case where postMessage isn't supported AND accessing a
2637 // window property across origins does NOT throw (e.g. old Safari br owser).
2638 else {
2639 supports = false;
2640 }
2641 } catch (e) {
2642 // This is the case where postMessage isn't supported AND accessing a
2643 // window property across origins throws (e.g. old Firefox browser).
2644 supports = false;
2645 }
2646 return supports;
2647 }
2648
2649 /**
2650 * Setup globals
2651 */
2652
2653 var tests = new Tests();
2654
2655 addEventListener("error", function(e) {
2656 if (tests.file_is_test) {
2657 var test = tests.tests[0];
2658 if (test.phase >= test.phases.HAS_RESULT) {
2659 return;
2660 }
2661 test.set_status(test.FAIL, e.message, e.stack);
2662 test.phase = test.phases.HAS_RESULT;
2663 test.done();
2664 done();
2665 } else if (!tests.allow_uncaught_exception) {
2666 tests.status.status = tests.status.ERROR;
2667 tests.status.message = e.message;
2668 tests.status.stack = e.stack;
2669 }
2670 });
2671
2672 test_environment.on_tests_ready();
2673
2674 })();
2675 // vim: set expandtab shiftwidth=4 tabstop=4:
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698