Move helper files for iOS touch gestures into LayoutTests/resources
[WebKit-https.git] / LayoutTests / resources / js-test.js
1 if (self.testRunner) {
2         // svg/dynamic-updates tests set enablePixelTesting=true, as we want to dump text + pixel results
3     if (self.enablePixelTesting)
4         testRunner.dumpAsTextWithPixelResults();
5     else
6         testRunner.dumpAsText();
7
8     // If the test file URL ends in "-private.html", enable private browsing.
9     if (window.location.href.endsWith("-private.html") || self.enablePrivateBrowsing)
10         testRunner.setPrivateBrowsingEnabled(true);
11 }
12
13 var description, debug, didFailSomeTests, successfullyParsed;
14
15 didFailSomeTests = false;
16
17 var expectingError; // set by shouldHaveError()
18 var expectedErrorMessage; // set by onerror when expectingError is true
19 var unexpectedErrorMessage; // set by onerror when expectingError is not true
20
21 (function() {
22
23     function createHTMLElement(tagName)
24     {
25         // FIXME: In an XML document, document.createElement() creates an element with a null namespace URI.
26         // So, we need use document.createElementNS() to explicitly create an element with the specified
27         // tag name in the HTML namespace. We can remove this function and use document.createElement()
28         // directly once we fix <https://bugs.webkit.org/show_bug.cgi?id=131074>.
29         if (document.createElementNS)
30             return document.createElementNS("http://www.w3.org/1999/xhtml", tagName);
31         return document.createElement(tagName);
32     }
33
34     var rootElement = null;
35     function ensureRootElement()
36     {
37         if (!rootElement || !rootElement.isConnected) {
38             rootElement = document.body || document.documentElement;
39             if (document.documentElement.namespaceURI == 'http://www.w3.org/2000/svg') {
40                 // FIXME: Make the test harness use SVG elements naively.
41                 var foreignObject = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
42                 foreignObject.setAttribute('x', '0px');
43                 foreignObject.setAttribute('y', '0px');
44                 foreignObject.setAttribute('width', '100%');
45                 foreignObject.setAttribute('height', '100%');
46                 foreignObject.setAttribute('style', 'padding: 10px; background-color: rgba(255, 255, 255, 0.5)');
47                 document.documentElement.appendChild(foreignObject);
48                 rootElement = foreignObject;
49             }
50         }
51         return rootElement;
52     }
53
54     moveForeignObjectToTopIfNeeded = function () {
55         if (rootElement && rootElement.localName == 'foreignObject')
56             document.documentElement.appendChild(rootElement);
57     }
58
59     function getOrCreate(id, tagName)
60     {
61         var element = document.getElementById(id);
62         if (element)
63             return element;
64
65         element = createHTMLElement(tagName);
66         element.id = id;
67         var refNode;
68         var parent = ensureRootElement();
69
70         if (id == "description")
71             refNode = getOrCreate("console", "div");
72         else
73             refNode = parent.firstChild;
74
75         parent.insertBefore(element, refNode);
76         return element;
77     }
78
79     description = function description(msg, quiet)
80     {
81         // For MSIE 6 compatibility
82         var span = createHTMLElement("span");
83         if (quiet)
84             span.innerHTML = '<p>' + msg + '</p><p>On success, you will see no "<span class="fail">FAIL</span>" messages, followed by "<span class="pass">TEST COMPLETE</span>".</p>';
85         else
86             span.innerHTML = '<p>' + msg + '</p><p>On success, you will see a series of "<span class="pass">PASS</span>" messages, followed by "<span class="pass">TEST COMPLETE</span>".</p>';
87
88         var description = getOrCreate("description", "p");
89         if (description.firstChild)
90             description.replaceChild(span, description.firstChild);
91         else
92             description.appendChild(span);
93     };
94
95     debug = function debug(msg)
96     {
97         var span = createHTMLElement("span");
98         span.innerHTML = msg + '<br />';
99         getOrCreate("console", "div").appendChild(span);
100     };
101
102     var css =
103         ".pass {" +
104             "font-weight: bold;" +
105             "color: green;" +
106         "}" +
107         ".fail {" +
108             "font-weight: bold;" +
109             "color: red;" +
110         "}" +
111         "#console {" +
112             "white-space: pre-wrap;" +
113             "font-family: monospace;" +
114         "}";
115
116     function insertStyleSheet()
117     {
118         var styleElement = createHTMLElement("style");
119         styleElement.textContent = css;
120         (document.head || ensureRootElement()).appendChild(styleElement);
121     }
122
123     function handleTestFinished()
124     {
125         // FIXME: Get rid of this boolean.
126         wasPostTestScriptParsed = true;
127         if (window.jsTestIsAsync) {
128             if (window.testRunner)
129                 testRunner.waitUntilDone();
130             if (window.wasFinishJSTestCalled)
131                 finishJSTest();
132         } else
133             finishJSTest();
134     }
135
136     if (!isWorker()) {
137         window.addEventListener('DOMContentLoaded', function() {
138             // Call waitUntilDone() as early as possible otherwise some tests may complete before
139             // the load event has fired.
140             if (window.jsTestIsAsync && window.testRunner)
141                 testRunner.waitUntilDone();
142
143             // Some tests set jsTestIsAsync in load event handler. Adding the listener late
144             // makes handleTestFinished() run after the test handles load events.
145             window.addEventListener("load", handleTestFinished, false);
146         }, false);
147         insertStyleSheet();
148     }
149
150     if (!self.isOnErrorTest) {
151         self.onerror = function(message)
152         {
153             if (self.expectingError) {
154                 self.expectedErrorMessage = message;
155                 self.expectingError = false;
156                 return;
157             }
158             self.unexpectedErrorMessage = message;
159             if (self.jsTestIsAsync) {
160                 self.testFailed("Unexpected error: " + message);
161                 finishJSTest();
162             }
163         };
164     }
165 })();
166
167 function isWorker()
168 {
169     // It's conceivable that someone would stub out 'document' in a worker so
170     // also check for childNodes, an arbitrary DOM-related object that is
171     // meaningless in a WorkerContext.
172     return (typeof document === 'undefined' || typeof document.childNodes === 'undefined') && !!self.importScripts;
173 }
174
175 function descriptionQuiet(msg) { description(msg, true); }
176
177 function escapeHTML(text)
178 {
179     return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/\0/g, "\\0");
180 }
181
182 function testPassed(msg)
183 {
184     debug('<span><span class="pass">PASS</span> ' + escapeHTML(msg) + '</span>');
185 }
186
187 function testFailed(msg)
188 {
189     didFailSomeTests = true;
190     debug('<span><span class="fail">FAIL</span> ' + escapeHTML(msg) + '</span>');
191 }
192
193 function areArraysEqual(a, b)
194 {
195     try {
196         if (a.length !== b.length)
197             return false;
198         for (var i = 0; i < a.length; i++)
199             if (a[i] !== b[i])
200                 return false;
201     } catch (ex) {
202         return false;
203     }
204     return true;
205 }
206
207 function isMinusZero(n)
208 {
209     // the only way to tell 0 from -0 in JS is the fact that 1/-0 is
210     // -Infinity instead of Infinity
211     return n === 0 && 1/n < 0;
212 }
213
214 function isNewSVGTearOffType(v)
215 {
216     return ['[object SVGLength]', '[object SVGLengthList]', '[object SVGPoint]', '[object SVGPointList]', '[object SVGNumber]'].indexOf(""+v) != -1;
217 }
218
219 function isResultCorrect(actual, expected)
220 {
221     if (expected === 0)
222         return actual === expected && (1/actual) === (1/expected);
223     if (actual === expected)
224         return true;
225     // http://crbug.com/308818 : The new implementation of SVGListProperties do not necessary return the same wrapper object, so === operator would not work. We compare for their string representation instead.
226     if (isNewSVGTearOffType(expected) && typeof(expected) == typeof(actual) && actual.valueAsString == expected.valueAsString)
227         return true;
228     if (typeof(expected) == "number" && isNaN(expected))
229         return typeof(actual) == "number" && isNaN(actual);
230     if (expected && (Object.prototype.toString.call(expected) == Object.prototype.toString.call([])))
231         return areArraysEqual(actual, expected);
232     return false;
233 }
234
235 function stringify(v)
236 {
237     if (isNewSVGTearOffType(v))
238         return v.valueAsString;
239     if (v === 0 && 1/v < 0)
240         return "-0";
241     else return "" + v;
242 }
243
244 function evalAndLog(_a, _quiet)
245 {
246   if (typeof _a != "string")
247     debug("WARN: tryAndLog() expects a string argument");
248
249   // Log first in case things go horribly wrong or this causes a sync event.
250   if (!_quiet)
251     debug(_a);
252
253   var _av;
254   try {
255      _av = eval(_a);
256   } catch (e) {
257     testFailed(_a + " threw exception " + e);
258   }
259   return _av;
260 }
261
262 function shouldBe(_a, _b, _quiet)
263 {
264     if ((typeof _a != "function" && typeof _a != "string") || (typeof _b != "function" && typeof _b != "string"))
265         debug("WARN: shouldBe() expects function or string arguments");
266     var _exception;
267     var _av;
268     try {
269         _av = (typeof _a == "function" ? _a() : eval(_a));
270     } catch (e) {
271         _exception = e;
272     }
273     var _bv = (typeof _b == "function" ? _b() : eval(_b));
274
275     if (_exception)
276         testFailed(_a + " should be " + stringify(_bv) + ". Threw exception " + _exception);
277     else if (isResultCorrect(_av, _bv)) {
278         if (!_quiet) {
279             testPassed(_a + " is " + (typeof _b == "function" ? _bv : _b));
280         }
281     } else if (typeof(_av) == typeof(_bv))
282         testFailed(_a + " should be " + stringify(_bv) + ". Was " + stringify(_av) + ".");
283     else
284         testFailed(_a + " should be " + stringify(_bv) + " (of type " + typeof _bv + "). Was " + _av + " (of type " + typeof _av + ").");
285 }
286
287 // Execute condition every 5 milliseconds until it succeeds.
288 function _waitForCondition(condition, completionHandler)
289 {
290   if (condition())
291     completionHandler();
292   else
293     setTimeout(_waitForCondition, 5, condition, completionHandler);
294 }
295
296 function shouldBecomeEqual(_a, _b, completionHandler)
297 {
298   if (typeof _a != "string" || typeof _b != "string")
299     debug("WARN: shouldBecomeEqual() expects string arguments");
300
301   function condition() {
302     var exception;
303     var _av;
304     try {
305       _av = eval(_a);
306     } catch (e) {
307       exception = e;
308     }
309     var _bv = eval(_b);
310     if (exception)
311       testFailed(_a + " should become " + _bv + ". Threw exception " + exception);
312     if (isResultCorrect(_av, _bv)) {
313       testPassed(_a + " became " + _b);
314       return true;
315     }
316     return false;
317   }
318   setTimeout(_waitForCondition, 0, condition, completionHandler);
319 }
320
321 function shouldBecomeEqualToString(value, reference, completionHandler)
322 {
323   if (typeof value !== "string" || typeof reference !== "string")
324     debug("WARN: shouldBecomeEqualToString() expects string arguments");
325   var unevaledString = JSON.stringify(reference);
326   shouldBecomeEqual(value, unevaledString, completionHandler);
327 }
328
329 function shouldBeType(_a, _type) {
330   var _exception;
331   var _av;
332   try {
333     _av = eval(_a);
334   } catch (e) {
335     _exception = e;
336   }
337
338   var _typev = eval(_type);
339   if (_av instanceof _typev) {
340     testPassed(_a + " is an instance of " + _type);
341   } else {
342     testFailed(_a + " is not an instance of " + _type);
343   }
344 }
345
346 // Variant of shouldBe()--confirms that result of eval(_to_eval) is within
347 // numeric _tolerance of numeric _target.
348 function shouldBeCloseTo(_to_eval, _target, _tolerance, _quiet)
349 {
350   if (typeof _to_eval != "string") {
351     testFailed("shouldBeCloseTo() requires string argument _to_eval. was type " + typeof _to_eval);
352     return;
353   }
354   if (typeof _target != "number") {
355     testFailed("shouldBeCloseTo() requires numeric argument _target. was type " + typeof _target);
356     return;
357   }
358   if (typeof _tolerance != "number") {
359     testFailed("shouldBeCloseTo() requires numeric argument _tolerance. was type " + typeof _tolerance);
360     return;
361   }
362
363   var _result;
364   try {
365      _result = eval(_to_eval);
366   } catch (e) {
367     testFailed(_to_eval + " should be within " + _tolerance + " of "
368                + _target + ". Threw exception " + e);
369     return;
370   }
371
372   if (typeof(_result) != typeof(_target)) {
373     testFailed(_to_eval + " should be of type " + typeof _target
374                + " but was of type " + typeof _result);
375   } else if (Math.abs(_result - _target) <= _tolerance) {
376     if (!_quiet) {
377         testPassed(_to_eval + " is within " + _tolerance + " of " + _target);
378     }
379   } else {
380     testFailed(_to_eval + " should be within " + _tolerance + " of " + _target
381                + ". Was " + _result + ".");
382   }
383 }
384
385 function shouldNotBe(_a, _b, _quiet)
386 {
387     if ((typeof _a != "function" && typeof _a != "string") || (typeof _b != "function" && typeof _b != "string"))
388         debug("WARN: shouldNotBe() expects function or string arguments");
389     var _exception;
390     var _av;
391     try {
392         _av = (typeof _a == "function" ? _a() : eval(_a));
393     } catch (e) {
394         _exception = e;
395     }
396     var _bv = (typeof _b == "function" ? _b() : eval(_b));
397
398     if (_exception)
399         testFailed(_a + " should not be " + _bv + ". Threw exception " + _exception);
400     else if (!isResultCorrect(_av, _bv)) {
401         if (!_quiet) {
402             testPassed(_a + " is not " + (typeof _b == "function" ? _bv : _b));
403         }
404     } else
405         testFailed(_a + " should not be " + _bv + ".");
406 }
407
408 function shouldBecomeDifferent(_a, _b, completionHandler)
409 {
410   if (typeof _a != "string" || typeof _b != "string")
411     debug("WARN: shouldBecomeDifferent() expects string arguments");
412
413   function condition() {
414     var exception;
415     var _av;
416     try {
417       _av = eval(_a);
418     } catch (e) {
419       exception = e;
420     }
421     var _bv = eval(_b);
422     if (exception)
423       testFailed(_a + " should became not equal to " + _bv + ". Threw exception " + exception);
424     if (!isResultCorrect(_av, _bv)) {
425       testPassed(_a + " became different from " + _b);
426       return true;
427     }
428     return false;
429   }
430   setTimeout(_waitForCondition, 0, condition, completionHandler);
431 }
432
433 function shouldBeTrue(a, quiet) { shouldBe(a, "true", quiet); }
434 function shouldBeTrueQuiet(a) { shouldBe(a, "true", true); }
435 function shouldBeFalse(a, quiet) { shouldBe(a, "false", quiet); }
436 function shouldBeNaN(a, quiet) { shouldBe(a, "NaN", quiet); }
437 function shouldBeNull(a, quiet) { shouldBe(a, "null", quiet); }
438 function shouldBeZero(a, quiet) { shouldBe(a, "0", quiet); }
439
440 function shouldBeEqualToString(a, b)
441 {
442   if (typeof a !== "string" || typeof b !== "string")
443     debug("WARN: shouldBeEqualToString() expects string arguments");
444   var unevaledString = JSON.stringify(b);
445   shouldBe(a, unevaledString);
446 }
447
448 function shouldBeEqualToNumber(a, b)
449 {
450   if (typeof a !== "string" || typeof b !== "number")
451     debug("WARN: shouldBeEqualToNumber() expects a string and a number arguments");
452   var unevaledString = JSON.stringify(b);
453   shouldBe(a, unevaledString);
454 }
455
456 function shouldBeEmptyString(a) { shouldBeEqualToString(a, ""); }
457
458 function shouldEvaluateTo(actual, expected) {
459   // A general-purpose comparator.  'actual' should be a string to be
460   // evaluated, as for shouldBe(). 'expected' may be any type and will be
461   // used without being eval'ed.
462   if (expected == null) {
463     // Do this before the object test, since null is of type 'object'.
464     shouldBeNull(actual);
465   } else if (typeof expected == "undefined") {
466     shouldBeUndefined(actual);
467   } else if (typeof expected == "function") {
468     // All this fuss is to avoid the string-arg warning from shouldBe().
469     try {
470       var actualValue = eval(actual);
471     } catch (e) {
472       testFailed("Evaluating " + actual + ": Threw exception " + e);
473       return;
474     }
475     shouldBe("'" + actualValue.toString().replace(/\n/g, "") + "'",
476              "'" + expected.toString().replace(/\n/g, "") + "'");
477   } else if (typeof expected == "object") {
478     shouldBeTrue(actual + " == '" + expected + "'");
479   } else if (typeof expected == "string") {
480     shouldBe(actual, expected);
481   } else if (typeof expected == "boolean") {
482     shouldBe("typeof " + actual, "'boolean'");
483     if (expected)
484       shouldBeTrue(actual);
485     else
486       shouldBeFalse(actual);
487   } else if (typeof expected == "number") {
488     shouldBe(actual, stringify(expected));
489   } else {
490     debug(expected + " is unknown type " + typeof expected);
491     shouldBeTrue(actual, "'"  +expected.toString() + "'");
492   }
493 }
494
495 function shouldBeNonZero(_a)
496 {
497   var _exception;
498   var _av;
499   try {
500      _av = eval(_a);
501   } catch (e) {
502      _exception = e;
503   }
504
505   if (_exception)
506     testFailed(_a + " should be non-zero. Threw exception " + _exception);
507   else if (_av != 0)
508     testPassed(_a + " is non-zero.");
509   else
510     testFailed(_a + " should be non-zero. Was " + _av);
511 }
512
513 function shouldBeNonNull(_a)
514 {
515   var _exception;
516   var _av;
517   try {
518      _av = eval(_a);
519   } catch (e) {
520      _exception = e;
521   }
522
523   if (_exception)
524     testFailed(_a + " should be non-null. Threw exception " + _exception);
525   else if (_av != null)
526     testPassed(_a + " is non-null.");
527   else
528     testFailed(_a + " should be non-null. Was " + _av);
529 }
530
531 function shouldBeUndefined(_a)
532 {
533   var _exception;
534   var _av;
535   try {
536      _av = eval(_a);
537   } catch (e) {
538       _exception = e;
539   }
540
541   if (_exception)
542     testFailed(_a + " should be undefined. Threw exception " + _exception);
543   else if (typeof _av == "undefined")
544     testPassed(_a + " is undefined.");
545   else
546     testFailed(_a + " should be undefined. Was " + _av);
547 }
548
549 function shouldBeDefined(_a)
550 {
551   var _exception;
552   var _av;
553   try {
554      _av = eval(_a);
555   } catch (e) {
556      _exception = e;
557   }
558
559   if (_exception)
560     testFailed(_a + " should be defined. Threw exception " + _exception);
561   else if (_av !== undefined)
562     testPassed(_a + " is defined.");
563   else
564     testFailed(_a + " should be defined. Was " + _av);
565 }
566
567 function shouldBeGreaterThanOrEqual(_a, _b) {
568     if (typeof _a != "string" || typeof _b != "string")
569         debug("WARN: shouldBeGreaterThanOrEqual expects string arguments");
570
571     var _exception;
572     var _av;
573     try {
574         _av = eval(_a);
575     } catch (e) {
576         _exception = e;
577     }
578     var _bv = eval(_b);
579
580     if (_exception)
581         testFailed(_a + " should be >= " + _b + ". Threw exception " + _exception);
582     else if (typeof _av == "undefined" || _av < _bv)
583         testFailed(_a + " should be >= " + _b + ". Was " + _av + " (of type " + typeof _av + ").");
584     else
585         testPassed(_a + " is >= " + _b);
586 }
587
588 function expectTrue(v, msg) {
589   if (v) {
590     testPassed(msg);
591   } else {
592     testFailed(msg);
593   }
594 }
595
596 function shouldNotThrow(_a, _message) {
597     try {
598         typeof _a == "function" ? _a() : eval(_a);
599         testPassed((_message ? _message : _a) + " did not throw exception.");
600     } catch (e) {
601         testFailed((_message ? _message : _a) + " should not throw exception. Threw exception " + e + ".");
602     }
603 }
604
605 function shouldThrow(_a, _e, _message)
606 {
607     var _exception;
608     var _av;
609     try {
610         _av = typeof _a == "function" ? _a() : eval(_a);
611     } catch (e) {
612         _exception = e;
613     }
614
615     var _ev;
616     if (_e)
617         _ev = eval(_e);
618
619     if (_exception) {
620         if (typeof _e == "undefined" || _exception == _ev)
621             testPassed((_message ? _message : _a) + " threw exception " + _exception + ".");
622         else
623             testFailed((_message ? _message : _a) + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Threw exception " + _exception + ".");
624     } else if (typeof _av == "undefined")
625         testFailed((_message ? _message : _a) + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was undefined.");
626     else
627         testFailed((_message ? _message : _a) + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was " + _av + ".");
628 }
629
630 function shouldBeNow(a, delta)
631 {
632     // Right now, V8 and Chromium / Blink use two different clock
633     // implementations. On Windows, the implementations are non-trivial and can
634     // be slightly out of sync. The delta is intended to compensate for that.
635     //
636     // FIXME: reconsider this when the V8 and Blink clocks get unified, see http://crbug.com/324110
637     if (delta === undefined)
638         delta = 1000;
639
640     for (var i = 0; i < 1000; ++i) {
641         var startDate = Date.now();
642         var av = eval(a);
643         var date = av.valueOf();
644         var endDate = Date.now();
645
646         // On some occasions such as NTP updates, the current time can go
647         // backwards. This should only happen rarely, so we can get away with
648         // retrying the test a few times if we detect the time going backwards.
649         if (startDate > endDate)
650             continue;
651
652         if (typeof date !== "number") {
653             testFailed(a + " is not a number or a Date. Got " + av);
654             return;
655         }
656         if (date < startDate - delta) {
657             testFailed(a + " is not the curent time. Got " + av + " which is " + (startDate - date) / 1000 + " seconds in the past.");
658             return;
659         }
660         if (date > endDate + delta) {
661             testFailed(a + " is not the current time. Got " + av + " which is " + (date - endDate) / 1000 + " seconds in the future.");
662             return;
663         }
664
665         testPassed(a + " is equivalent to Date.now().");
666         return;
667     }
668     testFailed(a + " cannot be tested against the current time. The clock is going backwards too often.");
669 }
670
671 function expectError()
672 {
673     if (expectingError) {
674         testFailed("shouldHaveError() called twice before an error occurred!");
675     }
676     expectingError = true;
677 }
678
679 function shouldReject(_a, _message)
680 {
681     var _exception;
682     var _av;
683     try {
684         _av = typeof _a == "function" ? _a() : eval(_a);
685     } catch (e) {
686         testFailed((_message ? _message : _a) + " should not throw exception. Threw exception " + e + ".");
687         return Promise.resolve();
688     }
689
690     return _av.then(function(result) {
691         testFailed((_message ? _message : _a) + " should reject promise. Resolved with " + result + ".");
692     }, function(error) {
693         testPassed((_message ? _message : _a) + " rejected promise  with " + error + ".");
694     });
695 }
696
697 function shouldThrowErrorName(_a, _name)
698 {
699     var _exception;
700     try {
701         typeof _a == "function" ? _a() : eval(_a);
702     } catch (e) {
703         _exception = e;
704     }
705
706     if (_exception) {
707         if (_exception.name == _name)
708             testPassed(_a + " threw exception " + _exception + ".");
709         else
710             testFailed(_a + " should throw a " + _name + ". Threw a " + _exception.name + ".");
711     } else
712         testFailed(_a + " should throw a " + _name + ". Did not throw.");
713 }
714
715 function shouldHaveHadError(message)
716 {
717     if (expectingError) {
718         testFailed("No error thrown between expectError() and shouldHaveHadError()");
719         return;
720     }
721
722     if (expectedErrorMessage) {
723         if (!message)
724             testPassed("Got expected error");
725         else if (expectedErrorMessage.indexOf(message) !== -1)
726             testPassed("Got expected error: '" + message + "'");
727         else
728             testFailed("Unexpected error '" + message + "'");
729         expectedErrorMessage = undefined;
730         return;
731     }
732
733     testFailed("expectError() not called before shouldHaveHadError()");
734 }
735
736 function gc() {
737     if (typeof GCController !== "undefined")
738         GCController.collect();
739     else {
740         var gcRec = function (n) {
741             if (n < 1)
742                 return {};
743             var temp = {i: "ab" + i + (i / 100000)};
744             temp += "foo";
745             gcRec(n-1);
746         };
747         for (var i = 0; i < 1000; i++)
748             gcRec(10);
749     }
750 }
751
752 function minorGC() {
753     if (typeof GCController !== "undefined")
754         GCController.minorCollect();
755     else
756         testFailed("Minor GC is available only when you enable the --expose-gc option in V8.");
757 }
758
759 function isSuccessfullyParsed()
760 {
761     // FIXME: Remove this and only report unexpected syntax errors.
762     successfullyParsed = !unexpectedErrorMessage;
763     shouldBeTrue("successfullyParsed");
764     if (didFailSomeTests)
765         debug("Some tests failed.");
766     debug('<br /><span class="pass">TEST COMPLETE</span>');
767 }
768
769 // It's possible for an async test to call finishJSTest() before js-test-post.js
770 // has been parsed.
771 function finishJSTest()
772 {
773     wasFinishJSTestCalled = true;
774     if (!self.wasPostTestScriptParsed)
775         return;
776     isSuccessfullyParsed();
777     moveForeignObjectToTopIfNeeded();
778     if (self.jsTestIsAsync && self.testRunner)
779         testRunner.notifyDone();
780 }
781
782 function areObjectsEqual(a, b) {
783         for (var property in a) {
784                 if (!b.hasOwnProperty(property))
785                         return false;
786
787                 switch (typeof (a[property])) {
788                 case 'function':
789                         if (typeof b[property] == 'undefined' || a[property].toString() != b[property].toString())
790                                 return false;
791                         break;
792                 case 'object':
793                         if (!areObjectsEqual(a, b))
794                                 return false;
795                         break;
796                 default:
797                         if (a[property] != b[property])
798                                 return false;
799                 }
800         }
801  
802         for (var property in b) {
803                 if (!a.hasOwnProperty(property))
804                         return false;
805         }
806         
807         return true;
808 };
809
810 function startWorker(testScriptURL)
811 {
812     self.jsTestIsAsync = true;
813     debug('Starting worker: ' + testScriptURL);
814     var worker = new Worker(testScriptURL);
815     worker.onmessage = function(event)
816     {
817         var workerPrefix = "[Worker] ";
818         if (event.data.length < 5 || event.data.charAt(4) != ':') {
819           debug(workerPrefix + event.data);
820           return;
821         }
822         var code = event.data.substring(0, 4);
823         var payload = workerPrefix + event.data.substring(5);
824         if (code == "PASS")
825             testPassed(payload);
826         else if (code == "FAIL")
827             testFailed(payload);
828         else if (code == "DESC")
829             description(payload);
830         else if (code == "DONE")
831             finishJSTest();
832         else
833             debug(workerPrefix + event.data);
834     };
835
836     worker.onerror = function(event)
837     {
838         debug('Got error from worker: ' + event.message);
839         finishJSTest();
840     };
841
842     return worker;
843 }
844
845 if (isWorker()) {
846     var workerPort = self;
847     description = function(msg, quiet) {
848         workerPort.postMessage('DESC:' + msg);
849     };
850     testFailed = function(msg) {
851         workerPort.postMessage('FAIL:' + msg);
852     };
853     testPassed = function(msg) {
854         workerPort.postMessage('PASS:' + msg);
855     };
856     finishJSTest = function() {
857         workerPort.postMessage('DONE:');
858     };
859     debug = function(msg) {
860         workerPort.postMessage(msg);
861     };
862 }