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

Side by Side Diff: polymer_0.5.0/bower_components/web-animations-js/test/blink/testharness/testharness.js

Issue 786953007: npm_modules: Fork bower_components into Polymer 0.4.0 and 0.5.0 versions (Closed) Base URL: https://chromium.googlesource.com/infra/third_party/npm_modules.git@master
Patch Set: Created 5 years, 11 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 };
27
28 var xhtml_ns = "http://www.w3.org/1999/xhtml";
29
30 // script_prefix is used by Output.prototype.show_results() to figure out
31 // where to get testharness.css from. It's enclosed in an extra closure to
32 // not pollute the library's namespace with variables like "src".
33 var script_prefix = null;
34 (function ()
35 {
36 var scripts = document.getElementsByTagName("script");
37 for (var i = 0; i < scripts.length; i++) {
38 var src;
39 if (scripts[i].src) {
40 src = scripts[i].src;
41 } else if (scripts[i].href) {
42 //SVG case
43 src = scripts[i].href.baseVal;
44 }
45
46 if (src && src.slice(src.length - "testharness.js".length) === "test harness.js") {
47 script_prefix = src.slice(0, src.length - "testharness.js".lengt h);
48 break;
49 }
50 }
51 })();
52
53 /*
54 * API functions
55 */
56
57 var name_counter = 0;
58 function next_default_name()
59 {
60 //Don't use document.title to work around an Opera bug in XHTML document s
61 var title = document.getElementsByTagName("title")[0];
62 var prefix = (title && title.firstChild && title.firstChild.data) || "Un titled";
63 var suffix = name_counter > 0 ? " " + name_counter : "";
64 name_counter++;
65 return prefix + suffix;
66 }
67
68 function test(func, name, properties)
69 {
70 var test_name = name ? name : next_default_name();
71 properties = properties ? properties : {};
72 var test_obj = new Test(test_name, properties);
73 test_obj.step(func, test_obj, test_obj);
74 if (test_obj.phase === test_obj.phases.STARTED) {
75 test_obj.done();
76 }
77 }
78
79 function async_test(func, name, properties)
80 {
81 if (typeof func !== "function") {
82 properties = name;
83 name = func;
84 func = null;
85 }
86 var test_name = name ? name : next_default_name();
87 properties = properties ? properties : {};
88 var test_obj = new Test(test_name, properties);
89 if (func) {
90 test_obj.step(func, test_obj, test_obj);
91 }
92 return test_obj;
93 }
94
95 function setup(func_or_properties, maybe_properties)
96 {
97 var func = null;
98 var properties = {};
99 if (arguments.length === 2) {
100 func = func_or_properties;
101 properties = maybe_properties;
102 } else if (func_or_properties instanceof Function) {
103 func = func_or_properties;
104 } else {
105 properties = func_or_properties;
106 }
107 tests.setup(func, properties);
108 output.setup(properties);
109 }
110
111 function done() {
112 if (tests.tests.length === 0) {
113 tests.set_file_is_test();
114 }
115 if (tests.file_is_test) {
116 tests.tests[0].done();
117 }
118 tests.end_wait();
119 }
120
121 function generate_tests(func, args, properties) {
122 forEach(args, function(x, i)
123 {
124 var name = x[0];
125 test(function()
126 {
127 func.apply(this, x.slice(1));
128 },
129 name,
130 Array.isArray(properties) ? properties[i] : properties) ;
131 });
132 }
133
134 function on_event(object, event, callback)
135 {
136 object.addEventListener(event, callback, false);
137 }
138
139 expose(test, 'test');
140 expose(async_test, 'async_test');
141 expose(generate_tests, 'generate_tests');
142 expose(setup, 'setup');
143 expose(done, 'done');
144 expose(on_event, 'on_event');
145
146 /*
147 * Return a string truncated to the given length, with ... added at the end
148 * if it was longer.
149 */
150 function truncate(s, len)
151 {
152 if (s.length > len) {
153 return s.substring(0, len - 3) + "...";
154 }
155 return s;
156 }
157
158 /*
159 * Return true if object is probably a Node object.
160 */
161 function is_node(object)
162 {
163 // I use duck-typing instead of instanceof, because
164 // instanceof doesn't work if the node is from another window (like an
165 // iframe's contentWindow):
166 // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
167 if ("nodeType" in object &&
168 "nodeName" in object &&
169 "nodeValue" in object &&
170 "childNodes" in object) {
171 try {
172 object.nodeType;
173 } catch (e) {
174 // The object is probably Node.prototype or another prototype
175 // object that inherits from it, and not a Node instance.
176 return false;
177 }
178 return true;
179 }
180 return false;
181 }
182
183 /*
184 * Convert a value to a nice, human-readable string
185 */
186 function format_value(val, seen)
187 {
188 if (!seen) {
189 seen = [];
190 }
191 if (typeof val === "object" && val !== null) {
192 if (seen.indexOf(val) >= 0) {
193 return "[...]";
194 }
195 seen.push(val);
196 }
197 if (Array.isArray(val)) {
198 return "[" + val.map(function(x) {return format_value(x, seen);}).jo in(", ") + "]";
199 }
200
201 switch (typeof val) {
202 case "string":
203 val = val.replace("\\", "\\\\");
204 for (var i = 0; i < 32; i++) {
205 var replace = "\\";
206 switch (i) {
207 case 0: replace += "0"; break;
208 case 1: replace += "x01"; break;
209 case 2: replace += "x02"; break;
210 case 3: replace += "x03"; break;
211 case 4: replace += "x04"; break;
212 case 5: replace += "x05"; break;
213 case 6: replace += "x06"; break;
214 case 7: replace += "x07"; break;
215 case 8: replace += "b"; break;
216 case 9: replace += "t"; break;
217 case 10: replace += "n"; break;
218 case 11: replace += "v"; break;
219 case 12: replace += "f"; break;
220 case 13: replace += "r"; break;
221 case 14: replace += "x0e"; break;
222 case 15: replace += "x0f"; break;
223 case 16: replace += "x10"; break;
224 case 17: replace += "x11"; break;
225 case 18: replace += "x12"; break;
226 case 19: replace += "x13"; break;
227 case 20: replace += "x14"; break;
228 case 21: replace += "x15"; break;
229 case 22: replace += "x16"; break;
230 case 23: replace += "x17"; break;
231 case 24: replace += "x18"; break;
232 case 25: replace += "x19"; break;
233 case 26: replace += "x1a"; break;
234 case 27: replace += "x1b"; break;
235 case 28: replace += "x1c"; break;
236 case 29: replace += "x1d"; break;
237 case 30: replace += "x1e"; break;
238 case 31: replace += "x1f"; break;
239 }
240 val = val.replace(RegExp(String.fromCharCode(i), "g"), replace);
241 }
242 return '"' + val.replace(/"/g, '\\"') + '"';
243 case "boolean":
244 case "undefined":
245 return String(val);
246 case "number":
247 // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
248 // special-case.
249 if (val === -0 && 1/val === -Infinity) {
250 return "-0";
251 }
252 return String(val);
253 case "object":
254 if (val === null) {
255 return "null";
256 }
257
258 // Special-case Node objects, since those come up a lot in my tests. I
259 // ignore namespaces.
260 if (is_node(val)) {
261 switch (val.nodeType) {
262 case Node.ELEMENT_NODE:
263 var ret = "<" + val.localName;
264 for (var i = 0; i < val.attributes.length; i++) {
265 ret += " " + val.attributes[i].name + '="' + val.attribu tes[i].value + '"';
266 }
267 ret += ">" + val.innerHTML + "</" + val.localName + ">";
268 return "Element node " + truncate(ret, 60);
269 case Node.TEXT_NODE:
270 return 'Text node "' + truncate(val.data, 60) + '"';
271 case Node.PROCESSING_INSTRUCTION_NODE:
272 return "ProcessingInstruction node with target " + format_va lue(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 6 0));
273 case Node.COMMENT_NODE:
274 return "Comment node <!--" + truncate(val.data, 60) + "-->";
275 case Node.DOCUMENT_NODE:
276 return "Document node with " + val.childNodes.length + (val. childNodes.length == 1 ? " child" : " children");
277 case Node.DOCUMENT_TYPE_NODE:
278 return "DocumentType node";
279 case Node.DOCUMENT_FRAGMENT_NODE:
280 return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
281 default:
282 return "Node object of unknown type";
283 }
284 }
285
286 /* falls through */
287 default:
288 return typeof val + ' "' + truncate(String(val), 60) + '"';
289 }
290 }
291 expose(format_value, "format_value");
292
293 /*
294 * Assertions
295 */
296
297 function assert_true(actual, description)
298 {
299 assert(actual === true, "assert_true", description,
300 "expected true got ${actual}", {actual:actual});
301 }
302 expose(assert_true, "assert_true");
303
304 function assert_false(actual, description)
305 {
306 assert(actual === false, "assert_false", description,
307 "expected false got ${actual}", {actual:actual} );
308 }
309 expose(assert_false, "assert_false");
310
311 function same_value(x, y) {
312 if (y !== y) {
313 //NaN case
314 return x !== x;
315 }
316 if (x === 0 && y === 0) {
317 //Distinguish +0 and -0
318 return 1/x === 1/y;
319 }
320 return x === y;
321 }
322
323 function assert_equals(actual, expected, description)
324 {
325 /*
326 * Test if two primitives are equal or two objects
327 * are the same object
328 */
329 if (typeof actual != typeof expected) {
330 assert(false, "assert_equals", description,
331 "expected (" + typeof expected + ") ${expected} but go t (" + typeof actual + ") ${actual}",
332 {expected:expected, actual:actual});
333 return;
334 }
335 assert(same_value(actual, expected), "assert_equals", description,
336 "expected ${expected} but got ${act ual}",
337 {expected:expected, actual:actual}) ;
338 }
339 expose(assert_equals, "assert_equals");
340
341 function assert_not_equals(actual, expected, description)
342 {
343 /*
344 * Test if two primitives are unequal or two objects
345 * are different objects
346 */
347 assert(!same_value(actual, expected), "assert_not_equals", description,
348 "got disallowed value ${actual}",
349 {actual:actual});
350 }
351 expose(assert_not_equals, "assert_not_equals");
352
353 function assert_in_array(actual, expected, description)
354 {
355 assert(expected.indexOf(actual) != -1, "assert_in_array", description,
356 "value ${actual} not in array ${e xpected}",
357 {actual:actual, expected:expected });
358 }
359 expose(assert_in_array, "assert_in_array");
360
361 function assert_object_equals(actual, expected, description)
362 {
363 //This needs to be improved a great deal
364 function check_equal(actual, expected, stack)
365 {
366 stack.push(actual);
367
368 var p;
369 for (p in actual) {
370 assert(expected.hasOwnProperty(p), "assert_object_equals", desc ription,
371 "unexpected property ${p}", {p:p});
372
373 if (typeof actual[p] === "object" && actual[p] !== null) {
374 if (stack.indexOf(actual[p]) === -1) {
375 check_equal(actual[p], expected[p], stack);
376 }
377 } else {
378 assert(same_value(actual[p], expected[p]), "assert_object_e quals", description,
379 "property ${p} expected $ {expected} got ${actual}",
380 {p:p, expected:expected, actual:actual});
381 }
382 }
383 for (p in expected) {
384 assert(actual.hasOwnProperty(p),
385 "assert_object_equals", description,
386 "expected property ${p} missing", {p:p});
387 }
388 stack.pop();
389 }
390 check_equal(actual, expected, []);
391 }
392 expose(assert_object_equals, "assert_object_equals");
393
394 function assert_array_equals(actual, expected, description)
395 {
396 assert(actual.length === expected.length,
397 "assert_array_equals", description,
398 "lengths differ, expected ${expected} got ${actual}",
399 {expected:expected.length, actual:actual.length});
400
401 for (var i = 0; i < actual.length; i++) {
402 assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
403 "assert_array_equals", description,
404 "property ${i}, property expected to be $expected but was $ac tual",
405 {i:i, expected:expected.hasOwnProperty(i) ? "present" : "miss ing",
406 actual:actual.hasOwnProperty(i) ? "present" : "missing"});
407 assert(same_value(expected[i], actual[i]),
408 "assert_array_equals", description,
409 "property ${i}, expected ${expected} but got ${actual}",
410 {i:i, expected:expected[i], actual:actual[i]});
411 }
412 }
413 expose(assert_array_equals, "assert_array_equals");
414
415 function assert_approx_equals(actual, expected, epsilon, description)
416 {
417 /*
418 * Test if two primitive numbers are equal withing +/- epsilon
419 */
420 assert(typeof actual === "number",
421 "assert_approx_equals", description,
422 "expected a number but got a ${type_actual}",
423 {type_actual:typeof actual});
424
425 assert(Math.abs(actual - expected) <= epsilon,
426 "assert_approx_equals", description,
427 "expected ${expected} +/- ${epsilon} but got ${actual}",
428 {expected:expected, actual:actual, epsilon:epsilon});
429 }
430 expose(assert_approx_equals, "assert_approx_equals");
431
432 function assert_less_than(actual, expected, description)
433 {
434 /*
435 * Test if a primitive number is less than another
436 */
437 assert(typeof actual === "number",
438 "assert_less_than", description,
439 "expected a number but got a ${type_actual}",
440 {type_actual:typeof actual});
441
442 assert(actual < expected,
443 "assert_less_than", description,
444 "expected a number less than ${expected} but got ${actual}",
445 {expected:expected, actual:actual});
446 }
447 expose(assert_less_than, "assert_less_than");
448
449 function assert_greater_than(actual, expected, description)
450 {
451 /*
452 * Test if a primitive number is greater than another
453 */
454 assert(typeof actual === "number",
455 "assert_greater_than", description,
456 "expected a number but got a ${type_actual}",
457 {type_actual:typeof actual});
458
459 assert(actual > expected,
460 "assert_greater_than", description,
461 "expected a number greater than ${expected} but got ${actual}",
462 {expected:expected, actual:actual});
463 }
464 expose(assert_greater_than, "assert_greater_than");
465
466 function assert_less_than_equal(actual, expected, description)
467 {
468 /*
469 * Test if a primitive number is less than or equal to another
470 */
471 assert(typeof actual === "number",
472 "assert_less_than_equal", description,
473 "expected a number but got a ${type_actual}",
474 {type_actual:typeof actual});
475
476 assert(actual <= expected,
477 "assert_less_than", description,
478 "expected a number less than or equal to ${expected} but got ${ac tual}",
479 {expected:expected, actual:actual});
480 }
481 expose(assert_less_than_equal, "assert_less_than_equal");
482
483 function assert_greater_than_equal(actual, expected, description)
484 {
485 /*
486 * Test if a primitive number is greater than or equal to another
487 */
488 assert(typeof actual === "number",
489 "assert_greater_than_equal", description,
490 "expected a number but got a ${type_actual}",
491 {type_actual:typeof actual});
492
493 assert(actual >= expected,
494 "assert_greater_than_equal", description,
495 "expected a number greater than or equal to ${expected} but got $ {actual}",
496 {expected:expected, actual:actual});
497 }
498 expose(assert_greater_than_equal, "assert_greater_than_equal");
499
500 function assert_regexp_match(actual, expected, description) {
501 /*
502 * Test if a string (actual) matches a regexp (expected)
503 */
504 assert(expected.test(actual),
505 "assert_regexp_match", description,
506 "expected ${expected} but got ${actual}",
507 {expected:expected, actual:actual});
508 }
509 expose(assert_regexp_match, "assert_regexp_match");
510
511 function assert_class_string(object, class_string, description) {
512 assert_equals({}.toString.call(object), "[object " + class_string + "]",
513 description);
514 }
515 expose(assert_class_string, "assert_class_string");
516
517
518 function _assert_own_property(name) {
519 return function(object, property_name, description)
520 {
521 assert(object.hasOwnProperty(property_name),
522 name, description,
523 "expected property ${p} missing", {p:property_name});
524 };
525 }
526 expose(_assert_own_property("assert_exists"), "assert_exists");
527 expose(_assert_own_property("assert_own_property"), "assert_own_property");
528
529 function assert_not_exists(object, property_name, description)
530 {
531 assert(!object.hasOwnProperty(property_name),
532 "assert_not_exists", description,
533 "unexpected property ${p} found", {p:property_name});
534 }
535 expose(assert_not_exists, "assert_not_exists");
536
537 function _assert_inherits(name) {
538 return function (object, property_name, description)
539 {
540 assert(typeof object === "object",
541 name, description,
542 "provided value is not an object");
543
544 assert("hasOwnProperty" in object,
545 name, description,
546 "provided value is an object but has no hasOwnProperty method ");
547
548 assert(!object.hasOwnProperty(property_name),
549 name, description,
550 "property ${p} found on object expected in prototype chain",
551 {p:property_name});
552
553 assert(property_name in object,
554 name, description,
555 "property ${p} not found in prototype chain",
556 {p:property_name});
557 };
558 }
559 expose(_assert_inherits("assert_inherits"), "assert_inherits");
560 expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
561
562 function assert_readonly(object, property_name, description)
563 {
564 var initial_value = object[property_name];
565 try {
566 //Note that this can have side effects in the case where
567 //the property has PutForwards
568 object[property_name] = initial_value + "a"; //XXX use some other v alue here?
569 assert(same_value(object[property_name], initial_value),
570 "assert_readonly", description,
571 "changing property ${p} succeeded",
572 {p:property_name});
573 } finally {
574 object[property_name] = initial_value;
575 }
576 }
577 expose(assert_readonly, "assert_readonly");
578
579 function assert_throws(code, func, description)
580 {
581 try {
582 func.call(this);
583 assert(false, "assert_throws", description,
584 "${func} did not throw", {func:func});
585 } catch (e) {
586 if (e instanceof AssertionError) {
587 throw e;
588 }
589 if (code === null) {
590 return;
591 }
592 if (typeof code === "object") {
593 assert(typeof e == "object" && "name" in e && e.name == code.nam e,
594 "assert_throws", description,
595 "${func} threw ${actual} (${actual_name}) expected ${expe cted} (${expected_name})",
596 {func:func, actual:e, actual_name:e.name,
597 expected:code,
598 expected_name:code.name});
599 return;
600 }
601
602 var code_name_map = {
603 INDEX_SIZE_ERR: 'IndexSizeError',
604 HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
605 WRONG_DOCUMENT_ERR: 'WrongDocumentError',
606 INVALID_CHARACTER_ERR: 'InvalidCharacterError',
607 NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
608 NOT_FOUND_ERR: 'NotFoundError',
609 NOT_SUPPORTED_ERR: 'NotSupportedError',
610 INVALID_STATE_ERR: 'InvalidStateError',
611 SYNTAX_ERR: 'SyntaxError',
612 INVALID_MODIFICATION_ERR: 'InvalidModificationError',
613 NAMESPACE_ERR: 'NamespaceError',
614 INVALID_ACCESS_ERR: 'InvalidAccessError',
615 TYPE_MISMATCH_ERR: 'TypeMismatchError',
616 SECURITY_ERR: 'SecurityError',
617 NETWORK_ERR: 'NetworkError',
618 ABORT_ERR: 'AbortError',
619 URL_MISMATCH_ERR: 'URLMismatchError',
620 QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
621 TIMEOUT_ERR: 'TimeoutError',
622 INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
623 DATA_CLONE_ERR: 'DataCloneError'
624 };
625
626 var name = code in code_name_map ? code_name_map[code] : code;
627
628 var name_code_map = {
629 IndexSizeError: 1,
630 HierarchyRequestError: 3,
631 WrongDocumentError: 4,
632 InvalidCharacterError: 5,
633 NoModificationAllowedError: 7,
634 NotFoundError: 8,
635 NotSupportedError: 9,
636 InvalidStateError: 11,
637 SyntaxError: 12,
638 InvalidModificationError: 13,
639 NamespaceError: 14,
640 InvalidAccessError: 15,
641 TypeMismatchError: 17,
642 SecurityError: 18,
643 NetworkError: 19,
644 AbortError: 20,
645 URLMismatchError: 21,
646 QuotaExceededError: 22,
647 TimeoutError: 23,
648 InvalidNodeTypeError: 24,
649 DataCloneError: 25,
650
651 UnknownError: 0,
652 ConstraintError: 0,
653 DataError: 0,
654 TransactionInactiveError: 0,
655 ReadOnlyError: 0,
656 VersionError: 0
657 };
658
659 if (!(name in name_code_map)) {
660 throw new AssertionError('Test bug: unrecognized DOMException co de "' + code + '" passed to assert_throws()');
661 }
662
663 var required_props = { code: name_code_map[name] };
664
665 if (required_props.code === 0 ||
666 ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DO MException")) {
667 // New style exception: also test the name property.
668 required_props.name = name;
669 }
670
671 //We'd like to test that e instanceof the appropriate interface,
672 //but we can't, because we don't know what window it was created
673 //in. It might be an instanceof the appropriate interface on some
674 //unknown other window. TODO: Work around this somehow?
675
676 assert(typeof e == "object",
677 "assert_throws", description,
678 "${func} threw ${e} with type ${type}, not an object",
679 {func:func, e:e, type:typeof e});
680
681 for (var prop in required_props) {
682 assert(typeof e == "object" && prop in e && e[prop] == required_ props[prop],
683 "assert_throws", description,
684 "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
685 {func:func, e:e, prop:prop, actual:e[prop], expected:requ ired_props[prop]});
686 }
687 }
688 }
689 expose(assert_throws, "assert_throws");
690
691 function assert_unreached(description) {
692 assert(false, "assert_unreached", description,
693 "Reached unreachable code");
694 }
695 expose(assert_unreached, "assert_unreached");
696
697 function assert_any(assert_func, actual, expected_array)
698 {
699 var args = [].slice.call(arguments, 3);
700 var errors = [];
701 var passed = false;
702 forEach(expected_array,
703 function(expected)
704 {
705 try {
706 assert_func.apply(this, [actual, expected].concat(args)) ;
707 passed = true;
708 } catch (e) {
709 errors.push(e.message);
710 }
711 });
712 if (!passed) {
713 throw new AssertionError(errors.join("\n\n"));
714 }
715 }
716 expose(assert_any, "assert_any");
717
718 function Test(name, properties)
719 {
720 if (tests.file_is_test && tests.tests.length) {
721 throw new Error("Tried to create a test with file_is_test");
722 }
723 this.name = name;
724
725 this.phases = {
726 INITIAL:0,
727 STARTED:1,
728 HAS_RESULT:2,
729 COMPLETE:3
730 };
731 this.phase = this.phases.INITIAL;
732
733 this.status = this.NOTRUN;
734 this.timeout_id = null;
735
736 this.properties = properties;
737 var timeout = properties.timeout ? properties.timeout : settings.test_ti meout;
738 if (timeout != null) {
739 this.timeout_length = timeout * tests.timeout_multiplier;
740 } else {
741 this.timeout_length = null;
742 }
743
744 this.message = null;
745
746 this.steps = [];
747
748 this.cleanup_callbacks = [];
749
750 tests.push(this);
751 }
752
753 Test.statuses = {
754 PASS:0,
755 FAIL:1,
756 TIMEOUT:2,
757 NOTRUN:3
758 };
759
760 Test.prototype = merge({}, Test.statuses);
761
762 Test.prototype.structured_clone = function()
763 {
764 if (!this._structured_clone) {
765 var msg = this.message;
766 msg = msg ? String(msg) : msg;
767 this._structured_clone = merge({
768 name:String(this.name),
769 status:this.status,
770 message:msg
771 }, Test.statuses);
772 }
773 return this._structured_clone;
774 };
775
776 Test.prototype.step = function(func, this_obj)
777 {
778 if (this.phase > this.phases.STARTED) {
779 return;
780 }
781 this.phase = this.phases.STARTED;
782 //If we don't get a result before the harness times out that will be a t est timout
783 this.set_status(this.TIMEOUT, "Test timed out");
784
785 tests.started = true;
786
787 if (this.timeout_id === null) {
788 this.set_timeout();
789 }
790
791 this.steps.push(func);
792
793 if (arguments.length === 1) {
794 this_obj = this;
795 }
796
797 try {
798 return func.apply(this_obj, Array.prototype.slice.call(arguments, 2) );
799 } catch (e) {
800 if (this.phase >= this.phases.HAS_RESULT) {
801 return;
802 }
803 var message = (typeof e === "object" && e !== null) ? e.message : e;
804 if (typeof e.stack != "undefined" && typeof e.message == "string") {
805 //Try to make it more informative for some exceptions, at least
806 //in Gecko and WebKit. This results in a stack dump instead of
807 //just errors like "Cannot read property 'parentNode' of null"
808 //or "root is null". Makes it a lot longer, of course.
809 message += "(stack: " + e.stack + ")";
810 }
811 this.set_status(this.FAIL, message);
812 this.phase = this.phases.HAS_RESULT;
813 this.done();
814 }
815 };
816
817 Test.prototype.step_func = function(func, this_obj)
818 {
819 var test_this = this;
820
821 if (arguments.length === 1) {
822 this_obj = test_this;
823 }
824
825 return function()
826 {
827 return test_this.step.apply(test_this, [func, this_obj].concat(
828 Array.prototype.slice.call(arguments)));
829 };
830 };
831
832 Test.prototype.step_func_done = function(func, this_obj)
833 {
834 var test_this = this;
835
836 if (arguments.length === 1) {
837 this_obj = test_this;
838 }
839
840 return function()
841 {
842 if (func) {
843 test_this.step.apply(test_this, [func, this_obj].concat(
844 Array.prototype.slice.call(arguments)));
845 }
846 test_this.done();
847 };
848 };
849
850 Test.prototype.unreached_func = function(description)
851 {
852 return this.step_func(function() {
853 assert_unreached(description);
854 });
855 };
856
857 Test.prototype.add_cleanup = function(callback) {
858 this.cleanup_callbacks.push(callback);
859 };
860
861 Test.prototype.force_timeout = function() {
862 this.set_status(this.TIMEOUT);
863 this.phase = this.phases.HAS_RESULT;
864 }
865
866 Test.prototype.set_timeout = function()
867 {
868 if (this.timeout_length !== null) {
869 var this_obj = this;
870 this.timeout_id = setTimeout(function()
871 {
872 this_obj.timeout();
873 }, this.timeout_length);
874 }
875 };
876
877 Test.prototype.set_status = function(status, message)
878 {
879 this.status = status;
880 this.message = message;
881 };
882
883 Test.prototype.timeout = function()
884 {
885 this.timeout_id = null;
886 this.set_status(this.TIMEOUT, "Test timed out");
887 this.phase = this.phases.HAS_RESULT;
888 this.done();
889 };
890
891 Test.prototype.done = function()
892 {
893 if (this.phase == this.phases.COMPLETE) {
894 return;
895 }
896
897 if (this.phase <= this.phases.STARTED) {
898 this.set_status(this.PASS, null);
899 }
900
901 if (this.status == this.NOTRUN) {
902 alert(this.phase);
903 }
904
905 this.phase = this.phases.COMPLETE;
906
907 clearTimeout(this.timeout_id);
908 tests.result(this);
909 this.cleanup();
910 };
911
912 Test.prototype.cleanup = function() {
913 forEach(this.cleanup_callbacks,
914 function(cleanup_callback) {
915 cleanup_callback();
916 });
917 };
918
919 /*
920 * Harness
921 */
922
923 function TestsStatus()
924 {
925 this.status = null;
926 this.message = null;
927 }
928
929 TestsStatus.statuses = {
930 OK:0,
931 ERROR:1,
932 TIMEOUT:2
933 };
934
935 TestsStatus.prototype = merge({}, TestsStatus.statuses);
936
937 TestsStatus.prototype.structured_clone = function()
938 {
939 if (!this._structured_clone) {
940 var msg = this.message;
941 msg = msg ? String(msg) : msg;
942 this._structured_clone = merge({
943 status:this.status,
944 message:msg
945 }, TestsStatus.statuses);
946 }
947 return this._structured_clone;
948 };
949
950 function Tests()
951 {
952 this.tests = [];
953 this.num_pending = 0;
954
955 this.phases = {
956 INITIAL:0,
957 SETUP:1,
958 HAVE_TESTS:2,
959 HAVE_RESULTS:3,
960 COMPLETE:4
961 };
962 this.phase = this.phases.INITIAL;
963
964 this.properties = {};
965
966 //All tests can't be done until the load event fires
967 this.all_loaded = false;
968 this.wait_for_finish = false;
969 this.processing_callbacks = false;
970
971 this.allow_uncaught_exception = false;
972
973 this.file_is_test = false;
974
975 this.timeout_multiplier = 1;
976 this.timeout_length = this.get_timeout();
977 this.timeout_id = null;
978
979 this.start_callbacks = [];
980 this.test_done_callbacks = [];
981 this.all_done_callbacks = [];
982
983 this.status = new TestsStatus();
984
985 var this_obj = this;
986
987 on_event(window, "load",
988 function()
989 {
990 this_obj.all_loaded = true;
991 if (this_obj.all_done())
992 {
993 this_obj.complete();
994 }
995 });
996
997 this.set_timeout();
998 }
999
1000 Tests.prototype.setup = function(func, properties)
1001 {
1002 if (this.phase >= this.phases.HAVE_RESULTS) {
1003 return;
1004 }
1005
1006 if (this.phase < this.phases.SETUP) {
1007 this.phase = this.phases.SETUP;
1008 }
1009
1010 this.properties = properties;
1011
1012 for (var p in properties) {
1013 if (properties.hasOwnProperty(p)) {
1014 var value = properties[p];
1015 if (p == "allow_uncaught_exception") {
1016 this.allow_uncaught_exception = value;
1017 } else if (p == "explicit_done" && value) {
1018 this.wait_for_finish = true;
1019 } else if (p == "explicit_timeout" && value) {
1020 this.timeout_length = null;
1021 if (this.timeout_id)
1022 {
1023 clearTimeout(this.timeout_id);
1024 }
1025 } else if (p == "timeout_multiplier") {
1026 this.timeout_multiplier = value;
1027 }
1028 }
1029 }
1030
1031 if (func) {
1032 try {
1033 func();
1034 } catch (e) {
1035 this.status.status = this.status.ERROR;
1036 this.status.message = String(e);
1037 }
1038 }
1039 this.set_timeout();
1040 };
1041
1042 Tests.prototype.set_file_is_test = function() {
1043 if (this.tests.length > 0) {
1044 throw new Error("Tried to set file as test after creating a test");
1045 }
1046 this.wait_for_finish = true;
1047 this.file_is_test = true;
1048 // Create the test, which will add it to the list of tests
1049 async_test();
1050 };
1051
1052 Tests.prototype.get_timeout = function() {
1053 var metas = document.getElementsByTagName("meta");
1054 for (var i = 0; i < metas.length; i++) {
1055 if (metas[i].name == "timeout") {
1056 if (metas[i].content == "long") {
1057 return settings.harness_timeout.long;
1058 }
1059 break;
1060 }
1061 }
1062 return settings.harness_timeout.normal;
1063 };
1064
1065 Tests.prototype.set_timeout = function() {
1066 var this_obj = this;
1067 clearTimeout(this.timeout_id);
1068 if (this.timeout_length !== null) {
1069 this.timeout_id = setTimeout(function() {
1070 this_obj.timeout();
1071 }, this.timeout_length);
1072 }
1073 };
1074
1075 Tests.prototype.timeout = function() {
1076 if (this.status.status === null) {
1077 this.status.status = this.status.TIMEOUT;
1078 }
1079 this.complete();
1080 };
1081
1082 Tests.prototype.end_wait = function()
1083 {
1084 this.wait_for_finish = false;
1085 if (this.all_done()) {
1086 this.complete();
1087 }
1088 };
1089
1090 Tests.prototype.push = function(test)
1091 {
1092 if (this.phase < this.phases.HAVE_TESTS) {
1093 this.start();
1094 }
1095 this.num_pending++;
1096 this.tests.push(test);
1097 };
1098
1099 Tests.prototype.all_done = function() {
1100 return (this.tests.length > 0 && this.all_loaded && this.num_pending === 0 &&
1101 !this.wait_for_finish && !this.processing_callbacks);
1102 };
1103
1104 Tests.prototype.start = function() {
1105 this.phase = this.phases.HAVE_TESTS;
1106 this.notify_start();
1107 };
1108
1109 Tests.prototype.notify_start = function() {
1110 var this_obj = this;
1111 forEach (this.start_callbacks,
1112 function(callback)
1113 {
1114 callback(this_obj.properties);
1115 });
1116 forEach_windows(
1117 function(w, is_same_origin)
1118 {
1119 if (is_same_origin && w.start_callback) {
1120 try {
1121 w.start_callback(this_obj.properties);
1122 } catch (e) {
1123 if (debug) {
1124 throw e;
1125 }
1126 }
1127 }
1128 if (supports_post_message(w) && w !== self) {
1129 w.postMessage({
1130 type: "start",
1131 properties: this_obj.properties
1132 }, "*");
1133 }
1134 });
1135 };
1136
1137 Tests.prototype.result = function(test)
1138 {
1139 if (this.phase > this.phases.HAVE_RESULTS) {
1140 return;
1141 }
1142 this.phase = this.phases.HAVE_RESULTS;
1143 this.num_pending--;
1144 this.notify_result(test);
1145 };
1146
1147 Tests.prototype.notify_result = function(test) {
1148 var this_obj = this;
1149 this.processing_callbacks = true;
1150 forEach(this.test_done_callbacks,
1151 function(callback)
1152 {
1153 callback(test, this_obj);
1154 });
1155
1156 forEach_windows(
1157 function(w, is_same_origin)
1158 {
1159 if (is_same_origin && w.result_callback) {
1160 try {
1161 w.result_callback(test);
1162 } catch (e) {
1163 if (debug) {
1164 throw e;
1165 }
1166 }
1167 }
1168 if (supports_post_message(w) && w !== self) {
1169 w.postMessage({
1170 type: "result",
1171 test: test.structured_clone()
1172 }, "*");
1173 }
1174 });
1175 this.processing_callbacks = false;
1176 if (this_obj.all_done()) {
1177 this_obj.complete();
1178 }
1179 };
1180
1181 Tests.prototype.complete = function() {
1182 if (this.phase === this.phases.COMPLETE) {
1183 return;
1184 }
1185 this.phase = this.phases.COMPLETE;
1186 var this_obj = this;
1187 this.tests.forEach(
1188 function(x)
1189 {
1190 if (x.status === x.NOTRUN) {
1191 this_obj.notify_result(x);
1192 x.cleanup();
1193 }
1194 }
1195 );
1196 this.notify_complete();
1197 };
1198
1199 Tests.prototype.notify_complete = function()
1200 {
1201 clearTimeout(this.timeout_id);
1202 var this_obj = this;
1203 var tests = map(this_obj.tests,
1204 function(test)
1205 {
1206 return test.structured_clone();
1207 });
1208 if (this.status.status === null) {
1209 this.status.status = this.status.OK;
1210 }
1211
1212 forEach (this.all_done_callbacks,
1213 function(callback)
1214 {
1215 callback(this_obj.tests, this_obj.status);
1216 });
1217
1218 forEach_windows(
1219 function(w, is_same_origin)
1220 {
1221 if (is_same_origin && w.completion_callback) {
1222 try {
1223 w.completion_callback(this_obj.tests, this_obj.statu s);
1224 } catch (e) {
1225 if (debug) {
1226 throw e;
1227 }
1228 }
1229 }
1230 if (supports_post_message(w) && w !== self) {
1231 w.postMessage({
1232 type: "complete",
1233 tests: tests,
1234 status: this_obj.status.structured_clone()
1235 }, "*");
1236 }
1237 });
1238 };
1239
1240 var tests = new Tests();
1241
1242 addEventListener("error", function(e) {
1243 if (tests.file_is_test) {
1244 var test = tests.tests[0];
1245 if (test.phase >= test.phases.HAS_RESULT) {
1246 return;
1247 }
1248 var message = e.message;
1249 test.set_status(test.FAIL, message);
1250 test.phase = test.phases.HAS_RESULT;
1251 test.done();
1252 done();
1253 } else if (!tests.allow_uncaught_exception) {
1254 tests.status.status = tests.status.ERROR;
1255 tests.status.message = e.message;
1256 }
1257 });
1258
1259 function timeout() {
1260 if (tests.timeout_length === null) {
1261 tests.timeout();
1262 }
1263 }
1264 expose(timeout, 'timeout');
1265
1266 function add_start_callback(callback) {
1267 tests.start_callbacks.push(callback);
1268 }
1269
1270 function add_result_callback(callback)
1271 {
1272 tests.test_done_callbacks.push(callback);
1273 }
1274
1275 function add_completion_callback(callback)
1276 {
1277 tests.all_done_callbacks.push(callback);
1278 }
1279
1280 expose(add_start_callback, 'add_start_callback');
1281 expose(add_result_callback, 'add_result_callback');
1282 expose(add_completion_callback, 'add_completion_callback');
1283
1284 /*
1285 * Output listener
1286 */
1287
1288 function Output() {
1289 this.output_document = document;
1290 this.output_node = null;
1291 this.done_count = 0;
1292 this.enabled = settings.output;
1293 this.phase = this.INITIAL;
1294 }
1295
1296 Output.prototype.INITIAL = 0;
1297 Output.prototype.STARTED = 1;
1298 Output.prototype.HAVE_RESULTS = 2;
1299 Output.prototype.COMPLETE = 3;
1300
1301 Output.prototype.setup = function(properties) {
1302 if (this.phase > this.INITIAL) {
1303 return;
1304 }
1305
1306 //If output is disabled in testharnessreport.js the test shouldn't be
1307 //able to override that
1308 this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
1309 properties.output : settings.output);
1310 };
1311
1312 Output.prototype.init = function(properties) {
1313 if (this.phase >= this.STARTED) {
1314 return;
1315 }
1316 if (properties.output_document) {
1317 this.output_document = properties.output_document;
1318 } else {
1319 this.output_document = document;
1320 }
1321 this.phase = this.STARTED;
1322 };
1323
1324 Output.prototype.resolve_log = function() {
1325 var output_document;
1326 if (typeof this.output_document === "function") {
1327 output_document = this.output_document.apply(undefined);
1328 } else {
1329 output_document = this.output_document;
1330 }
1331 if (!output_document) {
1332 return;
1333 }
1334 var node = output_document.getElementById("log");
1335 if (!node) {
1336 if (!document.body || document.readyState == "loading") {
1337 return;
1338 }
1339 node = output_document.createElement("div");
1340 node.id = "log";
1341 output_document.body.appendChild(node);
1342 }
1343 this.output_document = output_document;
1344 this.output_node = node;
1345 };
1346
1347 Output.prototype.show_status = function() {
1348 if (this.phase < this.STARTED) {
1349 this.init();
1350 }
1351 if (!this.enabled) {
1352 return;
1353 }
1354 if (this.phase < this.HAVE_RESULTS) {
1355 this.resolve_log();
1356 this.phase = this.HAVE_RESULTS;
1357 }
1358 this.done_count++;
1359 if (this.output_node) {
1360 if (this.done_count < 100 ||
1361 (this.done_count < 1000 && this.done_count % 100 === 0) ||
1362 this.done_count % 1000 === 0) {
1363 this.output_node.textContent = "Running, " +
1364 this.done_count + " complete, " +
1365 tests.num_pending + " remain";
1366 }
1367 }
1368 };
1369
1370 Output.prototype.show_results = function (tests, harness_status) {
1371 if (this.phase >= this.COMPLETE) {
1372 return;
1373 }
1374 if (!this.enabled) {
1375 return;
1376 }
1377 if (!this.output_node) {
1378 this.resolve_log();
1379 }
1380 this.phase = this.COMPLETE;
1381
1382 var log = this.output_node;
1383 if (!log) {
1384 return;
1385 }
1386 var output_document = this.output_document;
1387
1388 while (log.lastChild) {
1389 log.removeChild(log.lastChild);
1390 }
1391
1392 if (script_prefix != null) {
1393 var stylesheet = output_document.createElementNS(xhtml_ns, "link");
1394 stylesheet.setAttribute("rel", "stylesheet");
1395 stylesheet.setAttribute("href", script_prefix + "testharness.css");
1396 var heads = output_document.getElementsByTagName("head");
1397 if (heads.length) {
1398 heads[0].appendChild(stylesheet);
1399 }
1400 }
1401
1402 var status_text_harness = {};
1403 status_text_harness[harness_status.OK] = "OK";
1404 status_text_harness[harness_status.ERROR] = "Error";
1405 status_text_harness[harness_status.TIMEOUT] = "Timeout";
1406
1407 var status_text = {};
1408 status_text[Test.prototype.PASS] = "Pass";
1409 status_text[Test.prototype.FAIL] = "Fail";
1410 status_text[Test.prototype.TIMEOUT] = "Timeout";
1411 status_text[Test.prototype.NOTRUN] = "Not Run";
1412
1413 var status_number = {};
1414 forEach(tests,
1415 function(test) {
1416 var status = status_text[test.status];
1417 if (status_number.hasOwnProperty(status)) {
1418 status_number[status] += 1;
1419 } else {
1420 status_number[status] = 1;
1421 }
1422 });
1423
1424 function status_class(status)
1425 {
1426 return status.replace(/\s/g, '').toLowerCase();
1427 }
1428
1429 var summary_template = ["section", {"id":"summary"},
1430 ["h2", {}, "Summary"],
1431 function()
1432 {
1433
1434 var status = status_text_harness[harness_sta tus.status];
1435 var rv = [["section", {},
1436 ["p", {},
1437 "Harness status: ",
1438 ["span", {"class":status_class(s tatus)},
1439 status
1440 ],
1441 ]
1442 ]];
1443
1444 if (harness_status.status === harness_status .ERROR) {
1445 rv[0].push(["pre", {}, harness_status.me ssage]);
1446 }
1447 return rv;
1448 },
1449 ["p", {}, "Found ${num_tests} tests"],
1450 function() {
1451 var rv = [["div", {}]];
1452 var i = 0;
1453 while (status_text.hasOwnProperty(i)) {
1454 if (status_number.hasOwnProperty(status_ text[i])) {
1455 var status = status_text[i];
1456 rv[0].push(["div", {"class":status_c lass(status)},
1457 ["label", {},
1458 ["input", {type:"checkb ox", checked:"checked"}],
1459 status_number[status] + " " + status]]);
1460 }
1461 i++;
1462 }
1463 return rv;
1464 },
1465 ];
1466
1467 log.appendChild(render(summary_template, {num_tests:tests.length}, outpu t_document));
1468
1469 forEach(output_document.querySelectorAll("section#summary label"),
1470 function(element)
1471 {
1472 on_event(element, "click",
1473 function(e)
1474 {
1475 if (output_document.getElementById("results") = == null) {
1476 e.preventDefault();
1477 return;
1478 }
1479 var result_class = element.parentNode.getAttrib ute("class");
1480 var style_element = output_document.querySelect or("style#hide-" + result_class);
1481 var input_element = element.querySelector("inpu t");
1482 if (!style_element && !input_element.checked) {
1483 style_element = output_document.createEleme ntNS(xhtml_ns, "style");
1484 style_element.id = "hide-" + result_class;
1485 style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
1486 output_document.body.appendChild(style_elem ent);
1487 } else if (style_element && input_element.check ed) {
1488 style_element.parentNode.removeChild(style_ element);
1489 }
1490 });
1491 });
1492
1493 // This use of innerHTML plus manual escaping is not recommended in
1494 // general, but is necessary here for performance. Using textContent
1495 // on each individual <td> adds tens of seconds of execution time for
1496 // large test suites (tens of thousands of tests).
1497 function escape_html(s)
1498 {
1499 return s.replace(/\&/g, "&amp;")
1500 .replace(/</g, "&lt;")
1501 .replace(/"/g, "&quot;")
1502 .replace(/'/g, "&#39;");
1503 }
1504
1505 function has_assertions()
1506 {
1507 for (var i = 0; i < tests.length; i++) {
1508 if (tests[i].properties.hasOwnProperty("assert")) {
1509 return true;
1510 }
1511 }
1512 return false;
1513 }
1514
1515 function get_assertion(test)
1516 {
1517 if (test.properties.hasOwnProperty("assert")) {
1518 if (Array.isArray(test.properties.assert)) {
1519 return test.properties.assert.join(' ');
1520 }
1521 return test.properties.assert;
1522 }
1523 return '';
1524 }
1525
1526 log.appendChild(document.createElementNS(xhtml_ns, "section"));
1527 var assertions = has_assertions();
1528 var html = "<h2>Details</h2><table id='results' " + (assertions ? "class ='assertions'" : "" ) + ">" +
1529 "<thead><tr><th>Result</th><th>Test Name</th>" +
1530 (assertions ? "<th>Assertion</th>" : "") +
1531 "<th>Message</th></tr></thead>" +
1532 "<tbody>";
1533 for (var i = 0; i < tests.length; i++) {
1534 html += '<tr class="' +
1535 escape_html(status_class(status_text[tests[i].status])) +
1536 '"><td>' +
1537 escape_html(status_text[tests[i].status]) +
1538 "</td><td>" +
1539 escape_html(tests[i].name) +
1540 "</td><td>" +
1541 (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") +
1542 escape_html(tests[i].message ? tests[i].message : " ") +
1543 "</td></tr>";
1544 }
1545 html += "</tbody></table>";
1546 try {
1547 log.lastChild.innerHTML = html;
1548 } catch (e) {
1549 log.appendChild(document.createElementNS(xhtml_ns, "p"))
1550 .textContent = "Setting innerHTML for the log threw an exception. ";
1551 log.appendChild(document.createElementNS(xhtml_ns, "pre"))
1552 .textContent = html;
1553 }
1554 };
1555
1556 var output = new Output();
1557 add_start_callback(function (properties) {output.init(properties);});
1558 add_result_callback(function () {output.show_status();});
1559 add_completion_callback(function (tests, harness_status) {output.show_result s(tests, harness_status);});
1560
1561 /*
1562 * Template code
1563 *
1564 * A template is just a javascript structure. An element is represented as:
1565 *
1566 * [tag_name, {attr_name:attr_value}, child1, child2]
1567 *
1568 * the children can either be strings (which act like text nodes), other tem plates or
1569 * functions (see below)
1570 *
1571 * A text node is represented as
1572 *
1573 * ["{text}", value]
1574 *
1575 * String values have a simple substitution syntax; ${foo} represents a vari able foo.
1576 *
1577 * It is possible to embed logic in templates by using a function in a place where a
1578 * node would usually go. The function must either return part of a template or null.
1579 *
1580 * In cases where a set of nodes are required as output rather than a single node
1581 * with children it is possible to just use a list
1582 * [node1, node2, node3]
1583 *
1584 * Usage:
1585 *
1586 * render(template, substitutions) - take a template and an object mapping
1587 * variable names to parameters and return either a DOM node or a list of DO M nodes
1588 *
1589 * substitute(template, substitutions) - take a template and variable mappin g object,
1590 * make the variable substitutions and return the substituted template
1591 *
1592 */
1593
1594 function is_single_node(template)
1595 {
1596 return typeof template[0] === "string";
1597 }
1598
1599 function substitute(template, substitutions)
1600 {
1601 if (typeof template === "function") {
1602 var replacement = template(substitutions);
1603 if (!replacement) {
1604 return null;
1605 }
1606
1607 return substitute(replacement, substitutions);
1608 }
1609
1610 if (is_single_node(template)) {
1611 return substitute_single(template, substitutions);
1612 }
1613
1614 return filter(map(template, function(x) {
1615 return substitute(x, substitutions);
1616 }), function(x) {return x !== null;});
1617 }
1618
1619 function substitute_single(template, substitutions)
1620 {
1621 var substitution_re = /\$\{([^ }]*)\}/g;
1622
1623 function do_substitution(input) {
1624 var components = input.split(substitution_re);
1625 var rv = [];
1626 for (var i = 0; i < components.length; i += 2) {
1627 rv.push(components[i]);
1628 if (components[i + 1]) {
1629 rv.push(String(substitutions[components[i + 1]]));
1630 }
1631 }
1632 return rv;
1633 }
1634
1635 function substitute_attrs(attrs, rv)
1636 {
1637 rv[1] = {};
1638 for (var name in template[1]) {
1639 if (attrs.hasOwnProperty(name)) {
1640 var new_name = do_substitution(name).join("");
1641 var new_value = do_substitution(attrs[name]).join("");
1642 rv[1][new_name] = new_value;
1643 }
1644 }
1645 }
1646
1647 function substitute_children(children, rv)
1648 {
1649 for (var i = 0; i < children.length; i++) {
1650 if (children[i] instanceof Object) {
1651 var replacement = substitute(children[i], substitutions);
1652 if (replacement !== null) {
1653 if (is_single_node(replacement)) {
1654 rv.push(replacement);
1655 } else {
1656 extend(rv, replacement);
1657 }
1658 }
1659 } else {
1660 extend(rv, do_substitution(String(children[i])));
1661 }
1662 }
1663 return rv;
1664 }
1665
1666 var rv = [];
1667 rv.push(do_substitution(String(template[0])).join(""));
1668
1669 if (template[0] === "{text}") {
1670 substitute_children(template.slice(1), rv);
1671 } else {
1672 substitute_attrs(template[1], rv);
1673 substitute_children(template.slice(2), rv);
1674 }
1675
1676 return rv;
1677 }
1678
1679 function make_dom_single(template, doc)
1680 {
1681 var output_document = doc || document;
1682 var element;
1683 if (template[0] === "{text}") {
1684 element = output_document.createTextNode("");
1685 for (var i = 1; i < template.length; i++) {
1686 element.data += template[i];
1687 }
1688 } else {
1689 element = output_document.createElementNS(xhtml_ns, template[0]);
1690 for (var name in template[1]) {
1691 if (template[1].hasOwnProperty(name)) {
1692 element.setAttribute(name, template[1][name]);
1693 }
1694 }
1695 for (var i = 2; i < template.length; i++) {
1696 if (template[i] instanceof Object) {
1697 var sub_element = make_dom(template[i]);
1698 element.appendChild(sub_element);
1699 } else {
1700 var text_node = output_document.createTextNode(template[i]);
1701 element.appendChild(text_node);
1702 }
1703 }
1704 }
1705
1706 return element;
1707 }
1708
1709
1710
1711 function make_dom(template, substitutions, output_document)
1712 {
1713 if (is_single_node(template)) {
1714 return make_dom_single(template, output_document);
1715 }
1716
1717 return map(template, function(x) {
1718 return make_dom_single(x, output_document);
1719 });
1720 }
1721
1722 function render(template, substitutions, output_document)
1723 {
1724 return make_dom(substitute(template, substitutions), output_document);
1725 }
1726
1727 /*
1728 * Utility funcions
1729 */
1730 function assert(expected_true, function_name, description, error, substituti ons)
1731 {
1732 if (tests.tests.length === 0) {
1733 tests.set_file_is_test();
1734 }
1735 if (expected_true !== true) {
1736 var msg = make_message(function_name, description,
1737 error, substitutions);
1738 throw new AssertionError(msg);
1739 }
1740 }
1741
1742 function AssertionError(message)
1743 {
1744 this.message = message;
1745 }
1746
1747 AssertionError.prototype.toString = function() {
1748 return this.message;
1749 };
1750
1751 function make_message(function_name, description, error, substitutions)
1752 {
1753 for (var p in substitutions) {
1754 if (substitutions.hasOwnProperty(p)) {
1755 substitutions[p] = format_value(substitutions[p]);
1756 }
1757 }
1758 var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
1759 merge({function_name:function_name,
1760 description:(description?description + " ":"")},
1761 substitutions));
1762 return node_form.slice(1).join("");
1763 }
1764
1765 function filter(array, callable, thisObj) {
1766 var rv = [];
1767 for (var i = 0; i < array.length; i++) {
1768 if (array.hasOwnProperty(i)) {
1769 var pass = callable.call(thisObj, array[i], i, array);
1770 if (pass) {
1771 rv.push(array[i]);
1772 }
1773 }
1774 }
1775 return rv;
1776 }
1777
1778 function map(array, callable, thisObj)
1779 {
1780 var rv = [];
1781 rv.length = array.length;
1782 for (var i = 0; i < array.length; i++) {
1783 if (array.hasOwnProperty(i)) {
1784 rv[i] = callable.call(thisObj, array[i], i, array);
1785 }
1786 }
1787 return rv;
1788 }
1789
1790 function extend(array, items)
1791 {
1792 Array.prototype.push.apply(array, items);
1793 }
1794
1795 function forEach (array, callback, thisObj)
1796 {
1797 for (var i = 0; i < array.length; i++) {
1798 if (array.hasOwnProperty(i)) {
1799 callback.call(thisObj, array[i], i, array);
1800 }
1801 }
1802 }
1803
1804 function merge(a,b)
1805 {
1806 var rv = {};
1807 var p;
1808 for (p in a) {
1809 rv[p] = a[p];
1810 }
1811 for (p in b) {
1812 rv[p] = b[p];
1813 }
1814 return rv;
1815 }
1816
1817 function expose(object, name)
1818 {
1819 var components = name.split(".");
1820 var target = window;
1821 for (var i = 0; i < components.length - 1; i++) {
1822 if (!(components[i] in target)) {
1823 target[components[i]] = {};
1824 }
1825 target = target[components[i]];
1826 }
1827 target[components[components.length - 1]] = object;
1828 }
1829
1830 function forEach_windows(callback) {
1831 // Iterate of the the windows [self ... top, opener]. The callback is pa ssed
1832 // two objects, the first one is the windows object itself, the second o ne
1833 // is a boolean indicating whether or not its on the same origin as the
1834 // current window.
1835 var cache = forEach_windows.result_cache;
1836 if (!cache) {
1837 cache = [[self, true]];
1838 var w = self;
1839 var i = 0;
1840 var so;
1841 var origins = location.ancestorOrigins;
1842 while (w != w.parent) {
1843 w = w.parent;
1844 // In WebKit, calls to parent windows' properties that aren't on the same
1845 // origin cause an error message to be displayed in the error co nsole but
1846 // don't throw an exception. This is a deviation from the curren t HTML5
1847 // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
1848 // The problem with WebKit's behavior is that it pollutes the er ror console
1849 // with error messages that can't be caught.
1850 //
1851 // This issue can be mitigated by relying on the (for now) propr ietary
1852 // `location.ancestorOrigins` property which returns an ordered list of
1853 // the origins of enclosing windows. See:
1854 // http://trac.webkit.org/changeset/113945.
1855 if (origins) {
1856 so = (location.origin == origins[i]);
1857 } else {
1858 so = is_same_origin(w);
1859 }
1860 cache.push([w, so]);
1861 i++;
1862 }
1863 w = window.opener;
1864 if (w) {
1865 // window.opener isn't included in the `location.ancestorOrigins ` prop.
1866 // We'll just have to deal with a simple check and an error msg on WebKit
1867 // browsers in this case.
1868 cache.push([w, is_same_origin(w)]);
1869 }
1870 forEach_windows.result_cache = cache;
1871 }
1872
1873 forEach(cache,
1874 function(a)
1875 {
1876 callback.apply(null, a);
1877 });
1878 }
1879
1880 function is_same_origin(w) {
1881 try {
1882 'random_prop' in w;
1883 return true;
1884 } catch (e) {
1885 return false;
1886 }
1887 }
1888
1889 function supports_post_message(w)
1890 {
1891 var supports;
1892 var type;
1893 // Given IE implements postMessage across nested iframes but not across
1894 // windows or tabs, you can't infer cross-origin communication from the presence
1895 // of postMessage on the current window object only.
1896 //
1897 // Touching the postMessage prop on a window can throw if the window is
1898 // not from the same origin AND post message is not supported in that
1899 // browser. So just doing an existence test here won't do, you also need
1900 // to wrap it in a try..cacth block.
1901 try {
1902 type = typeof w.postMessage;
1903 if (type === "function") {
1904 supports = true;
1905 }
1906
1907 // IE8 supports postMessage, but implements it as a host object whic h
1908 // returns "object" as its `typeof`.
1909 else if (type === "object") {
1910 supports = true;
1911 }
1912
1913 // This is the case where postMessage isn't supported AND accessing a
1914 // window property across origins does NOT throw (e.g. old Safari br owser).
1915 else {
1916 supports = false;
1917 }
1918 } catch (e) {
1919 // This is the case where postMessage isn't supported AND accessing a
1920 // window property across origins throws (e.g. old Firefox browser).
1921 supports = false;
1922 }
1923 return supports;
1924 }
1925 })();
1926 // vim: set expandtab shiftwidth=4 tabstop=4:
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698