821 lines
25 KiB
JavaScript
821 lines
25 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
/* global waitUntilState, gBrowser */
|
|
/* exported addTestTab, checkTreeState, checkSidebarState, checkAuditState, selectRow,
|
|
toggleRow, toggleMenuItem, addA11yPanelTestsTask, navigate,
|
|
openSimulationMenu, toggleSimulationOption, TREE_FILTERS_MENU_ID,
|
|
PREFS_MENU_ID */
|
|
|
|
"use strict";
|
|
|
|
// Import framework's shared head.
|
|
Services.scriptloader.loadSubScript(
|
|
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
|
|
this
|
|
);
|
|
|
|
// Import inspector's shared head.
|
|
Services.scriptloader.loadSubScript(
|
|
"chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
|
|
this
|
|
);
|
|
|
|
const {
|
|
ORDERED_PROPS,
|
|
PREF_KEYS,
|
|
} = require("resource://devtools/client/accessibility/constants.js");
|
|
|
|
// Enable the Accessibility panel
|
|
Services.prefs.setBoolPref("devtools.accessibility.enabled", true);
|
|
|
|
const SIMULATION_MENU_BUTTON_ID = "#simulation-menu-button";
|
|
const TREE_FILTERS_MENU_ID = "accessibility-tree-filters-menu";
|
|
const PREFS_MENU_ID = "accessibility-tree-filters-prefs-menu";
|
|
|
|
const MENU_INDEXES = {
|
|
[TREE_FILTERS_MENU_ID]: 0,
|
|
[PREFS_MENU_ID]: 1,
|
|
};
|
|
|
|
/**
|
|
* Wait for accessibility service to shut down. We consider it shut down when
|
|
* an "a11y-init-or-shutdown" event is received with a value of "0".
|
|
*/
|
|
function waitForAccessibilityShutdown() {
|
|
return new Promise(resolve => {
|
|
if (!Services.appinfo.accessibilityEnabled) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
const observe = (subject, topic, data) => {
|
|
if (data === "0") {
|
|
Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
|
|
// Sanity check
|
|
ok(
|
|
!Services.appinfo.accessibilityEnabled,
|
|
"Accessibility disabled in this process"
|
|
);
|
|
resolve();
|
|
}
|
|
};
|
|
// This event is coming from Gecko accessibility module when the
|
|
// accessibility service is shutdown or initialzied. We attempt to shutdown
|
|
// accessibility service naturally if there are no more XPCOM references to
|
|
// a11y related objects (after GC/CC).
|
|
Services.obs.addObserver(observe, "a11y-init-or-shutdown");
|
|
|
|
// Force garbage collection.
|
|
SpecialPowers.gc();
|
|
SpecialPowers.forceShrinkingGC();
|
|
SpecialPowers.forceCC();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ensure that accessibility is completely shutdown.
|
|
*/
|
|
async function shutdownAccessibility(browser) {
|
|
await waitForAccessibilityShutdown();
|
|
await SpecialPowers.spawn(browser, [], waitForAccessibilityShutdown);
|
|
}
|
|
|
|
registerCleanupFunction(async () => {
|
|
info("Cleaning up...");
|
|
Services.prefs.clearUserPref("devtools.accessibility.enabled");
|
|
});
|
|
|
|
const EXPANDABLE_PROPS = ["actions", "states", "attributes"];
|
|
|
|
/**
|
|
* Add a new test tab in the browser and load the given url.
|
|
* @param {String} url
|
|
* The url to be loaded in the new tab
|
|
* @return a promise that resolves to the tab object when
|
|
* the url is loaded
|
|
*/
|
|
async function addTestTab(url) {
|
|
info("Adding a new test tab with URL: '" + url + "'");
|
|
|
|
const tab = await addTab(url);
|
|
const panel = await initAccessibilityPanel(tab);
|
|
const win = panel.panelWin;
|
|
const doc = win.document;
|
|
const store = win.view.store;
|
|
|
|
win.focus();
|
|
|
|
await waitUntilState(
|
|
store,
|
|
state =>
|
|
state.accessibles.size === 1 &&
|
|
state.details.accessible &&
|
|
state.details.accessible.role === "document"
|
|
);
|
|
|
|
return {
|
|
tab,
|
|
browser: tab.linkedBrowser,
|
|
panel,
|
|
win,
|
|
toolbox: panel._toolbox,
|
|
doc,
|
|
store,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Open the Accessibility panel for the given tab.
|
|
*
|
|
* @param {Element} tab
|
|
* Optional tab element for which you want open the Accessibility panel.
|
|
* The default tab is taken from the global variable |tab|.
|
|
* @return a promise that is resolved once the panel is open.
|
|
*/
|
|
async function initAccessibilityPanel(tab = gBrowser.selectedTab) {
|
|
const toolbox = await gDevTools.showToolboxForTab(tab, {
|
|
toolId: "accessibility",
|
|
});
|
|
return toolbox.getCurrentPanel();
|
|
}
|
|
|
|
/**
|
|
* Compare text within the list of potential badges rendered for accessibility
|
|
* tree row when its accessible object has accessibility failures.
|
|
* @param {DOMNode} badges
|
|
* Container element that contains badge elements.
|
|
* @param {Array|null} expected
|
|
* List of expected badge labels for failing accessibility checks.
|
|
*/
|
|
function compareBadges(badges, expected = []) {
|
|
const badgeEls = badges ? [...badges.querySelectorAll(".badge")] : [];
|
|
return (
|
|
badgeEls.length === expected.length &&
|
|
badgeEls.every((badge, i) => badge.textContent === expected[i])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Find an ancestor that is scrolled for a given DOMNode.
|
|
*
|
|
* @param {DOMNode} node
|
|
* DOMNode that to find an ancestor for that is scrolled.
|
|
*/
|
|
function closestScrolledParent(node) {
|
|
if (node == null) {
|
|
return null;
|
|
}
|
|
|
|
if (node.scrollHeight > node.clientHeight) {
|
|
return node;
|
|
}
|
|
|
|
return closestScrolledParent(node.parentNode);
|
|
}
|
|
|
|
/**
|
|
* Check if a given element is visible to the user and is not scrolled off
|
|
* because of the overflow.
|
|
*
|
|
* @param {Element} element
|
|
* Element to be checked whether it is visible and is not scrolled off.
|
|
*
|
|
* @returns {Boolean}
|
|
* True if the element is visible.
|
|
*/
|
|
function isVisible(element) {
|
|
const { top, bottom } = element.getBoundingClientRect();
|
|
const scrolledParent = closestScrolledParent(element.parentNode);
|
|
const scrolledParentRect = scrolledParent
|
|
? scrolledParent.getBoundingClientRect()
|
|
: null;
|
|
return (
|
|
!scrolledParent ||
|
|
(top >= scrolledParentRect.top && bottom <= scrolledParentRect.bottom)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check selected styling and visibility for a given row in the accessibility
|
|
* tree.
|
|
* @param {DOMNode} row
|
|
* DOMNode for a given accessibility row.
|
|
* @param {Boolean} expected
|
|
* Expected selected state.
|
|
*
|
|
* @returns {Boolean}
|
|
* True if visibility and styling matches expected selected state.
|
|
*/
|
|
function checkSelected(row, expected) {
|
|
if (!expected) {
|
|
return true;
|
|
}
|
|
|
|
if (row.classList.contains("selected") !== expected) {
|
|
return false;
|
|
}
|
|
|
|
return isVisible(row);
|
|
}
|
|
|
|
/**
|
|
* Check level for a given row in the accessibility tree.
|
|
* @param {DOMNode} row
|
|
* DOMNode for a given accessibility row.
|
|
* @param {Boolean} expected
|
|
* Expected row level (aria-level).
|
|
*
|
|
* @returns {Boolean}
|
|
* True if the aria-level for the row is as expected.
|
|
*/
|
|
function checkLevel(row, expected) {
|
|
if (!expected) {
|
|
return true;
|
|
}
|
|
|
|
return parseInt(row.getAttribute("aria-level"), 10) === expected;
|
|
}
|
|
|
|
/**
|
|
* Check the state of the accessibility tree.
|
|
* @param {document} doc panel documnent.
|
|
* @param {Array} expected an array that represents an expected row list.
|
|
*/
|
|
async function checkTreeState(doc, expected) {
|
|
info("Checking tree state.");
|
|
const hasExpectedStructure = await BrowserTestUtils.waitForCondition(() => {
|
|
const rows = [...doc.querySelectorAll(".treeRow")];
|
|
if (rows.length !== expected.length) {
|
|
return false;
|
|
}
|
|
|
|
return rows.every((row, i) => {
|
|
const { role, name, badges, selected, level } = expected[i];
|
|
return (
|
|
row.querySelector(".treeLabelCell").textContent === role &&
|
|
row.querySelector(".treeValueCell").textContent === name &&
|
|
compareBadges(row.querySelector(".badges"), badges) &&
|
|
checkSelected(row, selected) &&
|
|
checkLevel(row, level)
|
|
);
|
|
});
|
|
}, "Wait for the right tree update.");
|
|
|
|
ok(hasExpectedStructure, "Tree structure is correct.");
|
|
}
|
|
|
|
/**
|
|
* Check if relations object matches what is expected. Note: targets are matched by their
|
|
* name and role.
|
|
* @param {Object} relations Relations to test.
|
|
* @param {Object} expected Expected relations.
|
|
* @return {Boolean} True if relation types and their targers match what is
|
|
* expected.
|
|
*/
|
|
function relationsMatch(relations, expected) {
|
|
for (const relationType in expected) {
|
|
let expTargets = expected[relationType];
|
|
expTargets = Array.isArray(expTargets) ? expTargets : [expTargets];
|
|
|
|
let targets = relations ? relations[relationType] : [];
|
|
targets = Array.isArray(targets) ? targets : [targets];
|
|
|
|
for (const index in expTargets) {
|
|
if (!targets[index]) {
|
|
return false;
|
|
}
|
|
if (
|
|
expTargets[index].name !== targets[index].name ||
|
|
expTargets[index].role !== targets[index].role
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* When comparing numerical values (for example contrast), we only care about the 2
|
|
* decimal points.
|
|
* @param {String} _
|
|
* Key of the property that is parsed.
|
|
* @param {Any} value
|
|
* Value of the property that is parsed.
|
|
* @return {Any}
|
|
* Newly formatted value in case of the numeric value.
|
|
*/
|
|
function parseNumReplacer(_, value) {
|
|
if (typeof value === "number") {
|
|
return value.toFixed(2);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Check the state of the accessibility sidebar audit(checks).
|
|
* @param {Object} store React store for the panel (includes store for
|
|
* the audit).
|
|
* @param {Object} expectedState Expected state of the sidebar audit(checks).
|
|
*/
|
|
async function checkAuditState(store, expectedState) {
|
|
info("Checking audit state.");
|
|
await waitUntilState(store, ({ details }) => {
|
|
const { audit } = details;
|
|
|
|
for (const key in expectedState) {
|
|
const expected = expectedState[key];
|
|
if (expected && typeof expected === "object") {
|
|
if (
|
|
JSON.stringify(audit[key], parseNumReplacer) !==
|
|
JSON.stringify(expected, parseNumReplacer)
|
|
) {
|
|
return false;
|
|
}
|
|
} else if (audit && audit[key] !== expected) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ok(true, "Audit state is correct.");
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check the state of the accessibility sidebar.
|
|
* @param {Object} store React store for the panel (includes store for
|
|
* the sidebar).
|
|
* @param {Object} expectedState Expected state of the sidebar.
|
|
*/
|
|
async function checkSidebarState(store, expectedState) {
|
|
info("Checking sidebar state.");
|
|
await waitUntilState(store, ({ details }) => {
|
|
for (const key of ORDERED_PROPS) {
|
|
const expected = expectedState[key];
|
|
if (expected === undefined) {
|
|
continue;
|
|
}
|
|
|
|
if (key === "relations") {
|
|
if (!relationsMatch(details.relations, expected)) {
|
|
return false;
|
|
}
|
|
} else if (EXPANDABLE_PROPS.includes(key)) {
|
|
if (
|
|
JSON.stringify(details.accessible[key]) !== JSON.stringify(expected)
|
|
) {
|
|
return false;
|
|
}
|
|
} else if (details.accessible && details.accessible[key] !== expected) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ok(true, "Sidebar state is correct.");
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check the state of the accessibility related prefs.
|
|
* @param {Document} doc
|
|
* accessibility inspector panel document.
|
|
* @param {Object} toolbarPrefValues
|
|
* Expected state of the panel prefs as well as the redux state that
|
|
* keeps track of it. Includes:
|
|
* - SCROLL_INTO_VIEW (devtools.accessibility.scroll-into-view)
|
|
* @param {Object} store
|
|
* React store for the panel (includes store for the sidebar).
|
|
*/
|
|
async function checkToolbarPrefsState(doc, toolbarPrefValues, store) {
|
|
info("Checking toolbar prefs state.");
|
|
const [hasExpectedStructure] = await Promise.all([
|
|
// Check that appropriate preferences are set as expected.
|
|
BrowserTestUtils.waitForCondition(() => {
|
|
return Object.keys(toolbarPrefValues).every(
|
|
name =>
|
|
Services.prefs.getBoolPref(PREF_KEYS[name], false) ===
|
|
toolbarPrefValues[name]
|
|
);
|
|
}, "Wait for the right prefs state."),
|
|
// Check that ui state is set as expected.
|
|
waitUntilState(store, ({ ui }) => {
|
|
for (const name in toolbarPrefValues) {
|
|
if (ui[name] !== toolbarPrefValues[name]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ok(true, "UI pref state is correct.");
|
|
return true;
|
|
}),
|
|
]);
|
|
ok(hasExpectedStructure, "Prefs state is correct.");
|
|
}
|
|
|
|
/**
|
|
* Check the state of the accessibility checks toolbar.
|
|
* @param {Object} store
|
|
* React store for the panel (includes store for the sidebar).
|
|
* @param {Object} activeToolbarFilters
|
|
* Expected active state of the filters in the toolbar.
|
|
*/
|
|
async function checkToolbarState(doc, activeToolbarFilters) {
|
|
info("Checking toolbar state.");
|
|
const hasExpectedStructure = await BrowserTestUtils.waitForCondition(
|
|
() =>
|
|
[
|
|
...doc.querySelectorAll("#accessibility-tree-filters-menu .command"),
|
|
].every(
|
|
(filter, i) =>
|
|
(activeToolbarFilters[i] ? "true" : null) ===
|
|
filter.getAttribute("aria-checked")
|
|
),
|
|
"Wait for the right toolbar state."
|
|
);
|
|
|
|
ok(hasExpectedStructure, "Toolbar state is correct.");
|
|
}
|
|
|
|
/**
|
|
* Check the state of the simulation button and menu components.
|
|
* @param {Object} doc Panel document.
|
|
* @param {Object} expected Expected states of the simulation components:
|
|
* menuVisible, buttonActive, checkedOptionIndices (Optional)
|
|
*/
|
|
async function checkSimulationState(doc, expected) {
|
|
const { buttonActive, checkedOptionIndices } = expected;
|
|
const simulationMenuOptions = doc
|
|
.querySelector(SIMULATION_MENU_BUTTON_ID + "-menu")
|
|
.querySelectorAll(".menuitem");
|
|
|
|
// Check simulation menu button state
|
|
is(
|
|
doc.querySelector(SIMULATION_MENU_BUTTON_ID).className,
|
|
`devtools-button toolbar-menu-button simulation${
|
|
buttonActive ? " active" : ""
|
|
}`,
|
|
`Simulation menu button contains ${buttonActive ? "active" : "base"} class.`
|
|
);
|
|
|
|
// Check simulation menu options states, if specified
|
|
if (checkedOptionIndices) {
|
|
simulationMenuOptions.forEach((menuListItem, index) => {
|
|
const isChecked = checkedOptionIndices.includes(index);
|
|
const button = menuListItem.firstChild;
|
|
|
|
is(
|
|
button.getAttribute("aria-checked"),
|
|
isChecked ? "true" : null,
|
|
`Simulation option ${index} is ${isChecked ? "" : "not "}selected.`
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Focus accessibility properties tree in the a11y inspector sidebar. If focused for the
|
|
* first time, the tree will select first rendered node as defult selection for keyboard
|
|
* purposes.
|
|
*
|
|
* @param {Document} doc accessibility inspector panel document.
|
|
*/
|
|
async function focusAccessibleProperties(doc) {
|
|
const tree = doc.querySelector(".tree");
|
|
if (doc.activeElement !== tree) {
|
|
tree.focus();
|
|
await BrowserTestUtils.waitForCondition(
|
|
() => tree.querySelector(".node.focused"),
|
|
"Tree selected."
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select accessibility property in the sidebar.
|
|
* @param {Document} doc accessibility inspector panel document.
|
|
* @param {String} id id of the property to be selected.
|
|
* @return {DOMNode} Node that corresponds to the selected accessibility property.
|
|
*/
|
|
async function selectProperty(doc, id) {
|
|
const win = doc.defaultView;
|
|
let selected = false;
|
|
let node;
|
|
|
|
await focusAccessibleProperties(doc);
|
|
await BrowserTestUtils.waitForCondition(() => {
|
|
node = doc.getElementById(`${id}`);
|
|
if (node) {
|
|
if (selected) {
|
|
return node.firstChild.classList.contains("focused");
|
|
}
|
|
|
|
AccessibilityUtils.setEnv({
|
|
// Keyboard navigation is handled on the container level using arrow
|
|
// keys.
|
|
nonNegativeTabIndexRule: false,
|
|
});
|
|
EventUtils.sendMouseEvent({ type: "click" }, node, win);
|
|
AccessibilityUtils.resetEnv();
|
|
selected = true;
|
|
} else {
|
|
const tree = doc.querySelector(".tree");
|
|
tree.scrollTop = parseFloat(win.getComputedStyle(tree).height);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Select tree row.
|
|
* @param {document} doc panel documnent.
|
|
* @param {Number} rowNumber number of the row/tree node to be selected.
|
|
*/
|
|
function selectRow(doc, rowNumber) {
|
|
info(`Selecting row ${rowNumber}.`);
|
|
AccessibilityUtils.setEnv({
|
|
// Keyboard navigation is handled on the container level using arrow keys.
|
|
nonNegativeTabIndexRule: false,
|
|
});
|
|
EventUtils.sendMouseEvent(
|
|
{ type: "click" },
|
|
doc.querySelectorAll(".treeRow")[rowNumber],
|
|
doc.defaultView
|
|
);
|
|
AccessibilityUtils.resetEnv();
|
|
}
|
|
|
|
/**
|
|
* Toggle an expandable tree row.
|
|
* @param {document} doc panel documnent.
|
|
* @param {Number} rowNumber number of the row/tree node to be toggled.
|
|
*/
|
|
async function toggleRow(doc, rowNumber) {
|
|
const win = doc.defaultView;
|
|
const row = doc.querySelectorAll(".treeRow")[rowNumber];
|
|
const twisty = row.querySelector(".theme-twisty");
|
|
const expected = !twisty.classList.contains("open");
|
|
|
|
info(`${expected ? "Expanding" : "Collapsing"} row ${rowNumber}.`);
|
|
|
|
AccessibilityUtils.setEnv({
|
|
// We intentionally remove the twisty from the accessibility tree in the
|
|
// TreeView component and handle keyboard navigation using the arrow keys.
|
|
mustHaveAccessibleRule: false,
|
|
});
|
|
EventUtils.sendMouseEvent({ type: "click" }, twisty, win);
|
|
AccessibilityUtils.resetEnv();
|
|
await BrowserTestUtils.waitForCondition(
|
|
() =>
|
|
!twisty.classList.contains("devtools-throbber") &&
|
|
expected === twisty.classList.contains("open"),
|
|
"Twisty updated."
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Toggle a specific menu item based on its index in the menu.
|
|
* @param {document} toolboxDoc
|
|
* toolbox document.
|
|
* @param {document} doc
|
|
* panel document.
|
|
* @param {String} menuId
|
|
* The id of the menu (menuId passed to the MenuButton component)
|
|
* @param {Number} menuItemIndex
|
|
* index of the menu item to be toggled.
|
|
*/
|
|
async function toggleMenuItem(doc, toolboxDoc, menuId, menuItemIndex) {
|
|
const toolboxWin = toolboxDoc.defaultView;
|
|
const panelWin = doc.defaultView;
|
|
|
|
const menuButton = doc.querySelectorAll(".toolbar-menu-button")[
|
|
MENU_INDEXES[menuId]
|
|
];
|
|
ok(menuButton, "Expected menu button");
|
|
|
|
const menuEl = toolboxDoc.getElementById(menuId);
|
|
const menuItem = menuEl.querySelectorAll(".command")[menuItemIndex];
|
|
ok(menuItem, "Expected menu item");
|
|
|
|
const expected =
|
|
menuItem.getAttribute("aria-checked") === "true" ? null : "true";
|
|
|
|
// Make the menu visible first.
|
|
const onPopupShown = new Promise(r =>
|
|
toolboxDoc.addEventListener("popupshown", r, { once: true })
|
|
);
|
|
EventUtils.synthesizeMouseAtCenter(menuButton, {}, panelWin);
|
|
await onPopupShown;
|
|
const boundingRect = menuItem.getBoundingClientRect();
|
|
ok(
|
|
boundingRect.width > 0 && boundingRect.height > 0,
|
|
"Menu item is visible."
|
|
);
|
|
|
|
EventUtils.synthesizeMouseAtCenter(menuItem, {}, toolboxWin);
|
|
await BrowserTestUtils.waitForCondition(
|
|
() => expected === menuItem.getAttribute("aria-checked"),
|
|
"Menu item updated."
|
|
);
|
|
}
|
|
|
|
async function openSimulationMenu(doc) {
|
|
doc.querySelector(SIMULATION_MENU_BUTTON_ID).click();
|
|
|
|
await BrowserTestUtils.waitForCondition(() =>
|
|
doc
|
|
.querySelector(SIMULATION_MENU_BUTTON_ID + "-menu")
|
|
.classList.contains("tooltip-visible")
|
|
);
|
|
}
|
|
|
|
async function toggleSimulationOption(doc, optionIndex) {
|
|
const simulationMenu = doc.querySelector(SIMULATION_MENU_BUTTON_ID + "-menu");
|
|
simulationMenu.querySelectorAll(".menuitem")[optionIndex].firstChild.click();
|
|
|
|
await BrowserTestUtils.waitForCondition(
|
|
() => !simulationMenu.classList.contains("tooltip-visible")
|
|
);
|
|
}
|
|
|
|
async function findAccessibleFor(
|
|
{
|
|
toolbox: { target },
|
|
panel: {
|
|
accessibilityProxy: {
|
|
accessibilityFront: { accessibleWalkerFront },
|
|
},
|
|
},
|
|
},
|
|
selector
|
|
) {
|
|
const domWalker = (await target.getFront("inspector")).walker;
|
|
const node = await domWalker.querySelector(domWalker.rootNode, selector);
|
|
return accessibleWalkerFront.getAccessibleFor(node);
|
|
}
|
|
|
|
async function selectAccessibleForNode(env, selector) {
|
|
const { panel, win } = env;
|
|
const front = await findAccessibleFor(env, selector);
|
|
const { EVENTS } = win;
|
|
const onSelected = win.once(EVENTS.NEW_ACCESSIBLE_FRONT_SELECTED);
|
|
panel.selectAccessible(front);
|
|
await onSelected;
|
|
}
|
|
|
|
/**
|
|
* Iterate over setups/tests structure and test the state of the
|
|
* accessibility panel.
|
|
* @param {JSON} tests
|
|
* test data that has the format of:
|
|
* {
|
|
* desc {String} description for better logging
|
|
* setup {Function} An optional setup that needs to be
|
|
* performed before the state of the
|
|
* tree and the sidebar can be checked
|
|
* expected {JSON} An expected states for parts of
|
|
* accessibility panel:
|
|
* - tree: state of the accessibility tree widget
|
|
* - sidebar: state of the accessibility panel sidebar
|
|
* - audit: state of the audit redux state of the
|
|
* panel
|
|
* - toolbarPrefValues: state of the accessibility panel
|
|
* toolbar prefs and corresponding user
|
|
* preferences.
|
|
* - activeToolbarFilters: state of the accessibility panel
|
|
* toolbar filters.
|
|
* }
|
|
* @param {Object} env
|
|
* contains all relevant environment objects (same structure as the
|
|
* return value of 'addTestTab' funciton)
|
|
*/
|
|
async function runA11yPanelTests(tests, env) {
|
|
for (const { desc, setup, expected } of tests) {
|
|
info(desc);
|
|
|
|
if (setup) {
|
|
await setup(env);
|
|
}
|
|
|
|
const {
|
|
tree,
|
|
sidebar,
|
|
audit,
|
|
toolbarPrefValues,
|
|
activeToolbarFilters,
|
|
simulation,
|
|
} = expected;
|
|
if (tree) {
|
|
await checkTreeState(env.doc, tree);
|
|
}
|
|
|
|
if (sidebar) {
|
|
await checkSidebarState(env.store, sidebar);
|
|
}
|
|
|
|
if (activeToolbarFilters) {
|
|
await checkToolbarState(env.doc, activeToolbarFilters);
|
|
}
|
|
|
|
if (toolbarPrefValues) {
|
|
await checkToolbarPrefsState(env.doc, toolbarPrefValues, env.store);
|
|
}
|
|
|
|
if (typeof audit !== "undefined") {
|
|
await checkAuditState(env.store, audit);
|
|
}
|
|
|
|
if (simulation) {
|
|
await checkSimulationState(env.doc, simulation);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build a valid URL from an HTML snippet.
|
|
* @param {String} uri HTML snippet
|
|
* @param {Object} options options for the test
|
|
* @return {String} built URL
|
|
*/
|
|
function buildURL(uri, options = {}) {
|
|
if (options.remoteIframe) {
|
|
const srcURL = new URL(`http://example.net/document-builder.sjs`);
|
|
srcURL.searchParams.append(
|
|
"html",
|
|
`<html>
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<title>Accessibility Panel Test (OOP)</title>
|
|
</head>
|
|
<body>${uri}</body>
|
|
</html>`
|
|
);
|
|
uri = `<iframe title="Accessibility Panel Test (OOP)" src="${srcURL.href}"/>`;
|
|
}
|
|
|
|
return `data:text/html;charset=UTF-8,${encodeURIComponent(uri)}`;
|
|
}
|
|
|
|
/**
|
|
* Add a test task based on the test structure and a test URL.
|
|
* @param {JSON} tests test data that has the format of:
|
|
* {
|
|
* desc {String} description for better logging
|
|
* setup {Function} An optional setup that needs to be
|
|
* performed before the state of the
|
|
* tree and the sidebar can be checked
|
|
* expected {JSON} An expected states for the tree and
|
|
* the sidebar
|
|
* }
|
|
* @param {String} uri test URL
|
|
* @param {String} msg a message that is printed for the test
|
|
* @param {Object} options options for the test
|
|
*/
|
|
function addA11yPanelTestsTask(tests, uri, msg, options) {
|
|
addA11YPanelTask(msg, uri, env => runA11yPanelTests(tests, env), options);
|
|
}
|
|
|
|
/**
|
|
* Borrowed from framework's shared head. Close toolbox, completely disable
|
|
* accessibility and remove the tab.
|
|
* @param {Tab}
|
|
* tab The tab to close.
|
|
* @return {Promise}
|
|
* Resolves when the toolbox and tab have been destroyed and closed.
|
|
*/
|
|
async function closeTabToolboxAccessibility(tab = gBrowser.selectedTab) {
|
|
if (gDevTools.hasToolboxForTab(tab)) {
|
|
await gDevTools.closeToolboxForTab(tab);
|
|
}
|
|
|
|
await shutdownAccessibility(gBrowser.getBrowserForTab(tab));
|
|
await removeTab(tab);
|
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
}
|
|
|
|
/**
|
|
* A wrapper function around add_task that sets up the test environment, runs
|
|
* the test and then disables accessibility tools.
|
|
* @param {String} msg a message that is printed for the test
|
|
* @param {String} uri absolute test URL or HTML snippet
|
|
* @param {Function} task task function containing the tests.
|
|
* @param {Object} options options for the test
|
|
*/
|
|
function addA11YPanelTask(msg, uri, task, options = {}) {
|
|
add_task(async function a11YPanelTask() {
|
|
info(msg);
|
|
|
|
const env = await addTestTab(
|
|
uri.startsWith("http") ? uri : buildURL(uri, options)
|
|
);
|
|
await task(env);
|
|
await closeTabToolboxAccessibility(env.tab);
|
|
});
|
|
}
|