summaryrefslogtreecommitdiffstats
path: root/testing/mochitest/tests/SimpleTest/SimpleTest.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mochitest/tests/SimpleTest/SimpleTest.js')
-rw-r--r--testing/mochitest/tests/SimpleTest/SimpleTest.js1639
1 files changed, 1639 insertions, 0 deletions
diff --git a/testing/mochitest/tests/SimpleTest/SimpleTest.js b/testing/mochitest/tests/SimpleTest/SimpleTest.js
new file mode 100644
index 000000000..37713737c
--- /dev/null
+++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js
@@ -0,0 +1,1639 @@
+/* -*- js-indent-level: 4; tab-width: 4; indent-tabs-mode: nil -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/**
+ * SimpleTest, a partial Test.Simple/Test.More API compatible test library.
+ *
+ * Why?
+ *
+ * Test.Simple doesn't work on IE < 6.
+ * TODO:
+ * * Support the Test.Simple API used by MochiKit, to be able to test MochiKit
+ * itself against IE 5.5
+ *
+ * NOTE: Pay attention to cross-browser compatibility in this file. For
+ * instance, do not use const or JS > 1.5 features which are not yet
+ * implemented everywhere.
+ *
+**/
+
+var SimpleTest = { };
+var parentRunner = null;
+
+// In normal test runs, the window that has a TestRunner in its parent is
+// the primary window. In single test runs, if there is no parent and there
+// is no opener then it is the primary window.
+var isSingleTestRun = (parent == window && !opener)
+try {
+ var isPrimaryTestWindow = !!parent.TestRunner || isSingleTestRun;
+} catch(e) {
+ dump("TEST-UNEXPECTED-FAIL, Exception caught: " + e.message +
+ ", at: " + e.fileName + " (" + e.lineNumber +
+ "), location: " + window.location.href + "\n");
+}
+// Finds the TestRunner for this test run and the SpecialPowers object (in
+// case it is not defined) from a parent/opener window.
+//
+// Finding the SpecialPowers object is needed when we have ChromePowers in
+// harness.xul and we need SpecialPowers in the iframe, and also for tests
+// like test_focus.xul where we open a window which opens another window which
+// includes SimpleTest.js.
+(function() {
+ function ancestor(w) {
+ return w.parent != w ? w.parent : w.opener;
+ }
+
+ var w = ancestor(window);
+ while (w && (!parentRunner || !window.SpecialPowers)) {
+ if (!parentRunner) {
+ parentRunner = w.TestRunner;
+ if (!parentRunner && w.wrappedJSObject) {
+ parentRunner = w.wrappedJSObject.TestRunner;
+ }
+ }
+ if (!window.SpecialPowers) {
+ window.SpecialPowers = w.SpecialPowers;
+ }
+ w = ancestor(w);
+ }
+
+ if (parentRunner) {
+ SimpleTest.harnessParameters = parentRunner.getParameterInfo();
+ }
+})();
+
+/* Helper functions pulled out of various MochiKit modules */
+if (typeof(repr) == 'undefined') {
+ this.repr = function(o) {
+ if (typeof(o) == "undefined") {
+ return "undefined";
+ } else if (o === null) {
+ return "null";
+ }
+ try {
+ if (typeof(o.__repr__) == 'function') {
+ return o.__repr__();
+ } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
+ return o.repr();
+ }
+ } catch (e) {
+ }
+ try {
+ if (typeof(o.NAME) == 'string' && (
+ o.toString == Function.prototype.toString ||
+ o.toString == Object.prototype.toString
+ )) {
+ return o.NAME;
+ }
+ } catch (e) {
+ }
+ var ostring;
+ try {
+ if (o === 0) {
+ ostring = (1 / o > 0) ? "+0" : "-0";
+ } else if (typeof o === "string") {
+ ostring = JSON.stringify(o);
+ } else if (Array.isArray(o)) {
+ ostring = "[" + o.map(val => repr(val)).join(", ") + "]";
+ } else {
+ ostring = (o + "");
+ }
+ } catch (e) {
+ return "[" + typeof(o) + "]";
+ }
+ if (typeof(o) == "function") {
+ o = ostring.replace(/^\s+/, "");
+ var idx = o.indexOf("{");
+ if (idx != -1) {
+ o = o.substr(0, idx) + "{...}";
+ }
+ }
+ return ostring;
+ };
+}
+
+/* This returns a function that applies the previously given parameters.
+ * This is used by SimpleTest.showReport
+ */
+if (typeof(partial) == 'undefined') {
+ this.partial = function(func) {
+ var args = [];
+ for (var i = 1; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ return function() {
+ if (arguments.length > 0) {
+ for (var i = 1; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ }
+ func(args);
+ };
+ };
+}
+
+if (typeof(getElement) == 'undefined') {
+ this.getElement = function(id) {
+ return ((typeof(id) == "string") ?
+ document.getElementById(id) : id);
+ };
+ this.$ = this.getElement;
+}
+
+SimpleTest._newCallStack = function(path) {
+ var rval = function () {
+ var callStack = arguments.callee.callStack;
+ for (var i = 0; i < callStack.length; i++) {
+ if (callStack[i].apply(this, arguments) === false) {
+ break;
+ }
+ }
+ try {
+ this[path] = null;
+ } catch (e) {
+ // pass
+ }
+ };
+ rval.callStack = [];
+ return rval;
+};
+
+if (typeof(addLoadEvent) == 'undefined') {
+ this.addLoadEvent = function(func) {
+ var existing = window["onload"];
+ var regfunc = existing;
+ if (!(typeof(existing) == 'function'
+ && typeof(existing.callStack) == "object"
+ && existing.callStack !== null)) {
+ regfunc = SimpleTest._newCallStack("onload");
+ if (typeof(existing) == 'function') {
+ regfunc.callStack.push(existing);
+ }
+ window["onload"] = regfunc;
+ }
+ regfunc.callStack.push(func);
+ };
+}
+
+function createEl(type, attrs, html) {
+ //use createElementNS so the xul/xhtml tests have no issues
+ var el;
+ if (!document.body) {
+ el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
+ }
+ else {
+ el = document.createElement(type);
+ }
+ if (attrs !== null && attrs !== undefined) {
+ for (var k in attrs) {
+ el.setAttribute(k, attrs[k]);
+ }
+ }
+ if (html !== null && html !== undefined) {
+ el.appendChild(document.createTextNode(html));
+ }
+ return el;
+}
+
+/* lots of tests use this as a helper to get css properties */
+if (typeof(computedStyle) == 'undefined') {
+ this.computedStyle = function(elem, cssProperty) {
+ elem = getElement(elem);
+ if (elem.currentStyle) {
+ return elem.currentStyle[cssProperty];
+ }
+ if (typeof(document.defaultView) == 'undefined' || document === null) {
+ return undefined;
+ }
+ var style = document.defaultView.getComputedStyle(elem, null);
+ if (typeof(style) == 'undefined' || style === null) {
+ return undefined;
+ }
+
+ var selectorCase = cssProperty.replace(/([A-Z])/g, '-$1'
+ ).toLowerCase();
+
+ return style.getPropertyValue(selectorCase);
+ };
+}
+
+SimpleTest._tests = [];
+SimpleTest._stopOnLoad = true;
+SimpleTest._cleanupFunctions = [];
+SimpleTest._timeoutFunctions = [];
+SimpleTest.expected = 'pass';
+SimpleTest.num_failed = 0;
+SimpleTest._inChaosMode = false;
+
+SimpleTest.setExpected = function () {
+ if (parent.TestRunner) {
+ SimpleTest.expected = parent.TestRunner.expected;
+ }
+}
+SimpleTest.setExpected();
+
+/**
+ * Something like assert.
+**/
+SimpleTest.ok = function (condition, name, diag, stack = null) {
+
+ var test = {'result': !!condition, 'name': name, 'diag': diag};
+ if (SimpleTest.expected == 'fail') {
+ if (!test.result) {
+ SimpleTest.num_failed++;
+ test.result = !test.result;
+ }
+ var successInfo = {status:"FAIL", expected:"FAIL", message:"TEST-KNOWN-FAIL"};
+ var failureInfo = {status:"PASS", expected:"FAIL", message:"TEST-UNEXPECTED-PASS"};
+ } else {
+ var successInfo = {status:"PASS", expected:"PASS", message:"TEST-PASS"};
+ var failureInfo = {status:"FAIL", expected:"PASS", message:"TEST-UNEXPECTED-FAIL"};
+ }
+
+ if (condition) {
+ stack = null;
+ } else if (!stack) {
+ stack = (new Error).stack.replace(/^(.*@)http:\/\/mochi.test:8888\/tests\//gm, ' $1').split('\n');
+ stack.splice(0, 1);
+ stack = stack.join('\n');
+ }
+
+ SimpleTest._logResult(test, successInfo, failureInfo, stack);
+ SimpleTest._tests.push(test);
+};
+
+/**
+ * Roughly equivalent to ok(Object.is(a, b), name)
+**/
+SimpleTest.is = function (a, b, name) {
+ // Be lazy and use Object.is til we want to test a browser without it.
+ var pass = Object.is(a, b);
+ var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b)
+ SimpleTest.ok(pass, name, diag);
+};
+
+SimpleTest.isfuzzy = function (a, b, epsilon, name) {
+ var pass = (a >= b - epsilon) && (a <= b + epsilon);
+ var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) + " epsilon: +/- " + repr(epsilon)
+ SimpleTest.ok(pass, name, diag);
+};
+
+SimpleTest.isnot = function (a, b, name) {
+ var pass = !Object.is(a, b);
+ var diag = pass ? "" : "didn't expect " + repr(a) + ", but got it";
+ SimpleTest.ok(pass, name, diag);
+};
+
+/**
+ * Check that the function call throws an exception.
+ */
+SimpleTest.doesThrow = function(fn, name) {
+ var gotException = false;
+ try {
+ fn();
+ } catch (ex) { gotException = true; }
+ ok(gotException, name);
+};
+
+// --------------- Test.Builder/Test.More todo() -----------------
+
+SimpleTest.todo = function(condition, name, diag) {
+ var test = {'result': !!condition, 'name': name, 'diag': diag, todo: true};
+ var successInfo = {status:"PASS", expected:"FAIL", message:"TEST-UNEXPECTED-PASS"};
+ var failureInfo = {status:"FAIL", expected:"FAIL", message:"TEST-KNOWN-FAIL"};
+ SimpleTest._logResult(test, successInfo, failureInfo);
+ SimpleTest._tests.push(test);
+};
+
+/*
+ * Returns the absolute URL to a test data file from where tests
+ * are served. i.e. the file doesn't necessarely exists where tests
+ * are executed.
+ * (For android, mochitest are executed on the device, while
+ * all mochitest html (and others) files are served from the test runner
+ * slave)
+ */
+SimpleTest.getTestFileURL = function(path) {
+ var lastSlashIdx = path.lastIndexOf("/") + 1;
+ var filename = path.substr(lastSlashIdx);
+ var location = window.location;
+ // Remove mochitest html file name from the path
+ var remotePath = location.pathname.replace(/\/[^\/]+?$/,"");
+ var url = location.origin +
+ remotePath + "/" + path;
+ return url;
+};
+
+SimpleTest._getCurrentTestURL = function() {
+ return parentRunner && parentRunner.currentTestURL ||
+ typeof gTestPath == "string" && gTestPath ||
+ "unknown test url";
+};
+
+SimpleTest._forceLogMessageOutput = false;
+
+/**
+ * Force all test messages to be displayed. Only applies for the current test.
+ */
+SimpleTest.requestCompleteLog = function() {
+ if (!parentRunner || SimpleTest._forceLogMessageOutput) {
+ return;
+ }
+
+ parentRunner.structuredLogger.deactivateBuffering();
+ SimpleTest._forceLogMessageOutput = true;
+
+ SimpleTest.registerCleanupFunction(function() {
+ parentRunner.structuredLogger.activateBuffering();
+ SimpleTest._forceLogMessageOutput = false;
+ });
+};
+
+SimpleTest._logResult = function (test, passInfo, failInfo, stack) {
+ var url = SimpleTest._getCurrentTestURL();
+ var result = test.result ? passInfo : failInfo;
+ var diagnostic = test.diag || null;
+ // BUGFIX : coercing test.name to a string, because some a11y tests pass an xpconnect object
+ var subtest = test.name ? String(test.name) : null;
+ var isError = !test.result == !test.todo;
+
+ if (parentRunner) {
+ if (!result.status || !result.expected) {
+ if (diagnostic) {
+ parentRunner.structuredLogger.info(diagnostic);
+ }
+ return;
+ }
+
+ if (isError) {
+ parentRunner.addFailedTest(url);
+ }
+
+ parentRunner.structuredLogger.testStatus(url,
+ subtest,
+ result.status,
+ result.expected,
+ diagnostic,
+ stack);
+ } else if (typeof dump === "function") {
+ var diagMessage = test.name + (test.diag ? " - " + test.diag : "");
+ var debugMsg = [result.message, url, diagMessage].join(' | ');
+ dump(debugMsg + "\n");
+ } else {
+ // Non-Mozilla browser? Just do nothing.
+ }
+};
+
+SimpleTest.info = function(name, message) {
+ var log = message ? name + ' | ' + message : name;
+ if (parentRunner) {
+ parentRunner.structuredLogger.info(log);
+ } else {
+ dump(log + '\n');
+ }
+};
+
+/**
+ * Copies of is and isnot with the call to ok replaced by a call to todo.
+**/
+
+SimpleTest.todo_is = function (a, b, name) {
+ var pass = Object.is(a, b);
+ var diag = pass ? repr(a) + " should equal " + repr(b)
+ : "got " + repr(a) + ", expected " + repr(b);
+ SimpleTest.todo(pass, name, diag);
+};
+
+SimpleTest.todo_isnot = function (a, b, name) {
+ var pass = !Object.is(a, b);
+ var diag = pass ? repr(a) + " should not equal " + repr(b)
+ : "didn't expect " + repr(a) + ", but got it";
+ SimpleTest.todo(pass, name, diag);
+};
+
+
+/**
+ * Makes a test report, returns it as a DIV element.
+**/
+SimpleTest.report = function () {
+ var passed = 0;
+ var failed = 0;
+ var todo = 0;
+
+ var tallyAndCreateDiv = function (test) {
+ var cls, msg, div;
+ var diag = test.diag ? " - " + test.diag : "";
+ if (test.todo && !test.result) {
+ todo++;
+ cls = "test_todo";
+ msg = "todo | " + test.name + diag;
+ } else if (test.result && !test.todo) {
+ passed++;
+ cls = "test_ok";
+ msg = "passed | " + test.name + diag;
+ } else {
+ failed++;
+ cls = "test_not_ok";
+ msg = "failed | " + test.name + diag;
+ }
+ div = createEl('div', {'class': cls}, msg);
+ return div;
+ };
+ var results = [];
+ for (var d=0; d<SimpleTest._tests.length; d++) {
+ results.push(tallyAndCreateDiv(SimpleTest._tests[d]));
+ }
+
+ var summary_class = failed != 0 ? 'some_fail' :
+ passed == 0 ? 'todo_only' : 'all_pass';
+
+ var div1 = createEl('div', {'class': 'tests_report'});
+ var div2 = createEl('div', {'class': 'tests_summary ' + summary_class});
+ var div3 = createEl('div', {'class': 'tests_passed'}, 'Passed: ' + passed);
+ var div4 = createEl('div', {'class': 'tests_failed'}, 'Failed: ' + failed);
+ var div5 = createEl('div', {'class': 'tests_todo'}, 'Todo: ' + todo);
+ div2.appendChild(div3);
+ div2.appendChild(div4);
+ div2.appendChild(div5);
+ div1.appendChild(div2);
+ for (var t=0; t<results.length; t++) {
+ //iterate in order
+ div1.appendChild(results[t]);
+ }
+ return div1;
+};
+
+/**
+ * Toggle element visibility
+**/
+SimpleTest.toggle = function(el) {
+ if (computedStyle(el, 'display') == 'block') {
+ el.style.display = 'none';
+ } else {
+ el.style.display = 'block';
+ }
+};
+
+/**
+ * Toggle visibility for divs with a specific class.
+**/
+SimpleTest.toggleByClass = function (cls, evt) {
+ var children = document.getElementsByTagName('div');
+ var elements = [];
+ for (var i=0; i<children.length; i++) {
+ var child = children[i];
+ var clsName = child.className;
+ if (!clsName) {
+ continue;
+ }
+ var classNames = clsName.split(' ');
+ for (var j = 0; j < classNames.length; j++) {
+ if (classNames[j] == cls) {
+ elements.push(child);
+ break;
+ }
+ }
+ }
+ for (var t=0; t<elements.length; t++) {
+ //TODO: again, for-in loop over elems seems to break this
+ SimpleTest.toggle(elements[t]);
+ }
+ if (evt)
+ evt.preventDefault();
+};
+
+/**
+ * Shows the report in the browser
+**/
+SimpleTest.showReport = function() {
+ var togglePassed = createEl('a', {'href': '#'}, "Toggle passed checks");
+ var toggleFailed = createEl('a', {'href': '#'}, "Toggle failed checks");
+ var toggleTodo = createEl('a',{'href': '#'}, "Toggle todo checks");
+ togglePassed.onclick = partial(SimpleTest.toggleByClass, 'test_ok');
+ toggleFailed.onclick = partial(SimpleTest.toggleByClass, 'test_not_ok');
+ toggleTodo.onclick = partial(SimpleTest.toggleByClass, 'test_todo');
+ var body = document.body; // Handles HTML documents
+ if (!body) {
+ // Do the XML thing.
+ body = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml",
+ "body")[0];
+ }
+ var firstChild = body.childNodes[0];
+ var addNode;
+ if (firstChild) {
+ addNode = function (el) {
+ body.insertBefore(el, firstChild);
+ };
+ } else {
+ addNode = function (el) {
+ body.appendChild(el)
+ };
+ }
+ addNode(togglePassed);
+ addNode(createEl('span', null, " "));
+ addNode(toggleFailed);
+ addNode(createEl('span', null, " "));
+ addNode(toggleTodo);
+ addNode(SimpleTest.report());
+ // Add a separator from the test content.
+ addNode(createEl('hr'));
+};
+
+/**
+ * Tells SimpleTest to don't finish the test when the document is loaded,
+ * useful for asynchronous tests.
+ *
+ * When SimpleTest.waitForExplicitFinish is called,
+ * explicit SimpleTest.finish() is required.
+**/
+SimpleTest.waitForExplicitFinish = function () {
+ SimpleTest._stopOnLoad = false;
+};
+
+/**
+ * Multiply the timeout the parent runner uses for this test by the
+ * given factor.
+ *
+ * For example, in a test that may take a long time to complete, using
+ * "SimpleTest.requestLongerTimeout(5)" will give it 5 times as long to
+ * finish.
+ */
+SimpleTest.requestLongerTimeout = function (factor) {
+ if (parentRunner) {
+ parentRunner.requestLongerTimeout(factor);
+ }
+}
+
+/**
+ * Note that the given range of assertions is to be expected. When
+ * this function is not called, 0 assertions are expected. When only
+ * one argument is given, that number of assertions are expected.
+ *
+ * A test where we expect to have assertions (which should largely be a
+ * transitional mechanism to get assertion counts down from our current
+ * situation) can call the SimpleTest.expectAssertions() function, with
+ * either one or two arguments: one argument gives an exact number
+ * expected, and two arguments give a range. For example, a test might do
+ * one of the following:
+ *
+ * // Currently triggers two assertions (bug NNNNNN).
+ * SimpleTest.expectAssertions(2);
+ *
+ * // Currently triggers one assertion on Mac (bug NNNNNN).
+ * if (navigator.platform.indexOf("Mac") == 0) {
+ * SimpleTest.expectAssertions(1);
+ * }
+ *
+ * // Currently triggers two assertions on all platforms (bug NNNNNN),
+ * // but intermittently triggers two additional assertions (bug NNNNNN)
+ * // on Windows.
+ * if (navigator.platform.indexOf("Win") == 0) {
+ * SimpleTest.expectAssertions(2, 4);
+ * } else {
+ * SimpleTest.expectAssertions(2);
+ * }
+ *
+ * // Intermittently triggers up to three assertions (bug NNNNNN).
+ * SimpleTest.expectAssertions(0, 3);
+ */
+SimpleTest.expectAssertions = function(min, max) {
+ if (parentRunner) {
+ parentRunner.expectAssertions(min, max);
+ }
+}
+
+SimpleTest._flakyTimeoutIsOK = false;
+SimpleTest._originalSetTimeout = window.setTimeout;
+window.setTimeout = function SimpleTest_setTimeoutShim() {
+ // Don't break tests that are loaded without a parent runner.
+ if (parentRunner) {
+ // Right now, we only enable these checks for mochitest-plain.
+ switch (SimpleTest.harnessParameters.testRoot) {
+ case "browser":
+ case "chrome":
+ case "a11y":
+ break;
+ default:
+ if (!SimpleTest._alreadyFinished && arguments.length > 1 && arguments[1] > 0) {
+ if (SimpleTest._flakyTimeoutIsOK) {
+ SimpleTest.todo(false, "The author of the test has indicated that flaky timeouts are expected. Reason: " + SimpleTest._flakyTimeoutReason);
+ } else {
+ SimpleTest.ok(false, "Test attempted to use a flaky timeout value " + arguments[1]);
+ }
+ }
+ }
+ }
+ return SimpleTest._originalSetTimeout.apply(window, arguments);
+}
+
+/**
+ * Request the framework to allow usage of setTimeout(func, timeout)
+ * where |timeout > 0|. This is required to note that the author of
+ * the test is aware of the inherent flakiness in the test caused by
+ * that, and asserts that there is no way around using the magic timeout
+ * value number for some reason.
+ *
+ * The reason parameter should be a string representation of the
+ * reason why using such flaky timeouts.
+ *
+ * Use of this function is STRONGLY discouraged. Think twice before
+ * using it. Such magic timeout values could result in intermittent
+ * failures in your test, and are almost never necessary!
+ */
+SimpleTest.requestFlakyTimeout = function (reason) {
+ SimpleTest.is(typeof(reason), "string", "A valid string reason is expected");
+ SimpleTest.isnot(reason, "", "Reason cannot be empty");
+ SimpleTest._flakyTimeoutIsOK = true;
+ SimpleTest._flakyTimeoutReason = reason;
+}
+
+SimpleTest._pendingWaitForFocusCount = 0;
+
+/**
+ * Version of waitForFocus that returns a promise. The Promise will
+ * not resolve to the focused window, as it might be a CPOW (and Promises
+ * cannot be resolved with CPOWs). If you require the focused window,
+ * you should use waitForFocus instead.
+ */
+SimpleTest.promiseFocus = function *(targetWindow, expectBlankPage)
+{
+ return new Promise(function (resolve, reject) {
+ SimpleTest.waitForFocus(win => {
+ // Just resolve, without passing the window (see bug 1233497)
+ resolve();
+ }, targetWindow, expectBlankPage);
+ });
+}
+
+/**
+ * If the page is not yet loaded, waits for the load event. In addition, if
+ * the page is not yet focused, focuses and waits for the window to be
+ * focused. Calls the callback when completed. If the current page is
+ * 'about:blank', then the page is assumed to not yet be loaded. Pass true for
+ * expectBlankPage to not make this assumption if you expect a blank page to
+ * be present.
+ *
+ * targetWindow should be specified if it is different than 'window'. The actual
+ * focused window may be a descendant of targetWindow.
+ *
+ * @param callback
+ * function called when load and focus are complete
+ * @param targetWindow
+ * optional window to be loaded and focused, defaults to 'window'.
+ * This may also be a <browser> element, in which case the window within
+ * that browser will be focused.
+ * @param expectBlankPage
+ * true if targetWindow.location is 'about:blank'. Defaults to false
+ */
+SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) {
+ // A separate method is used that is serialized and passed to the child
+ // process via loadFrameScript. Once the child window is focused, the
+ // child will send the WaitForFocus:ChildFocused notification to the parent.
+ // If a child frame in a child process must be focused, a
+ // WaitForFocus:FocusChild message is then sent to the child to focus that
+ // child. This message is used so that the child frame can be passed to it.
+ function waitForFocusInner(targetWindow, isChildProcess, expectBlankPage)
+ {
+ /* Indicates whether the desired targetWindow has loaded or focused. The
+ finished flag is set when the callback has been called and is used to
+ reject extraneous events from invoking the callback again. */
+ var loaded = false, focused = false, finished = false;
+
+ function info(msg) {
+ if (!isChildProcess) {
+ SimpleTest.info(msg);
+ }
+ }
+
+ function focusedWindow() {
+ if (isChildProcess) {
+ return Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager).focusedWindow;
+ }
+ return SpecialPowers.focusedWindow();
+ }
+
+ function getHref(aWindow) {
+ return isChildProcess ? aWindow.location.href :
+ SpecialPowers.getPrivilegedProps(aWindow, 'location.href');
+ }
+
+ /* Event listener for the load or focus events. It will also be called with
+ event equal to null to check if the page is already focused and loaded. */
+ function focusedOrLoaded(event) {
+ try {
+ if (event) {
+ if (event.type == "load") {
+ if (expectBlankPage != (event.target.location == "about:blank")) {
+ return;
+ }
+
+ loaded = true;
+ } else if (event.type == "focus") {
+ focused = true;
+ }
+
+ event.currentTarget.removeEventListener(event.type, focusedOrLoaded, true);
+ }
+
+ if (loaded && focused && !finished) {
+ finished = true;
+ if (isChildProcess) {
+ sendAsyncMessage("WaitForFocus:ChildFocused", {}, null);
+ } else {
+ SimpleTest._pendingWaitForFocusCount--;
+ SimpleTest.executeSoon(function() { callback(targetWindow) });
+ }
+ }
+ } catch (e) {
+ if (!isChildProcess) {
+ SimpleTest.ok(false, "Exception caught in focusedOrLoaded: " + e.message +
+ ", at: " + e.fileName + " (" + e.lineNumber + ")");
+ }
+ }
+ }
+
+ function waitForLoadAndFocusOnWindow(desiredWindow) {
+ /* If the current document is about:blank and we are not expecting a blank
+ page (or vice versa), and the document has not yet loaded, wait for the
+ page to load. A common situation is to wait for a newly opened window
+ to load its content, and we want to skip over any intermediate blank
+ pages that load. This issue is described in bug 554873. */
+ loaded = expectBlankPage ?
+ getHref(desiredWindow) == "about:blank" :
+ getHref(desiredWindow) != "about:blank" &&
+ desiredWindow.document.readyState == "complete";
+ if (!loaded) {
+ info("must wait for load");
+ desiredWindow.addEventListener("load", focusedOrLoaded, true);
+ }
+
+ var childDesiredWindow = { };
+ if (isChildProcess) {
+ var fm = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+ fm.getFocusedElementForWindow(desiredWindow, true, childDesiredWindow);
+ childDesiredWindow = childDesiredWindow.value;
+ } else {
+ childDesiredWindow = SpecialPowers.getFocusedElementForWindow(desiredWindow, true);
+ }
+
+ /* If this is a child frame, ensure that the frame is focused. */
+ focused = (focusedWindow() == childDesiredWindow);
+ if (!focused) {
+ info("must wait for focus");
+ childDesiredWindow.addEventListener("focus", focusedOrLoaded, true);
+ if (isChildProcess) {
+ childDesiredWindow.focus();
+ }
+ else {
+ SpecialPowers.focus(childDesiredWindow);
+ }
+ }
+
+ focusedOrLoaded(null);
+ }
+
+ if (isChildProcess) {
+ /* This message is used when an inner child frame must be focused. */
+ addMessageListener("WaitForFocus:FocusChild", function focusChild(msg) {
+ removeMessageListener("WaitForFocus:FocusChild", focusChild);
+ finished = false;
+ waitForLoadAndFocusOnWindow(msg.objects.child);
+ });
+ }
+
+ waitForLoadAndFocusOnWindow(targetWindow);
+ }
+
+ SimpleTest._pendingWaitForFocusCount++;
+ if (!targetWindow) {
+ targetWindow = window;
+ }
+
+ expectBlankPage = !!expectBlankPage;
+
+ // If this is a request to focus a remote child window, the request must
+ // be forwarded to the child process.
+ // XXXndeakin now sure what this issue with Components.utils is about, but
+ // browser tests require the former and plain tests require the latter.
+ var Cu = Components.utils || SpecialPowers.Cu;
+ var Ci = Components.interfaces || SpecialPowers.Ci;
+
+ var browser = null;
+ if (typeof(XULElement) != "undefined" &&
+ targetWindow instanceof XULElement &&
+ targetWindow.localName == "browser") {
+ browser = targetWindow;
+ }
+
+ var isWrapper = Cu.isCrossProcessWrapper(targetWindow);
+ if (isWrapper || (browser && browser.isRemoteBrowser)) {
+ var mustFocusSubframe = false;
+ if (isWrapper) {
+ // Look for a tabbrowser and see if targetWindow corresponds to one
+ // within that tabbrowser. If not, just return.
+ var tabBrowser = document.getElementsByTagName("tabbrowser")[0] || null;
+ browser = tabBrowser ? tabBrowser.getBrowserForContentWindow(targetWindow.top) : null;
+ if (!browser) {
+ SimpleTest.info("child process window cannot be focused");
+ return;
+ }
+
+ mustFocusSubframe = (targetWindow != targetWindow.top);
+ }
+
+ // If a subframe in a child process needs to be focused, first focus the
+ // parent frame, then send a WaitForFocus:FocusChild message to the child
+ // containing the subframe to focus.
+ browser.messageManager.addMessageListener("WaitForFocus:ChildFocused", function waitTest(msg) {
+ if (mustFocusSubframe) {
+ mustFocusSubframe = false;
+ var mm = gBrowser.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("WaitForFocus:FocusChild", {}, { child: targetWindow } );
+ }
+ else {
+ browser.messageManager.removeMessageListener("WaitForFocus:ChildFocused", waitTest);
+ SimpleTest._pendingWaitForFocusCount--;
+ setTimeout(callback, 0, browser ? browser.contentWindowAsCPOW : targetWindow);
+ }
+ });
+
+ // Serialize the waitForFocusInner function and run it in the child process.
+ var frameScript = "data:,(" + waitForFocusInner.toString() +
+ ")(content, true, " + expectBlankPage + ");";
+ browser.messageManager.loadFrameScript(frameScript, true);
+ browser.focus();
+ }
+ else {
+ // Otherwise, this is an attempt to focus a single process or parent window,
+ // so pass false for isChildProcess.
+ if (browser) {
+ targetWindow = browser.contentWindow;
+ }
+
+ waitForFocusInner(targetWindow, false, expectBlankPage);
+ }
+};
+
+SimpleTest.waitForClipboard_polls = 0;
+
+/*
+ * Polls the clipboard waiting for the expected value. A known value different than
+ * the expected value is put on the clipboard first (and also polled for) so we
+ * can be sure the value we get isn't just the expected value because it was already
+ * on the clipboard. This only uses the global clipboard and only for text/unicode
+ * values.
+ *
+ * @param aExpectedStringOrValidatorFn
+ * The string value that is expected to be on the clipboard or a
+ * validator function getting cripboard data and returning a bool.
+ * @param aSetupFn
+ * A function responsible for setting the clipboard to the expected value,
+ * called after the known value setting succeeds.
+ * @param aSuccessFn
+ * A function called when the expected value is found on the clipboard.
+ * @param aFailureFn
+ * A function called if the expected value isn't found on the clipboard
+ * within 5s. It can also be called if the known value can't be found.
+ * @param aFlavor [optional] The flavor to look for. Defaults to "text/unicode".
+ * @param aTimeout [optional]
+ * The timeout (in milliseconds) to wait for a clipboard change.
+ * Defaults to 5000.
+ * @param aExpectFailure [optional]
+ * If true, fail if the clipboard contents are modified within the timeout
+ * interval defined by aTimeout. When aExpectFailure is true, the argument
+ * aExpectedStringOrValidatorFn must be null, as it won't be used.
+ * Defaults to false.
+ */
+SimpleTest.__waitForClipboardMonotonicCounter = 0;
+SimpleTest.__defineGetter__("_waitForClipboardMonotonicCounter", function () {
+ return SimpleTest.__waitForClipboardMonotonicCounter++;
+});
+SimpleTest.waitForClipboard = function(aExpectedStringOrValidatorFn, aSetupFn,
+ aSuccessFn, aFailureFn, aFlavor, aTimeout, aExpectFailure) {
+ var requestedFlavor = aFlavor || "text/unicode";
+
+ // The known value we put on the clipboard before running aSetupFn
+ var initialVal = SimpleTest._waitForClipboardMonotonicCounter +
+ "-waitForClipboard-known-value";
+
+ var inputValidatorFn;
+ if (aExpectFailure) {
+ // If we expect failure, the aExpectedStringOrValidatorFn should be null
+ if (aExpectedStringOrValidatorFn !== null) {
+ SimpleTest.ok(false, "When expecting failure, aExpectedStringOrValidatorFn must be null");
+ }
+
+ inputValidatorFn = function(aData) {
+ return aData != initialVal;
+ };
+ } else {
+ // Build a default validator function for common string input.
+ inputValidatorFn = typeof(aExpectedStringOrValidatorFn) == "string"
+ ? function(aData) { return aData == aExpectedStringOrValidatorFn; }
+ : aExpectedStringOrValidatorFn;
+ }
+
+ var maxPolls = aTimeout ? aTimeout / 100 : 50;
+
+ // reset for the next use
+ function reset() {
+ SimpleTest.waitForClipboard_polls = 0;
+ }
+
+ var lastValue;
+ function wait(validatorFn, successFn, failureFn, flavor) {
+ if (SimpleTest.waitForClipboard_polls == 0) {
+ lastValue = undefined;
+ }
+
+ if (++SimpleTest.waitForClipboard_polls > maxPolls) {
+ // Log the failure.
+ SimpleTest.ok(aExpectFailure, "Timed out while polling clipboard for pasted data");
+ dump("Got this value: " + lastValue);
+ reset();
+ failureFn();
+ return;
+ }
+
+ var data = SpecialPowers.getClipboardData(flavor);
+
+ if (validatorFn(data)) {
+ // Don't show the success message when waiting for preExpectedVal
+ if (preExpectedVal)
+ preExpectedVal = null;
+ else
+ SimpleTest.ok(!aExpectFailure, "Clipboard has the given value");
+ reset();
+ successFn();
+ } else {
+ lastValue = data;
+ SimpleTest._originalSetTimeout.apply(window, [function() { return wait(validatorFn, successFn, failureFn, flavor); }, 100]);
+ }
+ }
+
+ // First we wait for a known value different from the expected one.
+ var preExpectedVal = initialVal;
+ SpecialPowers.clipboardCopyString(preExpectedVal);
+ wait(function(aData) { return aData == preExpectedVal; },
+ function() {
+ // Call the original setup fn
+ aSetupFn();
+ wait(inputValidatorFn, aSuccessFn, aFailureFn, requestedFlavor);
+ }, aFailureFn, "text/unicode");
+}
+
+/**
+ * Wait for a condition for a while (actually up to 3s here).
+ *
+ * @param aCond
+ * A function returns the result of the condition
+ * @param aCallback
+ * A function called after the condition is passed or timeout.
+ * @param aErrorMsg
+ * The message displayed when the condition failed to pass
+ * before timeout.
+ */
+SimpleTest.waitForCondition = function (aCond, aCallback, aErrorMsg) {
+ var tries = 0;
+ var interval = setInterval(() => {
+ if (tries >= 30) {
+ ok(false, aErrorMsg);
+ moveOn();
+ return;
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = aCond();
+ } catch (e) {
+ ok(false, `${e}\n${e.stack}`);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = () => { clearInterval(interval); aCallback(); };
+};
+SimpleTest.promiseWaitForCondition = function (aCond, aErrorMsg) {
+ return new Promise(resolve => {
+ this.waitForCondition(aCond, resolve, aErrorMsg);
+ });
+};
+
+/**
+ * Executes a function shortly after the call, but lets the caller continue
+ * working (or finish).
+ */
+SimpleTest.executeSoon = function(aFunc) {
+ if ("SpecialPowers" in window) {
+ return SpecialPowers.executeSoon(aFunc, window);
+ }
+ setTimeout(aFunc, 0);
+ return null; // Avoid warning.
+};
+
+SimpleTest.registerCleanupFunction = function(aFunc) {
+ SimpleTest._cleanupFunctions.push(aFunc);
+};
+
+SimpleTest.registerTimeoutFunction = function(aFunc) {
+ SimpleTest._timeoutFunctions.push(aFunc);
+};
+
+SimpleTest.testInChaosMode = function() {
+ if (SimpleTest._inChaosMode) {
+ // It's already enabled for this test, don't enter twice
+ return;
+ }
+ SpecialPowers.DOMWindowUtils.enterChaosMode();
+ SimpleTest._inChaosMode = true;
+};
+
+SimpleTest.timeout = function() {
+ for (let func of SimpleTest._timeoutFunctions) {
+ func();
+ }
+ SimpleTest._timeoutFunctions = [];
+}
+
+/**
+ * Finishes the tests. This is automatically called, except when
+ * SimpleTest.waitForExplicitFinish() has been invoked.
+**/
+SimpleTest.finish = function() {
+ if (SimpleTest._alreadyFinished) {
+ var err = "[SimpleTest.finish()] this test already called finish!";
+ if (parentRunner) {
+ parentRunner.structuredLogger.error(err);
+ } else {
+ dump(err + '\n');
+ }
+ }
+
+ if (SimpleTest.expected == 'fail' && SimpleTest.num_failed <= 0) {
+ msg = 'We expected at least one failure';
+ var test = {'result': false, 'name': 'fail-if condition in manifest', 'diag': msg};
+ var successInfo = {status:"FAIL", expected:"FAIL", message:"TEST-KNOWN-FAIL"};
+ var failureInfo = {status:"PASS", expected:"FAIL", message:"TEST-UNEXPECTED-PASS"};
+
+ SimpleTest._logResult(test, successInfo, failureInfo);
+ SimpleTest._tests.push(test);
+ }
+
+ SimpleTest._timeoutFunctions = [];
+
+ SimpleTest.testsLength = SimpleTest._tests.length;
+
+ SimpleTest._alreadyFinished = true;
+
+ if (SimpleTest._inChaosMode) {
+ SpecialPowers.DOMWindowUtils.leaveChaosMode();
+ SimpleTest._inChaosMode = false;
+ }
+
+ var afterCleanup = function() {
+ SpecialPowers.removeFiles();
+
+ if (SpecialPowers.DOMWindowUtils.isTestControllingRefreshes) {
+ SimpleTest.ok(false, "test left refresh driver under test control");
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ }
+ if (SimpleTest._expectingUncaughtException) {
+ SimpleTest.ok(false, "expectUncaughtException was called but no uncaught exception was detected!");
+ }
+ if (SimpleTest._pendingWaitForFocusCount != 0) {
+ SimpleTest.is(SimpleTest._pendingWaitForFocusCount, 0,
+ "[SimpleTest.finish()] waitForFocus() was called a "
+ + "different number of times from the number of "
+ + "callbacks run. Maybe the test terminated "
+ + "prematurely -- be sure to use "
+ + "SimpleTest.waitForExplicitFinish().");
+ }
+ if (SimpleTest._tests.length == 0) {
+ SimpleTest.ok(false, "[SimpleTest.finish()] No checks actually run. "
+ + "(You need to call ok(), is(), or similar "
+ + "functions at least once. Make sure you use "
+ + "SimpleTest.waitForExplicitFinish() if you need "
+ + "it.)");
+ }
+ if (SimpleTest._expectingRegisteredServiceWorker) {
+ if (!SpecialPowers.isServiceWorkerRegistered()) {
+ SimpleTest.ok(false, "This test is expected to leave a service worker registered");
+ }
+ } else {
+ if (SpecialPowers.isServiceWorkerRegistered()) {
+ SimpleTest.ok(false, "This test left a service worker registered without cleaning it up");
+ }
+ }
+
+ if (parentRunner) {
+ /* We're running in an iframe, and the parent has a TestRunner */
+ parentRunner.testFinished(SimpleTest._tests);
+ }
+
+ if (!parentRunner || parentRunner.showTestReport) {
+ SpecialPowers.flushPermissions(function () {
+ SpecialPowers.flushPrefEnv(function() {
+ SimpleTest.showReport();
+ });
+ });
+ }
+ }
+
+ var executeCleanupFunction = function() {
+ var func = SimpleTest._cleanupFunctions.pop();
+
+ if (!func) {
+ afterCleanup();
+ return;
+ }
+
+ var ret;
+ try {
+ ret = func();
+ } catch (ex) {
+ SimpleTest.ok(false, "Cleanup function threw exception: " + ex);
+ }
+
+ if (ret && ret.constructor.name == "Promise") {
+ ret.then(executeCleanupFunction,
+ (ex) => SimpleTest.ok(false, "Cleanup promise rejected: " + ex));
+ } else {
+ executeCleanupFunction();
+ }
+ };
+
+ executeCleanupFunction();
+};
+
+/**
+ * Monitor console output from now until endMonitorConsole is called.
+ *
+ * Expect to receive all console messages described by the elements of
+ * |msgs|, an array, in the order listed in |msgs|; each element is an
+ * object which may have any number of the following properties:
+ * message, errorMessage, sourceName, sourceLine, category:
+ * string or regexp
+ * lineNumber, columnNumber: number
+ * isScriptError, isWarning, isException, isStrict: boolean
+ * Strings, numbers, and booleans must compare equal to the named
+ * property of the Nth console message. Regexps must match. Any
+ * fields present in the message but not in the pattern object are ignored.
+ *
+ * In addition to the above properties, elements in |msgs| may have a |forbid|
+ * boolean property. When |forbid| is true, a failure is logged each time a
+ * matching message is received.
+ *
+ * If |forbidUnexpectedMsgs| is true, then the messages received in the console
+ * must exactly match the non-forbidden messages in |msgs|; for each received
+ * message not described by the next element in |msgs|, a failure is logged. If
+ * false, then other non-forbidden messages are ignored, but all expected
+ * messages must still be received.
+ *
+ * After endMonitorConsole is called, |continuation| will be called
+ * asynchronously. (Normally, you will want to pass |SimpleTest.finish| here.)
+ *
+ * It is incorrect to use this function in a test which has not called
+ * SimpleTest.waitForExplicitFinish.
+ */
+SimpleTest.monitorConsole = function (continuation, msgs, forbidUnexpectedMsgs) {
+ if (SimpleTest._stopOnLoad) {
+ ok(false, "Console monitoring requires use of waitForExplicitFinish.");
+ }
+
+ function msgMatches(msg, pat) {
+ for (var k in pat) {
+ if (!(k in msg)) {
+ return false;
+ }
+ if (pat[k] instanceof RegExp && typeof(msg[k]) === 'string') {
+ if (!pat[k].test(msg[k])) {
+ return false;
+ }
+ } else if (msg[k] !== pat[k]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ var forbiddenMsgs = [];
+ var i = 0;
+ while (i < msgs.length) {
+ var pat = msgs[i];
+ if ("forbid" in pat) {
+ var forbid = pat.forbid;
+ delete pat.forbid;
+ if (forbid) {
+ forbiddenMsgs.push(pat);
+ msgs.splice(i, 1);
+ continue;
+ }
+ }
+ i++;
+ }
+
+ var counter = 0;
+ var assertionLabel = msgs.toSource();
+ function listener(msg) {
+ if (msg.message === "SENTINEL" && !msg.isScriptError) {
+ is(counter, msgs.length,
+ "monitorConsole | number of messages " + assertionLabel);
+ SimpleTest.executeSoon(continuation);
+ return;
+ }
+ for (var pat of forbiddenMsgs) {
+ if (msgMatches(msg, pat)) {
+ ok(false, "monitorConsole | observed forbidden message " +
+ JSON.stringify(msg));
+ return;
+ }
+ }
+ if (counter >= msgs.length) {
+ var str = "monitorConsole | extra message | " + JSON.stringify(msg);
+ if (forbidUnexpectedMsgs) {
+ ok(false, str);
+ } else {
+ info(str);
+ }
+ return;
+ }
+ var matches = msgMatches(msg, msgs[counter]);
+ if (forbidUnexpectedMsgs) {
+ ok(matches, "monitorConsole | [" + counter + "] must match " +
+ JSON.stringify(msg));
+ } else {
+ info("monitorConsole | [" + counter + "] " +
+ (matches ? "matched " : "did not match ") + JSON.stringify(msg));
+ }
+ if (matches)
+ counter++;
+ }
+ SpecialPowers.registerConsoleListener(listener);
+};
+
+/**
+ * Stop monitoring console output.
+ */
+SimpleTest.endMonitorConsole = function () {
+ SpecialPowers.postConsoleSentinel();
+};
+
+/**
+ * Run |testfn| synchronously, and monitor its console output.
+ *
+ * |msgs| is handled as described above for monitorConsole.
+ *
+ * After |testfn| returns, console monitoring will stop, and
+ * |continuation| will be called asynchronously.
+ */
+SimpleTest.expectConsoleMessages = function (testfn, msgs, continuation) {
+ SimpleTest.monitorConsole(continuation, msgs);
+ testfn();
+ SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
+};
+
+/**
+ * Wrapper around |expectConsoleMessages| for the case where the test has
+ * only one |testfn| to run.
+ */
+SimpleTest.runTestExpectingConsoleMessages = function(testfn, msgs) {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.expectConsoleMessages(testfn, msgs, SimpleTest.finish);
+};
+
+/**
+ * Indicates to the test framework that the current test expects one or
+ * more crashes (from plugins or IPC documents), and that the minidumps from
+ * those crashes should be removed.
+ */
+SimpleTest.expectChildProcessCrash = function () {
+ if (parentRunner) {
+ parentRunner.expectChildProcessCrash();
+ }
+};
+
+/**
+ * Indicates to the test framework that the next uncaught exception during
+ * the test is expected, and should not cause a test failure.
+ */
+SimpleTest.expectUncaughtException = function (aExpecting) {
+ SimpleTest._expectingUncaughtException = aExpecting === void 0 || !!aExpecting;
+};
+
+/**
+ * Returns whether the test has indicated that it expects an uncaught exception
+ * to occur.
+ */
+SimpleTest.isExpectingUncaughtException = function () {
+ return SimpleTest._expectingUncaughtException;
+};
+
+/**
+ * Indicates to the test framework that all of the uncaught exceptions
+ * during the test are known problems that should be fixed in the future,
+ * but which should not cause the test to fail currently.
+ */
+SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) {
+ SimpleTest._ignoringAllUncaughtExceptions = aIgnoring === void 0 || !!aIgnoring;
+};
+
+/**
+ * Returns whether the test has indicated that all uncaught exceptions should be
+ * ignored.
+ */
+SimpleTest.isIgnoringAllUncaughtExceptions = function () {
+ return SimpleTest._ignoringAllUncaughtExceptions;
+};
+
+/**
+ * Indicates to the test framework that this test is expected to leave a
+ * service worker registered when it finishes.
+ */
+SimpleTest.expectRegisteredServiceWorker = function () {
+ SimpleTest._expectingRegisteredServiceWorker = true;
+};
+
+/**
+ * Resets any state this SimpleTest object has. This is important for
+ * browser chrome mochitests, which reuse the same SimpleTest object
+ * across a run.
+ */
+SimpleTest.reset = function () {
+ SimpleTest._ignoringAllUncaughtExceptions = false;
+ SimpleTest._expectingUncaughtException = false;
+ SimpleTest._expectingRegisteredServiceWorker = false;
+ SimpleTest._bufferedMessages = [];
+};
+
+if (isPrimaryTestWindow) {
+ addLoadEvent(function() {
+ if (SimpleTest._stopOnLoad) {
+ SimpleTest.finish();
+ }
+ });
+}
+
+// --------------- Test.Builder/Test.More isDeeply() -----------------
+
+
+SimpleTest.DNE = {dne: 'Does not exist'};
+SimpleTest.LF = "\r\n";
+
+
+SimpleTest._deepCheck = function (e1, e2, stack, seen) {
+ var ok = false;
+ if (Object.is(e1, e2)) {
+ // Handles identical primitives and references.
+ ok = true;
+ } else if (typeof e1 != "object" || typeof e2 != "object" || e1 === null || e2 === null) {
+ // If either argument is a primitive or function, don't consider the arguments the same.
+ ok = false;
+ } else if (e1 == SimpleTest.DNE || e2 == SimpleTest.DNE) {
+ ok = false;
+ } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) {
+ ok = SimpleTest._eqArray(e1, e2, stack, seen);
+ } else {
+ ok = SimpleTest._eqAssoc(e1, e2, stack, seen);
+ }
+ return ok;
+};
+
+SimpleTest._eqArray = function (a1, a2, stack, seen) {
+ // Return if they're the same object.
+ if (a1 == a2) return true;
+
+ // JavaScript objects have no unique identifiers, so we have to store
+ // references to them all in an array, and then compare the references
+ // directly. It's slow, but probably won't be much of an issue in
+ // practice. Start by making a local copy of the array to as to avoid
+ // confusing a reference seen more than once (such as [a, a]) for a
+ // circular reference.
+ for (var j = 0; j < seen.length; j++) {
+ if (seen[j][0] == a1) {
+ return seen[j][1] == a2;
+ }
+ }
+
+ // If we get here, we haven't seen a1 before, so store it with reference
+ // to a2.
+ seen.push([ a1, a2 ]);
+
+ var ok = true;
+ // Only examines enumerable attributes. Only works for numeric arrays!
+ // Associative arrays return 0. So call _eqAssoc() for them, instead.
+ var max = Math.max(a1.length, a2.length);
+ if (max == 0) return SimpleTest._eqAssoc(a1, a2, stack, seen);
+ for (var i = 0; i < max; i++) {
+ var e1 = i < a1.length ? a1[i] : SimpleTest.DNE;
+ var e2 = i < a2.length ? a2[i] : SimpleTest.DNE;
+ stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
+ ok = SimpleTest._deepCheck(e1, e2, stack, seen);
+ if (ok) {
+ stack.pop();
+ } else {
+ break;
+ }
+ }
+ return ok;
+};
+
+SimpleTest._eqAssoc = function (o1, o2, stack, seen) {
+ // Return if they're the same object.
+ if (o1 == o2) return true;
+
+ // JavaScript objects have no unique identifiers, so we have to store
+ // references to them all in an array, and then compare the references
+ // directly. It's slow, but probably won't be much of an issue in
+ // practice. Start by making a local copy of the array to as to avoid
+ // confusing a reference seen more than once (such as [a, a]) for a
+ // circular reference.
+ seen = seen.slice(0);
+ for (var j = 0; j < seen.length; j++) {
+ if (seen[j][0] == o1) {
+ return seen[j][1] == o2;
+ }
+ }
+
+ // If we get here, we haven't seen o1 before, so store it with reference
+ // to o2.
+ seen.push([ o1, o2 ]);
+
+ // They should be of the same class.
+
+ var ok = true;
+ // Only examines enumerable attributes.
+ var o1Size = 0; for (var i in o1) o1Size++;
+ var o2Size = 0; for (var i in o2) o2Size++;
+ var bigger = o1Size > o2Size ? o1 : o2;
+ for (var i in bigger) {
+ var e1 = i in o1 ? o1[i] : SimpleTest.DNE;
+ var e2 = i in o2 ? o2[i] : SimpleTest.DNE;
+ stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
+ ok = SimpleTest._deepCheck(e1, e2, stack, seen)
+ if (ok) {
+ stack.pop();
+ } else {
+ break;
+ }
+ }
+ return ok;
+};
+
+SimpleTest._formatStack = function (stack) {
+ var variable = '$Foo';
+ for (var i = 0; i < stack.length; i++) {
+ var entry = stack[i];
+ var type = entry['type'];
+ var idx = entry['idx'];
+ if (idx != null) {
+ if (type == 'Array') {
+ // Numeric array index.
+ variable += '[' + idx + ']';
+ } else {
+ // Associative array index.
+ idx = idx.replace("'", "\\'");
+ variable += "['" + idx + "']";
+ }
+ }
+ }
+
+ var vals = stack[stack.length-1]['vals'].slice(0, 2);
+ var vars = [
+ variable.replace('$Foo', 'got'),
+ variable.replace('$Foo', 'expected')
+ ];
+
+ var out = "Structures begin differing at:" + SimpleTest.LF;
+ for (var i = 0; i < vals.length; i++) {
+ var val = vals[i];
+ if (val === SimpleTest.DNE) {
+ val = "Does not exist";
+ } else {
+ val = repr(val);
+ }
+ out += vars[i] + ' = ' + val + SimpleTest.LF;
+ }
+
+ return ' ' + out;
+};
+
+
+SimpleTest.isDeeply = function (it, as, name) {
+ var stack = [{ vals: [it, as] }];
+ var seen = [];
+ if ( SimpleTest._deepCheck(it, as, stack, seen)) {
+ SimpleTest.ok(true, name);
+ } else {
+ SimpleTest.ok(false, name, SimpleTest._formatStack(stack));
+ }
+};
+
+SimpleTest.typeOf = function (object) {
+ var c = Object.prototype.toString.apply(object);
+ var name = c.substring(8, c.length - 1);
+ if (name != 'Object') return name;
+ // It may be a non-core class. Try to extract the class name from
+ // the constructor function. This may not work in all implementations.
+ if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) {
+ return RegExp.$1;
+ }
+ // No idea. :-(
+ return name;
+};
+
+SimpleTest.isa = function (object, clas) {
+ return SimpleTest.typeOf(object) == clas;
+};
+
+// Global symbols:
+var ok = SimpleTest.ok;
+var is = SimpleTest.is;
+var isfuzzy = SimpleTest.isfuzzy;
+var isnot = SimpleTest.isnot;
+var todo = SimpleTest.todo;
+var todo_is = SimpleTest.todo_is;
+var todo_isnot = SimpleTest.todo_isnot;
+var isDeeply = SimpleTest.isDeeply;
+var info = SimpleTest.info;
+
+var gOldOnError = window.onerror;
+window.onerror = function simpletestOnerror(errorMsg, url, lineNumber,
+ columnNumber, originalException) {
+ // Log the message.
+ // XXX Chrome mochitests sometimes trigger this window.onerror handler,
+ // but there are a number of uncaught JS exceptions from those tests.
+ // For now, for tests that self identify as having unintentional uncaught
+ // exceptions, just dump it so that the error is visible but doesn't cause
+ // a test failure. See bug 652494.
+ var isExpected = !!SimpleTest._expectingUncaughtException;
+ var message = (isExpected ? "expected " : "") + "uncaught exception";
+ var error = errorMsg + " at ";
+ try {
+ error += originalException.stack;
+ } catch (e) {
+ // At least use the url+line+column we were given
+ error += url + ":" + lineNumber + ":" + columnNumber;
+ }
+ if (!SimpleTest._ignoringAllUncaughtExceptions) {
+ // Don't log if SimpleTest.finish() is already called, it would cause failures
+ if (!SimpleTest._alreadyFinished)
+ SimpleTest.ok(isExpected, message, error);
+ SimpleTest._expectingUncaughtException = false;
+ } else {
+ SimpleTest.todo(false, message + ": " + error);
+ }
+ // There is no Components.stack.caller to log. (See bug 511888.)
+
+ // Call previous handler.
+ if (gOldOnError) {
+ try {
+ // Ignore return value: always run default handler.
+ gOldOnError(errorMsg, url, lineNumber);
+ } catch (e) {
+ // Log the error.
+ SimpleTest.info("Exception thrown by gOldOnError(): " + e);
+ // Log its stack.
+ if (e.stack) {
+ SimpleTest.info("JavaScript error stack:\n" + e.stack);
+ }
+ }
+ }
+
+ if (!SimpleTest._stopOnLoad && !isExpected && !SimpleTest._alreadyFinished) {
+ // Need to finish() manually here, yet let the test actually end first.
+ SimpleTest.executeSoon(SimpleTest.finish);
+ }
+};
+
+// Lifted from dom/media/test/manifest.js
+// Make sure to not touch navigator in here, since we want to push prefs that
+// will affect the APIs it exposes, but the set of exposed APIs is determined
+// when Navigator.prototype is created. So if we touch navigator before pushing
+// the prefs, the APIs it exposes will not take those prefs into account. We
+// work around this by using a navigator object from a different global for our
+// UA string testing.
+var gAndroidSdk = null;
+function getAndroidSdk() {
+ if (gAndroidSdk === null) {
+ var iframe = document.documentElement.appendChild(document.createElement("iframe"));
+ iframe.style.display = "none";
+ var nav = iframe.contentWindow.navigator;
+ if (nav.userAgent.indexOf("Mobile") == -1 &&
+ nav.userAgent.indexOf("Tablet") == -1) {
+ gAndroidSdk = -1;
+ } else {
+ // See nsSystemInfo.cpp, the getProperty('version') returns different value
+ // on each platforms, so we need to distinguish the android platform.
+ var versionString = nav.userAgent.indexOf("Android") != -1 ?
+ 'version' : 'sdk_version';
+ gAndroidSdk = SpecialPowers.Cc['@mozilla.org/system-info;1']
+ .getService(SpecialPowers.Ci.nsIPropertyBag2)
+ .getProperty(versionString);
+ }
+ document.documentElement.removeChild(iframe);
+ }
+ return gAndroidSdk;
+}