summaryrefslogtreecommitdiffstats
path: root/js/src/tests/browser.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/tests/browser.js
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--js/src/tests/browser.js653
1 files changed, 653 insertions, 0 deletions
diff --git a/js/src/tests/browser.js b/js/src/tests/browser.js
new file mode 100644
index 0000000000..2c0dd5db8c
--- /dev/null
+++ b/js/src/tests/browser.js
@@ -0,0 +1,653 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// NOTE: If you're adding new test harness functionality to this file -- first,
+// should you at all? Most stuff is better in specific tests, or in
+// nested shell.js/browser.js. Second, can you instead add it to
+// shell.js? Our goal is to unify these two files for readability, and
+// the plan is to empty out this file into that one over time. Third,
+// supposing you must add to this file, please add it to this IIFE for
+// better modularity/resilience against tests that must do particularly
+// bizarre things that might break the harness.
+
+(function initializeUtilityExports(global, parent) {
+ "use strict";
+
+ /**********************************************************************
+ * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
+ **********************************************************************/
+
+ var Error = global.Error;
+ var String = global.String;
+ var GlobalEval = global.eval;
+ var ReflectApply = global.Reflect.apply;
+ var FunctionToString = global.Function.prototype.toString;
+ var ObjectDefineProperty = global.Object.defineProperty;
+
+ // BEWARE: ObjectGetOwnPropertyDescriptor is only safe to use if its result
+ // is inspected using own-property-examining functionality. Directly
+ // accessing properties on a returned descriptor without first
+ // verifying the property's existence can invoke user-modifiable
+ // behavior.
+ var ObjectGetOwnPropertyDescriptor = global.Object.getOwnPropertyDescriptor;
+
+ var Worker = global.Worker;
+ var Blob = global.Blob;
+ var URL = global.URL;
+
+ var document = global.document;
+ var documentAll = global.document.all;
+ var documentDocumentElement = global.document.documentElement;
+ var DocumentCreateElement = global.document.createElement;
+
+ var EventTargetPrototypeAddEventListener = global.EventTarget.prototype.addEventListener;
+ var HTMLElementPrototypeStyleSetter =
+ ObjectGetOwnPropertyDescriptor(global.HTMLElement.prototype, "style").set;
+ var HTMLIFramePrototypeContentWindowGetter =
+ ObjectGetOwnPropertyDescriptor(global.HTMLIFrameElement.prototype, "contentWindow").get;
+ var HTMLScriptElementTextSetter =
+ ObjectGetOwnPropertyDescriptor(global.HTMLScriptElement.prototype, "text").set;
+ var NodePrototypeAppendChild = global.Node.prototype.appendChild;
+ var NodePrototypeRemoveChild = global.Node.prototype.removeChild;
+ var {get: WindowOnErrorGetter, set: WindowOnErrorSetter} =
+ ObjectGetOwnPropertyDescriptor(global, "onerror");
+ var WorkerPrototypePostMessage = Worker.prototype.postMessage;
+ var URLCreateObjectURL = URL.createObjectURL;
+
+ // List of saved window.onerror handlers.
+ var savedGlobalOnError = [];
+
+ // Set |newOnError| as the current window.onerror handler.
+ function setGlobalOnError(newOnError) {
+ var currentOnError = ReflectApply(WindowOnErrorGetter, global, []);
+ ArrayPush(savedGlobalOnError, currentOnError);
+ ReflectApply(WindowOnErrorSetter, global, [newOnError]);
+ }
+
+ // Restore the previous window.onerror handler.
+ function restoreGlobalOnError() {
+ var previousOnError = ArrayPop(savedGlobalOnError);
+ ReflectApply(WindowOnErrorSetter, global, [previousOnError]);
+ }
+
+ /****************************
+ * GENERAL HELPER FUNCTIONS *
+ ****************************/
+
+ function ArrayPush(array, value) {
+ ReflectApply(ObjectDefineProperty, null, [
+ array, array.length,
+ {__proto__: null, value, writable: true, enumerable: true, configurable: true}
+ ]);
+ }
+
+ function ArrayPop(array) {
+ if (array.length) {
+ var item = array[array.length - 1];
+ array.length -= 1;
+ return item;
+ }
+ }
+
+ function AppendChild(elt, kid) {
+ ReflectApply(NodePrototypeAppendChild, elt, [kid]);
+ }
+
+ function CreateElement(name) {
+ return ReflectApply(DocumentCreateElement, document, [name]);
+ }
+
+ function RemoveChild(elt, kid) {
+ ReflectApply(NodePrototypeRemoveChild, elt, [kid]);
+ }
+
+ function CreateWorker(script) {
+ var blob = new Blob([script], {__proto__: null, type: "text/javascript"});
+ return new Worker(URLCreateObjectURL(blob));
+ }
+
+ /****************************
+ * UTILITY FUNCTION EXPORTS *
+ ****************************/
+
+ var evaluate = global.evaluate;
+ if (typeof evaluate !== "function") {
+ // Shim in "evaluate".
+ evaluate = function evaluate(code) {
+ if (typeof code !== "string")
+ throw Error("Expected string argument for evaluate()");
+
+ return GlobalEval(code);
+ };
+
+ global.evaluate = evaluate;
+ }
+
+ var evaluateScript = global.evaluateScript;
+ if (typeof evaluateScript !== "function") {
+ evaluateScript = function evaluateScript(code) {
+ code = String(code);
+ var script = CreateElement("script");
+
+ // Temporarily install a new onerror handler to catch script errors.
+ var hasUncaughtError = false;
+ var uncaughtError;
+ var eventOptions = {__proto__: null, once: true};
+ ReflectApply(EventTargetPrototypeAddEventListener, script, [
+ "beforescriptexecute", function() {
+ setGlobalOnError(function(messageOrEvent, source, lineno, colno, error) {
+ hasUncaughtError = true;
+ uncaughtError = error;
+ return true;
+ });
+ }, eventOptions
+ ]);
+ ReflectApply(EventTargetPrototypeAddEventListener, script, [
+ "afterscriptexecute", function() {
+ restoreGlobalOnError();
+ }, eventOptions
+ ]);
+
+ ReflectApply(HTMLScriptElementTextSetter, script, [code]);
+ AppendChild(documentDocumentElement, script);
+ RemoveChild(documentDocumentElement, script);
+
+ if (hasUncaughtError)
+ throw uncaughtError;
+ };
+
+ global.evaluateScript = evaluateScript;
+ }
+
+ var newGlobal = global.newGlobal;
+ if (typeof newGlobal !== "function") {
+ // Reuse the parent's newGlobal to ensure iframes can be added to the DOM.
+ newGlobal = parent ? parent.newGlobal : function newGlobal() {
+ var iframe = CreateElement("iframe");
+ AppendChild(documentDocumentElement, iframe);
+ var win =
+ ReflectApply(HTMLIFramePrototypeContentWindowGetter, iframe, []);
+
+ // Removing the iframe breaks evaluateScript() and detachArrayBuffer().
+ ReflectApply(HTMLElementPrototypeStyleSetter, iframe, ["display:none"]);
+
+ // Create utility functions in the new global object.
+ var initFunction = ReflectApply(FunctionToString, initializeUtilityExports, []);
+ win.Function("parent", initFunction + "; initializeUtilityExports(this, parent);")(global);
+
+ return win;
+ };
+
+ global.newGlobal = newGlobal;
+ }
+
+ var detachArrayBuffer = global.detachArrayBuffer;
+ if (typeof detachArrayBuffer !== "function") {
+ var worker = null;
+ detachArrayBuffer = function detachArrayBuffer(arrayBuffer) {
+ if (worker === null) {
+ worker = CreateWorker("/* black hole */");
+ }
+ ReflectApply(WorkerPrototypePostMessage, worker, ["detach", [arrayBuffer]]);
+ };
+
+ global.detachArrayBuffer = detachArrayBuffer;
+ }
+
+ var createIsHTMLDDA = global.createIsHTMLDDA;
+ if (typeof createIsHTMLDDA !== "function") {
+ createIsHTMLDDA = function() {
+ return documentAll;
+ };
+
+ global.createIsHTMLDDA = createIsHTMLDDA;
+ }
+})(this);
+
+(function(global) {
+ "use strict";
+
+ /**********************************************************************
+ * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
+ **********************************************************************/
+
+ var undefined; // sigh
+
+ var Error = global.Error;
+ var Number = global.Number;
+ var Object = global.Object;
+ var String = global.String;
+
+ var decodeURIComponent = global.decodeURIComponent;
+ var ReflectApply = global.Reflect.apply;
+ var ObjectDefineProperty = Object.defineProperty;
+ var ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
+ var ObjectPrototypeIsPrototypeOf = Object.prototype.isPrototypeOf;
+
+ // BEWARE: ObjectGetOwnPropertyDescriptor is only safe to use if its result
+ // is inspected using own-property-examining functionality. Directly
+ // accessing properties on a returned descriptor without first
+ // verifying the property's existence can invoke user-modifiable
+ // behavior.
+ var ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+
+ var window = global.window;
+ var document = global.document;
+ var documentDocumentElement = document.documentElement;
+ var DocumentCreateElement = document.createElement;
+ var ElementSetClassName =
+ ObjectGetOwnPropertyDescriptor(global.Element.prototype, "className").set;
+ var NodePrototypeAppendChild = global.Node.prototype.appendChild;
+ var NodePrototypeTextContentSetter =
+ ObjectGetOwnPropertyDescriptor(global.Node.prototype, "textContent").set;
+ var setTimeout = global.setTimeout;
+
+ // Saved harness functions.
+ var dump = global.dump;
+ var gczeal = global.gczeal;
+ var print = global.print;
+ var reportFailure = global.reportFailure;
+ var TestCase = global.TestCase;
+
+ var SpecialPowers = global.SpecialPowers;
+ var SpecialPowersCu = SpecialPowers.Cu;
+ var SpecialPowersForceGC = SpecialPowers.forceGC;
+ var TestingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
+ var ClearKeptObjects = TestingFunctions.clearKeptObjects;
+
+ // Cached DOM nodes used by the test harness itself. (We assume the test
+ // doesn't misbehave in a way that actively interferes with what the test
+ // harness runner observes, e.g. navigating the page to a different location.
+ // Short of running every test in a worker -- which has its own problems --
+ // there's no way to isolate a test from the page to that extent.)
+ var printOutputContainer =
+ global.document.getElementById("jsreftest-print-output-container");
+
+ /****************************
+ * GENERAL HELPER FUNCTIONS *
+ ****************************/
+
+ function ArrayPush(array, value) {
+ ReflectApply(ObjectDefineProperty, null, [
+ array, array.length,
+ {__proto__: null, value, writable: true, enumerable: true, configurable: true}
+ ]);
+ }
+
+ function ArrayPop(array) {
+ if (array.length) {
+ var item = array[array.length - 1];
+ array.length -= 1;
+ return item;
+ }
+ }
+
+ function HasOwnProperty(object, property) {
+ return ReflectApply(ObjectPrototypeHasOwnProperty, object, [property]);
+ }
+
+ function AppendChild(elt, kid) {
+ ReflectApply(NodePrototypeAppendChild, elt, [kid]);
+ }
+
+ function CreateElement(name) {
+ return ReflectApply(DocumentCreateElement, document, [name]);
+ }
+
+ function SetTextContent(element, text) {
+ ReflectApply(NodePrototypeTextContentSetter, element, [text]);
+ }
+
+ // Object containing the set options.
+ var currentOptions;
+
+ // browser.js version of shell.js' |shellOptionsClear| function.
+ function browserOptionsClear() {
+ for (var optionName in currentOptions) {
+ delete currentOptions[optionName];
+ SpecialPowersCu[optionName] = false;
+ }
+ }
+
+ // This function is *only* used by shell.js's for-browsers |print()| function!
+ // It's only defined/exported here because it needs CreateElement and friends,
+ // only defined here, and we're not yet ready to move them to shell.js.
+ function AddPrintOutput(s) {
+ var msgDiv = CreateElement("div");
+ SetTextContent(msgDiv, s);
+ AppendChild(printOutputContainer, msgDiv);
+ }
+ global.AddPrintOutput = AddPrintOutput;
+
+ /*************************************************************************
+ * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) *
+ *************************************************************************/
+
+ // This overwrites shell.js's version that merely prints the given string.
+ function writeHeaderToLog(string) {
+ string = String(string);
+
+ // First dump to the console.
+ dump(string + "\n");
+
+ // Then output to the page.
+ var h2 = CreateElement("h2");
+ SetTextContent(h2, string);
+ AppendChild(printOutputContainer, h2);
+ }
+ global.writeHeaderToLog = writeHeaderToLog;
+
+ /*************************
+ * GLOBAL ERROR HANDLING *
+ *************************/
+
+ // Possible values:
+ // - "Unknown" if no error is expected,
+ // - "error" if no specific error type is expected,
+ // - otherwise the error name, e.g. "TypeError" or "RangeError".
+ var expectedError;
+
+ window.onerror = function (msg, page, line, column, error) {
+ // Unset all options even when the test finished with an error.
+ browserOptionsClear();
+
+ if (DESCRIPTION === undefined) {
+ DESCRIPTION = "Unknown";
+ }
+
+ var actual = "error";
+ var expected = expectedError;
+ if (expected !== "error" && expected !== "Unknown") {
+ // Check the error type when an actual Error object is available.
+ // NB: The |error| parameter of the onerror handler is not required to
+ // be an Error instance.
+ if (ReflectApply(ObjectPrototypeIsPrototypeOf, Error.prototype, [error])) {
+ actual = error.constructor.name;
+ } else {
+ expected = "error";
+ }
+ }
+
+ var reason = `${page}:${line}: ${msg}`;
+ new TestCase(DESCRIPTION, expected, actual, reason);
+
+ reportFailure(msg);
+ };
+
+ /**********************************************
+ * BROWSER IMPLEMENTATION FOR SHELL FUNCTIONS *
+ **********************************************/
+
+ function gc() {
+ try {
+ SpecialPowersForceGC();
+ } catch (ex) {
+ print("gc: " + ex);
+ }
+ }
+ global.gc = gc;
+
+ global.clearKeptObjects = ClearKeptObjects;
+
+ function options(aOptionName) {
+ // return value of options() is a comma delimited list
+ // of the previously set values
+
+ var value = "";
+ for (var optionName in currentOptions) {
+ if (value)
+ value += ",";
+ value += optionName;
+ }
+
+ if (aOptionName) {
+ if (!HasOwnProperty(SpecialPowersCu, aOptionName)) {
+ // This test is trying to flip an unsupported option, so it's
+ // likely no longer testing what it was supposed to. Fail it
+ // hard.
+ throw "Unsupported JSContext option '" + aOptionName + "'";
+ }
+
+ if (aOptionName in currentOptions) {
+ // option is set, toggle it to unset
+ delete currentOptions[aOptionName];
+ SpecialPowersCu[aOptionName] = false;
+ } else {
+ // option is not set, toggle it to set
+ currentOptions[aOptionName] = true;
+ SpecialPowersCu[aOptionName] = true;
+ }
+ }
+
+ return value;
+ }
+ global.options = options;
+
+ /****************************************
+ * HARNESS SETUP AND TEARDOWN FUNCTIONS *
+ ****************************************/
+
+ function jsTestDriverBrowserInit() {
+ // Unset all options before running any test code, cf. the call to
+ // |shellOptionsClear| in shell.js' set-up code.
+ for (var optionName of ["strict_mode"]) {
+ if (!HasOwnProperty(SpecialPowersCu, optionName))
+ throw "options is out of sync with Components.utils";
+
+ // Option is set, toggle it to unset. (Reading an option is a cheap
+ // operation, but setting is relatively expensive, so only assign if
+ // necessary.)
+ if (SpecialPowersCu[optionName])
+ SpecialPowersCu[optionName] = false;
+ }
+
+ // Initialize with an empty set, because we just turned off all options.
+ currentOptions = Object.create(null);
+
+ if (document.location.search.indexOf("?") !== 0) {
+ // not called with a query string
+ return;
+ }
+
+ var properties = Object.create(null);
+ var fields = document.location.search.slice(1).split(";");
+ for (var i = 0; i < fields.length; i++) {
+ var propertycaptures = /^([^=]+)=(.*)$/.exec(fields[i]);
+ if (propertycaptures === null) {
+ properties[fields[i]] = true;
+ } else {
+ properties[propertycaptures[1]] = decodeURIComponent(propertycaptures[2]);
+ }
+ }
+
+ global.gTestPath = properties.test;
+
+ var testpathparts = properties.test.split("/");
+ if (testpathparts.length < 2) {
+ // must have at least suitepath/testcase.js
+ return;
+ }
+
+ var testFileName = testpathparts[testpathparts.length - 1];
+
+ if (testFileName.endsWith("-n.js")) {
+ // Negative test without a specific error type.
+ expectedError = "error";
+ } else if (properties.error) {
+ // Negative test which expects a specific error type.
+ expectedError = properties.error;
+ } else {
+ // No error is expected.
+ expectedError = "Unknown";
+ }
+
+ if (properties.gczeal) {
+ gczeal(Number(properties.gczeal));
+ }
+
+ // Display the test path in the title.
+ document.title = properties.test;
+
+ // Output script tags for shell.js, then browser.js, at each level of the
+ // test path hierarchy.
+ var prepath = "";
+ var scripts = [];
+ for (var i = 0; i < testpathparts.length - 1; i++) {
+ prepath += testpathparts[i] + "/";
+
+ scripts.push({src: prepath + "shell.js", module: false});
+ scripts.push({src: prepath + "browser.js", module: false});
+ }
+
+ // Output the test script itself.
+ var moduleTest = !!properties.module;
+ scripts.push({src: prepath + testFileName, module: moduleTest});
+
+ // Finally output the driver-end script to advance to the next test.
+ scripts.push({src: "js-test-driver-end.js", module: false});
+
+ if (properties.async) {
+ gDelayTestDriverEnd = true;
+ }
+
+ if (!moduleTest) {
+ for (var i = 0; i < scripts.length; i++) {
+ var src = scripts[i].src;
+ document.write(`<script src="${src}" charset="utf-8"><\/script>`);
+ }
+ } else {
+ // Modules are loaded asynchronously by default, but for the test harness
+ // we need to execute all scripts and modules one after the other.
+
+ // Appends the next script element to the DOM.
+ function appendScript(index) {
+ var script = scriptElements[index];
+ scriptElements[index] = null;
+ if (script !== null) {
+ ReflectApply(NodePrototypeAppendChild, documentDocumentElement, [script]);
+ }
+ }
+
+ // Create all script elements upfront, so we don't need to worry about
+ // modified built-ins.
+ var scriptElements = [];
+ for (var i = 0; i < scripts.length; i++) {
+ var spec = scripts[i];
+
+ var script = document.createElement("script");
+ script.charset = "utf-8";
+ if (spec.module) {
+ script.type = "module";
+ }
+ script.src = spec.src;
+
+ let nextScriptIndex = i + 1;
+ if (nextScriptIndex < scripts.length) {
+ var callNextAppend = () => appendScript(nextScriptIndex);
+ script.addEventListener("afterscriptexecute", callNextAppend, {once: true});
+
+ // Module scripts don't fire the "afterscriptexecute" event when there
+ // was an error, instead the "error" event is emitted. So listen for
+ // both events when creating module scripts.
+ if (spec.module) {
+ script.addEventListener("error", callNextAppend, {once: true});
+ }
+ }
+
+ scriptElements[i] = script;
+ }
+
+ // Append the first script.
+ appendScript(0);
+ }
+ }
+
+ global.gDelayTestDriverEnd = false;
+
+ function jsTestDriverEnd() {
+ // gDelayTestDriverEnd is used to delay collection of the test result and
+ // signal to Spider so that tests can continue to run after page load has
+ // fired. They are responsible for setting gDelayTestDriverEnd = true then
+ // when completed, setting gDelayTestDriverEnd = false then calling
+ // jsTestDriverEnd()
+
+ if (gDelayTestDriverEnd) {
+ return;
+ }
+
+ window.onerror = null;
+
+ // Unset all options when the test has finished.
+ browserOptionsClear();
+
+ if (window.opener && window.opener.runNextTest) {
+ if (window.opener.reportCallBack) {
+ window.opener.reportCallBack(window.opener.gWindow);
+ }
+
+ setTimeout("window.opener.runNextTest()", 250);
+ } else {
+ // tell reftest the test is complete.
+ ReflectApply(ElementSetClassName, documentDocumentElement, [""]);
+ // tell Spider page is complete
+ gPageCompleted = true;
+ }
+ }
+ global.jsTestDriverEnd = jsTestDriverEnd;
+
+ /***************************************************************************
+ * DIALOG CLOSER, PRESUMABLY TO CLOSE SLOW SCRIPT DIALOGS AND OTHER POPUPS *
+ ***************************************************************************/
+
+ // dialog closer from http://bclary.com/projects/spider/spider/chrome/content/spider/dialog-closer.js
+
+ // Use an array to handle the case where multiple dialogs appear at one time.
+ var dialogCloserSubjects = [];
+ var dialogCloser = SpecialPowers
+ .Cc["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(SpecialPowers.Ci.nsIWindowWatcher);
+ var dialogCloserObserver = {
+ observe(subject, topic, data) {
+ if (topic === "domwindowopened" && subject.isChromeWindow) {
+ ArrayPush(dialogCloserSubjects, subject);
+
+ // Timeout of 0 needed when running under reftest framework.
+ subject.setTimeout(closeDialog, 0);
+ }
+ }
+ };
+
+ function closeDialog() {
+ while (dialogCloserSubjects.length > 0) {
+ var subject = ArrayPop(dialogCloserSubjects);
+ subject.close();
+ }
+ }
+
+ function unregisterDialogCloser() {
+ gczeal(0);
+
+ if (!dialogCloserObserver || !dialogCloser) {
+ return;
+ }
+
+ dialogCloser.unregisterNotification(dialogCloserObserver);
+
+ dialogCloserObserver = null;
+ dialogCloser = null;
+ }
+
+ dialogCloser.registerNotification(dialogCloserObserver);
+ window.addEventListener("unload", unregisterDialogCloser, true);
+
+ /*******************************************
+ * RUN ONCE CODE TO SETUP ADDITIONAL STATE *
+ *******************************************/
+
+ jsTestDriverBrowserInit();
+
+})(this);
+
+var gPageCompleted;