From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- accessible/tests/browser/shared-head.js | 973 ++++++++++++++++++++++++++++++++ 1 file changed, 973 insertions(+) create mode 100644 accessible/tests/browser/shared-head.js (limited to 'accessible/tests/browser/shared-head.js') diff --git a/accessible/tests/browser/shared-head.js b/accessible/tests/browser/shared-head.js new file mode 100644 index 0000000000..fe87a77765 --- /dev/null +++ b/accessible/tests/browser/shared-head.js @@ -0,0 +1,973 @@ +/* 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/. */ + +"use strict"; + +/* import-globals-from ../mochitest/common.js */ +/* import-globals-from ../mochitest/layout.js */ +/* import-globals-from ../mochitest/promisified-events.js */ + +/* exported Logger, MOCHITESTS_DIR, invokeSetAttribute, invokeFocus, + invokeSetStyle, getAccessibleDOMNodeID, getAccessibleTagName, + addAccessibleTask, findAccessibleChildByID, isDefunct, + CURRENT_CONTENT_DIR, loadScripts, loadContentScripts, snippetToURL, + Cc, Cu, arrayFromChildren, forceGC, contentSpawnMutation, + DEFAULT_IFRAME_ID, DEFAULT_IFRAME_DOC_BODY_ID, invokeContentTask, + matchContentDoc, currentContentDoc, getContentDPR, + waitForImageMap, getContentBoundsForDOMElm, untilCacheIs, + untilCacheOk, testBoundsWithContent, waitForContentPaint, + runPython */ + +const CURRENT_FILE_DIR = "/browser/accessible/tests/browser/"; + +/** + * Current browser test directory path used to load subscripts. + */ +const CURRENT_DIR = `chrome://mochitests/content${CURRENT_FILE_DIR}`; +/** + * A11y mochitest directory where we find common files used in both browser and + * plain tests. + */ +const MOCHITESTS_DIR = + "chrome://mochitests/content/a11y/accessible/tests/mochitest/"; +/** + * A base URL for test files used in content. + */ +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const CURRENT_CONTENT_DIR = `http://example.com${CURRENT_FILE_DIR}`; + +const LOADED_CONTENT_SCRIPTS = new Map(); + +const DEFAULT_CONTENT_DOC_BODY_ID = "body"; +const DEFAULT_IFRAME_ID = "default-iframe-id"; +const DEFAULT_IFRAME_DOC_BODY_ID = "default-iframe-body-id"; + +const HTML_MIME_TYPE = "text/html"; +const XHTML_MIME_TYPE = "application/xhtml+xml"; + +function loadHTMLFromFile(path) { + // Load the HTML to return in the response from file. + // Since it's relative to the cwd of the test runner, we start there and + // append to get to the actual path of the file. + const testHTMLFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile); + const dirs = path.split("/"); + for (let i = 0; i < dirs.length; i++) { + testHTMLFile.append(dirs[i]); + } + + const testHTMLFileStream = Cc[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Ci.nsIFileInputStream); + testHTMLFileStream.init(testHTMLFile, -1, 0, 0); + const testHTML = NetUtil.readInputStreamToString( + testHTMLFileStream, + testHTMLFileStream.available() + ); + + return testHTML; +} + +let gIsIframe = false; +let gIsRemoteIframe = false; + +function currentContentDoc() { + return gIsIframe ? DEFAULT_IFRAME_DOC_BODY_ID : DEFAULT_CONTENT_DOC_BODY_ID; +} + +/** + * Accessible event match criteria based on the id of the current document + * accessible in test. + * + * @param {nsIAccessibleEvent} event + * Accessible event to be tested for a match. + * + * @return {Boolean} + * True if accessible event's accessible object ID matches current + * document accessible ID. + */ +function matchContentDoc(event) { + return getAccessibleDOMNodeID(event.accessible) === currentContentDoc(); +} + +/** + * Used to dump debug information. + */ +let Logger = { + /** + * Set up this variable to dump log messages into console. + */ + dumpToConsole: false, + + /** + * Set up this variable to dump log messages into error console. + */ + dumpToAppConsole: false, + + /** + * Return true if dump is enabled. + */ + get enabled() { + return this.dumpToConsole || this.dumpToAppConsole; + }, + + /** + * Dump information into console if applicable. + */ + log(msg) { + if (this.enabled) { + this.logToConsole(msg); + this.logToAppConsole(msg); + } + }, + + /** + * Log message to console. + */ + logToConsole(msg) { + if (this.dumpToConsole) { + dump(`\n${msg}\n`); + } + }, + + /** + * Log message to error console. + */ + logToAppConsole(msg) { + if (this.dumpToAppConsole) { + Services.console.logStringMessage(`${msg}`); + } + }, +}; + +/** + * Asynchronously set or remove content element's attribute (in content process + * if e10s is enabled). + * @param {Object} browser current "tabbrowser" element + * @param {String} id content element id + * @param {String} attr attribute name + * @param {String?} value optional attribute value, if not present, remove + * attribute + * @return {Promise} promise indicating that attribute is set/removed + */ +function invokeSetAttribute(browser, id, attr, value) { + if (value) { + Logger.log(`Setting ${attr} attribute to ${value} for node with id: ${id}`); + } else { + Logger.log(`Removing ${attr} attribute from node with id: ${id}`); + } + + return invokeContentTask( + browser, + [id, attr, value], + (contentId, contentAttr, contentValue) => { + let elm = content.document.getElementById(contentId); + if (contentValue) { + elm.setAttribute(contentAttr, contentValue); + } else { + elm.removeAttribute(contentAttr); + } + } + ); +} + +/** + * Asynchronously set or remove content element's style (in content process if + * e10s is enabled, or in fission process if fission is enabled and a fission + * frame is present). + * @param {Object} browser current "tabbrowser" element + * @param {String} id content element id + * @param {String} aStyle style property name + * @param {String?} aValue optional style property value, if not present, + * remove style + * @return {Promise} promise indicating that style is set/removed + */ +function invokeSetStyle(browser, id, style, value) { + if (value) { + Logger.log(`Setting ${style} style to ${value} for node with id: ${id}`); + } else { + Logger.log(`Removing ${style} style from node with id: ${id}`); + } + + return invokeContentTask( + browser, + [id, style, value], + (contentId, contentStyle, contentValue) => { + const elm = content.document.getElementById(contentId); + if (contentValue) { + elm.style[contentStyle] = contentValue; + } else { + delete elm.style[contentStyle]; + } + } + ); +} + +/** + * Asynchronously set focus on a content element (in content process if e10s is + * enabled, or in fission process if fission is enabled and a fission frame is + * present). + * @param {Object} browser current "tabbrowser" element + * @param {String} id content element id + * @return {Promise} promise indicating that focus is set + */ +function invokeFocus(browser, id) { + Logger.log(`Setting focus on a node with id: ${id}`); + + return invokeContentTask(browser, [id], contentId => { + const elm = content.document.getElementById(contentId); + if (elm.editor) { + elm.selectionStart = elm.selectionEnd = elm.value.length; + } + + elm.focus(); + }); +} + +/** + * Get DPR for a specific content window. + * @param browser + * Browser for which we want its content window's DPR reported. + * + * @return {Promise} + * Promise with the value that resolves to the devicePixelRatio of the + * content window of a given browser. + * + */ +function getContentDPR(browser) { + return invokeContentTask(browser, [], () => content.window.devicePixelRatio); +} + +/** + * Asynchronously perform a task in content (in content process if e10s is + * enabled, or in fission process if fission is enabled and a fission frame is + * present). + * @param {Object} browser current "tabbrowser" element + * @param {Array} args arguments for the content task + * @param {Function} task content task function + * + * @return {Promise} promise indicating that content task is complete + */ +function invokeContentTask(browser, args, task) { + return SpecialPowers.spawn( + browser, + [DEFAULT_IFRAME_ID, task.toString(), ...args], + (iframeId, contentTask, ...contentArgs) => { + // eslint-disable-next-line no-eval + const runnableTask = eval(` + (() => { + return (${contentTask}); + })();`); + const frame = content.document.getElementById(iframeId); + + return frame + ? SpecialPowers.spawn(frame, contentArgs, runnableTask) + : runnableTask.call(this, ...contentArgs); + } + ); +} + +/** + * Compare process ID's between the top level content process and possible + * remote/local iframe proccess. + * @param {Object} browser + * Top level browser object for a tab. + * @param {Boolean} isRemote + * Indicates if we expect the iframe content process to be remote or not. + */ +async function comparePIDs(browser, isRemote) { + function getProcessID() { + return Services.appinfo.processID; + } + + const contentPID = await SpecialPowers.spawn(browser, [], getProcessID); + const iframePID = await invokeContentTask(browser, [], getProcessID); + is( + isRemote, + contentPID !== iframePID, + isRemote + ? "Remote IFRAME is in a different process." + : "IFRAME is in the same process." + ); +} + +/** + * Load a list of scripts into the test + * @param {Array} scripts a list of scripts to load + */ +function loadScripts(...scripts) { + for (let script of scripts) { + let path = + typeof script === "string" + ? `${CURRENT_DIR}${script}` + : `${script.dir}${script.name}`; + Services.scriptloader.loadSubScript(path, this); + } +} + +/** + * Load a list of scripts into target's content. + * @param {Object} target + * target for loading scripts into + * @param {Array} scripts + * a list of scripts to load into content + */ +async function loadContentScripts(target, ...scripts) { + for (let { script, symbol } of scripts) { + let contentScript = `${CURRENT_DIR}${script}`; + let loadedScriptSet = LOADED_CONTENT_SCRIPTS.get(contentScript); + if (!loadedScriptSet) { + loadedScriptSet = new WeakSet(); + LOADED_CONTENT_SCRIPTS.set(contentScript, loadedScriptSet); + } else if (loadedScriptSet.has(target)) { + continue; + } + + await SpecialPowers.spawn( + target, + [contentScript, symbol], + async (_contentScript, importSymbol) => { + let module = ChromeUtils.importESModule(_contentScript); + content.window[importSymbol] = module[importSymbol]; + } + ); + loadedScriptSet.add(target); + } +} + +function attrsToString(attrs) { + return Object.entries(attrs) + .map(([attr, value]) => `${attr}=${JSON.stringify(value)}`) + .join(" "); +} + +function wrapWithIFrame(doc, options = {}) { + let src; + let { iframeAttrs = {}, iframeDocBodyAttrs = {} } = options; + iframeDocBodyAttrs = { + id: DEFAULT_IFRAME_DOC_BODY_ID, + ...iframeDocBodyAttrs, + }; + if (options.remoteIframe) { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + const srcURL = new URL(`http://example.net/document-builder.sjs`); + if (doc.endsWith("html")) { + srcURL.searchParams.append("file", `${CURRENT_FILE_DIR}${doc}`); + } else { + srcURL.searchParams.append( + "html", + ` + + + + Accessibility Fission Test + + ${doc} + ` + ); + } + src = srcURL.href; + } else { + const mimeType = doc.endsWith("xhtml") ? XHTML_MIME_TYPE : HTML_MIME_TYPE; + if (doc.endsWith("html")) { + doc = loadHTMLFromFile(`${CURRENT_FILE_DIR}${doc}`); + doc = doc.replace( + //, + `` + ); + } else { + doc = ` + ${doc}`; + } + + src = `data:${mimeType};charset=utf-8,${encodeURIComponent(doc)}`; + } + + iframeAttrs = { + id: DEFAULT_IFRAME_ID, + src, + ...iframeAttrs, + }; + + return `