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

Side by Side Diff: third_party/WebKit/LayoutTests/http/tests/resources/testharness.js

Issue 1898703003: Remove testharness.js and friends in LayoutTests/http/tests/. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 8 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) {
544 return promise.then(test.unreached_func("Should have rejected.")).catch( function(e) {
545 assert_throws(expected, function() { throw e });
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 if ("nodeType" in object &&
701 "nodeName" in object &&
702 "nodeValue" in object &&
703 "childNodes" in object) {
704 try {
705 object.nodeType;
706 } catch (e) {
707 // The object is probably Node.prototype or another prototype
708 // object that inherits from it, and not a Node instance.
709 return false;
710 }
711 return true;
712 }
713 return false;
714 }
715
716 /*
717 * Convert a value to a nice, human-readable string
718 */
719 function format_value(val, seen)
720 {
721 if (!seen) {
722 seen = [];
723 }
724 if (typeof val === "object" && val !== null) {
725 if (seen.indexOf(val) >= 0) {
726 return "[...]";
727 }
728 seen.push(val);
729 }
730 if (Array.isArray(val)) {
731 return "[" + val.map(function(x) {return format_value(x, seen);}).jo in(", ") + "]";
732 }
733
734 switch (typeof val) {
735 case "string":
736 val = val.replace("\\", "\\\\");
737 for (var i = 0; i < 32; i++) {
738 var replace = "\\";
739 switch (i) {
740 case 0: replace += "0"; break;
741 case 1: replace += "x01"; break;
742 case 2: replace += "x02"; break;
743 case 3: replace += "x03"; break;
744 case 4: replace += "x04"; break;
745 case 5: replace += "x05"; break;
746 case 6: replace += "x06"; break;
747 case 7: replace += "x07"; break;
748 case 8: replace += "b"; break;
749 case 9: replace += "t"; break;
750 case 10: replace += "n"; break;
751 case 11: replace += "v"; break;
752 case 12: replace += "f"; break;
753 case 13: replace += "r"; break;
754 case 14: replace += "x0e"; break;
755 case 15: replace += "x0f"; break;
756 case 16: replace += "x10"; break;
757 case 17: replace += "x11"; break;
758 case 18: replace += "x12"; break;
759 case 19: replace += "x13"; break;
760 case 20: replace += "x14"; break;
761 case 21: replace += "x15"; break;
762 case 22: replace += "x16"; break;
763 case 23: replace += "x17"; break;
764 case 24: replace += "x18"; break;
765 case 25: replace += "x19"; break;
766 case 26: replace += "x1a"; break;
767 case 27: replace += "x1b"; break;
768 case 28: replace += "x1c"; break;
769 case 29: replace += "x1d"; break;
770 case 30: replace += "x1e"; break;
771 case 31: replace += "x1f"; break;
772 }
773 val = val.replace(RegExp(String.fromCharCode(i), "g"), replace);
774 }
775 return '"' + val.replace(/"/g, '\\"') + '"';
776 case "boolean":
777 case "undefined":
778 return String(val);
779 case "number":
780 // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
781 // special-case.
782 if (val === -0 && 1/val === -Infinity) {
783 return "-0";
784 }
785 return String(val);
786 case "object":
787 if (val === null) {
788 return "null";
789 }
790
791 // Special-case Node objects, since those come up a lot in my tests. I
792 // ignore namespaces.
793 if (is_node(val)) {
794 switch (val.nodeType) {
795 case Node.ELEMENT_NODE:
796 var ret = "<" + val.localName;
797 for (var i = 0; i < val.attributes.length; i++) {
798 ret += " " + val.attributes[i].name + '="' + val.attribu tes[i].value + '"';
799 }
800 ret += ">" + val.innerHTML + "</" + val.localName + ">";
801 return "Element node " + truncate(ret, 60);
802 case Node.TEXT_NODE:
803 return 'Text node "' + truncate(val.data, 60) + '"';
804 case Node.PROCESSING_INSTRUCTION_NODE:
805 return "ProcessingInstruction node with target " + format_va lue(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 6 0));
806 case Node.COMMENT_NODE:
807 return "Comment node <!--" + truncate(val.data, 60) + "-->";
808 case Node.DOCUMENT_NODE:
809 return "Document node with " + val.childNodes.length + (val. childNodes.length == 1 ? " child" : " children");
810 case Node.DOCUMENT_TYPE_NODE:
811 return "DocumentType node";
812 case Node.DOCUMENT_FRAGMENT_NODE:
813 return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
814 default:
815 return "Node object of unknown type";
816 }
817 }
818
819 /* falls through */
820 default:
821 return typeof val + ' "' + truncate(String(val), 60) + '"';
822 }
823 }
824 expose(format_value, "format_value");
825
826 /*
827 * Assertions
828 */
829
830 function assert_true(actual, description)
831 {
832 assert(actual === true, "assert_true", description,
833 "expected true got ${actual}", {actual:actual});
834 }
835 expose(assert_true, "assert_true");
836
837 function assert_false(actual, description)
838 {
839 assert(actual === false, "assert_false", description,
840 "expected false got ${actual}", {actual:actual} );
841 }
842 expose(assert_false, "assert_false");
843
844 function same_value(x, y) {
845 if (y !== y) {
846 //NaN case
847 return x !== x;
848 }
849 if (x === 0 && y === 0) {
850 //Distinguish +0 and -0
851 return 1/x === 1/y;
852 }
853 return x === y;
854 }
855
856 function assert_equals(actual, expected, description)
857 {
858 /*
859 * Test if two primitives are equal or two objects
860 * are the same object
861 */
862 if (typeof actual != typeof expected) {
863 assert(false, "assert_equals", description,
864 "expected (" + typeof expected + ") ${expected} but go t (" + typeof actual + ") ${actual}",
865 {expected:expected, actual:actual});
866 return;
867 }
868 assert(same_value(actual, expected), "assert_equals", description,
869 "expected ${expected} but got ${act ual}",
870 {expected:expected, actual:actual}) ;
871 }
872 expose(assert_equals, "assert_equals");
873
874 function assert_not_equals(actual, expected, description)
875 {
876 /*
877 * Test if two primitives are unequal or two objects
878 * are different objects
879 */
880 assert(!same_value(actual, expected), "assert_not_equals", description,
881 "got disallowed value ${actual}",
882 {actual:actual});
883 }
884 expose(assert_not_equals, "assert_not_equals");
885
886 function assert_in_array(actual, expected, description)
887 {
888 assert(expected.indexOf(actual) != -1, "assert_in_array", description,
889 "value ${actual} not in array ${e xpected}",
890 {actual:actual, expected:expected });
891 }
892 expose(assert_in_array, "assert_in_array");
893
894 function assert_object_equals(actual, expected, description)
895 {
896 //This needs to be improved a great deal
897 function check_equal(actual, expected, stack)
898 {
899 stack.push(actual);
900
901 var p;
902 for (p in actual) {
903 assert(expected.hasOwnProperty(p), "assert_object_equals", desc ription,
904 "unexpected property ${p}", {p:p});
905
906 if (typeof actual[p] === "object" && actual[p] !== null) {
907 if (stack.indexOf(actual[p]) === -1) {
908 check_equal(actual[p], expected[p], stack);
909 }
910 } else {
911 assert(same_value(actual[p], expected[p]), "assert_object_e quals", description,
912 "property ${p} expected $ {expected} got ${actual}",
913 {p:p, expected:expected, actual:actual});
914 }
915 }
916 for (p in expected) {
917 assert(actual.hasOwnProperty(p),
918 "assert_object_equals", description,
919 "expected property ${p} missing", {p:p});
920 }
921 stack.pop();
922 }
923 check_equal(actual, expected, []);
924 }
925 expose(assert_object_equals, "assert_object_equals");
926
927 function assert_array_equals(actual, expected, description)
928 {
929 assert(actual.length === expected.length,
930 "assert_array_equals", description,
931 "lengths differ, expected ${expected} got ${actual}",
932 {expected:expected.length, actual:actual.length});
933
934 for (var i = 0; i < actual.length; i++) {
935 assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
936 "assert_array_equals", description,
937 "property ${i}, property expected to be ${expected} but was $ {actual}",
938 {i:i, expected:expected.hasOwnProperty(i) ? "present" : "miss ing",
939 actual:actual.hasOwnProperty(i) ? "present" : "missing"});
940 assert(same_value(expected[i], actual[i]),
941 "assert_array_equals", description,
942 "property ${i}, expected ${expected} but got ${actual}",
943 {i:i, expected:expected[i], actual:actual[i]});
944 }
945 }
946 expose(assert_array_equals, "assert_array_equals");
947
948 function assert_approx_equals(actual, expected, epsilon, description)
949 {
950 /*
951 * Test if two primitive numbers are equal withing +/- epsilon
952 */
953 assert(typeof actual === "number",
954 "assert_approx_equals", description,
955 "expected a number but got a ${type_actual}",
956 {type_actual:typeof actual});
957
958 assert(Math.abs(actual - expected) <= epsilon,
959 "assert_approx_equals", description,
960 "expected ${expected} +/- ${epsilon} but got ${actual}",
961 {expected:expected, actual:actual, epsilon:epsilon});
962 }
963 expose(assert_approx_equals, "assert_approx_equals");
964
965 function assert_less_than(actual, expected, description)
966 {
967 /*
968 * Test if a primitive number is less than another
969 */
970 assert(typeof actual === "number",
971 "assert_less_than", description,
972 "expected a number but got a ${type_actual}",
973 {type_actual:typeof actual});
974
975 assert(actual < expected,
976 "assert_less_than", description,
977 "expected a number less than ${expected} but got ${actual}",
978 {expected:expected, actual:actual});
979 }
980 expose(assert_less_than, "assert_less_than");
981
982 function assert_greater_than(actual, expected, description)
983 {
984 /*
985 * Test if a primitive number is greater than another
986 */
987 assert(typeof actual === "number",
988 "assert_greater_than", description,
989 "expected a number but got a ${type_actual}",
990 {type_actual:typeof actual});
991
992 assert(actual > expected,
993 "assert_greater_than", description,
994 "expected a number greater than ${expected} but got ${actual}",
995 {expected:expected, actual:actual});
996 }
997 expose(assert_greater_than, "assert_greater_than");
998
999 function assert_between_exclusive(actual, lower, upper, description)
1000 {
1001 /*
1002 * Test if a primitive number is between two others
1003 */
1004 assert(typeof actual === "number",
1005 "assert_between_exclusive", description,
1006 "expected a number but got a ${type_actual}",
1007 {type_actual:typeof actual});
1008
1009 assert(actual > lower && actual < upper,
1010 "assert_between_exclusive", description,
1011 "expected a number greater than ${lower} " +
1012 "and less than ${upper} but got ${actual}",
1013 {lower:lower, upper:upper, actual:actual});
1014 }
1015 expose(assert_between_exclusive, "assert_between_exclusive");
1016
1017 function assert_less_than_equal(actual, expected, description)
1018 {
1019 /*
1020 * Test if a primitive number is less than or equal to another
1021 */
1022 assert(typeof actual === "number",
1023 "assert_less_than_equal", description,
1024 "expected a number but got a ${type_actual}",
1025 {type_actual:typeof actual});
1026
1027 assert(actual <= expected,
1028 "assert_less_than_equal", description,
1029 "expected a number less than or equal to ${expected} but got ${ac tual}",
1030 {expected:expected, actual:actual});
1031 }
1032 expose(assert_less_than_equal, "assert_less_than_equal");
1033
1034 function assert_greater_than_equal(actual, expected, description)
1035 {
1036 /*
1037 * Test if a primitive number is greater than or equal to another
1038 */
1039 assert(typeof actual === "number",
1040 "assert_greater_than_equal", description,
1041 "expected a number but got a ${type_actual}",
1042 {type_actual:typeof actual});
1043
1044 assert(actual >= expected,
1045 "assert_greater_than_equal", description,
1046 "expected a number greater than or equal to ${expected} but got $ {actual}",
1047 {expected:expected, actual:actual});
1048 }
1049 expose(assert_greater_than_equal, "assert_greater_than_equal");
1050
1051 function assert_between_inclusive(actual, lower, upper, description)
1052 {
1053 /*
1054 * Test if a primitive number is between to two others or equal to eithe r of them
1055 */
1056 assert(typeof actual === "number",
1057 "assert_between_inclusive", description,
1058 "expected a number but got a ${type_actual}",
1059 {type_actual:typeof actual});
1060
1061 assert(actual >= lower && actual <= upper,
1062 "assert_between_inclusive", description,
1063 "expected a number greater than or equal to ${lower} " +
1064 "and less than or equal to ${upper} but got ${actual}",
1065 {lower:lower, upper:upper, actual:actual});
1066 }
1067 expose(assert_between_inclusive, "assert_between_inclusive");
1068
1069 function assert_regexp_match(actual, expected, description) {
1070 /*
1071 * Test if a string (actual) matches a regexp (expected)
1072 */
1073 assert(expected.test(actual),
1074 "assert_regexp_match", description,
1075 "expected ${expected} but got ${actual}",
1076 {expected:expected, actual:actual});
1077 }
1078 expose(assert_regexp_match, "assert_regexp_match");
1079
1080 function assert_class_string(object, class_string, description) {
1081 assert_equals({}.toString.call(object), "[object " + class_string + "]",
1082 description);
1083 }
1084 expose(assert_class_string, "assert_class_string");
1085
1086
1087 function _assert_own_property(name) {
1088 return function(object, property_name, description)
1089 {
1090 assert(object.hasOwnProperty(property_name),
1091 name, description,
1092 "expected property ${p} missing", {p:property_name});
1093 };
1094 }
1095 expose(_assert_own_property("assert_exists"), "assert_exists");
1096 expose(_assert_own_property("assert_own_property"), "assert_own_property");
1097
1098 function assert_not_exists(object, property_name, description)
1099 {
1100 assert(!object.hasOwnProperty(property_name),
1101 "assert_not_exists", description,
1102 "unexpected property ${p} found", {p:property_name});
1103 }
1104 expose(assert_not_exists, "assert_not_exists");
1105
1106 function _assert_inherits(name) {
1107 return function (object, property_name, description)
1108 {
1109 assert(typeof object === "object" || typeof object === "function",
1110 name, description,
1111 "provided value is not an object");
1112
1113 assert("hasOwnProperty" in object,
1114 name, description,
1115 "provided value is an object but has no hasOwnProperty method ");
1116
1117 assert(!object.hasOwnProperty(property_name),
1118 name, description,
1119 "property ${p} found on object expected in prototype chain",
1120 {p:property_name});
1121
1122 assert(property_name in object,
1123 name, description,
1124 "property ${p} not found in prototype chain",
1125 {p:property_name});
1126 };
1127 }
1128 expose(_assert_inherits("assert_inherits"), "assert_inherits");
1129 expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
1130
1131 function assert_readonly(object, property_name, description)
1132 {
1133 var initial_value = object[property_name];
1134 try {
1135 //Note that this can have side effects in the case where
1136 //the property has PutForwards
1137 object[property_name] = initial_value + "a"; //XXX use some other v alue here?
1138 assert(same_value(object[property_name], initial_value),
1139 "assert_readonly", description,
1140 "changing property ${p} succeeded",
1141 {p:property_name});
1142 } finally {
1143 object[property_name] = initial_value;
1144 }
1145 }
1146 expose(assert_readonly, "assert_readonly");
1147
1148 function assert_throws(code, func, description)
1149 {
1150 try {
1151 func.call(this);
1152 assert(false, "assert_throws", description,
1153 "${func} did not throw", {func:func});
1154 } catch (e) {
1155 if (e instanceof AssertionError) {
1156 throw e;
1157 }
1158 if (code === null) {
1159 return;
1160 }
1161 if (typeof code === "object") {
1162 assert(typeof e == "object" && "name" in e && e.name == code.nam e,
1163 "assert_throws", description,
1164 "${func} threw ${actual} (${actual_name}) expected ${expe cted} (${expected_name})",
1165 {func:func, actual:e, actual_name:e.name,
1166 expected:code,
1167 expected_name:code.name});
1168 return;
1169 }
1170
1171 var code_name_map = {
1172 INDEX_SIZE_ERR: 'IndexSizeError',
1173 HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
1174 WRONG_DOCUMENT_ERR: 'WrongDocumentError',
1175 INVALID_CHARACTER_ERR: 'InvalidCharacterError',
1176 NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
1177 NOT_FOUND_ERR: 'NotFoundError',
1178 NOT_SUPPORTED_ERR: 'NotSupportedError',
1179 INUSE_ATTRIBUTE_ERR: 'InUseAttributeError',
1180 INVALID_STATE_ERR: 'InvalidStateError',
1181 SYNTAX_ERR: 'SyntaxError',
1182 INVALID_MODIFICATION_ERR: 'InvalidModificationError',
1183 NAMESPACE_ERR: 'NamespaceError',
1184 INVALID_ACCESS_ERR: 'InvalidAccessError',
1185 TYPE_MISMATCH_ERR: 'TypeMismatchError',
1186 SECURITY_ERR: 'SecurityError',
1187 NETWORK_ERR: 'NetworkError',
1188 ABORT_ERR: 'AbortError',
1189 URL_MISMATCH_ERR: 'URLMismatchError',
1190 QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
1191 TIMEOUT_ERR: 'TimeoutError',
1192 INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
1193 DATA_CLONE_ERR: 'DataCloneError'
1194 };
1195
1196 var name = code in code_name_map ? code_name_map[code] : code;
1197
1198 var name_code_map = {
1199 IndexSizeError: 1,
1200 HierarchyRequestError: 3,
1201 WrongDocumentError: 4,
1202 InvalidCharacterError: 5,
1203 NoModificationAllowedError: 7,
1204 NotFoundError: 8,
1205 NotSupportedError: 9,
1206 InUseAttributeError: 10,
1207 InvalidStateError: 11,
1208 SyntaxError: 12,
1209 InvalidModificationError: 13,
1210 NamespaceError: 14,
1211 InvalidAccessError: 15,
1212 TypeMismatchError: 17,
1213 SecurityError: 18,
1214 NetworkError: 19,
1215 AbortError: 20,
1216 URLMismatchError: 21,
1217 QuotaExceededError: 22,
1218 TimeoutError: 23,
1219 InvalidNodeTypeError: 24,
1220 DataCloneError: 25,
1221
1222 EncodingError: 0,
1223 NotReadableError: 0,
1224 UnknownError: 0,
1225 ConstraintError: 0,
1226 DataError: 0,
1227 TransactionInactiveError: 0,
1228 ReadOnlyError: 0,
1229 VersionError: 0,
1230 OperationError: 0,
1231 };
1232
1233 if (!(name in name_code_map)) {
1234 throw new AssertionError('Test bug: unrecognized DOMException co de "' + code + '" passed to assert_throws()');
1235 }
1236
1237 var required_props = { code: name_code_map[name] };
1238
1239 if (required_props.code === 0 ||
1240 (typeof e == "object" &&
1241 "name" in e &&
1242 e.name !== e.name.toUpperCase() &&
1243 e.name !== "DOMException")) {
1244 // New style exception: also test the name property.
1245 required_props.name = name;
1246 }
1247
1248 //We'd like to test that e instanceof the appropriate interface,
1249 //but we can't, because we don't know what window it was created
1250 //in. It might be an instanceof the appropriate interface on some
1251 //unknown other window. TODO: Work around this somehow?
1252
1253 assert(typeof e == "object",
1254 "assert_throws", description,
1255 "${func} threw ${e} with type ${type}, not an object",
1256 {func:func, e:e, type:typeof e});
1257
1258 for (var prop in required_props) {
1259 assert(typeof e == "object" && prop in e && e[prop] == required_ props[prop],
1260 "assert_throws", description,
1261 "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
1262 {func:func, e:e, prop:prop, actual:e[prop], expected:requ ired_props[prop]});
1263 }
1264 }
1265 }
1266 expose(assert_throws, "assert_throws");
1267
1268 function assert_unreached(description) {
1269 assert(false, "assert_unreached", description,
1270 "Reached unreachable code");
1271 }
1272 expose(assert_unreached, "assert_unreached");
1273
1274 function assert_any(assert_func, actual, expected_array)
1275 {
1276 var args = [].slice.call(arguments, 3);
1277 var errors = [];
1278 var passed = false;
1279 forEach(expected_array,
1280 function(expected)
1281 {
1282 try {
1283 assert_func.apply(this, [actual, expected].concat(args)) ;
1284 passed = true;
1285 } catch (e) {
1286 errors.push(e.message);
1287 }
1288 });
1289 if (!passed) {
1290 throw new AssertionError(errors.join("\n\n"));
1291 }
1292 }
1293 expose(assert_any, "assert_any");
1294
1295 function Test(name, properties)
1296 {
1297 if (tests.file_is_test && tests.tests.length) {
1298 throw new Error("Tried to create a test with file_is_test");
1299 }
1300 this.name = name;
1301
1302 this.phase = this.phases.INITIAL;
1303
1304 this.status = this.NOTRUN;
1305 this.timeout_id = null;
1306 this.index = null;
1307
1308 this.properties = properties;
1309 var timeout = properties.timeout ? properties.timeout : settings.test_ti meout;
1310 if (timeout !== null) {
1311 this.timeout_length = timeout * tests.timeout_multiplier;
1312 } else {
1313 this.timeout_length = null;
1314 }
1315
1316 this.message = null;
1317 this.stack = null;
1318
1319 this.steps = [];
1320
1321 this.cleanup_callbacks = [];
1322
1323 tests.push(this);
1324 }
1325
1326 Test.statuses = {
1327 PASS:0,
1328 FAIL:1,
1329 TIMEOUT:2,
1330 NOTRUN:3
1331 };
1332
1333 Test.prototype = merge({}, Test.statuses);
1334
1335 Test.prototype.phases = {
1336 INITIAL:0,
1337 STARTED:1,
1338 HAS_RESULT:2,
1339 COMPLETE:3
1340 };
1341
1342 Test.prototype.structured_clone = function()
1343 {
1344 if (!this._structured_clone) {
1345 var msg = this.message;
1346 msg = msg ? String(msg) : msg;
1347 this._structured_clone = merge({
1348 name:String(this.name),
1349 properties:merge({}, this.properties),
1350 }, Test.statuses);
1351 }
1352 this._structured_clone.status = this.status;
1353 this._structured_clone.message = this.message;
1354 this._structured_clone.stack = this.stack;
1355 this._structured_clone.index = this.index;
1356 return this._structured_clone;
1357 };
1358
1359 Test.prototype.step = function(func, this_obj)
1360 {
1361 if (this.phase > this.phases.STARTED) {
1362 return;
1363 }
1364 this.phase = this.phases.STARTED;
1365 //If we don't get a result before the harness times out that will be a t est timout
1366 this.set_status(this.TIMEOUT, "Test timed out");
1367
1368 tests.started = true;
1369 tests.notify_test_state(this);
1370
1371 if (this.timeout_id === null) {
1372 this.set_timeout();
1373 }
1374
1375 this.steps.push(func);
1376
1377 if (arguments.length === 1) {
1378 this_obj = this;
1379 }
1380
1381 try {
1382 return func.apply(this_obj, Array.prototype.slice.call(arguments, 2) );
1383 } catch (e) {
1384 if (this.phase >= this.phases.HAS_RESULT) {
1385 return;
1386 }
1387 var message = String((typeof e === "object" && e !== null) ? e.messa ge : e);
1388 var stack = e.stack ? e.stack : null;
1389
1390 this.set_status(this.FAIL, message, stack);
1391 this.phase = this.phases.HAS_RESULT;
1392 this.done();
1393 }
1394 };
1395
1396 Test.prototype.step_func = function(func, this_obj)
1397 {
1398 var test_this = this;
1399
1400 if (arguments.length === 1) {
1401 this_obj = test_this;
1402 }
1403
1404 return function()
1405 {
1406 return test_this.step.apply(test_this, [func, this_obj].concat(
1407 Array.prototype.slice.call(arguments)));
1408 };
1409 };
1410
1411 Test.prototype.step_func_done = function(func, this_obj)
1412 {
1413 var test_this = this;
1414
1415 if (arguments.length === 1) {
1416 this_obj = test_this;
1417 }
1418
1419 return function()
1420 {
1421 if (func) {
1422 test_this.step.apply(test_this, [func, this_obj].concat(
1423 Array.prototype.slice.call(arguments)));
1424 }
1425 test_this.done();
1426 };
1427 };
1428
1429 Test.prototype.unreached_func = function(description)
1430 {
1431 return this.step_func(function() {
1432 assert_unreached(description);
1433 });
1434 };
1435
1436 Test.prototype.step_timeout = function(f, timeout) {
1437 var test_this = this;
1438 var args = Array.prototype.slice.call(arguments, 2);
1439 return setTimeout(this.step_func(function() {
1440 return f.apply(test_this, args);
1441 }, timeout * tests.timeout_multiplier));
1442 }
1443
1444 Test.prototype.add_cleanup = function(callback) {
1445 this.cleanup_callbacks.push(callback);
1446 };
1447
1448 Test.prototype.force_timeout = function() {
1449 this.set_status(this.TIMEOUT);
1450 this.phase = this.phases.HAS_RESULT;
1451 };
1452
1453 Test.prototype.set_timeout = function()
1454 {
1455 if (this.timeout_length !== null) {
1456 var this_obj = this;
1457 this.timeout_id = setTimeout(function()
1458 {
1459 this_obj.timeout();
1460 }, this.timeout_length);
1461 }
1462 };
1463
1464 Test.prototype.set_status = function(status, message, stack)
1465 {
1466 this.status = status;
1467 this.message = message;
1468 this.stack = stack ? stack : null;
1469 };
1470
1471 Test.prototype.timeout = function()
1472 {
1473 this.timeout_id = null;
1474 this.set_status(this.TIMEOUT, "Test timed out");
1475 this.phase = this.phases.HAS_RESULT;
1476 this.done();
1477 };
1478
1479 Test.prototype.done = function()
1480 {
1481 if (this.phase == this.phases.COMPLETE) {
1482 return;
1483 }
1484
1485 if (this.phase <= this.phases.STARTED) {
1486 this.set_status(this.PASS, null);
1487 }
1488
1489 this.phase = this.phases.COMPLETE;
1490
1491 clearTimeout(this.timeout_id);
1492 tests.result(this);
1493 this.cleanup();
1494 };
1495
1496 Test.prototype.cleanup = function() {
1497 forEach(this.cleanup_callbacks,
1498 function(cleanup_callback) {
1499 cleanup_callback();
1500 });
1501 };
1502
1503 /*
1504 * A RemoteTest object mirrors a Test object on a remote worker. The
1505 * associated RemoteWorker updates the RemoteTest object in response to
1506 * received events. In turn, the RemoteTest object replicates these events
1507 * on the local document. This allows listeners (test result reporting
1508 * etc..) to transparently handle local and remote events.
1509 */
1510 function RemoteTest(clone) {
1511 var this_obj = this;
1512 Object.keys(clone).forEach(
1513 function(key) {
1514 this_obj[key] = clone[key];
1515 });
1516 this.index = null;
1517 this.phase = this.phases.INITIAL;
1518 this.update_state_from(clone);
1519 tests.push(this);
1520 }
1521
1522 RemoteTest.prototype.structured_clone = function() {
1523 var clone = {};
1524 Object.keys(this).forEach(
1525 (function(key) {
1526 if (typeof(this[key]) === "object") {
1527 clone[key] = merge({}, this[key]);
1528 } else {
1529 clone[key] = this[key];
1530 }
1531 }).bind(this));
1532 clone.phases = merge({}, this.phases);
1533 return clone;
1534 };
1535
1536 RemoteTest.prototype.cleanup = function() {};
1537 RemoteTest.prototype.phases = Test.prototype.phases;
1538 RemoteTest.prototype.update_state_from = function(clone) {
1539 this.status = clone.status;
1540 this.message = clone.message;
1541 this.stack = clone.stack;
1542 if (this.phase === this.phases.INITIAL) {
1543 this.phase = this.phases.STARTED;
1544 }
1545 };
1546 RemoteTest.prototype.done = function() {
1547 this.phase = this.phases.COMPLETE;
1548 }
1549
1550 /*
1551 * A RemoteWorker listens for test events from a worker. These events are
1552 * then used to construct and maintain RemoteTest objects that mirror the
1553 * tests running on the remote worker.
1554 */
1555 function RemoteWorker(worker) {
1556 this.running = true;
1557 this.tests = new Array();
1558
1559 var this_obj = this;
1560 worker.onerror = function(error) { this_obj.worker_error(error); };
1561
1562 var message_port;
1563
1564 if (is_service_worker(worker)) {
1565 if (window.MessageChannel) {
1566 // The ServiceWorker's implicit MessagePort is currently not
1567 // reliably accessible from the ServiceWorkerGlobalScope due to
1568 // Blink setting MessageEvent.source to null for messages sent
1569 // via ServiceWorker.postMessage(). Until that's resolved,
1570 // create an explicit MessageChannel and pass one end to the
1571 // worker.
1572 var message_channel = new MessageChannel();
1573 message_port = message_channel.port1;
1574 message_port.start();
1575 worker.postMessage({type: "connect"}, [message_channel.port2]);
1576 } else {
1577 // If MessageChannel is not available, then try the
1578 // ServiceWorker.postMessage() approach using MessageEvent.sourc e
1579 // on the other end.
1580 message_port = navigator.serviceWorker;
1581 worker.postMessage({type: "connect"});
1582 }
1583 } else if (is_shared_worker(worker)) {
1584 message_port = worker.port;
1585 } else {
1586 message_port = worker;
1587 }
1588
1589 // Keeping a reference to the worker until worker_done() is seen
1590 // prevents the Worker object and its MessageChannel from going away
1591 // before all the messages are dispatched.
1592 this.worker = worker;
1593
1594 message_port.onmessage =
1595 function(message) {
1596 if (this_obj.running && (message.data.type in this_obj.message_h andlers)) {
1597 this_obj.message_handlers[message.data.type].call(this_obj, message.data);
1598 }
1599 };
1600 }
1601
1602 RemoteWorker.prototype.worker_error = function(error) {
1603 var message = error.message || String(error);
1604 var filename = (error.filename ? " " + error.filename: "");
1605 // FIXME: Display worker error states separately from main document
1606 // error state.
1607 this.worker_done({
1608 status: {
1609 status: tests.status.ERROR,
1610 message: "Error in worker" + filename + ": " + message,
1611 stack: error.stack
1612 }
1613 });
1614 error.preventDefault();
1615 };
1616
1617 RemoteWorker.prototype.test_state = function(data) {
1618 var remote_test = this.tests[data.test.index];
1619 if (!remote_test) {
1620 remote_test = new RemoteTest(data.test);
1621 this.tests[data.test.index] = remote_test;
1622 }
1623 remote_test.update_state_from(data.test);
1624 tests.notify_test_state(remote_test);
1625 };
1626
1627 RemoteWorker.prototype.test_done = function(data) {
1628 var remote_test = this.tests[data.test.index];
1629 remote_test.update_state_from(data.test);
1630 remote_test.done();
1631 tests.result(remote_test);
1632 };
1633
1634 RemoteWorker.prototype.worker_done = function(data) {
1635 if (tests.status.status === null &&
1636 data.status.status !== data.status.OK) {
1637 tests.status.status = data.status.status;
1638 tests.status.message = data.status.message;
1639 tests.status.stack = data.status.stack;
1640 }
1641 this.running = false;
1642 this.worker = null;
1643 if (tests.all_done()) {
1644 tests.complete();
1645 }
1646 };
1647
1648 RemoteWorker.prototype.message_handlers = {
1649 test_state: RemoteWorker.prototype.test_state,
1650 result: RemoteWorker.prototype.test_done,
1651 complete: RemoteWorker.prototype.worker_done
1652 };
1653
1654 /*
1655 * Harness
1656 */
1657
1658 function TestsStatus()
1659 {
1660 this.status = null;
1661 this.message = null;
1662 this.stack = null;
1663 }
1664
1665 TestsStatus.statuses = {
1666 OK:0,
1667 ERROR:1,
1668 TIMEOUT:2
1669 };
1670
1671 TestsStatus.prototype = merge({}, TestsStatus.statuses);
1672
1673 TestsStatus.prototype.structured_clone = function()
1674 {
1675 if (!this._structured_clone) {
1676 var msg = this.message;
1677 msg = msg ? String(msg) : msg;
1678 this._structured_clone = merge({
1679 status:this.status,
1680 message:msg,
1681 stack:this.stack
1682 }, TestsStatus.statuses);
1683 }
1684 return this._structured_clone;
1685 };
1686
1687 function Tests()
1688 {
1689 this.tests = [];
1690 this.num_pending = 0;
1691
1692 this.phases = {
1693 INITIAL:0,
1694 SETUP:1,
1695 HAVE_TESTS:2,
1696 HAVE_RESULTS:3,
1697 COMPLETE:4
1698 };
1699 this.phase = this.phases.INITIAL;
1700
1701 this.properties = {};
1702
1703 this.wait_for_finish = false;
1704 this.processing_callbacks = false;
1705
1706 this.allow_uncaught_exception = false;
1707
1708 this.file_is_test = false;
1709
1710 this.timeout_multiplier = 1;
1711 this.timeout_length = test_environment.test_timeout();
1712 this.timeout_id = null;
1713
1714 this.start_callbacks = [];
1715 this.test_state_callbacks = [];
1716 this.test_done_callbacks = [];
1717 this.all_done_callbacks = [];
1718
1719 this.pending_workers = [];
1720
1721 this.status = new TestsStatus();
1722
1723 var this_obj = this;
1724
1725 test_environment.add_on_loaded_callback(function() {
1726 if (this_obj.all_done()) {
1727 this_obj.complete();
1728 }
1729 });
1730
1731 this.set_timeout();
1732 }
1733
1734 Tests.prototype.setup = function(func, properties)
1735 {
1736 if (this.phase >= this.phases.HAVE_RESULTS) {
1737 return;
1738 }
1739
1740 if (this.phase < this.phases.SETUP) {
1741 this.phase = this.phases.SETUP;
1742 }
1743
1744 this.properties = properties;
1745
1746 for (var p in properties) {
1747 if (properties.hasOwnProperty(p)) {
1748 var value = properties[p];
1749 if (p == "allow_uncaught_exception") {
1750 this.allow_uncaught_exception = value;
1751 } else if (p == "explicit_done" && value) {
1752 this.wait_for_finish = true;
1753 } else if (p == "explicit_timeout" && value) {
1754 this.timeout_length = null;
1755 if (this.timeout_id)
1756 {
1757 clearTimeout(this.timeout_id);
1758 }
1759 } else if (p == "timeout_multiplier") {
1760 this.timeout_multiplier = value;
1761 }
1762 }
1763 }
1764
1765 if (func) {
1766 try {
1767 func();
1768 } catch (e) {
1769 this.status.status = this.status.ERROR;
1770 this.status.message = String(e);
1771 this.status.stack = e.stack ? e.stack : null;
1772 }
1773 }
1774 this.set_timeout();
1775 };
1776
1777 Tests.prototype.set_file_is_test = function() {
1778 if (this.tests.length > 0) {
1779 throw new Error("Tried to set file as test after creating a test");
1780 }
1781 this.wait_for_finish = true;
1782 this.file_is_test = true;
1783 // Create the test, which will add it to the list of tests
1784 async_test();
1785 };
1786
1787 Tests.prototype.set_timeout = function() {
1788 var this_obj = this;
1789 clearTimeout(this.timeout_id);
1790 if (this.timeout_length !== null) {
1791 this.timeout_id = setTimeout(function() {
1792 this_obj.timeout();
1793 }, this.timeout_length);
1794 }
1795 };
1796
1797 Tests.prototype.timeout = function() {
1798 if (this.status.status === null) {
1799 this.status.status = this.status.TIMEOUT;
1800 }
1801 this.complete();
1802 };
1803
1804 Tests.prototype.end_wait = function()
1805 {
1806 this.wait_for_finish = false;
1807 if (this.all_done()) {
1808 this.complete();
1809 }
1810 };
1811
1812 Tests.prototype.push = function(test)
1813 {
1814 if (this.phase < this.phases.HAVE_TESTS) {
1815 this.start();
1816 }
1817 this.num_pending++;
1818 test.index = this.tests.push(test);
1819 this.notify_test_state(test);
1820 };
1821
1822 Tests.prototype.notify_test_state = function(test) {
1823 var this_obj = this;
1824 forEach(this.test_state_callbacks,
1825 function(callback) {
1826 callback(test, this_obj);
1827 });
1828 };
1829
1830 Tests.prototype.all_done = function() {
1831 return (this.tests.length > 0 && test_environment.all_loaded &&
1832 this.num_pending === 0 && !this.wait_for_finish &&
1833 !this.processing_callbacks &&
1834 !this.pending_workers.some(function(w) { return w.running; }));
1835 };
1836
1837 Tests.prototype.start = function() {
1838 this.phase = this.phases.HAVE_TESTS;
1839 this.notify_start();
1840 };
1841
1842 Tests.prototype.notify_start = function() {
1843 var this_obj = this;
1844 forEach (this.start_callbacks,
1845 function(callback)
1846 {
1847 callback(this_obj.properties);
1848 });
1849 };
1850
1851 Tests.prototype.result = function(test)
1852 {
1853 if (this.phase > this.phases.HAVE_RESULTS) {
1854 return;
1855 }
1856 this.phase = this.phases.HAVE_RESULTS;
1857 this.num_pending--;
1858 this.notify_result(test);
1859 };
1860
1861 Tests.prototype.notify_result = function(test) {
1862 var this_obj = this;
1863 this.processing_callbacks = true;
1864 forEach(this.test_done_callbacks,
1865 function(callback)
1866 {
1867 callback(test, this_obj);
1868 });
1869 this.processing_callbacks = false;
1870 if (this_obj.all_done()) {
1871 this_obj.complete();
1872 }
1873 };
1874
1875 Tests.prototype.complete = function() {
1876 if (this.phase === this.phases.COMPLETE) {
1877 return;
1878 }
1879 this.phase = this.phases.COMPLETE;
1880 var this_obj = this;
1881 this.tests.forEach(
1882 function(x)
1883 {
1884 if (x.phase < x.phases.COMPLETE) {
1885 this_obj.notify_result(x);
1886 x.cleanup();
1887 x.phase = x.phases.COMPLETE;
1888 }
1889 }
1890 );
1891 this.notify_complete();
1892 };
1893
1894 Tests.prototype.notify_complete = function() {
1895 var this_obj = this;
1896 if (this.status.status === null) {
1897 this.status.status = this.status.OK;
1898 }
1899
1900 forEach (this.all_done_callbacks,
1901 function(callback)
1902 {
1903 callback(this_obj.tests, this_obj.status);
1904 });
1905 };
1906
1907 Tests.prototype.fetch_tests_from_worker = function(worker) {
1908 if (this.phase >= this.phases.COMPLETE) {
1909 return;
1910 }
1911
1912 this.pending_workers.push(new RemoteWorker(worker));
1913 };
1914
1915 function fetch_tests_from_worker(port) {
1916 tests.fetch_tests_from_worker(port);
1917 }
1918 expose(fetch_tests_from_worker, 'fetch_tests_from_worker');
1919
1920 function timeout() {
1921 if (tests.timeout_length === null) {
1922 tests.timeout();
1923 }
1924 }
1925 expose(timeout, 'timeout');
1926
1927 function add_start_callback(callback) {
1928 tests.start_callbacks.push(callback);
1929 }
1930
1931 function add_test_state_callback(callback) {
1932 tests.test_state_callbacks.push(callback);
1933 }
1934
1935 function add_result_callback(callback) {
1936 tests.test_done_callbacks.push(callback);
1937 }
1938
1939 function add_completion_callback(callback) {
1940 tests.all_done_callbacks.push(callback);
1941 }
1942
1943 expose(add_start_callback, 'add_start_callback');
1944 expose(add_test_state_callback, 'add_test_state_callback');
1945 expose(add_result_callback, 'add_result_callback');
1946 expose(add_completion_callback, 'add_completion_callback');
1947
1948 function remove(array, item) {
1949 var index = array.indexOf(item);
1950 if (index > -1) {
1951 array.splice(index, 1);
1952 }
1953 }
1954
1955 function remove_start_callback(callback) {
1956 remove(tests.start_callbacks, callback);
1957 }
1958
1959 function remove_test_state_callback(callback) {
1960 remove(tests.test_state_callbacks, callback);
1961 }
1962
1963 function remove_result_callback(callback) {
1964 remove(tests.test_done_callbacks, callback);
1965 }
1966
1967 function remove_completion_callback(callback) {
1968 remove(tests.all_done_callbacks, callback);
1969 }
1970
1971 /*
1972 * Output listener
1973 */
1974
1975 function Output() {
1976 this.output_document = document;
1977 this.output_node = null;
1978 this.enabled = settings.output;
1979 this.phase = this.INITIAL;
1980 }
1981
1982 Output.prototype.INITIAL = 0;
1983 Output.prototype.STARTED = 1;
1984 Output.prototype.HAVE_RESULTS = 2;
1985 Output.prototype.COMPLETE = 3;
1986
1987 Output.prototype.setup = function(properties) {
1988 if (this.phase > this.INITIAL) {
1989 return;
1990 }
1991
1992 //If output is disabled in testharnessreport.js the test shouldn't be
1993 //able to override that
1994 this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
1995 properties.output : settings.output);
1996 };
1997
1998 Output.prototype.init = function(properties) {
1999 if (this.phase >= this.STARTED) {
2000 return;
2001 }
2002 if (properties.output_document) {
2003 this.output_document = properties.output_document;
2004 } else {
2005 this.output_document = document;
2006 }
2007 this.phase = this.STARTED;
2008 };
2009
2010 Output.prototype.resolve_log = function() {
2011 var output_document;
2012 if (typeof this.output_document === "function") {
2013 output_document = this.output_document.apply(undefined);
2014 } else {
2015 output_document = this.output_document;
2016 }
2017 if (!output_document) {
2018 return;
2019 }
2020 var node = output_document.getElementById("log");
2021 if (!node) {
2022 if (!document.body || document.readyState == "loading") {
2023 return;
2024 }
2025 node = output_document.createElement("div");
2026 node.id = "log";
2027 output_document.body.appendChild(node);
2028 }
2029 this.output_document = output_document;
2030 this.output_node = node;
2031 };
2032
2033 Output.prototype.show_status = function() {
2034 if (this.phase < this.STARTED) {
2035 this.init();
2036 }
2037 if (!this.enabled) {
2038 return;
2039 }
2040 if (this.phase < this.HAVE_RESULTS) {
2041 this.resolve_log();
2042 this.phase = this.HAVE_RESULTS;
2043 }
2044 var done_count = tests.tests.length - tests.num_pending;
2045 if (this.output_node) {
2046 if (done_count < 100 ||
2047 (done_count < 1000 && done_count % 100 === 0) ||
2048 done_count % 1000 === 0) {
2049 this.output_node.textContent = "Running, " +
2050 done_count + " complete, " +
2051 tests.num_pending + " remain";
2052 }
2053 }
2054 };
2055
2056 Output.prototype.show_results = function (tests, harness_status) {
2057 if (this.phase >= this.COMPLETE) {
2058 return;
2059 }
2060 if (!this.enabled) {
2061 return;
2062 }
2063 if (!this.output_node) {
2064 this.resolve_log();
2065 }
2066 this.phase = this.COMPLETE;
2067
2068 var log = this.output_node;
2069 if (!log) {
2070 return;
2071 }
2072 var output_document = this.output_document;
2073
2074 while (log.lastChild) {
2075 log.removeChild(log.lastChild);
2076 }
2077
2078 var harness_url = get_harness_url();
2079 if (harness_url !== null) {
2080 var stylesheet = output_document.createElementNS(xhtml_ns, "link");
2081 stylesheet.setAttribute("rel", "stylesheet");
2082 stylesheet.setAttribute("href", harness_url + "testharness.css");
2083 var heads = output_document.getElementsByTagName("head");
2084 if (heads.length) {
2085 heads[0].appendChild(stylesheet);
2086 }
2087 }
2088
2089 var status_text_harness = {};
2090 status_text_harness[harness_status.OK] = "OK";
2091 status_text_harness[harness_status.ERROR] = "Error";
2092 status_text_harness[harness_status.TIMEOUT] = "Timeout";
2093
2094 var status_text = {};
2095 status_text[Test.prototype.PASS] = "Pass";
2096 status_text[Test.prototype.FAIL] = "Fail";
2097 status_text[Test.prototype.TIMEOUT] = "Timeout";
2098 status_text[Test.prototype.NOTRUN] = "Not Run";
2099
2100 var status_number = {};
2101 forEach(tests,
2102 function(test) {
2103 var status = status_text[test.status];
2104 if (status_number.hasOwnProperty(status)) {
2105 status_number[status] += 1;
2106 } else {
2107 status_number[status] = 1;
2108 }
2109 });
2110
2111 function status_class(status)
2112 {
2113 return status.replace(/\s/g, '').toLowerCase();
2114 }
2115
2116 var summary_template = ["section", {"id":"summary"},
2117 ["h2", {}, "Summary"],
2118 function()
2119 {
2120
2121 var status = status_text_harness[harness_sta tus.status];
2122 var rv = [["section", {},
2123 ["p", {},
2124 "Harness status: ",
2125 ["span", {"class":status_class(s tatus)},
2126 status
2127 ],
2128 ]
2129 ]];
2130
2131 if (harness_status.status === harness_status .ERROR) {
2132 rv[0].push(["pre", {}, harness_status.me ssage]);
2133 if (harness_status.stack) {
2134 rv[0].push(["pre", {}, harness_statu s.stack]);
2135 }
2136 }
2137 return rv;
2138 },
2139 ["p", {}, "Found ${num_tests} tests"],
2140 function() {
2141 var rv = [["div", {}]];
2142 var i = 0;
2143 while (status_text.hasOwnProperty(i)) {
2144 if (status_number.hasOwnProperty(status_ text[i])) {
2145 var status = status_text[i];
2146 rv[0].push(["div", {"class":status_c lass(status)},
2147 ["label", {},
2148 ["input", {type:"checkb ox", checked:"checked"}],
2149 status_number[status] + " " + status]]);
2150 }
2151 i++;
2152 }
2153 return rv;
2154 },
2155 ];
2156
2157 log.appendChild(render(summary_template, {num_tests:tests.length}, outpu t_document));
2158
2159 forEach(output_document.querySelectorAll("section#summary label"),
2160 function(element)
2161 {
2162 on_event(element, "click",
2163 function(e)
2164 {
2165 if (output_document.getElementById("results") = == null) {
2166 e.preventDefault();
2167 return;
2168 }
2169 var result_class = element.parentNode.getAttrib ute("class");
2170 var style_element = output_document.querySelect or("style#hide-" + result_class);
2171 var input_element = element.querySelector("inpu t");
2172 if (!style_element && !input_element.checked) {
2173 style_element = output_document.createEleme ntNS(xhtml_ns, "style");
2174 style_element.id = "hide-" + result_class;
2175 style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
2176 output_document.body.appendChild(style_elem ent);
2177 } else if (style_element && input_element.check ed) {
2178 style_element.parentNode.removeChild(style_ element);
2179 }
2180 });
2181 });
2182
2183 // This use of innerHTML plus manual escaping is not recommended in
2184 // general, but is necessary here for performance. Using textContent
2185 // on each individual <td> adds tens of seconds of execution time for
2186 // large test suites (tens of thousands of tests).
2187 function escape_html(s)
2188 {
2189 return s.replace(/\&/g, "&amp;")
2190 .replace(/</g, "&lt;")
2191 .replace(/"/g, "&quot;")
2192 .replace(/'/g, "&#39;");
2193 }
2194
2195 function has_assertions()
2196 {
2197 for (var i = 0; i < tests.length; i++) {
2198 if (tests[i].properties.hasOwnProperty("assert")) {
2199 return true;
2200 }
2201 }
2202 return false;
2203 }
2204
2205 function get_assertion(test)
2206 {
2207 if (test.properties.hasOwnProperty("assert")) {
2208 if (Array.isArray(test.properties.assert)) {
2209 return test.properties.assert.join(' ');
2210 }
2211 return test.properties.assert;
2212 }
2213 return '';
2214 }
2215
2216 log.appendChild(document.createElementNS(xhtml_ns, "section"));
2217 var assertions = has_assertions();
2218 var html = "<h2>Details</h2><table id='results' " + (assertions ? "class ='assertions'" : "" ) + ">" +
2219 "<thead><tr><th>Result</th><th>Test Name</th>" +
2220 (assertions ? "<th>Assertion</th>" : "") +
2221 "<th>Message</th></tr></thead>" +
2222 "<tbody>";
2223 for (var i = 0; i < tests.length; i++) {
2224 html += '<tr class="' +
2225 escape_html(status_class(status_text[tests[i].status])) +
2226 '"><td>' +
2227 escape_html(status_text[tests[i].status]) +
2228 "</td><td>" +
2229 escape_html(tests[i].name) +
2230 "</td><td>" +
2231 (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") +
2232 escape_html(tests[i].message ? tests[i].message : " ") +
2233 (tests[i].stack ? "<pre>" +
2234 escape_html(tests[i].stack) +
2235 "</pre>": "") +
2236 "</td></tr>";
2237 }
2238 html += "</tbody></table>";
2239 try {
2240 log.lastChild.innerHTML = html;
2241 } catch (e) {
2242 log.appendChild(document.createElementNS(xhtml_ns, "p"))
2243 .textContent = "Setting innerHTML for the log threw an exception. ";
2244 log.appendChild(document.createElementNS(xhtml_ns, "pre"))
2245 .textContent = html;
2246 }
2247 };
2248
2249 /*
2250 * Template code
2251 *
2252 * A template is just a javascript structure. An element is represented as:
2253 *
2254 * [tag_name, {attr_name:attr_value}, child1, child2]
2255 *
2256 * the children can either be strings (which act like text nodes), other tem plates or
2257 * functions (see below)
2258 *
2259 * A text node is represented as
2260 *
2261 * ["{text}", value]
2262 *
2263 * String values have a simple substitution syntax; ${foo} represents a vari able foo.
2264 *
2265 * It is possible to embed logic in templates by using a function in a place where a
2266 * node would usually go. The function must either return part of a template or null.
2267 *
2268 * In cases where a set of nodes are required as output rather than a single node
2269 * with children it is possible to just use a list
2270 * [node1, node2, node3]
2271 *
2272 * Usage:
2273 *
2274 * render(template, substitutions) - take a template and an object mapping
2275 * variable names to parameters and return either a DOM node or a list of DO M nodes
2276 *
2277 * substitute(template, substitutions) - take a template and variable mappin g object,
2278 * make the variable substitutions and return the substituted template
2279 *
2280 */
2281
2282 function is_single_node(template)
2283 {
2284 return typeof template[0] === "string";
2285 }
2286
2287 function substitute(template, substitutions)
2288 {
2289 if (typeof template === "function") {
2290 var replacement = template(substitutions);
2291 if (!replacement) {
2292 return null;
2293 }
2294
2295 return substitute(replacement, substitutions);
2296 }
2297
2298 if (is_single_node(template)) {
2299 return substitute_single(template, substitutions);
2300 }
2301
2302 return filter(map(template, function(x) {
2303 return substitute(x, substitutions);
2304 }), function(x) {return x !== null;});
2305 }
2306
2307 function substitute_single(template, substitutions)
2308 {
2309 var substitution_re = /\$\{([^ }]*)\}/g;
2310
2311 function do_substitution(input) {
2312 var components = input.split(substitution_re);
2313 var rv = [];
2314 for (var i = 0; i < components.length; i += 2) {
2315 rv.push(components[i]);
2316 if (components[i + 1]) {
2317 rv.push(String(substitutions[components[i + 1]]));
2318 }
2319 }
2320 return rv;
2321 }
2322
2323 function substitute_attrs(attrs, rv)
2324 {
2325 rv[1] = {};
2326 for (var name in template[1]) {
2327 if (attrs.hasOwnProperty(name)) {
2328 var new_name = do_substitution(name).join("");
2329 var new_value = do_substitution(attrs[name]).join("");
2330 rv[1][new_name] = new_value;
2331 }
2332 }
2333 }
2334
2335 function substitute_children(children, rv)
2336 {
2337 for (var i = 0; i < children.length; i++) {
2338 if (children[i] instanceof Object) {
2339 var replacement = substitute(children[i], substitutions);
2340 if (replacement !== null) {
2341 if (is_single_node(replacement)) {
2342 rv.push(replacement);
2343 } else {
2344 extend(rv, replacement);
2345 }
2346 }
2347 } else {
2348 extend(rv, do_substitution(String(children[i])));
2349 }
2350 }
2351 return rv;
2352 }
2353
2354 var rv = [];
2355 rv.push(do_substitution(String(template[0])).join(""));
2356
2357 if (template[0] === "{text}") {
2358 substitute_children(template.slice(1), rv);
2359 } else {
2360 substitute_attrs(template[1], rv);
2361 substitute_children(template.slice(2), rv);
2362 }
2363
2364 return rv;
2365 }
2366
2367 function make_dom_single(template, doc)
2368 {
2369 var output_document = doc || document;
2370 var element;
2371 if (template[0] === "{text}") {
2372 element = output_document.createTextNode("");
2373 for (var i = 1; i < template.length; i++) {
2374 element.data += template[i];
2375 }
2376 } else {
2377 element = output_document.createElementNS(xhtml_ns, template[0]);
2378 for (var name in template[1]) {
2379 if (template[1].hasOwnProperty(name)) {
2380 element.setAttribute(name, template[1][name]);
2381 }
2382 }
2383 for (var i = 2; i < template.length; i++) {
2384 if (template[i] instanceof Object) {
2385 var sub_element = make_dom(template[i]);
2386 element.appendChild(sub_element);
2387 } else {
2388 var text_node = output_document.createTextNode(template[i]);
2389 element.appendChild(text_node);
2390 }
2391 }
2392 }
2393
2394 return element;
2395 }
2396
2397 function make_dom(template, substitutions, output_document)
2398 {
2399 if (is_single_node(template)) {
2400 return make_dom_single(template, output_document);
2401 }
2402
2403 return map(template, function(x) {
2404 return make_dom_single(x, output_document);
2405 });
2406 }
2407
2408 function render(template, substitutions, output_document)
2409 {
2410 return make_dom(substitute(template, substitutions), output_document);
2411 }
2412
2413 /*
2414 * Utility funcions
2415 */
2416 function assert(expected_true, function_name, description, error, substituti ons)
2417 {
2418 if (tests.tests.length === 0) {
2419 tests.set_file_is_test();
2420 }
2421 if (expected_true !== true) {
2422 var msg = make_message(function_name, description,
2423 error, substitutions);
2424 throw new AssertionError(msg);
2425 }
2426 }
2427
2428 function AssertionError(message)
2429 {
2430 this.message = message;
2431 this.stack = this.get_stack();
2432 }
2433
2434 AssertionError.prototype = Object.create(Error.prototype);
2435
2436 AssertionError.prototype.get_stack = function() {
2437 var stack = new Error().stack;
2438 // IE11 does not initialize 'Error.stack' until the object is thrown.
2439 if (!stack) {
2440 try {
2441 throw new Error();
2442 } catch (e) {
2443 stack = e.stack;
2444 }
2445 }
2446
2447 var lines = stack.split("\n");
2448
2449 // Create a pattern to match stack frames originating within testharness .js. These include the
2450 // script URL, followed by the line/col (e.g., '/resources/testharness.j s:120:21').
2451 var re = new RegExp((get_script_url() || "\\btestharness.js") + ":\\d+:\ \d+");
2452
2453 // Some browsers include a preamble that specifies the type of the error object. Skip this by
2454 // advancing until we find the first stack frame originating from testha rness.js.
2455 var i = 0;
2456 while (!re.test(lines[i]) && i < lines.length) {
2457 i++;
2458 }
2459
2460 // Then skip the top frames originating from testharness.js to begin the stack at the test code.
2461 while (re.test(lines[i]) && i < lines.length) {
2462 i++;
2463 }
2464
2465 // Paranoid check that we didn't skip all frames. If so, return the ori ginal stack unmodified.
2466 if (i >= lines.length) {
2467 return stack;
2468 }
2469
2470 return lines.slice(i).join("\n");
2471 }
2472
2473 function make_message(function_name, description, error, substitutions)
2474 {
2475 for (var p in substitutions) {
2476 if (substitutions.hasOwnProperty(p)) {
2477 substitutions[p] = format_value(substitutions[p]);
2478 }
2479 }
2480 var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
2481 merge({function_name:function_name,
2482 description:(description?description + " ":"")},
2483 substitutions));
2484 return node_form.slice(1).join("");
2485 }
2486
2487 function filter(array, callable, thisObj) {
2488 var rv = [];
2489 for (var i = 0; i < array.length; i++) {
2490 if (array.hasOwnProperty(i)) {
2491 var pass = callable.call(thisObj, array[i], i, array);
2492 if (pass) {
2493 rv.push(array[i]);
2494 }
2495 }
2496 }
2497 return rv;
2498 }
2499
2500 function map(array, callable, thisObj)
2501 {
2502 var rv = [];
2503 rv.length = array.length;
2504 for (var i = 0; i < array.length; i++) {
2505 if (array.hasOwnProperty(i)) {
2506 rv[i] = callable.call(thisObj, array[i], i, array);
2507 }
2508 }
2509 return rv;
2510 }
2511
2512 function extend(array, items)
2513 {
2514 Array.prototype.push.apply(array, items);
2515 }
2516
2517 function forEach(array, callback, thisObj)
2518 {
2519 for (var i = 0; i < array.length; i++) {
2520 if (array.hasOwnProperty(i)) {
2521 callback.call(thisObj, array[i], i, array);
2522 }
2523 }
2524 }
2525
2526 function merge(a,b)
2527 {
2528 var rv = {};
2529 var p;
2530 for (p in a) {
2531 rv[p] = a[p];
2532 }
2533 for (p in b) {
2534 rv[p] = b[p];
2535 }
2536 return rv;
2537 }
2538
2539 function expose(object, name)
2540 {
2541 var components = name.split(".");
2542 var target = test_environment.global_scope();
2543 for (var i = 0; i < components.length - 1; i++) {
2544 if (!(components[i] in target)) {
2545 target[components[i]] = {};
2546 }
2547 target = target[components[i]];
2548 }
2549 target[components[components.length - 1]] = object;
2550 }
2551
2552 function is_same_origin(w) {
2553 try {
2554 'random_prop' in w;
2555 return true;
2556 } catch (e) {
2557 return false;
2558 }
2559 }
2560
2561 /** Returns the 'src' URL of the first <script> tag in the page to include t he file 'testharness.js'. */
2562 function get_script_url()
2563 {
2564 if (!('document' in self)) {
2565 return undefined;
2566 }
2567
2568 var scripts = document.getElementsByTagName("script");
2569 for (var i = 0; i < scripts.length; i++) {
2570 var src;
2571 if (scripts[i].src) {
2572 src = scripts[i].src;
2573 } else if (scripts[i].href) {
2574 //SVG case
2575 src = scripts[i].href.baseVal;
2576 }
2577
2578 var matches = src && src.match(/^(.*\/|)testharness\.js$/);
2579 if (matches) {
2580 return src;
2581 }
2582 }
2583 return undefined;
2584 }
2585
2586 /** Returns the URL path at which the files for testharness.js are assumed t o reside (e.g., '/resources/').
2587 The path is derived from inspecting the 'src' of the <script> tag that i ncluded 'testharness.js'. */
2588 function get_harness_url()
2589 {
2590 var script_url = get_script_url();
2591
2592 // Exclude the 'testharness.js' file from the returned path, but '+ 1' t o include the trailing slash.
2593 return script_url ? script_url.slice(0, script_url.lastIndexOf('/') + 1) : undefined;
2594 }
2595
2596 function supports_post_message(w)
2597 {
2598 var supports;
2599 var type;
2600 // Given IE implements postMessage across nested iframes but not across
2601 // windows or tabs, you can't infer cross-origin communication from the presence
2602 // of postMessage on the current window object only.
2603 //
2604 // Touching the postMessage prop on a window can throw if the window is
2605 // not from the same origin AND post message is not supported in that
2606 // browser. So just doing an existence test here won't do, you also need
2607 // to wrap it in a try..cacth block.
2608 try {
2609 type = typeof w.postMessage;
2610 if (type === "function") {
2611 supports = true;
2612 }
2613
2614 // IE8 supports postMessage, but implements it as a host object whic h
2615 // returns "object" as its `typeof`.
2616 else if (type === "object") {
2617 supports = true;
2618 }
2619
2620 // This is the case where postMessage isn't supported AND accessing a
2621 // window property across origins does NOT throw (e.g. old Safari br owser).
2622 else {
2623 supports = false;
2624 }
2625 } catch (e) {
2626 // This is the case where postMessage isn't supported AND accessing a
2627 // window property across origins throws (e.g. old Firefox browser).
2628 supports = false;
2629 }
2630 return supports;
2631 }
2632
2633 /**
2634 * Setup globals
2635 */
2636
2637 var tests = new Tests();
2638
2639 addEventListener("error", function(e) {
2640 if (tests.file_is_test) {
2641 var test = tests.tests[0];
2642 if (test.phase >= test.phases.HAS_RESULT) {
2643 return;
2644 }
2645 test.set_status(test.FAIL, e.message, e.stack);
2646 test.phase = test.phases.HAS_RESULT;
2647 test.done();
2648 done();
2649 } else if (!tests.allow_uncaught_exception) {
2650 tests.status.status = tests.status.ERROR;
2651 tests.status.message = e.message;
2652 tests.status.stack = e.stack;
2653 }
2654 });
2655
2656 test_environment.on_tests_ready();
2657
2658 })();
2659 // vim: set expandtab shiftwidth=4 tabstop=4:
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698