testing/mochitest/tests/SimpleTest/SimpleTest.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- js-indent-level: 4; tab-width: 4; indent-tabs-mode: nil -*- */
     2 /* vim:set ts=4 sw=4 sts=4 et: */
     3 /**
     4  * SimpleTest, a partial Test.Simple/Test.More API compatible test library.
     5  *
     6  * Why?
     7  *
     8  * Test.Simple doesn't work on IE < 6.
     9  * TODO:
    10  *  * Support the Test.Simple API used by MochiKit, to be able to test MochiKit
    11  * itself against IE 5.5
    12  *
    13  * NOTE: Pay attention to cross-browser compatibility in this file. For
    14  * instance, do not use const or JS > 1.5 features which are not yet
    15  * implemented everywhere.
    16  *
    17 **/
    19 var SimpleTest = { };
    20 var parentRunner = null;
    22 // In normal test runs, the window that has a TestRunner in its parent is
    23 // the primary window.  In single test runs, if there is no parent and there
    24 // is no opener then it is the primary window.
    25 var isSingleTestRun = (parent == window && !opener)
    26 var isPrimaryTestWindow = !!parent.TestRunner || isSingleTestRun;
    28 // Finds the TestRunner for this test run and the SpecialPowers object (in
    29 // case it is not defined) from a parent/opener window.
    30 //
    31 // Finding the SpecialPowers object is needed when we have ChromePowers in
    32 // harness.xul and we need SpecialPowers in the iframe, and also for tests
    33 // like test_focus.xul where we open a window which opens another window which
    34 // includes SimpleTest.js.
    35 (function() {
    36     function ancestor(w) {
    37         return w.parent != w ? w.parent : w.opener;
    38     }
    40     var w = ancestor(window);
    41     while (w && (!parentRunner || !window.SpecialPowers)) {
    42         if (!parentRunner) {
    43             parentRunner = w.TestRunner;
    44             if (!parentRunner && w.wrappedJSObject) {
    45                 parentRunner = w.wrappedJSObject.TestRunner;
    46             }
    47         }
    48         if (!window.SpecialPowers) {
    49             window.SpecialPowers = w.SpecialPowers;
    50         }
    51         w = ancestor(w);
    52     }
    53 })();
    55 /* Helper functions pulled out of various MochiKit modules */
    56 if (typeof(repr) == 'undefined') {
    57     this.repr = function(o) {
    58         if (typeof(o) == "undefined") {
    59             return "undefined";
    60         } else if (o === null) {
    61             return "null";
    62         }
    63         try {
    64             if (typeof(o.__repr__) == 'function') {
    65                 return o.__repr__();
    66             } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
    67                 return o.repr();
    68             }
    69        } catch (e) {
    70        }
    71        try {
    72             if (typeof(o.NAME) == 'string' && (
    73                     o.toString == Function.prototype.toString ||
    74                     o.toString == Object.prototype.toString
    75                 )) {
    76                 return o.NAME;
    77             }
    78         } catch (e) {
    79         }
    80         try {
    81             var ostring = (o + "");
    82         } catch (e) {
    83             return "[" + typeof(o) + "]";
    84         }
    85         if (typeof(o) == "function") {
    86             o = ostring.replace(/^\s+/, "");
    87             var idx = o.indexOf("{");
    88             if (idx != -1) {
    89                 o = o.substr(0, idx) + "{...}";
    90             }
    91         }
    92         return ostring;
    93     };
    94 } 
    96 /* This returns a function that applies the previously given parameters.
    97  * This is used by SimpleTest.showReport
    98  */
    99 if (typeof(partial) == 'undefined') {
   100     this.partial = function(func) {
   101         var args = [];
   102         for (var i = 1; i < arguments.length; i++) {
   103             args.push(arguments[i]);
   104         }
   105         return function() {
   106             if (arguments.length > 0) {
   107                 for (var i = 1; i < arguments.length; i++) {
   108                     args.push(arguments[i]);
   109                 }
   110             }
   111             func(args);
   112         };
   113     };
   114 }
   116 if (typeof(getElement) == 'undefined') {
   117     this.getElement = function(id) {
   118         return ((typeof(id) == "string") ?
   119             document.getElementById(id) : id); 
   120     };
   121     this.$ = this.getElement;
   122 }
   124 SimpleTest._newCallStack = function(path) {
   125     var rval = function () {
   126         var callStack = arguments.callee.callStack;
   127         for (var i = 0; i < callStack.length; i++) {
   128             if (callStack[i].apply(this, arguments) === false) {
   129                 break;
   130             }
   131         }
   132         try {
   133             this[path] = null;
   134         } catch (e) {
   135             // pass
   136         }
   137     };
   138     rval.callStack = [];
   139     return rval;
   140 };
   142 if (typeof(addLoadEvent) == 'undefined') {
   143     this.addLoadEvent = function(func) {
   144         var existing = window["onload"];
   145         var regfunc = existing;
   146         if (!(typeof(existing) == 'function'
   147                 && typeof(existing.callStack) == "object"
   148                 && existing.callStack !== null)) {
   149             regfunc = SimpleTest._newCallStack("onload");
   150             if (typeof(existing) == 'function') {
   151                 regfunc.callStack.push(existing);
   152             }
   153             window["onload"] = regfunc;
   154         }
   155         regfunc.callStack.push(func);
   156     };
   157 }
   159 function createEl(type, attrs, html) {
   160     //use createElementNS so the xul/xhtml tests have no issues
   161     var el;
   162     if (!document.body) {
   163         el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
   164     }
   165     else {
   166         el = document.createElement(type);
   167     }
   168     if (attrs !== null && attrs !== undefined) {
   169         for (var k in attrs) {
   170             el.setAttribute(k, attrs[k]);
   171         }
   172     }
   173     if (html !== null && html !== undefined) {
   174         el.appendChild(document.createTextNode(html));
   175     }
   176     return el;
   177 }
   179 /* lots of tests use this as a helper to get css properties */
   180 if (typeof(computedStyle) == 'undefined') {
   181     this.computedStyle = function(elem, cssProperty) {
   182         elem = getElement(elem);
   183         if (elem.currentStyle) {
   184             return elem.currentStyle[cssProperty];
   185         }
   186         if (typeof(document.defaultView) == 'undefined' || document === null) {
   187             return undefined;
   188         }
   189         var style = document.defaultView.getComputedStyle(elem, null);
   190         if (typeof(style) == 'undefined' || style === null) {
   191             return undefined;
   192         }
   194         var selectorCase = cssProperty.replace(/([A-Z])/g, '-$1'
   195             ).toLowerCase();
   197         return style.getPropertyValue(selectorCase);
   198     };
   199 }
   201 /**
   202  * Check for OOP test plugin
   203 **/
   204 SimpleTest.testPluginIsOOP = function () {
   205     var testPluginIsOOP = false;
   206     if (navigator.platform.indexOf("Mac") == 0) {
   207         if (SpecialPowers.XPCOMABI.match(/x86-/)) {
   208             try {
   209                 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386.test.plugin");
   210             } catch (e) {
   211                 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386");
   212             }
   213         }
   214         else if (SpecialPowers.XPCOMABI.match(/x86_64-/)) {
   215             try {
   216                 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64.test.plugin");
   217             } catch (e) {
   218                 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64");
   219             }
   220         }
   221     }
   222     else {
   223         testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled");
   224     }
   226     return testPluginIsOOP;
   227 };
   229 SimpleTest._tests = [];
   230 SimpleTest._stopOnLoad = true;
   231 SimpleTest._cleanupFunctions = [];
   233 /**
   234  * Something like assert.
   235 **/
   236 SimpleTest.ok = function (condition, name, diag) {
   237     var test = {'result': !!condition, 'name': name, 'diag': diag};
   238     SimpleTest._logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
   239     SimpleTest._tests.push(test);
   240 };
   242 /**
   243  * Roughly equivalent to ok(a==b, name)
   244 **/
   245 SimpleTest.is = function (a, b, name) {
   246     var pass = (a == b);
   247     var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b)
   248     SimpleTest.ok(pass, name, diag);
   249 };
   251 SimpleTest.isfuzzy = function (a, b, epsilon, name) {
   252   var pass = (a > b - epsilon) && (a < b + epsilon);
   253   var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) + " epsilon: +/- " + repr(epsilon)
   254   SimpleTest.ok(pass, name, diag);
   255 };
   257 SimpleTest.isnot = function (a, b, name) {
   258     var pass = (a != b);
   259     var diag = pass ? "" : "didn't expect " + repr(a) + ", but got it";
   260     SimpleTest.ok(pass, name, diag);
   261 };
   263 /**
   264  * Roughly equivalent to ok(a===b, name)
   265 **/
   266 SimpleTest.ise = function (a, b, name) {
   267     var pass = (a === b);
   268     var diag = pass ? "" : "got " + repr(a) + ", strictly expected " + repr(b)
   269     SimpleTest.ok(pass, name, diag);
   270 };
   272 /**
   273  * Check that the function call throws an exception.
   274  */
   275 SimpleTest.doesThrow = function(fn, name) {
   276     var gotException = false;
   277     try {
   278       fn();
   279     } catch (ex) { gotException = true; }
   280     ok(gotException, name);
   281 };
   283 //  --------------- Test.Builder/Test.More todo() -----------------
   285 SimpleTest.todo = function(condition, name, diag) {
   286     var test = {'result': !!condition, 'name': name, 'diag': diag, todo: true};
   287     SimpleTest._logResult(test, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL");
   288     SimpleTest._tests.push(test);
   289 };
   291 /*
   292  * Returns the absolute URL to a test data file from where tests
   293  * are served. i.e. the file doesn't necessarely exists where tests
   294  * are executed.
   295  * (For b2g and android, mochitest are executed on the device, while
   296  * all mochitest html (and others) files are served from the test runner
   297  * slave)
   298  */
   299 SimpleTest.getTestFileURL = function(path) {
   300   var lastSlashIdx = path.lastIndexOf("/") + 1;
   301   var filename = path.substr(lastSlashIdx);
   302   var location = window.location;
   303   // Remove mochitest html file name from the path
   304   var remotePath = location.pathname.replace(/\/[^\/]+?$/,"");
   305   var url = location.origin +
   306             remotePath + "/" + path;
   307   return url;
   308 };
   310 SimpleTest._getCurrentTestURL = function() {
   311     return parentRunner && parentRunner.currentTestURL ||
   312            typeof gTestPath == "string" && gTestPath ||
   313            "unknown test url";
   314 };
   316 SimpleTest._forceLogMessageOutput = parentRunner && !parentRunner.quiet;
   318 /**
   319  * Force all test messages to be displayed.  Only applies for the current test.
   320  */
   321 SimpleTest.requestCompleteLog = function() {
   322     if (SimpleTest._forceLogMessageOutput)
   323         return;
   325     SimpleTest._forceLogMessageOutput = true;
   326     SimpleTest.registerCleanupFunction(function() {
   327         SimpleTest._forceLogMessageOutput = false;
   328     });
   329 };
   331 /**
   332  * A circular buffer, managed by _logResult.  We explicitly manage the
   333  * circularness of the buffer, rather than resorting to .shift()/.push()
   334  * because explicit management is much faster.
   335  */
   336 SimpleTest._bufferedMessages = [];
   337 SimpleTest._logResult = (function () {
   338     var bufferingThreshold = 100;
   339     var outputIndex = 0;
   341     function logResult(test, passString, failString) {
   342         var url = SimpleTest._getCurrentTestURL();
   343         var resultString = test.result ? passString : failString;
   344         var diagnostic = test.name + (test.diag ? " - " + test.diag : "");
   345         var msg = [resultString, url, diagnostic].join(" | ");
   346         var isError = !test.result == !test.todo;
   348         // Due to JavaScript's name lookup rules, it is important that
   349         // the second parameter here be named identically to the isError
   350         // variable declared above.
   351         function dumpMessage(msg, isError) {
   352             if (parentRunner) {
   353                 if (isError) {
   354                     parentRunner.addFailedTest(url);
   355                     parentRunner.error(msg);
   356                 } else {
   357                     parentRunner.log(msg);
   358                 }
   359             } else if (typeof dump === "function") {
   360                 dump(msg + "\n");
   361             } else {
   362                 // Non-Mozilla browser?  Just do nothing.
   363             }
   364         }
   366         // Detect when SimpleTest.reset() has been called, so we can
   367         // reset outputIndex.  We store outputIndex as local state to
   368         // avoid adding even more state to SimpleTest.
   369         if (SimpleTest._bufferedMessages.length == 0) {
   370             outputIndex = 0;
   371         }
   373         // We want to eliminate mundane TEST-PASS/TEST-KNOWN-FAIL
   374         // output, since some tests produce tens of thousands of of such
   375         // messages.  These messages can consume a lot of memory to
   376         // generate and take a significant amount of time to output.
   377         // However, the reality is that TEST-PASS messages can also be
   378         // used as a form of logging via constructs like:
   379         //
   380         // SimpleTest.ok(true, "some informative message");
   381         //
   382         // And eliding the logging can be very confusing when trying to
   383         // debug test failures.
   384         //
   385         // Hence the compromise adopted here: We buffer messages up to
   386         // some limit and dump the buffer when a test failure happens.
   387         // This behavior ought to provide enough context for developers
   388         // looking to understand where in the test things failed.
   389         if (isError) {
   390             // Display this message and all the messages we have buffered.
   391             if (SimpleTest._bufferedMessages.length > 0) {
   392                 dumpMessage("TEST-INFO | dumping last " + SimpleTest._bufferedMessages.length + " message(s)");
   393                 dumpMessage("TEST-INFO | if you need more context, please use SimpleTest.requestCompleteLog() in your test");
   395                 function dumpBufferedMessage(m) {
   396                     dumpMessage(m, false);
   397                 }
   398                 // The latest message is just before outputIndex.
   399                 // The earliest message is located at outputIndex.
   400                 var earliest = SimpleTest._bufferedMessages.slice(outputIndex);
   401                 var latest = SimpleTest._bufferedMessages.slice(0, outputIndex);
   402                 earliest.map(dumpBufferedMessage);
   403                 latest.map(dumpBufferedMessage);
   405                 SimpleTest._bufferedMessages = [];
   406             }
   408             dumpMessage(msg);
   409             return;
   410         }
   412         var runningSingleTest = ((parentRunner &&
   413                                   parentRunner._urls.length == 1) ||
   414                                  isSingleTestRun);
   415         var shouldLogImmediately = (runningSingleTest ||
   416                                     SimpleTest._forceLogMessageOutput);
   418         if (!shouldLogImmediately) {
   419             // Buffer the message for possible later output.
   420             if (SimpleTest._bufferedMessages.length >= bufferingThreshold) {
   421                 if (outputIndex >= bufferingThreshold) {
   422                     outputIndex = 0;
   423                 }
   424                 SimpleTest._bufferedMessages[outputIndex] = msg;
   425                 outputIndex++;
   426             } else {
   427                 SimpleTest._bufferedMessages.push(msg);
   428             }
   429             return;
   430         }
   432         dumpMessage(msg);
   433     }
   435     return logResult;
   436 })();
   438 SimpleTest.info = function(name, message) {
   439     SimpleTest._logResult({result:true, name:name, diag:message}, "TEST-INFO");
   440 };
   442 /**
   443  * Copies of is and isnot with the call to ok replaced by a call to todo.
   444 **/
   446 SimpleTest.todo_is = function (a, b, name) {
   447     var pass = (a == b);
   448     var diag = pass ? repr(a) + " should equal " + repr(b)
   449                     : "got " + repr(a) + ", expected " + repr(b);
   450     SimpleTest.todo(pass, name, diag);
   451 };
   453 SimpleTest.todo_isnot = function (a, b, name) {
   454     var pass = (a != b);
   455     var diag = pass ? repr(a) + " should not equal " + repr(b)
   456                     : "didn't expect " + repr(a) + ", but got it";
   457     SimpleTest.todo(pass, name, diag);
   458 };
   461 /**
   462  * Makes a test report, returns it as a DIV element.
   463 **/
   464 SimpleTest.report = function () {
   465     var passed = 0;
   466     var failed = 0;
   467     var todo = 0;
   469     var tallyAndCreateDiv = function (test) {
   470             var cls, msg, div;
   471             var diag = test.diag ? " - " + test.diag : "";
   472             if (test.todo && !test.result) {
   473                 todo++;
   474                 cls = "test_todo";
   475                 msg = "todo | " + test.name + diag;
   476             } else if (test.result && !test.todo) {
   477                 passed++;
   478                 cls = "test_ok";
   479                 msg = "passed | " + test.name + diag;
   480             } else {
   481                 failed++;
   482                 cls = "test_not_ok";
   483                 msg = "failed | " + test.name + diag;
   484             }
   485           div = createEl('div', {'class': cls}, msg);
   486           return div;
   487         };
   488     var results = [];
   489     for (var d=0; d<SimpleTest._tests.length; d++) {
   490         results.push(tallyAndCreateDiv(SimpleTest._tests[d]));
   491     }
   493     var summary_class = failed != 0 ? 'some_fail' :
   494                           passed == 0 ? 'todo_only' : 'all_pass';
   496     var div1 = createEl('div', {'class': 'tests_report'});
   497     var div2 = createEl('div', {'class': 'tests_summary ' + summary_class});
   498     var div3 = createEl('div', {'class': 'tests_passed'}, 'Passed: ' + passed);
   499     var div4 = createEl('div', {'class': 'tests_failed'}, 'Failed: ' + failed);
   500     var div5 = createEl('div', {'class': 'tests_todo'}, 'Todo: ' + todo);
   501     div2.appendChild(div3);
   502     div2.appendChild(div4);
   503     div2.appendChild(div5);
   504     div1.appendChild(div2);
   505     for (var t=0; t<results.length; t++) {
   506         //iterate in order
   507         div1.appendChild(results[t]);
   508     }
   509     return div1;
   510 };
   512 /**
   513  * Toggle element visibility
   514 **/
   515 SimpleTest.toggle = function(el) {
   516     if (computedStyle(el, 'display') == 'block') {
   517         el.style.display = 'none';
   518     } else {
   519         el.style.display = 'block';
   520     }
   521 };
   523 /**
   524  * Toggle visibility for divs with a specific class.
   525 **/
   526 SimpleTest.toggleByClass = function (cls, evt) {
   527     var children = document.getElementsByTagName('div');
   528     var elements = [];
   529     for (var i=0; i<children.length; i++) {
   530         var child = children[i];
   531         var clsName = child.className;
   532         if (!clsName) {
   533             continue;
   534         }    
   535         var classNames = clsName.split(' ');
   536         for (var j = 0; j < classNames.length; j++) {
   537             if (classNames[j] == cls) {
   538                 elements.push(child);
   539                 break;
   540             }    
   541         }    
   542     }
   543     for (var t=0; t<elements.length; t++) {
   544         //TODO: again, for-in loop over elems seems to break this
   545         SimpleTest.toggle(elements[t]);
   546     }
   547     if (evt)
   548         evt.preventDefault();
   549 };
   551 /**
   552  * Shows the report in the browser
   553 **/
   554 SimpleTest.showReport = function() {
   555     var togglePassed = createEl('a', {'href': '#'}, "Toggle passed checks");
   556     var toggleFailed = createEl('a', {'href': '#'}, "Toggle failed checks");
   557     var toggleTodo = createEl('a',{'href': '#'}, "Toggle todo checks");
   558     togglePassed.onclick = partial(SimpleTest.toggleByClass, 'test_ok');
   559     toggleFailed.onclick = partial(SimpleTest.toggleByClass, 'test_not_ok');
   560     toggleTodo.onclick = partial(SimpleTest.toggleByClass, 'test_todo');
   561     var body = document.body;  // Handles HTML documents
   562     if (!body) {
   563         // Do the XML thing.
   564         body = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml",
   565                                                "body")[0];
   566     }
   567     var firstChild = body.childNodes[0];
   568     var addNode;
   569     if (firstChild) {
   570         addNode = function (el) {
   571             body.insertBefore(el, firstChild);
   572         };
   573     } else {
   574         addNode = function (el) {
   575             body.appendChild(el)
   576         };
   577     }
   578     addNode(togglePassed);
   579     addNode(createEl('span', null, " "));
   580     addNode(toggleFailed);
   581     addNode(createEl('span', null, " "));
   582     addNode(toggleTodo);
   583     addNode(SimpleTest.report());
   584     // Add a separator from the test content.
   585     addNode(createEl('hr'));
   586 };
   588 /**
   589  * Tells SimpleTest to don't finish the test when the document is loaded,
   590  * useful for asynchronous tests.
   591  *
   592  * When SimpleTest.waitForExplicitFinish is called,
   593  * explicit SimpleTest.finish() is required.
   594 **/
   595 SimpleTest.waitForExplicitFinish = function () {
   596     SimpleTest._stopOnLoad = false;
   597 };
   599 /**
   600  * Multiply the timeout the parent runner uses for this test by the
   601  * given factor.
   602  *
   603  * For example, in a test that may take a long time to complete, using
   604  * "SimpleTest.requestLongerTimeout(5)" will give it 5 times as long to
   605  * finish.
   606  */
   607 SimpleTest.requestLongerTimeout = function (factor) {
   608     if (parentRunner) {
   609         parentRunner.requestLongerTimeout(factor);
   610     }
   611 }
   613 /**
   614  * Note that the given range of assertions is to be expected.  When
   615  * this function is not called, 0 assertions are expected.  When only
   616  * one argument is given, that number of assertions are expected.
   617  *
   618  * A test where we expect to have assertions (which should largely be a
   619  * transitional mechanism to get assertion counts down from our current
   620  * situation) can call the SimpleTest.expectAssertions() function, with
   621  * either one or two arguments:  one argument gives an exact number
   622  * expected, and two arguments give a range.  For example, a test might do
   623  * one of the following:
   624  *
   625  *   // Currently triggers two assertions (bug NNNNNN).
   626  *   SimpleTest.expectAssertions(2);
   627  *
   628  *   // Currently triggers one assertion on Mac (bug NNNNNN).
   629  *   if (navigator.platform.indexOf("Mac") == 0) {
   630  *     SimpleTest.expectAssertions(1);
   631  *   }
   632  *
   633  *   // Currently triggers two assertions on all platforms (bug NNNNNN),
   634  *   // but intermittently triggers two additional assertions (bug NNNNNN)
   635  *   // on Windows.
   636  *   if (navigator.platform.indexOf("Win") == 0) {
   637  *     SimpleTest.expectAssertions(2, 4);
   638  *   } else {
   639  *     SimpleTest.expectAssertions(2);
   640  *   }
   641  *
   642  *   // Intermittently triggers up to three assertions (bug NNNNNN).
   643  *   SimpleTest.expectAssertions(0, 3);
   644  */
   645 SimpleTest.expectAssertions = function(min, max) {
   646     if (parentRunner) {
   647         parentRunner.expectAssertions(min, max);
   648     }
   649 }
   651 SimpleTest.waitForFocus_started = false;
   652 SimpleTest.waitForFocus_loaded = false;
   653 SimpleTest.waitForFocus_focused = false;
   654 SimpleTest._pendingWaitForFocusCount = 0;
   656 /**
   657  * If the page is not yet loaded, waits for the load event. In addition, if
   658  * the page is not yet focused, focuses and waits for the window to be
   659  * focused. Calls the callback when completed. If the current page is
   660  * 'about:blank', then the page is assumed to not yet be loaded. Pass true for
   661  * expectBlankPage to not make this assumption if you expect a blank page to
   662  * be present.
   663  *
   664  * targetWindow should be specified if it is different than 'window'. The actual
   665  * focused window may be a descendant of targetWindow.
   666  *
   667  * @param callback
   668  *        function called when load and focus are complete
   669  * @param targetWindow
   670  *        optional window to be loaded and focused, defaults to 'window'
   671  * @param expectBlankPage
   672  *        true if targetWindow.location is 'about:blank'. Defaults to false
   673  */
   674 SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) {
   675     SimpleTest._pendingWaitForFocusCount++;
   676     if (!targetWindow)
   677       targetWindow = window;
   679     SimpleTest.waitForFocus_started = false;
   680     expectBlankPage = !!expectBlankPage;
   682     var childTargetWindow = {};
   683     SpecialPowers.getFocusedElementForWindow(targetWindow, true, childTargetWindow);
   684     childTargetWindow = childTargetWindow.value;
   686     function info(msg) {
   687         SimpleTest.info(msg);
   688     }
   689     function getHref(aWindow) {
   690       return SpecialPowers.getPrivilegedProps(aWindow, 'location.href');
   691     }
   693     function maybeRunTests() {
   694         if (SimpleTest.waitForFocus_loaded &&
   695             SimpleTest.waitForFocus_focused &&
   696             !SimpleTest.waitForFocus_started) {
   697             SimpleTest._pendingWaitForFocusCount--;
   698             SimpleTest.waitForFocus_started = true;
   699             setTimeout(callback, 0, targetWindow);
   700         }
   701     }
   703     function waitForEvent(event) {
   704         try {
   705             // Check to make sure that this isn't a load event for a blank or
   706             // non-blank page that wasn't desired.
   707             if (event.type == "load" && (expectBlankPage != (event.target.location == "about:blank")))
   708                 return;
   710             SimpleTest["waitForFocus_" + event.type + "ed"] = true;
   711             var win = (event.type == "load") ? targetWindow : childTargetWindow;
   712             win.removeEventListener(event.type, waitForEvent, true);
   713             maybeRunTests();
   714         } catch (e) {
   715             SimpleTest.ok(false, "Exception caught in waitForEvent: " + e.message +
   716                 ", at: " + e.fileName + " (" + e.lineNumber + ")");
   717         }
   718     }
   720     // If the current document is about:blank and we are not expecting a blank
   721     // page (or vice versa), and the document has not yet loaded, wait for the
   722     // page to load. A common situation is to wait for a newly opened window
   723     // to load its content, and we want to skip over any intermediate blank
   724     // pages that load. This issue is described in bug 554873.
   725     SimpleTest.waitForFocus_loaded =
   726         expectBlankPage ?
   727             getHref(targetWindow) == "about:blank" :
   728             getHref(targetWindow) != "about:blank" && targetWindow.document.readyState == "complete";
   729     if (!SimpleTest.waitForFocus_loaded) {
   730         info("must wait for load");
   731         targetWindow.addEventListener("load", waitForEvent, true);
   732     }
   734     // Check if the desired window is already focused.
   735     var focusedChildWindow = { };
   736     if (SpecialPowers.activeWindow()) {
   737         SpecialPowers.getFocusedElementForWindow(SpecialPowers.activeWindow(), true, focusedChildWindow);
   738         focusedChildWindow = focusedChildWindow.value;
   739     }
   741     // If this is a child frame, ensure that the frame is focused.
   742     SimpleTest.waitForFocus_focused = (focusedChildWindow == childTargetWindow);
   743     if (SimpleTest.waitForFocus_focused) {
   744         // If the frame is already focused and loaded, call the callback directly.
   745         maybeRunTests();
   746     }
   747     else {
   748         info("must wait for focus");
   749         childTargetWindow.addEventListener("focus", waitForEvent, true);
   750         SpecialPowers.focus(childTargetWindow);
   751     }
   752 };
   754 SimpleTest.waitForClipboard_polls = 0;
   756 /*
   757  * Polls the clipboard waiting for the expected value. A known value different than
   758  * the expected value is put on the clipboard first (and also polled for) so we
   759  * can be sure the value we get isn't just the expected value because it was already
   760  * on the clipboard. This only uses the global clipboard and only for text/unicode
   761  * values.
   762  *
   763  * @param aExpectedStringOrValidatorFn
   764  *        The string value that is expected to be on the clipboard or a
   765  *        validator function getting cripboard data and returning a bool.
   766  * @param aSetupFn
   767  *        A function responsible for setting the clipboard to the expected value,
   768  *        called after the known value setting succeeds.
   769  * @param aSuccessFn
   770  *        A function called when the expected value is found on the clipboard.
   771  * @param aFailureFn
   772  *        A function called if the expected value isn't found on the clipboard
   773  *        within 5s. It can also be called if the known value can't be found.
   774  * @param aFlavor [optional] The flavor to look for.  Defaults to "text/unicode".
   775  */
   776 SimpleTest.__waitForClipboardMonotonicCounter = 0;
   777 SimpleTest.__defineGetter__("_waitForClipboardMonotonicCounter", function () {
   778   return SimpleTest.__waitForClipboardMonotonicCounter++;
   779 });
   780 SimpleTest.waitForClipboard = function(aExpectedStringOrValidatorFn, aSetupFn,
   781                                        aSuccessFn, aFailureFn, aFlavor) {
   782     var requestedFlavor = aFlavor || "text/unicode";
   784     // Build a default validator function for common string input.
   785     var inputValidatorFn = typeof(aExpectedStringOrValidatorFn) == "string"
   786         ? function(aData) { return aData == aExpectedStringOrValidatorFn; }
   787         : aExpectedStringOrValidatorFn;
   789     // reset for the next use
   790     function reset() {
   791         SimpleTest.waitForClipboard_polls = 0;
   792     }
   794     function wait(validatorFn, successFn, failureFn, flavor) {
   795         if (++SimpleTest.waitForClipboard_polls > 50) {
   796             // Log the failure.
   797             SimpleTest.ok(false, "Timed out while polling clipboard for pasted data.");
   798             reset();
   799             failureFn();
   800             return;
   801         }
   803         var data = SpecialPowers.getClipboardData(flavor);
   805         if (validatorFn(data)) {
   806             // Don't show the success message when waiting for preExpectedVal
   807             if (preExpectedVal)
   808                 preExpectedVal = null;
   809             else
   810                 SimpleTest.ok(true, "Clipboard has the correct value");
   811             reset();
   812             successFn();
   813         } else {
   814             setTimeout(function() { return wait(validatorFn, successFn, failureFn, flavor); }, 100);
   815         }
   816     }
   818     // First we wait for a known value different from the expected one.
   819     var preExpectedVal = SimpleTest._waitForClipboardMonotonicCounter +
   820                          "-waitForClipboard-known-value";
   821     SpecialPowers.clipboardCopyString(preExpectedVal);
   822     wait(function(aData) { return aData  == preExpectedVal; },
   823          function() {
   824            // Call the original setup fn
   825            aSetupFn();
   826            wait(inputValidatorFn, aSuccessFn, aFailureFn, requestedFlavor);
   827          }, aFailureFn, "text/unicode");
   828 }
   830 /**
   831  * Executes a function shortly after the call, but lets the caller continue
   832  * working (or finish).
   833  */
   834 SimpleTest.executeSoon = function(aFunc) {
   835     if ("SpecialPowers" in window) {
   836         return SpecialPowers.executeSoon(aFunc, window);
   837     }
   838     setTimeout(aFunc, 0);
   839     return null;		// Avoid warning.
   840 };
   842 SimpleTest.registerCleanupFunction = function(aFunc) {
   843     SimpleTest._cleanupFunctions.push(aFunc);
   844 };
   846 /**
   847  * Finishes the tests. This is automatically called, except when
   848  * SimpleTest.waitForExplicitFinish() has been invoked.
   849 **/
   850 SimpleTest.finish = function() {
   851     var Task = SpecialPowers.Cu.import("resource://gre/modules/Task.jsm").Task;
   853     if (SimpleTest._alreadyFinished) {
   854         SimpleTest.ok(false, "[SimpleTest.finish()] this test already called finish!");
   855     }
   857     SimpleTest._alreadyFinished = true;
   859     Task.spawn(function*() {
   860         // Execute all of our cleanup functions.
   861         var func;
   862         while ((func = SimpleTest._cleanupFunctions.pop())) {
   863           try {
   864             yield func();
   865           }
   866           catch (ex) {
   867             SimpleTest.ok(false, "Cleanup function threw exception: " + ex);
   868           }
   869         }
   871         if (SpecialPowers.DOMWindowUtils.isTestControllingRefreshes) {
   872             SimpleTest.ok(false, "test left refresh driver under test control");
   873             SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
   874         }
   875         if (SimpleTest._expectingUncaughtException) {
   876             SimpleTest.ok(false, "expectUncaughtException was called but no uncaught exception was detected!");
   877         }
   878         if (SimpleTest._pendingWaitForFocusCount != 0) {
   879             SimpleTest.is(SimpleTest._pendingWaitForFocusCount, 0,
   880                           "[SimpleTest.finish()] waitForFocus() was called a "
   881                           + "different number of times from the number of "
   882                           + "callbacks run.  Maybe the test terminated "
   883                           + "prematurely -- be sure to use "
   884                           + "SimpleTest.waitForExplicitFinish().");
   885         }
   886         if (SimpleTest._tests.length == 0) {
   887             SimpleTest.ok(false, "[SimpleTest.finish()] No checks actually run. "
   888                                + "(You need to call ok(), is(), or similar "
   889                                + "functions at least once.  Make sure you use "
   890                                + "SimpleTest.waitForExplicitFinish() if you need "
   891                                + "it.)");
   892         }
   894         if (parentRunner) {
   895             /* We're running in an iframe, and the parent has a TestRunner */
   896             parentRunner.testFinished(SimpleTest._tests);
   897         } else {
   898             SpecialPowers.flushAllAppsLaunchable();
   899             SpecialPowers.flushPermissions(function () {
   900               SpecialPowers.flushPrefEnv(function() {
   901                 SimpleTest.showReport();
   902               });
   903             });
   904         }
   905     });
   906 };
   908 /**
   909  * Monitor console output from now until endMonitorConsole is called.
   910  *
   911  * Expect to receive all console messages described by the elements of
   912  * |msgs|, an array, in the order listed in |msgs|; each element is an
   913  * object which may have any number of the following properties:
   914  *   message, errorMessage, sourceName, sourceLine, category:
   915  *     string or regexp
   916  *   lineNumber, columnNumber: number
   917  *   isScriptError, isWarning, isException, isStrict: boolean
   918  * Strings, numbers, and booleans must compare equal to the named
   919  * property of the Nth console message.  Regexps must match.  Any
   920  * fields present in the message but not in the pattern object are ignored.
   921  *
   922  * In addition to the above properties, elements in |msgs| may have a |forbid|
   923  * boolean property.  When |forbid| is true, a failure is logged each time a
   924  * matching message is received.
   925  *
   926  * If |forbidUnexpectedMsgs| is true, then the messages received in the console
   927  * must exactly match the non-forbidden messages in |msgs|; for each received
   928  * message not described by the next element in |msgs|, a failure is logged.  If
   929  * false, then other non-forbidden messages are ignored, but all expected
   930  * messages must still be received.
   931  *
   932  * After endMonitorConsole is called, |continuation| will be called
   933  * asynchronously.  (Normally, you will want to pass |SimpleTest.finish| here.)
   934  *
   935  * It is incorrect to use this function in a test which has not called
   936  * SimpleTest.waitForExplicitFinish.
   937  */
   938 SimpleTest.monitorConsole = function (continuation, msgs, forbidUnexpectedMsgs) {
   939   if (SimpleTest._stopOnLoad) {
   940     ok(false, "Console monitoring requires use of waitForExplicitFinish.");
   941   }
   943   function msgMatches(msg, pat) {
   944     for (k in pat) {
   945       if (!(k in msg)) {
   946         return false;
   947       }
   948       if (pat[k] instanceof RegExp && typeof(msg[k]) === 'string') {
   949         if (!pat[k].test(msg[k])) {
   950           return false;
   951         }
   952       } else if (msg[k] !== pat[k]) {
   953         return false;
   954       }
   955     }
   956     return true;
   957   }
   959   var forbiddenMsgs = [];
   960   var i = 0;
   961   while (i < msgs.length) {
   962     var pat = msgs[i];
   963     if ("forbid" in pat) {
   964       var forbid = pat.forbid;
   965       delete pat.forbid;
   966       if (forbid) {
   967         forbiddenMsgs.push(pat);
   968         msgs.splice(i, 1);
   969         continue;
   970       }
   971     }
   972     i++;
   973   }
   975   var counter = 0;
   976   function listener(msg) {
   977     if (msg.message === "SENTINEL" && !msg.isScriptError) {
   978       is(counter, msgs.length, "monitorConsole | number of messages");
   979       SimpleTest.executeSoon(continuation);
   980       return;
   981     }
   982     for (var pat of forbiddenMsgs) {
   983       if (msgMatches(msg, pat)) {
   984         ok(false, "monitorConsole | observed forbidden message " +
   985                   JSON.stringify(msg));
   986         return;
   987       }
   988     }
   989     if (counter >= msgs.length) {
   990       var str = "monitorConsole | extra message | " + JSON.stringify(msg);
   991       if (forbidUnexpectedMsgs) {
   992         ok(false, str);
   993       } else {
   994         info(str);
   995       }
   996       return;
   997     }
   998     var matches = msgMatches(msg, msgs[counter]);
   999     if (forbidUnexpectedMsgs) {
  1000       ok(matches, "monitorConsole | [" + counter + "] must match " +
  1001                   JSON.stringify(msg));
  1002     } else {
  1003       info("monitorConsole | [" + counter + "] " +
  1004            (matches ? "matched " : "did not match ") + JSON.stringify(msg));
  1006     if (matches)
  1007       counter++;
  1009   SpecialPowers.registerConsoleListener(listener);
  1010 };
  1012 /**
  1013  * Stop monitoring console output.
  1014  */
  1015 SimpleTest.endMonitorConsole = function () {
  1016   SpecialPowers.postConsoleSentinel();
  1017 };
  1019 /**
  1020  * Run |testfn| synchronously, and monitor its console output.
  1022  * |msgs| is handled as described above for monitorConsole.
  1024  * After |testfn| returns, console monitoring will stop, and
  1025  * |continuation| will be called asynchronously.
  1026  */
  1027 SimpleTest.expectConsoleMessages = function (testfn, msgs, continuation) {
  1028   SimpleTest.monitorConsole(continuation, msgs);
  1029   testfn();
  1030   SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
  1031 };
  1033 /**
  1034  * Wrapper around |expectConsoleMessages| for the case where the test has
  1035  * only one |testfn| to run.
  1036  */
  1037 SimpleTest.runTestExpectingConsoleMessages = function(testfn, msgs) {
  1038   SimpleTest.waitForExplicitFinish();
  1039   SimpleTest.expectConsoleMessages(testfn, msgs, SimpleTest.finish);
  1040 };
  1042 /**
  1043  * Indicates to the test framework that the current test expects one or
  1044  * more crashes (from plugins or IPC documents), and that the minidumps from
  1045  * those crashes should be removed.
  1046  */
  1047 SimpleTest.expectChildProcessCrash = function () {
  1048     if (parentRunner) {
  1049         parentRunner.expectChildProcessCrash();
  1051 };
  1053 /**
  1054  * Indicates to the test framework that the next uncaught exception during
  1055  * the test is expected, and should not cause a test failure.
  1056  */
  1057 SimpleTest.expectUncaughtException = function (aExpecting) {
  1058     SimpleTest._expectingUncaughtException = aExpecting === void 0 || !!aExpecting;
  1059 };
  1061 /**
  1062  * Returns whether the test has indicated that it expects an uncaught exception
  1063  * to occur.
  1064  */
  1065 SimpleTest.isExpectingUncaughtException = function () {
  1066     return SimpleTest._expectingUncaughtException;
  1067 };
  1069 /**
  1070  * Indicates to the test framework that all of the uncaught exceptions
  1071  * during the test are known problems that should be fixed in the future,
  1072  * but which should not cause the test to fail currently.
  1073  */
  1074 SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) {
  1075     SimpleTest._ignoringAllUncaughtExceptions = aIgnoring === void 0 || !!aIgnoring;
  1076 };
  1078 /**
  1079  * Returns whether the test has indicated that all uncaught exceptions should be
  1080  * ignored.
  1081  */
  1082 SimpleTest.isIgnoringAllUncaughtExceptions = function () {
  1083     return SimpleTest._ignoringAllUncaughtExceptions;
  1084 };
  1086 /**
  1087  * Resets any state this SimpleTest object has.  This is important for
  1088  * browser chrome mochitests, which reuse the same SimpleTest object
  1089  * across a run.
  1090  */
  1091 SimpleTest.reset = function () {
  1092     SimpleTest._ignoringAllUncaughtExceptions = false;
  1093     SimpleTest._expectingUncaughtException = false;
  1094     SimpleTest._bufferedMessages = [];
  1095 };
  1097 if (isPrimaryTestWindow) {
  1098     addLoadEvent(function() {
  1099         if (SimpleTest._stopOnLoad) {
  1100             SimpleTest.finish();
  1102     });
  1105 //  --------------- Test.Builder/Test.More isDeeply() -----------------
  1108 SimpleTest.DNE = {dne: 'Does not exist'};
  1109 SimpleTest.LF = "\r\n";
  1110 SimpleTest._isRef = function (object) {
  1111     var type = typeof(object);
  1112     return type == 'object' || type == 'function';
  1113 };
  1116 SimpleTest._deepCheck = function (e1, e2, stack, seen) {
  1117     var ok = false;
  1118     // Either they're both references or both not.
  1119     var sameRef = !(!SimpleTest._isRef(e1) ^ !SimpleTest._isRef(e2));
  1120     if (e1 == null && e2 == null) {
  1121         ok = true;
  1122     } else if (e1 != null ^ e2 != null) {
  1123         ok = false;
  1124     } else if (e1 == SimpleTest.DNE ^ e2 == SimpleTest.DNE) {
  1125         ok = false;
  1126     } else if (sameRef && e1 == e2) {
  1127         // Handles primitives and any variables that reference the same
  1128         // object, including functions.
  1129         ok = true;
  1130     } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) {
  1131         ok = SimpleTest._eqArray(e1, e2, stack, seen);
  1132     } else if (typeof e1 == "object" && typeof e2 == "object") {
  1133         ok = SimpleTest._eqAssoc(e1, e2, stack, seen);
  1134     } else if (typeof e1 == "number" && typeof e2 == "number"
  1135                && isNaN(e1) && isNaN(e2)) {
  1136         ok = true;
  1137     } else {
  1138         // If we get here, they're not the same (function references must
  1139         // always simply reference the same function).
  1140         stack.push({ vals: [e1, e2] });
  1141         ok = false;
  1143     return ok;
  1144 };
  1146 SimpleTest._eqArray = function (a1, a2, stack, seen) {
  1147     // Return if they're the same object.
  1148     if (a1 == a2) return true;
  1150     // JavaScript objects have no unique identifiers, so we have to store
  1151     // references to them all in an array, and then compare the references
  1152     // directly. It's slow, but probably won't be much of an issue in
  1153     // practice. Start by making a local copy of the array to as to avoid
  1154     // confusing a reference seen more than once (such as [a, a]) for a
  1155     // circular reference.
  1156     for (var j = 0; j < seen.length; j++) {
  1157         if (seen[j][0] == a1) {
  1158             return seen[j][1] == a2;
  1162     // If we get here, we haven't seen a1 before, so store it with reference
  1163     // to a2.
  1164     seen.push([ a1, a2 ]);
  1166     var ok = true;
  1167     // Only examines enumerable attributes. Only works for numeric arrays!
  1168     // Associative arrays return 0. So call _eqAssoc() for them, instead.
  1169     var max = a1.length > a2.length ? a1.length : a2.length;
  1170     if (max == 0) return SimpleTest._eqAssoc(a1, a2, stack, seen);
  1171     for (var i = 0; i < max; i++) {
  1172         var e1 = i > a1.length - 1 ? SimpleTest.DNE : a1[i];
  1173         var e2 = i > a2.length - 1 ? SimpleTest.DNE : a2[i];
  1174         stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
  1175         ok = SimpleTest._deepCheck(e1, e2, stack, seen);
  1176         if (ok) {
  1177             stack.pop();
  1178         } else {
  1179             break;
  1182     return ok;
  1183 };
  1185 SimpleTest._eqAssoc = function (o1, o2, stack, seen) {
  1186     // Return if they're the same object.
  1187     if (o1 == o2) return true;
  1189     // JavaScript objects have no unique identifiers, so we have to store
  1190     // references to them all in an array, and then compare the references
  1191     // directly. It's slow, but probably won't be much of an issue in
  1192     // practice. Start by making a local copy of the array to as to avoid
  1193     // confusing a reference seen more than once (such as [a, a]) for a
  1194     // circular reference.
  1195     seen = seen.slice(0);
  1196     for (var j = 0; j < seen.length; j++) {
  1197         if (seen[j][0] == o1) {
  1198             return seen[j][1] == o2;
  1202     // If we get here, we haven't seen o1 before, so store it with reference
  1203     // to o2.
  1204     seen.push([ o1, o2 ]);
  1206     // They should be of the same class.
  1208     var ok = true;
  1209     // Only examines enumerable attributes.
  1210     var o1Size = 0; for (var i in o1) o1Size++;
  1211     var o2Size = 0; for (var i in o2) o2Size++;
  1212     var bigger = o1Size > o2Size ? o1 : o2;
  1213     for (var i in bigger) {
  1214         var e1 = o1[i] == undefined ? SimpleTest.DNE : o1[i];
  1215         var e2 = o2[i] == undefined ? SimpleTest.DNE : o2[i];
  1216         stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
  1217         ok = SimpleTest._deepCheck(e1, e2, stack, seen)
  1218         if (ok) {
  1219             stack.pop();
  1220         } else {
  1221             break;
  1224     return ok;
  1225 };
  1227 SimpleTest._formatStack = function (stack) {
  1228     var variable = '$Foo';
  1229     for (var i = 0; i < stack.length; i++) {
  1230         var entry = stack[i];
  1231         var type = entry['type'];
  1232         var idx = entry['idx'];
  1233         if (idx != null) {
  1234             if (/^\d+$/.test(idx)) {
  1235                 // Numeric array index.
  1236                 variable += '[' + idx + ']';
  1237             } else {
  1238                 // Associative array index.
  1239                 idx = idx.replace("'", "\\'");
  1240                 variable += "['" + idx + "']";
  1245     var vals = stack[stack.length-1]['vals'].slice(0, 2);
  1246     var vars = [
  1247         variable.replace('$Foo',     'got'),
  1248         variable.replace('$Foo',     'expected')
  1249     ];
  1251     var out = "Structures begin differing at:" + SimpleTest.LF;
  1252     for (var i = 0; i < vals.length; i++) {
  1253         var val = vals[i];
  1254         if (val == null) {
  1255             val = 'undefined';
  1256         } else {
  1257             val == SimpleTest.DNE ? "Does not exist" : "'" + val + "'";
  1261     out += vars[0] + ' = ' + vals[0] + SimpleTest.LF;
  1262     out += vars[1] + ' = ' + vals[1] + SimpleTest.LF;
  1264     return '    ' + out;
  1265 };
  1268 SimpleTest.isDeeply = function (it, as, name) {
  1269     var ok;
  1270     // ^ is the XOR operator.
  1271     if (SimpleTest._isRef(it) ^ SimpleTest._isRef(as)) {
  1272         // One's a reference, one isn't.
  1273         ok = false;
  1274     } else if (!SimpleTest._isRef(it) && !SimpleTest._isRef(as)) {
  1275         // Neither is an object.
  1276         ok = SimpleTest.is(it, as, name);
  1277     } else {
  1278         // We have two objects. Do a deep comparison.
  1279         var stack = [], seen = [];
  1280         if ( SimpleTest._deepCheck(it, as, stack, seen)) {
  1281             ok = SimpleTest.ok(true, name);
  1282         } else {
  1283             ok = SimpleTest.ok(false, name, SimpleTest._formatStack(stack));
  1286     return ok;
  1287 };
  1289 SimpleTest.typeOf = function (object) {
  1290     var c = Object.prototype.toString.apply(object);
  1291     var name = c.substring(8, c.length - 1);
  1292     if (name != 'Object') return name;
  1293     // It may be a non-core class. Try to extract the class name from
  1294     // the constructor function. This may not work in all implementations.
  1295     if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) {
  1296         return RegExp.$1;
  1298     // No idea. :-(
  1299     return name;
  1300 };
  1302 SimpleTest.isa = function (object, clas) {
  1303     return SimpleTest.typeOf(object) == clas;
  1304 };
  1306 // Global symbols:
  1307 var ok = SimpleTest.ok;
  1308 var is = SimpleTest.is;
  1309 var isfuzzy = SimpleTest.isfuzzy;
  1310 var isnot = SimpleTest.isnot;
  1311 var ise = SimpleTest.ise;
  1312 var todo = SimpleTest.todo;
  1313 var todo_is = SimpleTest.todo_is;
  1314 var todo_isnot = SimpleTest.todo_isnot;
  1315 var isDeeply = SimpleTest.isDeeply;
  1316 var info = SimpleTest.info;
  1318 var gOldOnError = window.onerror;
  1319 window.onerror = function simpletestOnerror(errorMsg, url, lineNumber) {
  1320     // Log the message.
  1321     // XXX Chrome mochitests sometimes trigger this window.onerror handler,
  1322     // but there are a number of uncaught JS exceptions from those tests.
  1323     // For now, for tests that self identify as having unintentional uncaught
  1324     // exceptions, just dump it so that the error is visible but doesn't cause
  1325     // a test failure.  See bug 652494.
  1326     var isExpected = !!SimpleTest._expectingUncaughtException;
  1327     var message = (isExpected ? "expected " : "") + "uncaught exception";
  1328     var error = errorMsg + " at " + url + ":" + lineNumber;
  1329     if (!SimpleTest._ignoringAllUncaughtExceptions) {
  1330         SimpleTest.ok(isExpected, message, error);
  1331         SimpleTest._expectingUncaughtException = false;
  1332     } else {
  1333         SimpleTest.todo(false, message + ": " + error);
  1335     // There is no Components.stack.caller to log. (See bug 511888.)
  1337     // Call previous handler.
  1338     if (gOldOnError) {
  1339         try {
  1340             // Ignore return value: always run default handler.
  1341             gOldOnError(errorMsg, url, lineNumber);
  1342         } catch (e) {
  1343             // Log the error.
  1344             SimpleTest.info("Exception thrown by gOldOnError(): " + e);
  1345             // Log its stack.
  1346             if (e.stack) {
  1347                 SimpleTest.info("JavaScript error stack:\n" + e.stack);
  1352     if (!SimpleTest._stopOnLoad && !isExpected) {
  1353         // Need to finish() manually here, yet let the test actually end first.
  1354         SimpleTest.executeSoon(SimpleTest.finish);
  1356 };

mercurial