Bug 1543575 - provide an option for running accessibility browser chrome e10s tests in fission process. r=Jamie

Differential Revision: https://phabricator.services.mozilla.com/D48549

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Yura Zenevich 2019-10-17 15:14:50 +00:00
Родитель 85544e7033
Коммит 08b6f8ee1e
5 изменённых файлов: 269 добавлений и 49 удалений

Просмотреть файл

@ -5,6 +5,7 @@ support-files =
*.jsm
head.js
shared-head.js
fission_document_builder.sjs
[browser_shutdown_acc_reference.js]
skip-if = (os == 'linux' && debug && bits == 64) #Bug 1421307

Просмотреть файл

@ -19,7 +19,7 @@ async function runTests(browser, accDoc) {
onFocus = waitForEvent(EVENT_FOCUS, "buttonInputDoc");
let url = snippetToURL(`<input id="input" type="button" value="button">`, {
id: "buttonInputDoc",
contentDocBodyAttrs: { id: "buttonInputDoc" },
});
browser.loadURI(url, {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),

Просмотреть файл

@ -63,7 +63,9 @@ addAccessibleTask(
`
<button id="button">button</button>
<iframe id="editabledoc"
src="${snippetToURL("", { id: "body2", contentEditable: "true" })}">
src="${snippetToURL("", {
contentDocBodyAttrs: { id: "body2", contentEditable: "true" },
})}">
</iframe>
<div id="alertdialog" style="display: none" tabindex="-1" role="alertdialog" aria-labelledby="title2" aria-describedby="desc2">
<div id="title2">Blah blah</div>

Просмотреть файл

@ -0,0 +1,60 @@
/* 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";
// eslint-disable-next-line mozilla/use-chromeutils-import
Cu.import("resource://gre/modules/NetUtil.jsm");
// eslint-disable-next-line mozilla/reject-importGlobalProperties
Cu.importGlobalProperties(["URLSearchParams"]);
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 =
// eslint-disable-next-line mozilla/use-services
Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.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;
}
// eslint-disable-next-line no-unused-vars
function handleRequest(request, response) {
const queryString = new URLSearchParams(request.queryString);
const html = queryString.get("html");
response.setHeader("Cache-Control", "no-cache", false);
if (html) {
response.setHeader("Content-Type", "text/html", false);
response.write(html);
return;
}
const path = queryString.get("file");
const doc = loadHTMLFromFile(path);
response.setHeader(
"Content-Type",
path.endsWith(".xhtml") ? "application/xhtml+xml" : "text/html",
false
);
// This is a hack to set the correct id for the content document that is to be
// loaded in the fission frame.
response.write(doc.replace(`id="body"`, `id="fission-body"`));
}

Просмотреть файл

@ -11,13 +11,16 @@
invokeSetStyle, getAccessibleDOMNodeID, getAccessibleTagName,
addAccessibleTask, findAccessibleChildByID, isDefunct,
CURRENT_CONTENT_DIR, loadScripts, loadContentScripts, snippetToURL,
Cc, Cu, arrayFromChildren, forceGC, contentSpawnMutation */
Cc, Cu, arrayFromChildren, forceGC, contentSpawnMutation,
FISSION_IFRAME_ID, DEFAULT_FISSION_DOC_BODY_ID, invokeContentTask,
matchContentDoc, currentContentDoc */
const CURRENT_FILE_DIR = "/browser/accessible/tests/browser/";
/**
* Current browser test directory path used to load subscripts.
*/
const CURRENT_DIR =
"chrome://mochitests/content/browser/accessible/tests/browser/";
const CURRENT_DIR = `chrome://mochitests/content${CURRENT_FILE_DIR}`;
/**
* A11y mochitest directory where we find common files used in both browser and
* plain tests.
@ -27,11 +30,35 @@ const MOCHITESTS_DIR =
/**
* A base URL for test files used in content.
*/
const CURRENT_CONTENT_DIR =
"http://example.com/browser/accessible/tests/browser/";
const CURRENT_CONTENT_DIR = `http://example.com${CURRENT_FILE_DIR}`;
const LOADED_CONTENT_SCRIPTS = new Map();
const DEFAULT_CONTENT_DOC_BODY_ID = "body";
const FISSION_IFRAME_ID = "fission-iframe";
const DEFAULT_FISSION_DOC_BODY_ID = "fission-body";
let gIsFission = false;
function currentContentDoc() {
return gIsFission ? DEFAULT_FISSION_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.
*/
@ -98,7 +125,8 @@ function invokeSetAttribute(browser, id, attr, value) {
} else {
Logger.log(`Removing ${attr} attribute from node with id: ${id}`);
}
return SpecialPowers.spawn(
return invokeContentTask(
browser,
[id, attr, value],
(contentId, contentAttr, contentValue) => {
@ -114,7 +142,8 @@ function invokeSetAttribute(browser, id, attr, value) {
/**
* Asynchronously set or remove content element's style (in content process if
* e10s is enabled).
* 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
@ -128,11 +157,12 @@ function invokeSetStyle(browser, id, style, value) {
} else {
Logger.log(`Removing ${style} style from node with id: ${id}`);
}
return SpecialPowers.spawn(
return invokeContentTask(
browser,
[id, style, value],
(contentId, contentStyle, contentValue) => {
let elm = content.document.getElementById(contentId);
const elm = content.document.getElementById(contentId);
if (contentValue) {
elm.style[contentStyle] = contentValue;
} else {
@ -144,22 +174,54 @@ function invokeSetStyle(browser, id, style, value) {
/**
* Asynchronously set focus on a content element (in content process if e10s is
* enabled).
* 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 SpecialPowers.spawn(browser, [id], contentId => {
let elm = content.document.getElementById(contentId);
return invokeContentTask(browser, [id], contentId => {
const elm = content.document.getElementById(contentId);
if (elm.editor) {
elm.selectionStart = elm.selectionEnd = elm.value.length;
}
elm.focus();
});
}
/**
* 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,
[FISSION_IFRAME_ID, task.toString(), ...args],
(fissionFrameId, contentTask, ...contentArgs) => {
// eslint-disable-next-line no-eval
const runnableTask = eval(`
(() => {
return (${contentTask});
})();`);
const frame = content.document.getElementById(fissionFrameId);
return frame
? SpecialPowers.spawn(frame, contentArgs, runnableTask)
: runnableTask.call(this, ...contentArgs);
}
);
}
/**
* Load a list of scripts into the test
* @param {Array} scripts a list of scripts to load
@ -207,49 +269,87 @@ async function loadContentScripts(target, ...scripts) {
}
}
/**
* Takes an HTML snippet and returns an encoded URI for a full document
* with the snippet.
* @param {String} snippet a markup snippet.
* @param {Object} bodyAttrs extra attributes to use in the body tag. Default is
* { id: "body "}.
* @return {String} a base64 encoded data url of the document container the
* snippet.
**/
function snippetToURL(snippet, bodyAttrs = {}) {
let attrs = Object.assign({}, { id: "body" }, bodyAttrs);
let attrsString = Object.entries(attrs)
function attrsToString(attrs) {
return Object.entries(attrs)
.map(([attr, value]) => `${attr}=${JSON.stringify(value)}`)
.join(" ");
let encodedDoc = encodeURIComponent(
}
function wrapWithFissionIFrame(doc, options = {}) {
const srcURL = new URL(`${CURRENT_CONTENT_DIR}fission_document_builder.sjs`);
if (doc.endsWith("html")) {
srcURL.searchParams.append("file", `${CURRENT_FILE_DIR}e10s/${doc}`);
} else {
const { fissionDocBodyAttrs = {} } = options;
const attrs = {
id: DEFAULT_FISSION_DOC_BODY_ID,
...fissionDocBodyAttrs,
};
srcURL.searchParams.append(
"html",
`<html>
<head>
<meta charset="utf-8"/>
<title>Accessibility Fission Test</title>
</head>
<body ${attrsToString(attrs)}>${doc}</body>
</html>`
);
}
return `<iframe id="${FISSION_IFRAME_ID}" src="${srcURL.href}"/>`;
}
/**
* Takes an HTML snippet or HTML doc url and returns an encoded URI for a full
* document with the snippet or the URL as a source for the IFRAME.
* @param {String} doc
* a markup snippet or url.
* @param {Object} options
* additonal options:
* - {Boolean} fission
* true if the content document should be wrapped inside a fission
* iframe
* - {Object} contentDocBodyAttrs
* a set of attributes to be applied to a top level content document
* body
* - {Object} fissionDocBodyAttrs
* a set of attributes to be applied to a fission content document body
* @return {String}
* a base64 encoded data url of the document container the snippet.
**/
function snippetToURL(doc, options = {}) {
const { contentDocBodyAttrs = {} } = options;
const attrs = {
id: DEFAULT_CONTENT_DOC_BODY_ID,
...contentDocBodyAttrs,
};
if (options.fission) {
doc = wrapWithFissionIFrame(doc, options);
}
const encodedDoc = encodeURIComponent(
`<html>
<head>
<meta charset="utf-8"/>
<title>Accessibility Test</title>
</head>
<body ${attrsString}>${snippet}</body>
<body ${attrsToString(attrs)}>${doc}</body>
</html>`
);
return `data:text/html;charset=utf-8,${encodedDoc}`;
}
/**
* A wrapper around browser test add_task that triggers an accessible test task
* as a new browser test task with given document, data URL or markup snippet.
* @param {String} doc URL (relative to current directory) or
* data URL or markup snippet that is used
* to test content with
* @param {Function|AsyncFunction} task a generator or a function with tests to
* run
*/
function addAccessibleTask(doc, task) {
add_task(async function() {
function accessibleTask(doc, task, options = {}) {
return async function() {
let url;
if (doc.includes("doc_")) {
if (doc.endsWith("html") && !options.fission) {
url = `${CURRENT_CONTENT_DIR}e10s/${doc}`;
} else {
url = snippetToURL(doc);
url = snippetToURL(doc, options);
}
registerCleanupFunction(() => {
@ -260,7 +360,21 @@ function addAccessibleTask(doc, task) {
}
});
let onDocLoad = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, "body");
const onContentDocLoad = waitForEvent(
EVENT_DOCUMENT_LOAD_COMPLETE,
DEFAULT_CONTENT_DOC_BODY_ID
);
let onFissionDocLoad;
if (options.fission) {
gIsFission = true;
if (gFissionBrowser) {
onFissionDocLoad = waitForEvent(
EVENT_DOCUMENT_LOAD_COMPLETE,
DEFAULT_FISSION_DOC_BODY_ID
);
}
}
await BrowserTestUtils.withNewTab(
{
@ -285,11 +399,54 @@ function addAccessibleTask(doc, task) {
);
Logger.log(`Actually remote browser: ${browser.isRemoteBrowser}`);
let event = await onDocLoad;
await task(browser, event.accessible);
const { accessible: docAccessible } = await onContentDocLoad;
let fissionDocAccessible;
if (options.fission) {
fissionDocAccessible = gFissionBrowser
? (await onFissionDocLoad).accessible
: findAccessibleChildByID(docAccessible, FISSION_IFRAME_ID)
.firstChild;
}
await task(
browser,
fissionDocAccessible || docAccessible,
fissionDocAccessible && docAccessible
);
}
);
});
};
}
/**
* A wrapper around browser test add_task that triggers an accessible test task
* as a new browser test task with given document, data URL or markup snippet.
* @param {String} doc
* URL (relative to current directory) or data URL or markup snippet
* that is used to test content with
* @param {Function|AsyncFunction} task
* a generator or a function with tests to run
* @param {null|Object} options
* Options for running accessibility test tasks:
* - {Boolean} topLevel
* Flag to run the test with content in the top level content process.
* Default is true.
* - {Boolean} iframe
* Flag to run the test with content wrapped in an iframe. Default is
* false.
*/
function addAccessibleTask(
doc,
task,
{ topLevel = true, iframe = false } = {}
) {
if (topLevel) {
add_task(accessibleTask(doc, task));
}
if (iframe) {
add_task(accessibleTask(doc, task, { fission: true }));
}
}
/**
@ -401,20 +558,20 @@ async function contentSpawnMutation(browser, waitFor, func, args = []) {
// This stops the refreh driver from doing its regular ticks, and leaves
// us in control.
await SpecialPowers.spawn(browser, [], tick);
await invokeContentTask(browser, [], tick);
// Perform the tree mutation.
await SpecialPowers.spawn(browser, args, func);
await invokeContentTask(browser, args, func);
// Do one tick to flush our queue (insertions, relocations, etc.)
await SpecialPowers.spawn(browser, [], tick);
await invokeContentTask(browser, [], tick);
let events = await onReorders;
unexpectedListener.stop();
// Go back to normal refresh driver ticks.
await SpecialPowers.spawn(browser, [], function() {
await invokeContentTask(browser, [], function() {
content.windowUtils.restoreNormalRefresh();
});