testing/mochitest/tests/SimpleTest/SimpleTest.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1356 @@
     1.4 +/* -*- js-indent-level: 4; tab-width: 4; indent-tabs-mode: nil -*- */
     1.5 +/* vim:set ts=4 sw=4 sts=4 et: */
     1.6 +/**
     1.7 + * SimpleTest, a partial Test.Simple/Test.More API compatible test library.
     1.8 + *
     1.9 + * Why?
    1.10 + *
    1.11 + * Test.Simple doesn't work on IE < 6.
    1.12 + * TODO:
    1.13 + *  * Support the Test.Simple API used by MochiKit, to be able to test MochiKit
    1.14 + * itself against IE 5.5
    1.15 + *
    1.16 + * NOTE: Pay attention to cross-browser compatibility in this file. For
    1.17 + * instance, do not use const or JS > 1.5 features which are not yet
    1.18 + * implemented everywhere.
    1.19 + *
    1.20 +**/
    1.21 +
    1.22 +var SimpleTest = { };
    1.23 +var parentRunner = null;
    1.24 +
    1.25 +// In normal test runs, the window that has a TestRunner in its parent is
    1.26 +// the primary window.  In single test runs, if there is no parent and there
    1.27 +// is no opener then it is the primary window.
    1.28 +var isSingleTestRun = (parent == window && !opener)
    1.29 +var isPrimaryTestWindow = !!parent.TestRunner || isSingleTestRun;
    1.30 +
    1.31 +// Finds the TestRunner for this test run and the SpecialPowers object (in
    1.32 +// case it is not defined) from a parent/opener window.
    1.33 +//
    1.34 +// Finding the SpecialPowers object is needed when we have ChromePowers in
    1.35 +// harness.xul and we need SpecialPowers in the iframe, and also for tests
    1.36 +// like test_focus.xul where we open a window which opens another window which
    1.37 +// includes SimpleTest.js.
    1.38 +(function() {
    1.39 +    function ancestor(w) {
    1.40 +        return w.parent != w ? w.parent : w.opener;
    1.41 +    }
    1.42 +
    1.43 +    var w = ancestor(window);
    1.44 +    while (w && (!parentRunner || !window.SpecialPowers)) {
    1.45 +        if (!parentRunner) {
    1.46 +            parentRunner = w.TestRunner;
    1.47 +            if (!parentRunner && w.wrappedJSObject) {
    1.48 +                parentRunner = w.wrappedJSObject.TestRunner;
    1.49 +            }
    1.50 +        }
    1.51 +        if (!window.SpecialPowers) {
    1.52 +            window.SpecialPowers = w.SpecialPowers;
    1.53 +        }
    1.54 +        w = ancestor(w);
    1.55 +    }
    1.56 +})();
    1.57 +
    1.58 +/* Helper functions pulled out of various MochiKit modules */
    1.59 +if (typeof(repr) == 'undefined') {
    1.60 +    this.repr = function(o) {
    1.61 +        if (typeof(o) == "undefined") {
    1.62 +            return "undefined";
    1.63 +        } else if (o === null) {
    1.64 +            return "null";
    1.65 +        }
    1.66 +        try {
    1.67 +            if (typeof(o.__repr__) == 'function') {
    1.68 +                return o.__repr__();
    1.69 +            } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
    1.70 +                return o.repr();
    1.71 +            }
    1.72 +       } catch (e) {
    1.73 +       }
    1.74 +       try {
    1.75 +            if (typeof(o.NAME) == 'string' && (
    1.76 +                    o.toString == Function.prototype.toString ||
    1.77 +                    o.toString == Object.prototype.toString
    1.78 +                )) {
    1.79 +                return o.NAME;
    1.80 +            }
    1.81 +        } catch (e) {
    1.82 +        }
    1.83 +        try {
    1.84 +            var ostring = (o + "");
    1.85 +        } catch (e) {
    1.86 +            return "[" + typeof(o) + "]";
    1.87 +        }
    1.88 +        if (typeof(o) == "function") {
    1.89 +            o = ostring.replace(/^\s+/, "");
    1.90 +            var idx = o.indexOf("{");
    1.91 +            if (idx != -1) {
    1.92 +                o = o.substr(0, idx) + "{...}";
    1.93 +            }
    1.94 +        }
    1.95 +        return ostring;
    1.96 +    };
    1.97 +} 
    1.98 +
    1.99 +/* This returns a function that applies the previously given parameters.
   1.100 + * This is used by SimpleTest.showReport
   1.101 + */
   1.102 +if (typeof(partial) == 'undefined') {
   1.103 +    this.partial = function(func) {
   1.104 +        var args = [];
   1.105 +        for (var i = 1; i < arguments.length; i++) {
   1.106 +            args.push(arguments[i]);
   1.107 +        }
   1.108 +        return function() {
   1.109 +            if (arguments.length > 0) {
   1.110 +                for (var i = 1; i < arguments.length; i++) {
   1.111 +                    args.push(arguments[i]);
   1.112 +                }
   1.113 +            }
   1.114 +            func(args);
   1.115 +        };
   1.116 +    };
   1.117 +}
   1.118 +
   1.119 +if (typeof(getElement) == 'undefined') {
   1.120 +    this.getElement = function(id) {
   1.121 +        return ((typeof(id) == "string") ?
   1.122 +            document.getElementById(id) : id); 
   1.123 +    };
   1.124 +    this.$ = this.getElement;
   1.125 +}
   1.126 +
   1.127 +SimpleTest._newCallStack = function(path) {
   1.128 +    var rval = function () {
   1.129 +        var callStack = arguments.callee.callStack;
   1.130 +        for (var i = 0; i < callStack.length; i++) {
   1.131 +            if (callStack[i].apply(this, arguments) === false) {
   1.132 +                break;
   1.133 +            }
   1.134 +        }
   1.135 +        try {
   1.136 +            this[path] = null;
   1.137 +        } catch (e) {
   1.138 +            // pass
   1.139 +        }
   1.140 +    };
   1.141 +    rval.callStack = [];
   1.142 +    return rval;
   1.143 +};
   1.144 +
   1.145 +if (typeof(addLoadEvent) == 'undefined') {
   1.146 +    this.addLoadEvent = function(func) {
   1.147 +        var existing = window["onload"];
   1.148 +        var regfunc = existing;
   1.149 +        if (!(typeof(existing) == 'function'
   1.150 +                && typeof(existing.callStack) == "object"
   1.151 +                && existing.callStack !== null)) {
   1.152 +            regfunc = SimpleTest._newCallStack("onload");
   1.153 +            if (typeof(existing) == 'function') {
   1.154 +                regfunc.callStack.push(existing);
   1.155 +            }
   1.156 +            window["onload"] = regfunc;
   1.157 +        }
   1.158 +        regfunc.callStack.push(func);
   1.159 +    };
   1.160 +}
   1.161 +
   1.162 +function createEl(type, attrs, html) {
   1.163 +    //use createElementNS so the xul/xhtml tests have no issues
   1.164 +    var el;
   1.165 +    if (!document.body) {
   1.166 +        el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
   1.167 +    }
   1.168 +    else {
   1.169 +        el = document.createElement(type);
   1.170 +    }
   1.171 +    if (attrs !== null && attrs !== undefined) {
   1.172 +        for (var k in attrs) {
   1.173 +            el.setAttribute(k, attrs[k]);
   1.174 +        }
   1.175 +    }
   1.176 +    if (html !== null && html !== undefined) {
   1.177 +        el.appendChild(document.createTextNode(html));
   1.178 +    }
   1.179 +    return el;
   1.180 +}
   1.181 +
   1.182 +/* lots of tests use this as a helper to get css properties */
   1.183 +if (typeof(computedStyle) == 'undefined') {
   1.184 +    this.computedStyle = function(elem, cssProperty) {
   1.185 +        elem = getElement(elem);
   1.186 +        if (elem.currentStyle) {
   1.187 +            return elem.currentStyle[cssProperty];
   1.188 +        }
   1.189 +        if (typeof(document.defaultView) == 'undefined' || document === null) {
   1.190 +            return undefined;
   1.191 +        }
   1.192 +        var style = document.defaultView.getComputedStyle(elem, null);
   1.193 +        if (typeof(style) == 'undefined' || style === null) {
   1.194 +            return undefined;
   1.195 +        }
   1.196 +        
   1.197 +        var selectorCase = cssProperty.replace(/([A-Z])/g, '-$1'
   1.198 +            ).toLowerCase();
   1.199 +            
   1.200 +        return style.getPropertyValue(selectorCase);
   1.201 +    };
   1.202 +}
   1.203 +
   1.204 +/**
   1.205 + * Check for OOP test plugin
   1.206 +**/
   1.207 +SimpleTest.testPluginIsOOP = function () {
   1.208 +    var testPluginIsOOP = false;
   1.209 +    if (navigator.platform.indexOf("Mac") == 0) {
   1.210 +        if (SpecialPowers.XPCOMABI.match(/x86-/)) {
   1.211 +            try {
   1.212 +                testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386.test.plugin");
   1.213 +            } catch (e) {
   1.214 +                testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386");
   1.215 +            }
   1.216 +        }
   1.217 +        else if (SpecialPowers.XPCOMABI.match(/x86_64-/)) {
   1.218 +            try {
   1.219 +                testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64.test.plugin");
   1.220 +            } catch (e) {
   1.221 +                testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64");
   1.222 +            }
   1.223 +        }
   1.224 +    }
   1.225 +    else {
   1.226 +        testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled");
   1.227 +    }
   1.228 +
   1.229 +    return testPluginIsOOP;
   1.230 +};
   1.231 +
   1.232 +SimpleTest._tests = [];
   1.233 +SimpleTest._stopOnLoad = true;
   1.234 +SimpleTest._cleanupFunctions = [];
   1.235 +
   1.236 +/**
   1.237 + * Something like assert.
   1.238 +**/
   1.239 +SimpleTest.ok = function (condition, name, diag) {
   1.240 +    var test = {'result': !!condition, 'name': name, 'diag': diag};
   1.241 +    SimpleTest._logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
   1.242 +    SimpleTest._tests.push(test);
   1.243 +};
   1.244 +
   1.245 +/**
   1.246 + * Roughly equivalent to ok(a==b, name)
   1.247 +**/
   1.248 +SimpleTest.is = function (a, b, name) {
   1.249 +    var pass = (a == b);
   1.250 +    var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b)
   1.251 +    SimpleTest.ok(pass, name, diag);
   1.252 +};
   1.253 +
   1.254 +SimpleTest.isfuzzy = function (a, b, epsilon, name) {
   1.255 +  var pass = (a > b - epsilon) && (a < b + epsilon);
   1.256 +  var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) + " epsilon: +/- " + repr(epsilon)
   1.257 +  SimpleTest.ok(pass, name, diag);
   1.258 +};
   1.259 +
   1.260 +SimpleTest.isnot = function (a, b, name) {
   1.261 +    var pass = (a != b);
   1.262 +    var diag = pass ? "" : "didn't expect " + repr(a) + ", but got it";
   1.263 +    SimpleTest.ok(pass, name, diag);
   1.264 +};
   1.265 +
   1.266 +/**
   1.267 + * Roughly equivalent to ok(a===b, name)
   1.268 +**/
   1.269 +SimpleTest.ise = function (a, b, name) {
   1.270 +    var pass = (a === b);
   1.271 +    var diag = pass ? "" : "got " + repr(a) + ", strictly expected " + repr(b)
   1.272 +    SimpleTest.ok(pass, name, diag);
   1.273 +};
   1.274 +
   1.275 +/**
   1.276 + * Check that the function call throws an exception.
   1.277 + */
   1.278 +SimpleTest.doesThrow = function(fn, name) {
   1.279 +    var gotException = false;
   1.280 +    try {
   1.281 +      fn();
   1.282 +    } catch (ex) { gotException = true; }
   1.283 +    ok(gotException, name);
   1.284 +};
   1.285 +
   1.286 +//  --------------- Test.Builder/Test.More todo() -----------------
   1.287 +
   1.288 +SimpleTest.todo = function(condition, name, diag) {
   1.289 +    var test = {'result': !!condition, 'name': name, 'diag': diag, todo: true};
   1.290 +    SimpleTest._logResult(test, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL");
   1.291 +    SimpleTest._tests.push(test);
   1.292 +};
   1.293 +
   1.294 +/*
   1.295 + * Returns the absolute URL to a test data file from where tests
   1.296 + * are served. i.e. the file doesn't necessarely exists where tests
   1.297 + * are executed.
   1.298 + * (For b2g and android, mochitest are executed on the device, while
   1.299 + * all mochitest html (and others) files are served from the test runner
   1.300 + * slave)
   1.301 + */
   1.302 +SimpleTest.getTestFileURL = function(path) {
   1.303 +  var lastSlashIdx = path.lastIndexOf("/") + 1;
   1.304 +  var filename = path.substr(lastSlashIdx);
   1.305 +  var location = window.location;
   1.306 +  // Remove mochitest html file name from the path
   1.307 +  var remotePath = location.pathname.replace(/\/[^\/]+?$/,"");
   1.308 +  var url = location.origin +
   1.309 +            remotePath + "/" + path;
   1.310 +  return url;
   1.311 +};
   1.312 +
   1.313 +SimpleTest._getCurrentTestURL = function() {
   1.314 +    return parentRunner && parentRunner.currentTestURL ||
   1.315 +           typeof gTestPath == "string" && gTestPath ||
   1.316 +           "unknown test url";
   1.317 +};
   1.318 +
   1.319 +SimpleTest._forceLogMessageOutput = parentRunner && !parentRunner.quiet;
   1.320 +
   1.321 +/**
   1.322 + * Force all test messages to be displayed.  Only applies for the current test.
   1.323 + */
   1.324 +SimpleTest.requestCompleteLog = function() {
   1.325 +    if (SimpleTest._forceLogMessageOutput)
   1.326 +        return;
   1.327 +
   1.328 +    SimpleTest._forceLogMessageOutput = true;
   1.329 +    SimpleTest.registerCleanupFunction(function() {
   1.330 +        SimpleTest._forceLogMessageOutput = false;
   1.331 +    });
   1.332 +};
   1.333 +
   1.334 +/**
   1.335 + * A circular buffer, managed by _logResult.  We explicitly manage the
   1.336 + * circularness of the buffer, rather than resorting to .shift()/.push()
   1.337 + * because explicit management is much faster.
   1.338 + */
   1.339 +SimpleTest._bufferedMessages = [];
   1.340 +SimpleTest._logResult = (function () {
   1.341 +    var bufferingThreshold = 100;
   1.342 +    var outputIndex = 0;
   1.343 +
   1.344 +    function logResult(test, passString, failString) {
   1.345 +        var url = SimpleTest._getCurrentTestURL();
   1.346 +        var resultString = test.result ? passString : failString;
   1.347 +        var diagnostic = test.name + (test.diag ? " - " + test.diag : "");
   1.348 +        var msg = [resultString, url, diagnostic].join(" | ");
   1.349 +        var isError = !test.result == !test.todo;
   1.350 +
   1.351 +        // Due to JavaScript's name lookup rules, it is important that
   1.352 +        // the second parameter here be named identically to the isError
   1.353 +        // variable declared above.
   1.354 +        function dumpMessage(msg, isError) {
   1.355 +            if (parentRunner) {
   1.356 +                if (isError) {
   1.357 +                    parentRunner.addFailedTest(url);
   1.358 +                    parentRunner.error(msg);
   1.359 +                } else {
   1.360 +                    parentRunner.log(msg);
   1.361 +                }
   1.362 +            } else if (typeof dump === "function") {
   1.363 +                dump(msg + "\n");
   1.364 +            } else {
   1.365 +                // Non-Mozilla browser?  Just do nothing.
   1.366 +            }
   1.367 +        }
   1.368 +
   1.369 +        // Detect when SimpleTest.reset() has been called, so we can
   1.370 +        // reset outputIndex.  We store outputIndex as local state to
   1.371 +        // avoid adding even more state to SimpleTest.
   1.372 +        if (SimpleTest._bufferedMessages.length == 0) {
   1.373 +            outputIndex = 0;
   1.374 +        }
   1.375 +
   1.376 +        // We want to eliminate mundane TEST-PASS/TEST-KNOWN-FAIL
   1.377 +        // output, since some tests produce tens of thousands of of such
   1.378 +        // messages.  These messages can consume a lot of memory to
   1.379 +        // generate and take a significant amount of time to output.
   1.380 +        // However, the reality is that TEST-PASS messages can also be
   1.381 +        // used as a form of logging via constructs like:
   1.382 +        //
   1.383 +        // SimpleTest.ok(true, "some informative message");
   1.384 +        //
   1.385 +        // And eliding the logging can be very confusing when trying to
   1.386 +        // debug test failures.
   1.387 +        //
   1.388 +        // Hence the compromise adopted here: We buffer messages up to
   1.389 +        // some limit and dump the buffer when a test failure happens.
   1.390 +        // This behavior ought to provide enough context for developers
   1.391 +        // looking to understand where in the test things failed.
   1.392 +        if (isError) {
   1.393 +            // Display this message and all the messages we have buffered.
   1.394 +            if (SimpleTest._bufferedMessages.length > 0) {
   1.395 +                dumpMessage("TEST-INFO | dumping last " + SimpleTest._bufferedMessages.length + " message(s)");
   1.396 +                dumpMessage("TEST-INFO | if you need more context, please use SimpleTest.requestCompleteLog() in your test");
   1.397 +
   1.398 +                function dumpBufferedMessage(m) {
   1.399 +                    dumpMessage(m, false);
   1.400 +                }
   1.401 +                // The latest message is just before outputIndex.
   1.402 +                // The earliest message is located at outputIndex.
   1.403 +                var earliest = SimpleTest._bufferedMessages.slice(outputIndex);
   1.404 +                var latest = SimpleTest._bufferedMessages.slice(0, outputIndex);
   1.405 +                earliest.map(dumpBufferedMessage);
   1.406 +                latest.map(dumpBufferedMessage);
   1.407 +
   1.408 +                SimpleTest._bufferedMessages = [];
   1.409 +            }
   1.410 +
   1.411 +            dumpMessage(msg);
   1.412 +            return;
   1.413 +        }
   1.414 +
   1.415 +        var runningSingleTest = ((parentRunner &&
   1.416 +                                  parentRunner._urls.length == 1) ||
   1.417 +                                 isSingleTestRun);
   1.418 +        var shouldLogImmediately = (runningSingleTest ||
   1.419 +                                    SimpleTest._forceLogMessageOutput);
   1.420 +
   1.421 +        if (!shouldLogImmediately) {
   1.422 +            // Buffer the message for possible later output.
   1.423 +            if (SimpleTest._bufferedMessages.length >= bufferingThreshold) {
   1.424 +                if (outputIndex >= bufferingThreshold) {
   1.425 +                    outputIndex = 0;
   1.426 +                }
   1.427 +                SimpleTest._bufferedMessages[outputIndex] = msg;
   1.428 +                outputIndex++;
   1.429 +            } else {
   1.430 +                SimpleTest._bufferedMessages.push(msg);
   1.431 +            }
   1.432 +            return;
   1.433 +        }
   1.434 +
   1.435 +        dumpMessage(msg);
   1.436 +    }
   1.437 +
   1.438 +    return logResult;
   1.439 +})();
   1.440 +
   1.441 +SimpleTest.info = function(name, message) {
   1.442 +    SimpleTest._logResult({result:true, name:name, diag:message}, "TEST-INFO");
   1.443 +};
   1.444 +
   1.445 +/**
   1.446 + * Copies of is and isnot with the call to ok replaced by a call to todo.
   1.447 +**/
   1.448 +
   1.449 +SimpleTest.todo_is = function (a, b, name) {
   1.450 +    var pass = (a == b);
   1.451 +    var diag = pass ? repr(a) + " should equal " + repr(b)
   1.452 +                    : "got " + repr(a) + ", expected " + repr(b);
   1.453 +    SimpleTest.todo(pass, name, diag);
   1.454 +};
   1.455 +
   1.456 +SimpleTest.todo_isnot = function (a, b, name) {
   1.457 +    var pass = (a != b);
   1.458 +    var diag = pass ? repr(a) + " should not equal " + repr(b)
   1.459 +                    : "didn't expect " + repr(a) + ", but got it";
   1.460 +    SimpleTest.todo(pass, name, diag);
   1.461 +};
   1.462 +
   1.463 +
   1.464 +/**
   1.465 + * Makes a test report, returns it as a DIV element.
   1.466 +**/
   1.467 +SimpleTest.report = function () {
   1.468 +    var passed = 0;
   1.469 +    var failed = 0;
   1.470 +    var todo = 0;
   1.471 +
   1.472 +    var tallyAndCreateDiv = function (test) {
   1.473 +            var cls, msg, div;
   1.474 +            var diag = test.diag ? " - " + test.diag : "";
   1.475 +            if (test.todo && !test.result) {
   1.476 +                todo++;
   1.477 +                cls = "test_todo";
   1.478 +                msg = "todo | " + test.name + diag;
   1.479 +            } else if (test.result && !test.todo) {
   1.480 +                passed++;
   1.481 +                cls = "test_ok";
   1.482 +                msg = "passed | " + test.name + diag;
   1.483 +            } else {
   1.484 +                failed++;
   1.485 +                cls = "test_not_ok";
   1.486 +                msg = "failed | " + test.name + diag;
   1.487 +            }
   1.488 +          div = createEl('div', {'class': cls}, msg);
   1.489 +          return div;
   1.490 +        };
   1.491 +    var results = [];
   1.492 +    for (var d=0; d<SimpleTest._tests.length; d++) {
   1.493 +        results.push(tallyAndCreateDiv(SimpleTest._tests[d]));
   1.494 +    }
   1.495 +
   1.496 +    var summary_class = failed != 0 ? 'some_fail' :
   1.497 +                          passed == 0 ? 'todo_only' : 'all_pass';
   1.498 +
   1.499 +    var div1 = createEl('div', {'class': 'tests_report'});
   1.500 +    var div2 = createEl('div', {'class': 'tests_summary ' + summary_class});
   1.501 +    var div3 = createEl('div', {'class': 'tests_passed'}, 'Passed: ' + passed);
   1.502 +    var div4 = createEl('div', {'class': 'tests_failed'}, 'Failed: ' + failed);
   1.503 +    var div5 = createEl('div', {'class': 'tests_todo'}, 'Todo: ' + todo);
   1.504 +    div2.appendChild(div3);
   1.505 +    div2.appendChild(div4);
   1.506 +    div2.appendChild(div5);
   1.507 +    div1.appendChild(div2);
   1.508 +    for (var t=0; t<results.length; t++) {
   1.509 +        //iterate in order
   1.510 +        div1.appendChild(results[t]);
   1.511 +    }
   1.512 +    return div1;
   1.513 +};
   1.514 +
   1.515 +/**
   1.516 + * Toggle element visibility
   1.517 +**/
   1.518 +SimpleTest.toggle = function(el) {
   1.519 +    if (computedStyle(el, 'display') == 'block') {
   1.520 +        el.style.display = 'none';
   1.521 +    } else {
   1.522 +        el.style.display = 'block';
   1.523 +    }
   1.524 +};
   1.525 +
   1.526 +/**
   1.527 + * Toggle visibility for divs with a specific class.
   1.528 +**/
   1.529 +SimpleTest.toggleByClass = function (cls, evt) {
   1.530 +    var children = document.getElementsByTagName('div');
   1.531 +    var elements = [];
   1.532 +    for (var i=0; i<children.length; i++) {
   1.533 +        var child = children[i];
   1.534 +        var clsName = child.className;
   1.535 +        if (!clsName) {
   1.536 +            continue;
   1.537 +        }    
   1.538 +        var classNames = clsName.split(' ');
   1.539 +        for (var j = 0; j < classNames.length; j++) {
   1.540 +            if (classNames[j] == cls) {
   1.541 +                elements.push(child);
   1.542 +                break;
   1.543 +            }    
   1.544 +        }    
   1.545 +    }
   1.546 +    for (var t=0; t<elements.length; t++) {
   1.547 +        //TODO: again, for-in loop over elems seems to break this
   1.548 +        SimpleTest.toggle(elements[t]);
   1.549 +    }
   1.550 +    if (evt)
   1.551 +        evt.preventDefault();
   1.552 +};
   1.553 +
   1.554 +/**
   1.555 + * Shows the report in the browser
   1.556 +**/
   1.557 +SimpleTest.showReport = function() {
   1.558 +    var togglePassed = createEl('a', {'href': '#'}, "Toggle passed checks");
   1.559 +    var toggleFailed = createEl('a', {'href': '#'}, "Toggle failed checks");
   1.560 +    var toggleTodo = createEl('a',{'href': '#'}, "Toggle todo checks");
   1.561 +    togglePassed.onclick = partial(SimpleTest.toggleByClass, 'test_ok');
   1.562 +    toggleFailed.onclick = partial(SimpleTest.toggleByClass, 'test_not_ok');
   1.563 +    toggleTodo.onclick = partial(SimpleTest.toggleByClass, 'test_todo');
   1.564 +    var body = document.body;  // Handles HTML documents
   1.565 +    if (!body) {
   1.566 +        // Do the XML thing.
   1.567 +        body = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml",
   1.568 +                                               "body")[0];
   1.569 +    }
   1.570 +    var firstChild = body.childNodes[0];
   1.571 +    var addNode;
   1.572 +    if (firstChild) {
   1.573 +        addNode = function (el) {
   1.574 +            body.insertBefore(el, firstChild);
   1.575 +        };
   1.576 +    } else {
   1.577 +        addNode = function (el) {
   1.578 +            body.appendChild(el)
   1.579 +        };
   1.580 +    }
   1.581 +    addNode(togglePassed);
   1.582 +    addNode(createEl('span', null, " "));
   1.583 +    addNode(toggleFailed);
   1.584 +    addNode(createEl('span', null, " "));
   1.585 +    addNode(toggleTodo);
   1.586 +    addNode(SimpleTest.report());
   1.587 +    // Add a separator from the test content.
   1.588 +    addNode(createEl('hr'));
   1.589 +};
   1.590 +
   1.591 +/**
   1.592 + * Tells SimpleTest to don't finish the test when the document is loaded,
   1.593 + * useful for asynchronous tests.
   1.594 + *
   1.595 + * When SimpleTest.waitForExplicitFinish is called,
   1.596 + * explicit SimpleTest.finish() is required.
   1.597 +**/
   1.598 +SimpleTest.waitForExplicitFinish = function () {
   1.599 +    SimpleTest._stopOnLoad = false;
   1.600 +};
   1.601 +
   1.602 +/**
   1.603 + * Multiply the timeout the parent runner uses for this test by the
   1.604 + * given factor.
   1.605 + *
   1.606 + * For example, in a test that may take a long time to complete, using
   1.607 + * "SimpleTest.requestLongerTimeout(5)" will give it 5 times as long to
   1.608 + * finish.
   1.609 + */
   1.610 +SimpleTest.requestLongerTimeout = function (factor) {
   1.611 +    if (parentRunner) {
   1.612 +        parentRunner.requestLongerTimeout(factor);
   1.613 +    }
   1.614 +}
   1.615 +
   1.616 +/**
   1.617 + * Note that the given range of assertions is to be expected.  When
   1.618 + * this function is not called, 0 assertions are expected.  When only
   1.619 + * one argument is given, that number of assertions are expected.
   1.620 + *
   1.621 + * A test where we expect to have assertions (which should largely be a
   1.622 + * transitional mechanism to get assertion counts down from our current
   1.623 + * situation) can call the SimpleTest.expectAssertions() function, with
   1.624 + * either one or two arguments:  one argument gives an exact number
   1.625 + * expected, and two arguments give a range.  For example, a test might do
   1.626 + * one of the following:
   1.627 + *
   1.628 + *   // Currently triggers two assertions (bug NNNNNN).
   1.629 + *   SimpleTest.expectAssertions(2);
   1.630 + *
   1.631 + *   // Currently triggers one assertion on Mac (bug NNNNNN).
   1.632 + *   if (navigator.platform.indexOf("Mac") == 0) {
   1.633 + *     SimpleTest.expectAssertions(1);
   1.634 + *   }
   1.635 + *
   1.636 + *   // Currently triggers two assertions on all platforms (bug NNNNNN),
   1.637 + *   // but intermittently triggers two additional assertions (bug NNNNNN)
   1.638 + *   // on Windows.
   1.639 + *   if (navigator.platform.indexOf("Win") == 0) {
   1.640 + *     SimpleTest.expectAssertions(2, 4);
   1.641 + *   } else {
   1.642 + *     SimpleTest.expectAssertions(2);
   1.643 + *   }
   1.644 + *
   1.645 + *   // Intermittently triggers up to three assertions (bug NNNNNN).
   1.646 + *   SimpleTest.expectAssertions(0, 3);
   1.647 + */
   1.648 +SimpleTest.expectAssertions = function(min, max) {
   1.649 +    if (parentRunner) {
   1.650 +        parentRunner.expectAssertions(min, max);
   1.651 +    }
   1.652 +}
   1.653 +
   1.654 +SimpleTest.waitForFocus_started = false;
   1.655 +SimpleTest.waitForFocus_loaded = false;
   1.656 +SimpleTest.waitForFocus_focused = false;
   1.657 +SimpleTest._pendingWaitForFocusCount = 0;
   1.658 +
   1.659 +/**
   1.660 + * If the page is not yet loaded, waits for the load event. In addition, if
   1.661 + * the page is not yet focused, focuses and waits for the window to be
   1.662 + * focused. Calls the callback when completed. If the current page is
   1.663 + * 'about:blank', then the page is assumed to not yet be loaded. Pass true for
   1.664 + * expectBlankPage to not make this assumption if you expect a blank page to
   1.665 + * be present.
   1.666 + *
   1.667 + * targetWindow should be specified if it is different than 'window'. The actual
   1.668 + * focused window may be a descendant of targetWindow.
   1.669 + *
   1.670 + * @param callback
   1.671 + *        function called when load and focus are complete
   1.672 + * @param targetWindow
   1.673 + *        optional window to be loaded and focused, defaults to 'window'
   1.674 + * @param expectBlankPage
   1.675 + *        true if targetWindow.location is 'about:blank'. Defaults to false
   1.676 + */
   1.677 +SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) {
   1.678 +    SimpleTest._pendingWaitForFocusCount++;
   1.679 +    if (!targetWindow)
   1.680 +      targetWindow = window;
   1.681 +
   1.682 +    SimpleTest.waitForFocus_started = false;
   1.683 +    expectBlankPage = !!expectBlankPage;
   1.684 +
   1.685 +    var childTargetWindow = {};
   1.686 +    SpecialPowers.getFocusedElementForWindow(targetWindow, true, childTargetWindow);
   1.687 +    childTargetWindow = childTargetWindow.value;
   1.688 +
   1.689 +    function info(msg) {
   1.690 +        SimpleTest.info(msg);
   1.691 +    }
   1.692 +    function getHref(aWindow) {
   1.693 +      return SpecialPowers.getPrivilegedProps(aWindow, 'location.href');
   1.694 +    }
   1.695 +
   1.696 +    function maybeRunTests() {
   1.697 +        if (SimpleTest.waitForFocus_loaded &&
   1.698 +            SimpleTest.waitForFocus_focused &&
   1.699 +            !SimpleTest.waitForFocus_started) {
   1.700 +            SimpleTest._pendingWaitForFocusCount--;
   1.701 +            SimpleTest.waitForFocus_started = true;
   1.702 +            setTimeout(callback, 0, targetWindow);
   1.703 +        }
   1.704 +    }
   1.705 +
   1.706 +    function waitForEvent(event) {
   1.707 +        try {
   1.708 +            // Check to make sure that this isn't a load event for a blank or
   1.709 +            // non-blank page that wasn't desired.
   1.710 +            if (event.type == "load" && (expectBlankPage != (event.target.location == "about:blank")))
   1.711 +                return;
   1.712 +
   1.713 +            SimpleTest["waitForFocus_" + event.type + "ed"] = true;
   1.714 +            var win = (event.type == "load") ? targetWindow : childTargetWindow;
   1.715 +            win.removeEventListener(event.type, waitForEvent, true);
   1.716 +            maybeRunTests();
   1.717 +        } catch (e) {
   1.718 +            SimpleTest.ok(false, "Exception caught in waitForEvent: " + e.message +
   1.719 +                ", at: " + e.fileName + " (" + e.lineNumber + ")");
   1.720 +        }
   1.721 +    }
   1.722 +
   1.723 +    // If the current document is about:blank and we are not expecting a blank
   1.724 +    // page (or vice versa), and the document has not yet loaded, wait for the
   1.725 +    // page to load. A common situation is to wait for a newly opened window
   1.726 +    // to load its content, and we want to skip over any intermediate blank
   1.727 +    // pages that load. This issue is described in bug 554873.
   1.728 +    SimpleTest.waitForFocus_loaded =
   1.729 +        expectBlankPage ?
   1.730 +            getHref(targetWindow) == "about:blank" :
   1.731 +            getHref(targetWindow) != "about:blank" && targetWindow.document.readyState == "complete";
   1.732 +    if (!SimpleTest.waitForFocus_loaded) {
   1.733 +        info("must wait for load");
   1.734 +        targetWindow.addEventListener("load", waitForEvent, true);
   1.735 +    }
   1.736 +
   1.737 +    // Check if the desired window is already focused.
   1.738 +    var focusedChildWindow = { };
   1.739 +    if (SpecialPowers.activeWindow()) {
   1.740 +        SpecialPowers.getFocusedElementForWindow(SpecialPowers.activeWindow(), true, focusedChildWindow);
   1.741 +        focusedChildWindow = focusedChildWindow.value;
   1.742 +    }
   1.743 +
   1.744 +    // If this is a child frame, ensure that the frame is focused.
   1.745 +    SimpleTest.waitForFocus_focused = (focusedChildWindow == childTargetWindow);
   1.746 +    if (SimpleTest.waitForFocus_focused) {
   1.747 +        // If the frame is already focused and loaded, call the callback directly.
   1.748 +        maybeRunTests();
   1.749 +    }
   1.750 +    else {
   1.751 +        info("must wait for focus");
   1.752 +        childTargetWindow.addEventListener("focus", waitForEvent, true);
   1.753 +        SpecialPowers.focus(childTargetWindow);
   1.754 +    }
   1.755 +};
   1.756 +
   1.757 +SimpleTest.waitForClipboard_polls = 0;
   1.758 +
   1.759 +/*
   1.760 + * Polls the clipboard waiting for the expected value. A known value different than
   1.761 + * the expected value is put on the clipboard first (and also polled for) so we
   1.762 + * can be sure the value we get isn't just the expected value because it was already
   1.763 + * on the clipboard. This only uses the global clipboard and only for text/unicode
   1.764 + * values.
   1.765 + *
   1.766 + * @param aExpectedStringOrValidatorFn
   1.767 + *        The string value that is expected to be on the clipboard or a
   1.768 + *        validator function getting cripboard data and returning a bool.
   1.769 + * @param aSetupFn
   1.770 + *        A function responsible for setting the clipboard to the expected value,
   1.771 + *        called after the known value setting succeeds.
   1.772 + * @param aSuccessFn
   1.773 + *        A function called when the expected value is found on the clipboard.
   1.774 + * @param aFailureFn
   1.775 + *        A function called if the expected value isn't found on the clipboard
   1.776 + *        within 5s. It can also be called if the known value can't be found.
   1.777 + * @param aFlavor [optional] The flavor to look for.  Defaults to "text/unicode".
   1.778 + */
   1.779 +SimpleTest.__waitForClipboardMonotonicCounter = 0;
   1.780 +SimpleTest.__defineGetter__("_waitForClipboardMonotonicCounter", function () {
   1.781 +  return SimpleTest.__waitForClipboardMonotonicCounter++;
   1.782 +});
   1.783 +SimpleTest.waitForClipboard = function(aExpectedStringOrValidatorFn, aSetupFn,
   1.784 +                                       aSuccessFn, aFailureFn, aFlavor) {
   1.785 +    var requestedFlavor = aFlavor || "text/unicode";
   1.786 +
   1.787 +    // Build a default validator function for common string input.
   1.788 +    var inputValidatorFn = typeof(aExpectedStringOrValidatorFn) == "string"
   1.789 +        ? function(aData) { return aData == aExpectedStringOrValidatorFn; }
   1.790 +        : aExpectedStringOrValidatorFn;
   1.791 +
   1.792 +    // reset for the next use
   1.793 +    function reset() {
   1.794 +        SimpleTest.waitForClipboard_polls = 0;
   1.795 +    }
   1.796 +
   1.797 +    function wait(validatorFn, successFn, failureFn, flavor) {
   1.798 +        if (++SimpleTest.waitForClipboard_polls > 50) {
   1.799 +            // Log the failure.
   1.800 +            SimpleTest.ok(false, "Timed out while polling clipboard for pasted data.");
   1.801 +            reset();
   1.802 +            failureFn();
   1.803 +            return;
   1.804 +        }
   1.805 +
   1.806 +        var data = SpecialPowers.getClipboardData(flavor);
   1.807 +
   1.808 +        if (validatorFn(data)) {
   1.809 +            // Don't show the success message when waiting for preExpectedVal
   1.810 +            if (preExpectedVal)
   1.811 +                preExpectedVal = null;
   1.812 +            else
   1.813 +                SimpleTest.ok(true, "Clipboard has the correct value");
   1.814 +            reset();
   1.815 +            successFn();
   1.816 +        } else {
   1.817 +            setTimeout(function() { return wait(validatorFn, successFn, failureFn, flavor); }, 100);
   1.818 +        }
   1.819 +    }
   1.820 +
   1.821 +    // First we wait for a known value different from the expected one.
   1.822 +    var preExpectedVal = SimpleTest._waitForClipboardMonotonicCounter +
   1.823 +                         "-waitForClipboard-known-value";
   1.824 +    SpecialPowers.clipboardCopyString(preExpectedVal);
   1.825 +    wait(function(aData) { return aData  == preExpectedVal; },
   1.826 +         function() {
   1.827 +           // Call the original setup fn
   1.828 +           aSetupFn();
   1.829 +           wait(inputValidatorFn, aSuccessFn, aFailureFn, requestedFlavor);
   1.830 +         }, aFailureFn, "text/unicode");
   1.831 +}
   1.832 +
   1.833 +/**
   1.834 + * Executes a function shortly after the call, but lets the caller continue
   1.835 + * working (or finish).
   1.836 + */
   1.837 +SimpleTest.executeSoon = function(aFunc) {
   1.838 +    if ("SpecialPowers" in window) {
   1.839 +        return SpecialPowers.executeSoon(aFunc, window);
   1.840 +    }
   1.841 +    setTimeout(aFunc, 0);
   1.842 +    return null;		// Avoid warning.
   1.843 +};
   1.844 +
   1.845 +SimpleTest.registerCleanupFunction = function(aFunc) {
   1.846 +    SimpleTest._cleanupFunctions.push(aFunc);
   1.847 +};
   1.848 +
   1.849 +/**
   1.850 + * Finishes the tests. This is automatically called, except when
   1.851 + * SimpleTest.waitForExplicitFinish() has been invoked.
   1.852 +**/
   1.853 +SimpleTest.finish = function() {
   1.854 +    var Task = SpecialPowers.Cu.import("resource://gre/modules/Task.jsm").Task;
   1.855 +
   1.856 +    if (SimpleTest._alreadyFinished) {
   1.857 +        SimpleTest.ok(false, "[SimpleTest.finish()] this test already called finish!");
   1.858 +    }
   1.859 +
   1.860 +    SimpleTest._alreadyFinished = true;
   1.861 +
   1.862 +    Task.spawn(function*() {
   1.863 +        // Execute all of our cleanup functions.
   1.864 +        var func;
   1.865 +        while ((func = SimpleTest._cleanupFunctions.pop())) {
   1.866 +          try {
   1.867 +            yield func();
   1.868 +          }
   1.869 +          catch (ex) {
   1.870 +            SimpleTest.ok(false, "Cleanup function threw exception: " + ex);
   1.871 +          }
   1.872 +        }
   1.873 +
   1.874 +        if (SpecialPowers.DOMWindowUtils.isTestControllingRefreshes) {
   1.875 +            SimpleTest.ok(false, "test left refresh driver under test control");
   1.876 +            SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
   1.877 +        }
   1.878 +        if (SimpleTest._expectingUncaughtException) {
   1.879 +            SimpleTest.ok(false, "expectUncaughtException was called but no uncaught exception was detected!");
   1.880 +        }
   1.881 +        if (SimpleTest._pendingWaitForFocusCount != 0) {
   1.882 +            SimpleTest.is(SimpleTest._pendingWaitForFocusCount, 0,
   1.883 +                          "[SimpleTest.finish()] waitForFocus() was called a "
   1.884 +                          + "different number of times from the number of "
   1.885 +                          + "callbacks run.  Maybe the test terminated "
   1.886 +                          + "prematurely -- be sure to use "
   1.887 +                          + "SimpleTest.waitForExplicitFinish().");
   1.888 +        }
   1.889 +        if (SimpleTest._tests.length == 0) {
   1.890 +            SimpleTest.ok(false, "[SimpleTest.finish()] No checks actually run. "
   1.891 +                               + "(You need to call ok(), is(), or similar "
   1.892 +                               + "functions at least once.  Make sure you use "
   1.893 +                               + "SimpleTest.waitForExplicitFinish() if you need "
   1.894 +                               + "it.)");
   1.895 +        }
   1.896 +
   1.897 +        if (parentRunner) {
   1.898 +            /* We're running in an iframe, and the parent has a TestRunner */
   1.899 +            parentRunner.testFinished(SimpleTest._tests);
   1.900 +        } else {
   1.901 +            SpecialPowers.flushAllAppsLaunchable();
   1.902 +            SpecialPowers.flushPermissions(function () {
   1.903 +              SpecialPowers.flushPrefEnv(function() {
   1.904 +                SimpleTest.showReport();
   1.905 +              });
   1.906 +            });
   1.907 +        }
   1.908 +    });
   1.909 +};
   1.910 +
   1.911 +/**
   1.912 + * Monitor console output from now until endMonitorConsole is called.
   1.913 + *
   1.914 + * Expect to receive all console messages described by the elements of
   1.915 + * |msgs|, an array, in the order listed in |msgs|; each element is an
   1.916 + * object which may have any number of the following properties:
   1.917 + *   message, errorMessage, sourceName, sourceLine, category:
   1.918 + *     string or regexp
   1.919 + *   lineNumber, columnNumber: number
   1.920 + *   isScriptError, isWarning, isException, isStrict: boolean
   1.921 + * Strings, numbers, and booleans must compare equal to the named
   1.922 + * property of the Nth console message.  Regexps must match.  Any
   1.923 + * fields present in the message but not in the pattern object are ignored.
   1.924 + *
   1.925 + * In addition to the above properties, elements in |msgs| may have a |forbid|
   1.926 + * boolean property.  When |forbid| is true, a failure is logged each time a
   1.927 + * matching message is received.
   1.928 + *
   1.929 + * If |forbidUnexpectedMsgs| is true, then the messages received in the console
   1.930 + * must exactly match the non-forbidden messages in |msgs|; for each received
   1.931 + * message not described by the next element in |msgs|, a failure is logged.  If
   1.932 + * false, then other non-forbidden messages are ignored, but all expected
   1.933 + * messages must still be received.
   1.934 + *
   1.935 + * After endMonitorConsole is called, |continuation| will be called
   1.936 + * asynchronously.  (Normally, you will want to pass |SimpleTest.finish| here.)
   1.937 + *
   1.938 + * It is incorrect to use this function in a test which has not called
   1.939 + * SimpleTest.waitForExplicitFinish.
   1.940 + */
   1.941 +SimpleTest.monitorConsole = function (continuation, msgs, forbidUnexpectedMsgs) {
   1.942 +  if (SimpleTest._stopOnLoad) {
   1.943 +    ok(false, "Console monitoring requires use of waitForExplicitFinish.");
   1.944 +  }
   1.945 +
   1.946 +  function msgMatches(msg, pat) {
   1.947 +    for (k in pat) {
   1.948 +      if (!(k in msg)) {
   1.949 +        return false;
   1.950 +      }
   1.951 +      if (pat[k] instanceof RegExp && typeof(msg[k]) === 'string') {
   1.952 +        if (!pat[k].test(msg[k])) {
   1.953 +          return false;
   1.954 +        }
   1.955 +      } else if (msg[k] !== pat[k]) {
   1.956 +        return false;
   1.957 +      }
   1.958 +    }
   1.959 +    return true;
   1.960 +  }
   1.961 +
   1.962 +  var forbiddenMsgs = [];
   1.963 +  var i = 0;
   1.964 +  while (i < msgs.length) {
   1.965 +    var pat = msgs[i];
   1.966 +    if ("forbid" in pat) {
   1.967 +      var forbid = pat.forbid;
   1.968 +      delete pat.forbid;
   1.969 +      if (forbid) {
   1.970 +        forbiddenMsgs.push(pat);
   1.971 +        msgs.splice(i, 1);
   1.972 +        continue;
   1.973 +      }
   1.974 +    }
   1.975 +    i++;
   1.976 +  }
   1.977 +
   1.978 +  var counter = 0;
   1.979 +  function listener(msg) {
   1.980 +    if (msg.message === "SENTINEL" && !msg.isScriptError) {
   1.981 +      is(counter, msgs.length, "monitorConsole | number of messages");
   1.982 +      SimpleTest.executeSoon(continuation);
   1.983 +      return;
   1.984 +    }
   1.985 +    for (var pat of forbiddenMsgs) {
   1.986 +      if (msgMatches(msg, pat)) {
   1.987 +        ok(false, "monitorConsole | observed forbidden message " +
   1.988 +                  JSON.stringify(msg));
   1.989 +        return;
   1.990 +      }
   1.991 +    }
   1.992 +    if (counter >= msgs.length) {
   1.993 +      var str = "monitorConsole | extra message | " + JSON.stringify(msg);
   1.994 +      if (forbidUnexpectedMsgs) {
   1.995 +        ok(false, str);
   1.996 +      } else {
   1.997 +        info(str);
   1.998 +      }
   1.999 +      return;
  1.1000 +    }
  1.1001 +    var matches = msgMatches(msg, msgs[counter]);
  1.1002 +    if (forbidUnexpectedMsgs) {
  1.1003 +      ok(matches, "monitorConsole | [" + counter + "] must match " +
  1.1004 +                  JSON.stringify(msg));
  1.1005 +    } else {
  1.1006 +      info("monitorConsole | [" + counter + "] " +
  1.1007 +           (matches ? "matched " : "did not match ") + JSON.stringify(msg));
  1.1008 +    }
  1.1009 +    if (matches)
  1.1010 +      counter++;
  1.1011 +  }
  1.1012 +  SpecialPowers.registerConsoleListener(listener);
  1.1013 +};
  1.1014 +
  1.1015 +/**
  1.1016 + * Stop monitoring console output.
  1.1017 + */
  1.1018 +SimpleTest.endMonitorConsole = function () {
  1.1019 +  SpecialPowers.postConsoleSentinel();
  1.1020 +};
  1.1021 +
  1.1022 +/**
  1.1023 + * Run |testfn| synchronously, and monitor its console output.
  1.1024 + *
  1.1025 + * |msgs| is handled as described above for monitorConsole.
  1.1026 + *
  1.1027 + * After |testfn| returns, console monitoring will stop, and
  1.1028 + * |continuation| will be called asynchronously.
  1.1029 + */
  1.1030 +SimpleTest.expectConsoleMessages = function (testfn, msgs, continuation) {
  1.1031 +  SimpleTest.monitorConsole(continuation, msgs);
  1.1032 +  testfn();
  1.1033 +  SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
  1.1034 +};
  1.1035 +
  1.1036 +/**
  1.1037 + * Wrapper around |expectConsoleMessages| for the case where the test has
  1.1038 + * only one |testfn| to run.
  1.1039 + */
  1.1040 +SimpleTest.runTestExpectingConsoleMessages = function(testfn, msgs) {
  1.1041 +  SimpleTest.waitForExplicitFinish();
  1.1042 +  SimpleTest.expectConsoleMessages(testfn, msgs, SimpleTest.finish);
  1.1043 +};
  1.1044 +
  1.1045 +/**
  1.1046 + * Indicates to the test framework that the current test expects one or
  1.1047 + * more crashes (from plugins or IPC documents), and that the minidumps from
  1.1048 + * those crashes should be removed.
  1.1049 + */
  1.1050 +SimpleTest.expectChildProcessCrash = function () {
  1.1051 +    if (parentRunner) {
  1.1052 +        parentRunner.expectChildProcessCrash();
  1.1053 +    }
  1.1054 +};
  1.1055 +
  1.1056 +/**
  1.1057 + * Indicates to the test framework that the next uncaught exception during
  1.1058 + * the test is expected, and should not cause a test failure.
  1.1059 + */
  1.1060 +SimpleTest.expectUncaughtException = function (aExpecting) {
  1.1061 +    SimpleTest._expectingUncaughtException = aExpecting === void 0 || !!aExpecting;
  1.1062 +};
  1.1063 +
  1.1064 +/**
  1.1065 + * Returns whether the test has indicated that it expects an uncaught exception
  1.1066 + * to occur.
  1.1067 + */
  1.1068 +SimpleTest.isExpectingUncaughtException = function () {
  1.1069 +    return SimpleTest._expectingUncaughtException;
  1.1070 +};
  1.1071 +
  1.1072 +/**
  1.1073 + * Indicates to the test framework that all of the uncaught exceptions
  1.1074 + * during the test are known problems that should be fixed in the future,
  1.1075 + * but which should not cause the test to fail currently.
  1.1076 + */
  1.1077 +SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) {
  1.1078 +    SimpleTest._ignoringAllUncaughtExceptions = aIgnoring === void 0 || !!aIgnoring;
  1.1079 +};
  1.1080 +
  1.1081 +/**
  1.1082 + * Returns whether the test has indicated that all uncaught exceptions should be
  1.1083 + * ignored.
  1.1084 + */
  1.1085 +SimpleTest.isIgnoringAllUncaughtExceptions = function () {
  1.1086 +    return SimpleTest._ignoringAllUncaughtExceptions;
  1.1087 +};
  1.1088 +
  1.1089 +/**
  1.1090 + * Resets any state this SimpleTest object has.  This is important for
  1.1091 + * browser chrome mochitests, which reuse the same SimpleTest object
  1.1092 + * across a run.
  1.1093 + */
  1.1094 +SimpleTest.reset = function () {
  1.1095 +    SimpleTest._ignoringAllUncaughtExceptions = false;
  1.1096 +    SimpleTest._expectingUncaughtException = false;
  1.1097 +    SimpleTest._bufferedMessages = [];
  1.1098 +};
  1.1099 +
  1.1100 +if (isPrimaryTestWindow) {
  1.1101 +    addLoadEvent(function() {
  1.1102 +        if (SimpleTest._stopOnLoad) {
  1.1103 +            SimpleTest.finish();
  1.1104 +        }
  1.1105 +    });
  1.1106 +}
  1.1107 +
  1.1108 +//  --------------- Test.Builder/Test.More isDeeply() -----------------
  1.1109 +
  1.1110 +
  1.1111 +SimpleTest.DNE = {dne: 'Does not exist'};
  1.1112 +SimpleTest.LF = "\r\n";
  1.1113 +SimpleTest._isRef = function (object) {
  1.1114 +    var type = typeof(object);
  1.1115 +    return type == 'object' || type == 'function';
  1.1116 +};
  1.1117 +
  1.1118 +
  1.1119 +SimpleTest._deepCheck = function (e1, e2, stack, seen) {
  1.1120 +    var ok = false;
  1.1121 +    // Either they're both references or both not.
  1.1122 +    var sameRef = !(!SimpleTest._isRef(e1) ^ !SimpleTest._isRef(e2));
  1.1123 +    if (e1 == null && e2 == null) {
  1.1124 +        ok = true;
  1.1125 +    } else if (e1 != null ^ e2 != null) {
  1.1126 +        ok = false;
  1.1127 +    } else if (e1 == SimpleTest.DNE ^ e2 == SimpleTest.DNE) {
  1.1128 +        ok = false;
  1.1129 +    } else if (sameRef && e1 == e2) {
  1.1130 +        // Handles primitives and any variables that reference the same
  1.1131 +        // object, including functions.
  1.1132 +        ok = true;
  1.1133 +    } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) {
  1.1134 +        ok = SimpleTest._eqArray(e1, e2, stack, seen);
  1.1135 +    } else if (typeof e1 == "object" && typeof e2 == "object") {
  1.1136 +        ok = SimpleTest._eqAssoc(e1, e2, stack, seen);
  1.1137 +    } else if (typeof e1 == "number" && typeof e2 == "number"
  1.1138 +               && isNaN(e1) && isNaN(e2)) {
  1.1139 +        ok = true;
  1.1140 +    } else {
  1.1141 +        // If we get here, they're not the same (function references must
  1.1142 +        // always simply reference the same function).
  1.1143 +        stack.push({ vals: [e1, e2] });
  1.1144 +        ok = false;
  1.1145 +    }
  1.1146 +    return ok;
  1.1147 +};
  1.1148 +
  1.1149 +SimpleTest._eqArray = function (a1, a2, stack, seen) {
  1.1150 +    // Return if they're the same object.
  1.1151 +    if (a1 == a2) return true;
  1.1152 +
  1.1153 +    // JavaScript objects have no unique identifiers, so we have to store
  1.1154 +    // references to them all in an array, and then compare the references
  1.1155 +    // directly. It's slow, but probably won't be much of an issue in
  1.1156 +    // practice. Start by making a local copy of the array to as to avoid
  1.1157 +    // confusing a reference seen more than once (such as [a, a]) for a
  1.1158 +    // circular reference.
  1.1159 +    for (var j = 0; j < seen.length; j++) {
  1.1160 +        if (seen[j][0] == a1) {
  1.1161 +            return seen[j][1] == a2;
  1.1162 +        }
  1.1163 +    }
  1.1164 +
  1.1165 +    // If we get here, we haven't seen a1 before, so store it with reference
  1.1166 +    // to a2.
  1.1167 +    seen.push([ a1, a2 ]);
  1.1168 +
  1.1169 +    var ok = true;
  1.1170 +    // Only examines enumerable attributes. Only works for numeric arrays!
  1.1171 +    // Associative arrays return 0. So call _eqAssoc() for them, instead.
  1.1172 +    var max = a1.length > a2.length ? a1.length : a2.length;
  1.1173 +    if (max == 0) return SimpleTest._eqAssoc(a1, a2, stack, seen);
  1.1174 +    for (var i = 0; i < max; i++) {
  1.1175 +        var e1 = i > a1.length - 1 ? SimpleTest.DNE : a1[i];
  1.1176 +        var e2 = i > a2.length - 1 ? SimpleTest.DNE : a2[i];
  1.1177 +        stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
  1.1178 +        ok = SimpleTest._deepCheck(e1, e2, stack, seen);
  1.1179 +        if (ok) {
  1.1180 +            stack.pop();
  1.1181 +        } else {
  1.1182 +            break;
  1.1183 +        }
  1.1184 +    }
  1.1185 +    return ok;
  1.1186 +};
  1.1187 +
  1.1188 +SimpleTest._eqAssoc = function (o1, o2, stack, seen) {
  1.1189 +    // Return if they're the same object.
  1.1190 +    if (o1 == o2) return true;
  1.1191 +
  1.1192 +    // JavaScript objects have no unique identifiers, so we have to store
  1.1193 +    // references to them all in an array, and then compare the references
  1.1194 +    // directly. It's slow, but probably won't be much of an issue in
  1.1195 +    // practice. Start by making a local copy of the array to as to avoid
  1.1196 +    // confusing a reference seen more than once (such as [a, a]) for a
  1.1197 +    // circular reference.
  1.1198 +    seen = seen.slice(0);
  1.1199 +    for (var j = 0; j < seen.length; j++) {
  1.1200 +        if (seen[j][0] == o1) {
  1.1201 +            return seen[j][1] == o2;
  1.1202 +        }
  1.1203 +    }
  1.1204 +
  1.1205 +    // If we get here, we haven't seen o1 before, so store it with reference
  1.1206 +    // to o2.
  1.1207 +    seen.push([ o1, o2 ]);
  1.1208 +
  1.1209 +    // They should be of the same class.
  1.1210 +
  1.1211 +    var ok = true;
  1.1212 +    // Only examines enumerable attributes.
  1.1213 +    var o1Size = 0; for (var i in o1) o1Size++;
  1.1214 +    var o2Size = 0; for (var i in o2) o2Size++;
  1.1215 +    var bigger = o1Size > o2Size ? o1 : o2;
  1.1216 +    for (var i in bigger) {
  1.1217 +        var e1 = o1[i] == undefined ? SimpleTest.DNE : o1[i];
  1.1218 +        var e2 = o2[i] == undefined ? SimpleTest.DNE : o2[i];
  1.1219 +        stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
  1.1220 +        ok = SimpleTest._deepCheck(e1, e2, stack, seen)
  1.1221 +        if (ok) {
  1.1222 +            stack.pop();
  1.1223 +        } else {
  1.1224 +            break;
  1.1225 +        }
  1.1226 +    }
  1.1227 +    return ok;
  1.1228 +};
  1.1229 +
  1.1230 +SimpleTest._formatStack = function (stack) {
  1.1231 +    var variable = '$Foo';
  1.1232 +    for (var i = 0; i < stack.length; i++) {
  1.1233 +        var entry = stack[i];
  1.1234 +        var type = entry['type'];
  1.1235 +        var idx = entry['idx'];
  1.1236 +        if (idx != null) {
  1.1237 +            if (/^\d+$/.test(idx)) {
  1.1238 +                // Numeric array index.
  1.1239 +                variable += '[' + idx + ']';
  1.1240 +            } else {
  1.1241 +                // Associative array index.
  1.1242 +                idx = idx.replace("'", "\\'");
  1.1243 +                variable += "['" + idx + "']";
  1.1244 +            }
  1.1245 +        }
  1.1246 +    }
  1.1247 +
  1.1248 +    var vals = stack[stack.length-1]['vals'].slice(0, 2);
  1.1249 +    var vars = [
  1.1250 +        variable.replace('$Foo',     'got'),
  1.1251 +        variable.replace('$Foo',     'expected')
  1.1252 +    ];
  1.1253 +
  1.1254 +    var out = "Structures begin differing at:" + SimpleTest.LF;
  1.1255 +    for (var i = 0; i < vals.length; i++) {
  1.1256 +        var val = vals[i];
  1.1257 +        if (val == null) {
  1.1258 +            val = 'undefined';
  1.1259 +        } else {
  1.1260 +            val == SimpleTest.DNE ? "Does not exist" : "'" + val + "'";
  1.1261 +        }
  1.1262 +    }
  1.1263 +
  1.1264 +    out += vars[0] + ' = ' + vals[0] + SimpleTest.LF;
  1.1265 +    out += vars[1] + ' = ' + vals[1] + SimpleTest.LF;
  1.1266 +
  1.1267 +    return '    ' + out;
  1.1268 +};
  1.1269 +
  1.1270 +
  1.1271 +SimpleTest.isDeeply = function (it, as, name) {
  1.1272 +    var ok;
  1.1273 +    // ^ is the XOR operator.
  1.1274 +    if (SimpleTest._isRef(it) ^ SimpleTest._isRef(as)) {
  1.1275 +        // One's a reference, one isn't.
  1.1276 +        ok = false;
  1.1277 +    } else if (!SimpleTest._isRef(it) && !SimpleTest._isRef(as)) {
  1.1278 +        // Neither is an object.
  1.1279 +        ok = SimpleTest.is(it, as, name);
  1.1280 +    } else {
  1.1281 +        // We have two objects. Do a deep comparison.
  1.1282 +        var stack = [], seen = [];
  1.1283 +        if ( SimpleTest._deepCheck(it, as, stack, seen)) {
  1.1284 +            ok = SimpleTest.ok(true, name);
  1.1285 +        } else {
  1.1286 +            ok = SimpleTest.ok(false, name, SimpleTest._formatStack(stack));
  1.1287 +        }
  1.1288 +    }
  1.1289 +    return ok;
  1.1290 +};
  1.1291 +
  1.1292 +SimpleTest.typeOf = function (object) {
  1.1293 +    var c = Object.prototype.toString.apply(object);
  1.1294 +    var name = c.substring(8, c.length - 1);
  1.1295 +    if (name != 'Object') return name;
  1.1296 +    // It may be a non-core class. Try to extract the class name from
  1.1297 +    // the constructor function. This may not work in all implementations.
  1.1298 +    if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) {
  1.1299 +        return RegExp.$1;
  1.1300 +    }
  1.1301 +    // No idea. :-(
  1.1302 +    return name;
  1.1303 +};
  1.1304 +
  1.1305 +SimpleTest.isa = function (object, clas) {
  1.1306 +    return SimpleTest.typeOf(object) == clas;
  1.1307 +};
  1.1308 +
  1.1309 +// Global symbols:
  1.1310 +var ok = SimpleTest.ok;
  1.1311 +var is = SimpleTest.is;
  1.1312 +var isfuzzy = SimpleTest.isfuzzy;
  1.1313 +var isnot = SimpleTest.isnot;
  1.1314 +var ise = SimpleTest.ise;
  1.1315 +var todo = SimpleTest.todo;
  1.1316 +var todo_is = SimpleTest.todo_is;
  1.1317 +var todo_isnot = SimpleTest.todo_isnot;
  1.1318 +var isDeeply = SimpleTest.isDeeply;
  1.1319 +var info = SimpleTest.info;
  1.1320 +
  1.1321 +var gOldOnError = window.onerror;
  1.1322 +window.onerror = function simpletestOnerror(errorMsg, url, lineNumber) {
  1.1323 +    // Log the message.
  1.1324 +    // XXX Chrome mochitests sometimes trigger this window.onerror handler,
  1.1325 +    // but there are a number of uncaught JS exceptions from those tests.
  1.1326 +    // For now, for tests that self identify as having unintentional uncaught
  1.1327 +    // exceptions, just dump it so that the error is visible but doesn't cause
  1.1328 +    // a test failure.  See bug 652494.
  1.1329 +    var isExpected = !!SimpleTest._expectingUncaughtException;
  1.1330 +    var message = (isExpected ? "expected " : "") + "uncaught exception";
  1.1331 +    var error = errorMsg + " at " + url + ":" + lineNumber;
  1.1332 +    if (!SimpleTest._ignoringAllUncaughtExceptions) {
  1.1333 +        SimpleTest.ok(isExpected, message, error);
  1.1334 +        SimpleTest._expectingUncaughtException = false;
  1.1335 +    } else {
  1.1336 +        SimpleTest.todo(false, message + ": " + error);
  1.1337 +    }
  1.1338 +    // There is no Components.stack.caller to log. (See bug 511888.)
  1.1339 +
  1.1340 +    // Call previous handler.
  1.1341 +    if (gOldOnError) {
  1.1342 +        try {
  1.1343 +            // Ignore return value: always run default handler.
  1.1344 +            gOldOnError(errorMsg, url, lineNumber);
  1.1345 +        } catch (e) {
  1.1346 +            // Log the error.
  1.1347 +            SimpleTest.info("Exception thrown by gOldOnError(): " + e);
  1.1348 +            // Log its stack.
  1.1349 +            if (e.stack) {
  1.1350 +                SimpleTest.info("JavaScript error stack:\n" + e.stack);
  1.1351 +            }
  1.1352 +        }
  1.1353 +    }
  1.1354 +
  1.1355 +    if (!SimpleTest._stopOnLoad && !isExpected) {
  1.1356 +        // Need to finish() manually here, yet let the test actually end first.
  1.1357 +        SimpleTest.executeSoon(SimpleTest.finish);
  1.1358 +    }
  1.1359 +};

mercurial