summaryrefslogtreecommitdiffstats
path: root/devtools/client/accessibility/test/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/accessibility/test/browser
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/accessibility/test/browser')
-rw-r--r--devtools/client/accessibility/test/browser/browser.ini49
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_context_menu_browser.js74
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_context_menu_inspector.js104
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_fission_switch_target.js58
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_mutations.js217
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_panel_audit_hidden_iframe.js66
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_panel_audit_oop.js109
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_checks.js114
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_pref_scroll.js73
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_print_to_json.js245
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_relation_navigation.js169
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_reload.js107
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_sidebar.js81
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_sidebar_checks.js80
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_sidebar_dom_nodes.js111
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_simulation.js99
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tabbing_order_highlighter.js140
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tabbing_order_highlighter_iframe_picker.js195
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tree.js75
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tree_audit.js137
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_long.js104
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_reset.js109
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_toolbar.js112
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tree_contrast.js62
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tree_iframe_picker.js121
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tree_navigation.js186
-rw-r--r--devtools/client/accessibility/test/browser/browser_accessibility_tree_navigation_oop.js149
-rw-r--r--devtools/client/accessibility/test/browser/head.js823
28 files changed, 3969 insertions, 0 deletions
diff --git a/devtools/client/accessibility/test/browser/browser.ini b/devtools/client/accessibility/test/browser/browser.ini
new file mode 100644
index 0000000000..3058fac933
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser.ini
@@ -0,0 +1,49 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+skip-if = (os == 'win' && processor == 'aarch64')
+support-files =
+ head.js
+ !/devtools/client/shared/test/shared-head.js
+ !/devtools/client/shared/test/highlighter-test-actor.js
+ !/devtools/client/inspector/test/shared-head.js
+ !/devtools/client/shared/test/telemetry-test-helpers.js
+
+[browser_accessibility_context_menu_browser.js]
+skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184
+[browser_accessibility_context_menu_inspector.js]
+skip-if =
+ (os == 'win' && processor == 'aarch64') # bug 1533484
+ win10_2004 # Bug 1723573
+ apple_catalina # Bug 1713392
+[browser_accessibility_fission_switch_target.js]
+https_first_disabled = true
+skip-if = (os == 'linux' && asan) # bug 1666940
+[browser_accessibility_mutations.js]
+skip-if =
+ os == 'win' && processor == 'aarch64' # bug 1533534
+ os == 'linux' && bits == 64 && fission # Bug 1675445
+[browser_accessibility_panel_audit_hidden_iframe.js]
+[browser_accessibility_panel_audit_oop.js]
+[browser_accessibility_panel_toolbar_checks.js]
+[browser_accessibility_panel_toolbar_pref_scroll.js]
+skip-if = true # bug 1674060
+[browser_accessibility_print_to_json.js]
+[browser_accessibility_relation_navigation.js]
+[browser_accessibility_reload.js]
+[browser_accessibility_sidebar_checks.js]
+[browser_accessibility_sidebar_dom_nodes.js]
+[browser_accessibility_sidebar.js]
+[browser_accessibility_simulation.js]
+skip-if = true # bug 1674060
+[browser_accessibility_tabbing_order_highlighter_iframe_picker.js]
+[browser_accessibility_tabbing_order_highlighter.js]
+[browser_accessibility_tree_audit_long.js]
+[browser_accessibility_tree_audit_reset.js]
+[browser_accessibility_tree_audit_toolbar.js]
+[browser_accessibility_tree_audit.js]
+[browser_accessibility_tree_contrast.js]
+[browser_accessibility_tree_iframe_picker.js]
+[browser_accessibility_tree_navigation_oop.js]
+[browser_accessibility_tree_navigation.js]
+[browser_accessibility_tree.js]
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_context_menu_browser.js b/devtools/client/accessibility/test/browser/browser_accessibility_context_menu_browser.js
new file mode 100644
index 0000000000..0491b29eb4
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_context_menu_browser.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = '<h1 id="h1">header</h1><p id="p">paragraph</p>';
+
+addA11YPanelTask(
+ "Test show accessibility properties context menu in browser.",
+ TEST_URI,
+ async function ({ panel, toolbox, browser }) {
+ // Load the inspector to ensure it to use in this test.
+ await toolbox.loadTool("inspector");
+
+ const headerSelector = "#h1";
+
+ const contextMenu = document.getElementById("contentAreaContextMenu");
+ const awaitPopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ headerSelector,
+ 0,
+ 0,
+ {
+ type: "contextmenu",
+ button: 2,
+ centered: true,
+ },
+ browser
+ );
+ await awaitPopupShown;
+
+ const inspectA11YPropsItem = contextMenu.querySelector(
+ "#context-inspect-a11y"
+ );
+
+ info(
+ "Triggering 'Inspect Accessibility Properties' and waiting for " +
+ "accessibility panel to open"
+ );
+ const popupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.activateItem(inspectA11YPropsItem);
+ await popupHidden;
+
+ const selected = await panel.once("new-accessible-front-selected");
+ const expectedSelectedNode = await getNodeFront(
+ headerSelector,
+ toolbox.getPanel("inspector")
+ );
+ const expectedSelected =
+ await panel.accessibilityProxy.accessibilityFront.accessibleWalkerFront.getAccessibleFor(
+ expectedSelectedNode
+ );
+ is(
+ toolbox.getCurrentPanel(),
+ panel,
+ "Accessibility panel is currently selected"
+ );
+ is(selected, expectedSelected, "Accessible front selected correctly");
+
+ const doc = panel.panelWin.document;
+ const propertiesTree = doc.querySelector(".tree");
+ is(doc.activeElement, propertiesTree, "Properties list must be focused.");
+ ok(
+ isVisible(doc.querySelector(".treeTable .treeRow.selected")),
+ "Selected row is visible."
+ );
+ }
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_context_menu_inspector.js b/devtools/client/accessibility/test/browser/browser_accessibility_context_menu_inspector.js
new file mode 100644
index 0000000000..03273a30c2
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_context_menu_inspector.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const TEST_URI = `
+ <h1 id="h1">header</h1>
+ <p id="p">paragraph</p>
+ <span id="span-1">text</span>
+ <span id="span-2">
+ IamaverylongtextwhichdoesntfitinInlineTextChildReallyIdontIamtoobig
+ </span>`;
+
+async function openContextMenuForNode({ toolbox }, selector) {
+ info("Selecting Inspector tab and opening a context menu");
+ const inspector = await toolbox.selectTool("inspector");
+
+ if (!selector) {
+ ok(inspector.selection.isBodyNode(), "Default selection is a body node.");
+ } else if (typeof selector === "string") {
+ await selectNode(selector, inspector, "test");
+ } else {
+ const updated = inspector.once("inspector-updated");
+ inspector.selection.setNodeFront(selector, { reason: "test" });
+ await updated;
+ }
+
+ return openContextMenuAndGetAllItems(inspector);
+}
+
+function checkShowA11YPropertiesNode(allMenuItems) {
+ const showA11YPropertiesNode = allMenuItems.find(
+ item => item.id === "node-menu-showaccessibilityproperties"
+ );
+ ok(
+ showA11YPropertiesNode,
+ "the popup menu now has a show accessibility properties item"
+ );
+ return showA11YPropertiesNode;
+}
+
+async function checkAccessibleObjectSelection(
+ { toolbox, panel },
+ menuItem,
+ isText
+) {
+ const inspector = await toolbox.getPanel("inspector");
+ info(
+ "Triggering 'Show Accessibility Properties' and waiting for " +
+ "accessibility panel to open"
+ );
+ const panelSelected = toolbox.once("accessibility-selected");
+ const objectSelected = panel.once("new-accessible-front-selected");
+ menuItem.click();
+ await panelSelected;
+ const selected = await objectSelected;
+
+ const expectedNode = isText
+ ? inspector.selection.nodeFront.inlineTextChild
+ : inspector.selection.nodeFront;
+ const expectedSelected =
+ await panel.accessibilityProxy.accessibilityFront.accessibleWalkerFront.getAccessibleFor(
+ expectedNode
+ );
+ is(selected, expectedSelected, "Accessible front selected correctly");
+
+ const doc = panel.panelWin.document;
+ const propertiesTree = doc.querySelector(".tree");
+ is(doc.activeElement, propertiesTree, "Properties list must be focused.");
+ ok(
+ isVisible(doc.querySelector(".treeTable .treeRow.selected")),
+ "Selected row is visible."
+ );
+}
+
+addA11YPanelTask(
+ "Test show accessibility properties context menu.",
+ TEST_URI,
+ async function testShowAccessibilityPropertiesContextMenu(env) {
+ // Load the inspector to ensure it to use in this test.
+ await env.toolbox.loadTool("inspector");
+
+ let allMenuItems = await openContextMenuForNode(env);
+ let showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems);
+
+ allMenuItems = await openContextMenuForNode(env, "#h1");
+ showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems);
+ await checkAccessibleObjectSelection(env, showA11YPropertiesNode);
+
+ allMenuItems = await openContextMenuForNode(env, "#span-1");
+ showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems);
+ await checkAccessibleObjectSelection(env, showA11YPropertiesNode, true);
+
+ allMenuItems = await openContextMenuForNode(env, "#span-2");
+ showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems);
+
+ const inspector = env.toolbox.getPanel("inspector");
+ const span2 = await getNodeFront("#span-2", inspector);
+ await inspector.markup.expandNode(span2);
+ const { nodes } = await inspector.walker.children(span2);
+ allMenuItems = await openContextMenuForNode(env, nodes[0]);
+ showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems);
+ await checkAccessibleObjectSelection(env, showA11YPropertiesNode, false);
+ }
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_fission_switch_target.js b/devtools/client/accessibility/test/browser/browser_accessibility_fission_switch_target.js
new file mode 100644
index 0000000000..9da56a0b1b
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_fission_switch_target.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test switching for the top-level target.
+
+const MAIN_PROCESS_URL = "about:robots";
+const MAIN_PROCESS_EXPECTED = [
+ {
+ expected: {
+ sidebar: {
+ name: "Gort! Klaatu barada nikto!",
+ role: "document",
+ },
+ },
+ },
+];
+
+const CONTENT_PROCESS_URL = buildURL(`<title>Test page</title>`);
+const CONTENT_PROCESS_EXPECTED = [
+ {
+ expected: {
+ sidebar: {
+ name: "Test page",
+ role: "document",
+ relations: {
+ "containing document": {
+ role: "document",
+ name: "Test page",
+ },
+ embeds: {
+ role: "document",
+ name: "Test page",
+ },
+ },
+ },
+ },
+ },
+];
+
+add_task(async () => {
+ info(
+ "Open a test page running on the content process and accessibility panel"
+ );
+ const env = await addTestTab(CONTENT_PROCESS_URL);
+ await runA11yPanelTests(CONTENT_PROCESS_EXPECTED, env);
+
+ info("Navigate to a page running on the main process");
+ await navigateTo(MAIN_PROCESS_URL);
+ await runA11yPanelTests(MAIN_PROCESS_EXPECTED, env);
+
+ info("Back to a page running on the content process");
+ await navigateTo(CONTENT_PROCESS_URL);
+ await runA11yPanelTests(CONTENT_PROCESS_EXPECTED, env);
+
+ await closeTabToolboxAccessibility(env.tab);
+});
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_mutations.js b/devtools/client/accessibility/test/browser/browser_accessibility_mutations.js
new file mode 100644
index 0000000000..4c564747f9
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_mutations.js
@@ -0,0 +1,217 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1 id="h1">Top level header</h1>
+ <p id="p">This is a paragraph.</p>
+ </body>
+</html>`;
+
+const documentRow = {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+};
+const documentRowOOP = {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+};
+const subtree = [
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header"`,
+ },
+ {
+ role: "paragraph",
+ name: `""`,
+ },
+];
+const frameSubtree = [
+ { role: "internal frame", name: `"Accessibility Panel Test (OOP)"` },
+ {
+ role: "document",
+ name: `"Accessibility Panel Test (OOP)"`,
+ },
+];
+const subtreeOOP = [...frameSubtree, ...subtree];
+const renamed = [
+ {
+ role: "heading",
+ name: `"New Header"`,
+ },
+ {
+ role: "text leaf",
+ name: `"New Header"`,
+ },
+];
+const paragraphSidebar = {
+ name: null,
+ role: "paragraph",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 1,
+ indexInParent: 1,
+ states: ["selectable text", "opaque", "enabled", "sensitive"],
+};
+const headerSidebar = {
+ name: "Top level header",
+ role: "text leaf",
+};
+const newHeaderSidebar = {
+ name: "New Header",
+};
+
+function removeRow(rowNumber) {
+ return async ({ doc, browser }) => {
+ is(
+ doc.querySelectorAll(".treeRow").length,
+ rowNumber,
+ "Tree size is correct."
+ );
+ await SpecialPowers.spawn(browser, [], async () => {
+ const iframe = content.document.getElementsByTagName("iframe")[0];
+ if (iframe) {
+ await SpecialPowers.spawn(iframe, [], () =>
+ content.document.getElementById("p").remove()
+ );
+ return;
+ }
+
+ content.document.getElementById("p").remove();
+ });
+ await BrowserTestUtils.waitForCondition(
+ () => doc.querySelectorAll(".treeRow").length === rowNumber - 1,
+ "Tree updated."
+ );
+ };
+}
+
+async function rename({ browser }) {
+ await SpecialPowers.spawn(browser, [], async () => {
+ const iframe = content.document.getElementsByTagName("iframe")[0];
+ if (iframe) {
+ await SpecialPowers.spawn(
+ iframe,
+ [],
+ () => (content.document.getElementById("h1").textContent = "New Header")
+ );
+ return;
+ }
+
+ content.document.getElementById("h1").textContent = "New Header";
+ });
+}
+
+/**
+ * Test data 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.
+ * }
+ */
+const testsTopLevel = [
+ {
+ desc: "Expand first and second rows, select third row.",
+ setup: async ({ doc }) => {
+ await toggleRow(doc, 0);
+ await toggleRow(doc, 1);
+ selectRow(doc, 3);
+ },
+ expected: {
+ tree: [documentRow, ...subtree],
+ sidebar: paragraphSidebar,
+ },
+ },
+ {
+ desc: "Remove a child from a document.",
+ setup: removeRow(4),
+ expected: {
+ tree: [documentRow, ...subtree.slice(0, -1)],
+ sidebar: headerSidebar,
+ },
+ },
+ {
+ desc: "Update child's text content.",
+ setup: rename,
+ expected: {
+ tree: [documentRow, ...renamed],
+ },
+ },
+ {
+ desc: "Select third row in the tree.",
+ setup: ({ doc }) => selectRow(doc, 1),
+ expected: {
+ sidebar: newHeaderSidebar,
+ },
+ },
+];
+
+const testsOOP = [
+ {
+ desc: "Expand rows until we reach an internal OOP frame.",
+ setup: async ({ doc }) => {
+ await toggleRow(doc, 0);
+ await toggleRow(doc, 1);
+ await toggleRow(doc, 2);
+ await toggleRow(doc, 3);
+ selectRow(doc, 5);
+ },
+ expected: {
+ tree: [documentRowOOP, ...subtreeOOP],
+ sidebar: paragraphSidebar,
+ },
+ },
+ {
+ desc: "Remove a child from a document.",
+ setup: removeRow(6),
+ expected: {
+ tree: [documentRowOOP, ...subtreeOOP.slice(0, -1)],
+ sidebar: headerSidebar,
+ },
+ },
+ {
+ desc: "Update child's text content.",
+ setup: rename,
+ expected: {
+ tree: [documentRowOOP, ...frameSubtree, ...renamed],
+ },
+ },
+ {
+ desc: "Select third row in the tree.",
+ setup: ({ doc }) => selectRow(doc, 1),
+ expected: {
+ sidebar: newHeaderSidebar,
+ },
+ },
+];
+
+/**
+ * Tests that checks the Accessibility panel after DOM tree mutations.
+ */
+addA11yPanelTestsTask(
+ testsTopLevel,
+ TEST_URI,
+ "Test Accessibility panel after DOM tree mutations."
+);
+
+addA11yPanelTestsTask(
+ testsOOP,
+ TEST_URI,
+ "Test Accessibility panel after DOM tree mutations in the OOP frame.",
+ { remoteIframe: true }
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_panel_audit_hidden_iframe.js b/devtools/client/accessibility/test/browser/browser_accessibility_panel_audit_hidden_iframe.js
new file mode 100644
index 0000000000..1e7d0e26c2
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_panel_audit_hidden_iframe.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global toggleMenuItem, TREE_FILTERS_MENU_ID */
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1 style="color:rgba(255,0,0,0.1); background-color:rgba(255,255,255,1);">
+ Top level header
+ </h1>
+ <iframe style="display: none"></iframe>
+ <iframe style="display: none" src="data:text/html,iframe"></iframe>
+ <iframe style="display: none" src="https://example.com/document-builder.sjs?html=oop"></iframe>
+ </body>
+ </html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Initial state.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Click on the Check for issues - all.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ level: 1,
+ },
+ ],
+ },
+ },
+];
+
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel tree audit on a page with hidden iframes."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_panel_audit_oop.js b/devtools/client/accessibility/test/browser/browser_accessibility_panel_audit_oop.js
new file mode 100644
index 0000000000..d1ccbe9d5c
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_panel_audit_oop.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global toggleMenuItem, TREE_FILTERS_MENU_ID */
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1 style="color:rgba(255,0,0,0.1); background-color:rgba(255,255,255,1);">
+ Top level header
+ </h1>
+ </body>
+ </html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Initial state.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+ level: 1,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Click on the Check for issues - all.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+ level: 1,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ level: 1,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Click on the Check for issues - all again.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+ level: 1,
+ },
+ {
+ role: "internal frame",
+ name: `"Accessibility Panel Test (OOP)"`,
+ level: 2,
+ },
+ {
+ role: "document",
+ name: `"Accessibility Panel Test (OOP)"`,
+ level: 3,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ level: 4,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ level: 5,
+ },
+ ],
+ },
+ },
+];
+
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel tree audit on a page with an OOP document.",
+ { remoteIframe: true }
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_checks.js b/devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_checks.js
new file mode 100644
index 0000000000..b7d541853b
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_checks.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global toggleMenuItem, TREE_FILTERS_MENU_ID */
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body></body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Check initial state.",
+ expected: {
+ activeToolbarFilters: [true, false, false, false, false],
+ },
+ },
+ {
+ desc: "Toggle first filter (all) to activate.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ activeToolbarFilters: [false, true, true, true, true],
+ },
+ },
+ {
+ desc: "Click on the filter again.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ activeToolbarFilters: [true, false, false, false, false],
+ },
+ },
+ {
+ desc: "Toggle first custom filter to activate.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 2);
+ },
+ expected: {
+ activeToolbarFilters: [false, false, true, false, false],
+ },
+ },
+ {
+ desc: "Click on the filter again.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 2);
+ },
+ expected: {
+ activeToolbarFilters: [true, false, false, false, false],
+ },
+ },
+ {
+ desc: "Toggle first custom filter to activate.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 2);
+ },
+ expected: {
+ activeToolbarFilters: [false, false, true, false, false],
+ },
+ },
+ {
+ desc: "Toggle second custom filter to activate.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 3);
+ },
+ expected: {
+ activeToolbarFilters: [false, false, true, true, false],
+ },
+ },
+ {
+ desc: "Toggle third custom filter to activate.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 4);
+ },
+ expected: {
+ activeToolbarFilters: [false, true, true, true, true],
+ },
+ },
+ {
+ desc: "Click on the none filter to de-activate all.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 0);
+ },
+ expected: {
+ activeToolbarFilters: [true, false, false, false, false],
+ },
+ },
+];
+
+/**
+ * Simple test that checks toggle states for filters in the Accessibility panel
+ * toolbar.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel filter toggle states."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_pref_scroll.js b/devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_pref_scroll.js
new file mode 100644
index 0000000000..9f8434f695
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_pref_scroll.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global toggleMenuItem, PREFS_MENU_ID */
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body></body>
+</html>`;
+
+const {
+ PREFS: { SCROLL_INTO_VIEW },
+} = require("resource://devtools/client/accessibility/constants.js");
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Check initial state. All filters are disabled (except none). Scroll into view pref disabled.",
+ expected: {
+ activeToolbarFilters: [true, false, false, false],
+ toolbarPrefValues: {
+ [SCROLL_INTO_VIEW]: false,
+ },
+ },
+ },
+ {
+ desc: "Toggle scroll into view checkbox to set the pref. Scroll into view pref should be enabled.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, PREFS_MENU_ID, 0);
+ },
+ expected: {
+ activeToolbarFilters: [true, false, false, false],
+ toolbarPrefValues: {
+ [SCROLL_INTO_VIEW]: true,
+ },
+ },
+ },
+ {
+ desc: "Toggle off scroll into view checkbox to unset the pref. Scroll into view pref disabled.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, PREFS_MENU_ID, 0);
+ },
+ expected: {
+ activeToolbarFilters: [true, false, false, false],
+ toolbarPrefValues: {
+ [SCROLL_INTO_VIEW]: false,
+ },
+ },
+ },
+];
+
+/**
+ * Simple test that checks toggle state and pref set for automatic scroll into
+ * view setting.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel scroll into view pref."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_print_to_json.js b/devtools/client/accessibility/test/browser/browser_accessibility_print_to_json.js
new file mode 100644
index 0000000000..b7641430c0
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_print_to_json.js
@@ -0,0 +1,245 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = "<h1>Top level header</h1>";
+
+function getMenuItems(toolbox) {
+ const menuDoc = toolbox.doc.defaultView.windowRoot.ownerGlobal.document;
+ const menu = menuDoc.getElementById("accessibility-row-contextmenu");
+ return {
+ menu,
+ items: [...menu.getElementsByTagName("menuitem")],
+ };
+}
+
+async function newTabSelected(tab) {
+ info("Waiting for the JSON viewer tab.");
+ await BrowserTestUtils.waitForCondition(
+ () => gBrowser.selectedTab !== tab,
+ "Current tab updated."
+ );
+ return gBrowser.selectedTab;
+}
+
+function parseSnapshotFromTabURI(tab) {
+ let snapshot = tab.label.split("data:application/json;charset=UTF-8,")[1];
+ snapshot = decodeURIComponent(snapshot);
+ return JSON.parse(snapshot);
+}
+
+async function checkJSONSnapshotForRow({ doc, tab, toolbox }, index, expected) {
+ info(`Triggering context menu for row #${index}.`);
+ EventUtils.synthesizeMouseAtCenter(
+ doc.querySelectorAll(".treeRow")[index],
+ { type: "contextmenu" },
+ doc.defaultView
+ );
+
+ info(`Triggering "Print To JSON" menu item for row ${index}.`);
+ const {
+ menu,
+ items: [printToJSON],
+ } = getMenuItems(toolbox);
+
+ await BrowserTestUtils.waitForPopupEvent(menu, "shown");
+
+ menu.activateItem(printToJSON);
+
+ const jsonViewTab = await newTabSelected(tab);
+ Assert.deepEqual(
+ parseSnapshotFromTabURI(jsonViewTab),
+ expected,
+ "JSON snapshot for the whole document is correct"
+ );
+
+ await removeTab(jsonViewTab);
+}
+
+const OOP_FRAME_DOCUMENT_SNAPSHOT = {
+ childCount: 1,
+ description: "",
+ indexInParent: 0,
+ keyboardShortcut: "",
+ name: "Accessibility Panel Test (OOP)",
+ nodeCssSelector: "",
+ nodeType: 9,
+ role: "document",
+ value: "",
+ actions: [],
+ attributes: {
+ display: "block",
+ "explicit-name": "true",
+ "margin-bottom": "8px",
+ "margin-left": "8px",
+ "margin-right": "8px",
+ "margin-top": "8px",
+ tag: "body",
+ "text-align": "start",
+ "text-indent": "0px",
+ },
+ states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
+ children: [
+ {
+ childCount: 1,
+ description: "",
+ indexInParent: 0,
+ keyboardShortcut: "",
+ name: "Top level header",
+ nodeCssSelector: "body > h1:nth-child(1)",
+ nodeType: 1,
+ role: "heading",
+ value: "",
+ actions: [],
+ attributes: {
+ display: "block",
+ formatting: "block",
+ level: "1",
+ "margin-bottom": "21.44px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "0px",
+ tag: "h1",
+ "text-align": "start",
+ "text-indent": "0px",
+ },
+ states: ["selectable text", "opaque", "enabled", "sensitive"],
+ children: [
+ {
+ childCount: 0,
+ description: "",
+ indexInParent: 0,
+ keyboardShortcut: "",
+ name: "Top level header",
+ nodeCssSelector: "body > h1:nth-child(1)#text",
+ nodeType: 3,
+ role: "text leaf",
+ value: "",
+ actions: [],
+ attributes: {
+ "explicit-name": "true",
+ },
+ states: ["opaque", "enabled", "sensitive"],
+ children: [],
+ },
+ ],
+ },
+ ],
+};
+
+const OOP_FRAME_SNAPSHOT = {
+ childCount: 1,
+ description: "",
+ indexInParent: 0,
+ keyboardShortcut: "",
+ name: "Accessibility Panel Test (OOP)",
+ nodeCssSelector: "body > iframe:nth-child(1)",
+ nodeType: 1,
+ role: "internal frame",
+ value: "",
+ actions: [],
+ attributes: {
+ display: "inline",
+ "explicit-name": "true",
+ "margin-bottom": "0px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "0px",
+ tag: "iframe",
+ "text-align": "start",
+ "text-indent": "0px",
+ },
+ states: ["focusable", "opaque", "enabled", "sensitive"],
+ children: [OOP_FRAME_DOCUMENT_SNAPSHOT],
+};
+
+const EXPECTED_SNAPSHOT = {
+ childCount: 1,
+ description: "",
+ indexInParent: 0,
+ keyboardShortcut: "",
+ name: "",
+ nodeCssSelector: "",
+ nodeType: 9,
+ role: "document",
+ value: "",
+ actions: [],
+ attributes: {
+ display: "block",
+ "explicit-name": "true",
+ "margin-bottom": "8px",
+ "margin-left": "8px",
+ "margin-right": "8px",
+ "margin-top": "8px",
+ tag: "body",
+ "text-align": "start",
+ "text-indent": "0px",
+ },
+ states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
+ children: [OOP_FRAME_SNAPSHOT],
+};
+
+addA11YPanelTask(
+ "Test print to JSON functionality.",
+ TEST_URI,
+ async env => {
+ const { doc } = env;
+ await runA11yPanelTests(
+ [
+ {
+ desc: "Test the initial accessibility tree.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+ },
+ ],
+ },
+ },
+ ],
+ env
+ );
+
+ await toggleRow(doc, 0);
+ await toggleRow(doc, 1);
+
+ await runA11yPanelTests(
+ [
+ {
+ desc: "Test expanded accessibility tree.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+ },
+ {
+ role: "internal frame",
+ name: `"Accessibility Panel Test (OOP)"`,
+ },
+ {
+ role: "document",
+ name: `"Accessibility Panel Test (OOP)"`,
+ },
+ ],
+ },
+ },
+ ],
+ env
+ );
+
+ // Complete snapshot that includes OOP frame document (crossing process boundry).
+ await checkJSONSnapshotForRow(env, 0, EXPECTED_SNAPSHOT);
+ // Snapshot of an OOP frame (crossing process boundry).
+ await checkJSONSnapshotForRow(env, 1, OOP_FRAME_SNAPSHOT);
+ // Snapshot of an OOP frame document (not crossing process boundry).
+ await checkJSONSnapshotForRow(env, 2, OOP_FRAME_DOCUMENT_SNAPSHOT);
+ },
+ {
+ remoteIframe: true,
+ }
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_relation_navigation.js b/devtools/client/accessibility/test/browser/browser_accessibility_relation_navigation.js
new file mode 100644
index 0000000000..5f9b5e6eb5
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_relation_navigation.js
@@ -0,0 +1,169 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {
+ L10N,
+} = require("resource://devtools/client/accessibility/utils/l10n.js");
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1>Top level header</h1>
+ <p>This is a paragraph.</p>
+ </body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Test the initial accessibility tree and sidebar states.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ ],
+ sidebar: {
+ name: "Accessibility Panel Test",
+ role: "document",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 2,
+ indexInParent: 0,
+ states: [
+ // The focused state is an outdated state, since the toolbox should now
+ // have the focus and not the content page. See Bug 1702709.
+ "focused",
+ "readonly",
+ "focusable",
+ "opaque",
+ "enabled",
+ "sensitive",
+ ],
+ },
+ },
+ },
+ {
+ desc: "Expand first tree node.",
+ setup: ({ doc }) => toggleRow(doc, 0),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "paragraph",
+ name: `""`,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Select second tree node.",
+ setup: ({ doc }) => selectRow(doc, 1),
+ expected: {
+ sidebar: {
+ name: "Top level header",
+ role: "heading",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 1,
+ indexInParent: 0,
+ relations: {
+ "containing document": {
+ role: "document",
+ name: "Accessibility Panel Test",
+ },
+ },
+ states: ["selectable text", "opaque", "enabled", "sensitive"],
+ },
+ },
+ },
+ {
+ desc: "Select containing document.",
+ setup: async ({ doc, win }) => {
+ const relations = await selectProperty(doc, "/relations");
+ AccessibilityUtils.setEnv({
+ // Keyboard navigation is handled on the container level using arrow
+ // keys.
+ mustHaveAccessibleRule: false,
+ });
+ EventUtils.sendMouseEvent(
+ { type: "click" },
+ relations.querySelector(".arrow"),
+ win
+ );
+ AccessibilityUtils.resetEnv();
+ const containingDocRelation = await selectProperty(
+ doc,
+ "/relations/containing document"
+ );
+ AccessibilityUtils.setEnv({
+ // Keyboard interaction is only enabled when the row is selected and
+ // activated.
+ nonNegativeTabIndexRule: false,
+ });
+
+ const selectElementInTreeButton = containingDocRelation.querySelector(
+ ".open-accessibility-inspector"
+ );
+ ok(!!selectElementInTreeButton, "There's a button to select the element");
+ is(
+ selectElementInTreeButton.getAttribute("title"),
+ L10N.getStr("accessibility.accessible.selectElement.title"),
+ "The button has the expected title"
+ );
+ EventUtils.sendMouseEvent(
+ { type: "click" },
+ selectElementInTreeButton,
+ win
+ );
+ AccessibilityUtils.resetEnv();
+ },
+ expected: {
+ sidebar: {
+ name: "Accessibility Panel Test",
+ role: "document",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 2,
+ indexInParent: 0,
+ states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
+ },
+ },
+ },
+];
+
+/**
+ * Check navigation within the tree.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel relation navigation."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_reload.js b/devtools/client/accessibility/test/browser/browser_accessibility_reload.js
new file mode 100644
index 0000000000..c4ffdf30dd
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_reload.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI_1 = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1>Top level header</h1>
+ <p>This is a paragraph.</p>
+ </body>
+</html>`;
+
+const TEST_URI_2 = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Navigation Accessibility Panel</title>
+ </head>
+ <body></body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Test the initial accessibility tree state after first row is expanded.",
+ setup: async ({ doc }) => toggleRow(doc, 0),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "paragraph",
+ name: `""`,
+ },
+ ],
+ sidebar: {
+ name: "Accessibility Panel Test",
+ role: "document",
+ },
+ },
+ },
+ {
+ desc: "Reload the page.",
+ setup: async ({ panel }) => {
+ const onReloaded = panel.once("reloaded");
+ panel.accessibilityProxy.commands.targetCommand.reloadTopLevelTarget();
+ await onReloaded;
+ },
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ ],
+ sidebar: {
+ name: "Accessibility Panel Test",
+ role: "document",
+ },
+ },
+ },
+ {
+ desc: "Navigate to a new page.",
+ setup: async () => {
+ // `navigate` waits for the "reloaded" event so we don't need to do it explicitly here
+ await navigateTo(buildURL(TEST_URI_2));
+ },
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Navigation Accessibility Panel"`,
+ },
+ ],
+ sidebar: {
+ name: "Navigation Accessibility Panel",
+ role: "document",
+ },
+ },
+ },
+];
+
+/**
+ * Simple test that checks content of the Accessibility panel tree on reload.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI_1,
+ "Test Accessibility panel tree on reload."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_sidebar.js b/devtools/client/accessibility/test/browser/browser_accessibility_sidebar.js
new file mode 100644
index 0000000000..e7c2795ced
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_sidebar.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body></body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Test the initial accessibility sidebar state.",
+ expected: {
+ sidebar: {
+ name: "Accessibility Panel Test",
+ role: "document",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 0,
+ indexInParent: 0,
+ states: [
+ // The focused state is an outdated state, since the toolbox should now
+ // have the focus and not the content page. See Bug 1702709.
+ "focused",
+ "readonly",
+ "focusable",
+ "opaque",
+ "enabled",
+ "sensitive",
+ ],
+ },
+ },
+ },
+ {
+ desc: "Mark document as disabled for accessibility.",
+ setup: async ({ browser }) =>
+ SpecialPowers.spawn(browser, [], () =>
+ content.document.body.setAttribute("aria-disabled", true)
+ ),
+ expected: {
+ sidebar: {
+ states: ["unavailable", "readonly", "focusable", "opaque"],
+ },
+ },
+ },
+ {
+ desc: "Append a new child to the document.",
+ setup: async ({ browser }) =>
+ SpecialPowers.spawn(browser, [], () => {
+ const doc = content.document;
+ const button = doc.createElement("button");
+ button.textContent = "Press Me!";
+ doc.body.appendChild(button);
+ }),
+ expected: {
+ sidebar: {
+ childCount: 1,
+ },
+ },
+ },
+];
+
+/**
+ * Test that checks the Accessibility panel sidebar.
+ */
+addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel sidebar.");
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_sidebar_checks.js b/devtools/client/accessibility/test/browser/browser_accessibility_sidebar_checks.js
new file mode 100644
index 0000000000..304d562189
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_sidebar_checks.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {
+ accessibility: { SCORES },
+} = require("resource://devtools/shared/constants.js");
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <p style="color: red;">Red</p>
+ <p style="color: blue;">Blue</p>
+ <p style="color: gray; background: linear-gradient(#e66465, #9198e5);">Gray</p>
+ </body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Test the initial accessibility audit state.",
+ expected: {
+ audit: { CONTRAST: null },
+ },
+ },
+ {
+ desc: "Check accessible representing text node in red.",
+ setup: async ({ doc }) => {
+ await toggleRow(doc, 0);
+ await toggleRow(doc, 1);
+ await selectRow(doc, 2);
+ },
+ expected: {
+ audit: {
+ CONTRAST: {
+ value: 4.0,
+ color: [255, 0, 0, 1],
+ backgroundColor: [255, 255, 255, 1],
+ isLargeText: false,
+ score: SCORES.FAIL,
+ },
+ },
+ },
+ },
+ {
+ desc: "Check accessible representing text node in blue.",
+ setup: async ({ doc }) => {
+ await toggleRow(doc, 3);
+ await selectRow(doc, 4);
+ },
+ expected: {
+ audit: {
+ CONTRAST: {
+ value: 8.59,
+ color: [0, 0, 255, 1],
+ backgroundColor: [255, 255, 255, 1],
+ isLargeText: false,
+ score: SCORES.AAA,
+ },
+ },
+ },
+ },
+];
+
+/**
+ * Test that checks the Accessibility panel sidebar.
+ */
+addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel sidebar.");
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_sidebar_dom_nodes.js b/devtools/client/accessibility/test/browser/browser_accessibility_sidebar_dom_nodes.js
new file mode 100644
index 0000000000..075354b0c3
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_sidebar_dom_nodes.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {
+ L10N,
+} = require("resource://devtools/client/accessibility/utils/l10n.js");
+
+// Check that DOM nodes in the sidebar can be highlighted and that clicking on the icon
+// next to them opens the inspector.
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Sidebar DOM Nodes Test</title>
+ </head>
+ <body>
+ <h1 id="select-me">Hello</h1>
+ </body>
+</html>`;
+
+/**
+ * Test that checks the Accessibility panel sidebar.
+ */
+addA11YPanelTask(
+ "Check behavior of DOM nodes in side panel",
+ TEST_URI,
+ async ({ toolbox, doc }) => {
+ info("Select an item having an actual associated DOM node");
+ await toggleRow(doc, 0);
+ selectRow(doc, 1);
+
+ await BrowserTestUtils.waitForCondition(
+ () => getPropertyValue(doc, "name") === `"Hello"`,
+ "Wait until the sidebar is updated"
+ );
+
+ info("Check DOMNode");
+ const domNodeEl = getPropertyItem(doc, "DOMNode");
+ ok(domNodeEl, "The DOMNode item was retrieved");
+
+ const openInspectorButton = domNodeEl.querySelector(".open-inspector");
+ ok(openInspectorButton, "The open inspector button is displayed");
+ is(
+ openInspectorButton.getAttribute("title"),
+ L10N.getStr("accessibility.accessible.selectNodeInInspector.title"),
+ "The open inspector button has expected title"
+ );
+
+ info("Check that hovering DOMNode triggers the highlight");
+ // Loading the inspector panel at first, to make it possible to listen for
+ // new node selections
+ await toolbox.loadTool("inspector");
+ const highlighter = toolbox.getHighlighter();
+ const highlighterTestFront = await getHighlighterTestFront(toolbox);
+
+ const onHighlighterShown = highlighter.waitForHighlighterShown();
+
+ EventUtils.synthesizeMouseAtCenter(
+ openInspectorButton,
+ { type: "mousemove" },
+ doc.defaultView
+ );
+
+ const { nodeFront } = await onHighlighterShown;
+ is(nodeFront.displayName, "h1", "The correct node was highlighted");
+ isVisible = await highlighterTestFront.isHighlighting();
+ ok(isVisible, "Highlighter is displayed");
+
+ info("Unhighlight the node by moving away from the node");
+ const onHighlighterHidden = highlighter.waitForHighlighterHidden();
+ EventUtils.synthesizeMouseAtCenter(
+ getPropertyItem(doc, "name"),
+ { type: "mousemove" },
+ doc.defaultView
+ );
+
+ await onHighlighterHidden;
+ ok(true, "The highlighter was closed when moving away from the node");
+
+ info(
+ "Clicking on the inspector icon and waiting for the inspector to be selected"
+ );
+ const onNewNode = toolbox.selection.once("new-node-front");
+ openInspectorButton.click();
+ const inspectorSelectedNodeFront = await onNewNode;
+
+ ok(true, "Inspector selected and new node got selected");
+ is(
+ inspectorSelectedNodeFront.id,
+ "select-me",
+ "The expected node was selected"
+ );
+ }
+);
+
+function getPropertyItem(doc, label) {
+ const labelEl = Array.from(
+ doc.querySelectorAll("#accessibility-properties .object-label")
+ ).find(el => el.textContent === label);
+ if (!labelEl) {
+ return null;
+ }
+ return labelEl.closest(".node");
+}
+
+function getPropertyValue(doc, label) {
+ return getPropertyItem(doc, label)?.querySelector(".object-value")
+ ?.textContent;
+}
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_simulation.js b/devtools/client/accessibility/test/browser/browser_accessibility_simulation.js
new file mode 100644
index 0000000000..85a9be6b36
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_simulation.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global openSimulationMenu, toggleSimulationOption */
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Simulations Test</title>
+ </head>
+ <body>
+ <h1 style="color:blue; background-color:rgba(255,255,255,1);">
+ Top level header
+ </h1>
+ <h2 style="color:green; background-color:rgba(255,255,255,1);">
+ Second level header
+ </h2>
+ </body>
+</html>`;
+
+/**
+ * Test data 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 simulation components can be checked.
+ * expected {JSON} An expected states for the simulation components.
+ * }
+ */
+const tests = [
+ {
+ desc: "Check that the menu button is inactivate and the menu is closed initially.",
+ expected: {
+ simulation: {
+ buttonActive: false,
+ },
+ },
+ },
+ {
+ desc: "Clicking the menu button shows the menu with No Simulation selected.",
+ setup: async ({ doc }) => {
+ await openSimulationMenu(doc);
+ },
+ expected: {
+ simulation: {
+ buttonActive: false,
+ checkedOptionIndices: [0],
+ },
+ },
+ },
+ {
+ desc: "Selecting an option renders the menu button active and closes the menu.",
+ setup: async ({ doc }) => {
+ await toggleSimulationOption(doc, 2);
+ },
+ expected: {
+ simulation: {
+ buttonActive: true,
+ checkedOptionIndices: [2],
+ },
+ },
+ },
+ {
+ desc: "Reopening the menu preserves the previously selected option.",
+ setup: async ({ doc }) => {
+ await openSimulationMenu(doc);
+ },
+ expected: {
+ simulation: {
+ buttonActive: true,
+ checkedOptionIndices: [2],
+ },
+ },
+ },
+ {
+ desc: "Unselecting the option renders the button inactive and closes the menu.",
+ setup: async ({ doc }) => {
+ await toggleSimulationOption(doc, 2);
+ },
+ expected: {
+ simulation: {
+ buttonActive: false,
+ checkedOptionIndices: [0],
+ },
+ },
+ },
+];
+
+/**
+ * Test that checks state of simulation button and menu when
+ * options are selected/unselected with web render enabled.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test selecting and unselecting simulation options updates UI."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tabbing_order_highlighter.js b/devtools/client/accessibility/test/browser/browser_accessibility_tabbing_order_highlighter.js
new file mode 100644
index 0000000000..ed872f46e0
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tabbing_order_highlighter.js
@@ -0,0 +1,140 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check "Show tabbing order" works as expected
+
+const TEST_URI = `https://example.com/document-builder.sjs?html=
+ <button id=top-btn-1>Top level button before iframe</button>
+ <iframe src="https://example.org/document-builder.sjs?html=${encodeURIComponent(`
+ <button id=iframe-org-btn-1>in iframe button 1</button>
+ <button id=iframe-org-btn-2>in iframe button 2</button>
+ `)}"></iframe>
+ <button id=top-btn-2>Top level button after iframe</button>
+ <iframe src="https://example.net/document-builder.sjs?html=${encodeURIComponent(`
+ <button id=iframe-net-btn-1>in iframe button 1</button>
+ `)}"></iframe>`;
+
+add_task(async () => {
+ const { doc, store, tab, toolbox } = await addTestTab(TEST_URI);
+
+ const topLevelFrameHighlighterTestFront = await toolbox.target.getFront(
+ "highlighterTest"
+ );
+
+ const frameTargets = toolbox.commands.targetCommand.getAllTargets([
+ toolbox.commands.targetCommand.TYPES.FRAME,
+ ]);
+ const orgIframeTarget = frameTargets.find(t =>
+ t.url.startsWith("https://example.org")
+ );
+ const netIframeTarget = frameTargets.find(t =>
+ t.url.startsWith("https://example.net")
+ );
+
+ // The iframe only has a dedicated target when Fission or EFT is enabled
+ const orgIframeHighlighterTestFront = orgIframeTarget
+ ? await orgIframeTarget.getFront("highlighterTest")
+ : null;
+ const netIframeHighlighterTestFront = netIframeTarget
+ ? await netIframeTarget.getFront("highlighterTest")
+ : null;
+
+ let tabbingOrderHighlighterData =
+ await topLevelFrameHighlighterTestFront.getTabbingOrderHighlighterData();
+ is(
+ tabbingOrderHighlighterData.length,
+ 0,
+ "Tabbing order is not visible at first"
+ );
+
+ info(`Click on "Show Tabbing Order" checkbox`);
+ const tabbingOrderCheckbox = doc.getElementById(
+ "devtools-display-tabbing-order-checkbox"
+ );
+ tabbingOrderCheckbox.click();
+
+ await waitUntilState(store, state => state.ui.tabbingOrderDisplayed === true);
+
+ is(tabbingOrderCheckbox.checked, true, "Checkbox is checked");
+ tabbingOrderHighlighterData =
+ await topLevelFrameHighlighterTestFront.getTabbingOrderHighlighterData();
+ if (isFissionEnabled()) {
+ // ⚠️ We don't get the highlighter for the <html> node of the iframe when Fission is enabled.
+ // This should be fix as part of Bug 1740509.
+ is(
+ JSON.stringify(tabbingOrderHighlighterData),
+ JSON.stringify([`button#top-btn-1 : 1`, `button#top-btn-2 : 4`]),
+ "Tabbing order is visible for the top level target after clicking the checkbox"
+ );
+
+ const orgIframeTabingOrderHighlighterData =
+ await orgIframeHighlighterTestFront.getTabbingOrderHighlighterData();
+ is(
+ JSON.stringify(orgIframeTabingOrderHighlighterData),
+ JSON.stringify([
+ `button#iframe-org-btn-1 : 2`,
+ `button#iframe-org-btn-2 : 3`,
+ ]),
+ "Tabbing order is visible for the org iframe after clicking the checkbox"
+ );
+
+ const netIframeTabingOrderHighlighterData =
+ await netIframeHighlighterTestFront.getTabbingOrderHighlighterData();
+ is(
+ JSON.stringify(netIframeTabingOrderHighlighterData),
+ JSON.stringify([`button#iframe-net-btn-1 : 5`]),
+ "Tabbing order is visible for the net iframe after clicking the checkbox"
+ );
+ } else {
+ is(
+ JSON.stringify(tabbingOrderHighlighterData),
+ JSON.stringify([
+ `button#top-btn-1 : 1`,
+ `html : 2`,
+ `button#iframe-org-btn-1 : 3`,
+ `button#iframe-org-btn-2 : 4`,
+ `button#top-btn-2 : 5`,
+ `html : 6`,
+ `button#iframe-net-btn-1 : 7`,
+ ]),
+ "Tabbing order is visible for the top level target after clicking the checkbox"
+ );
+ }
+
+ info(`Clicking on the checkbox again hides the highlighter`);
+ tabbingOrderCheckbox.click();
+ await waitUntilState(
+ store,
+ state => state.ui.tabbingOrderDisplayed === false
+ );
+
+ is(tabbingOrderCheckbox.checked, false, "Checkbox is unchecked");
+ tabbingOrderHighlighterData =
+ await topLevelFrameHighlighterTestFront.getTabbingOrderHighlighterData();
+ is(
+ tabbingOrderHighlighterData.length,
+ 0,
+ "Tabbing order is not visible anymore after unchecking the checkbox"
+ );
+
+ if (isFissionEnabled()) {
+ const orgIframeTabingOrderHighlighterData =
+ await orgIframeHighlighterTestFront.getTabbingOrderHighlighterData();
+ is(
+ orgIframeTabingOrderHighlighterData.length,
+ 0,
+ "Tabbing order is also hidden on the org iframe target"
+ );
+ const netIframeTabingOrderHighlighterData =
+ await netIframeHighlighterTestFront.getTabbingOrderHighlighterData();
+ is(
+ netIframeTabingOrderHighlighterData.length,
+ 0,
+ "Tabbing order is also hidden on the net iframe target"
+ );
+ }
+
+ await closeTabToolboxAccessibility(tab);
+});
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tabbing_order_highlighter_iframe_picker.js b/devtools/client/accessibility/test/browser/browser_accessibility_tabbing_order_highlighter_iframe_picker.js
new file mode 100644
index 0000000000..788813d343
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tabbing_order_highlighter_iframe_picker.js
@@ -0,0 +1,195 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check "Show tabbing order" works as expected when used with the iframe picker
+
+const TEST_URI = `https://example.com/document-builder.sjs?html=
+ <button id=top-btn-1>Top level button before iframe</button>
+ <iframe src="https://example.org/document-builder.sjs?html=${encodeURIComponent(`
+ <button id=iframe-btn-1>in iframe button 1</button>
+ <button id=iframe-btn-2>in iframe button 2</button>
+ `)}"></iframe>
+ <button id=top-btn-2>Top level button after iframe</button>`;
+
+add_task(async () => {
+ const env = await addTestTab(TEST_URI);
+ const { doc, panel, store, toolbox, win } = env;
+
+ const topLevelFrameHighlighterTestFront = await toolbox.target.getFront(
+ "highlighterTest"
+ );
+
+ const iframeTarget = toolbox.commands.targetCommand
+ .getAllTargets([toolbox.commands.targetCommand.TYPES.FRAME])
+ .find(t => t.url.startsWith("https://example.org"));
+
+ // The iframe only has a dedicated target when Fission or EFT is enabled
+ const iframeHighlighterTestFront = iframeTarget
+ ? await iframeTarget.getFront("highlighterTest")
+ : null;
+
+ const topLevelAccessibilityFrontActorID =
+ panel.accessibilityProxy.accessibilityFront.actorID;
+
+ const iframeAccessibilityFrontActorID = iframeTarget
+ ? (await iframeTarget.getFront("accessibility")).actorID
+ : null;
+
+ info(`Click on "Show Tabbing Order" checkbox`);
+ const tabbingOrderCheckbox = doc.getElementById(
+ "devtools-display-tabbing-order-checkbox"
+ );
+ tabbingOrderCheckbox.click();
+ await waitUntilState(store, state => state.ui.tabbingOrderDisplayed === true);
+
+ is(tabbingOrderCheckbox.checked, true, "Checkbox is checked");
+ let tabbingOrderHighlighterData =
+ await topLevelFrameHighlighterTestFront.getTabbingOrderHighlighterData(
+ topLevelAccessibilityFrontActorID
+ );
+ if (isFissionEnabled()) {
+ // ⚠️ We don't get the highlighter for the <html> node of the iframe when Fission is enabled.
+ // This should be fix as part of Bug 1740509.
+ is(
+ JSON.stringify(tabbingOrderHighlighterData),
+ JSON.stringify([`button#top-btn-1 : 1`, `button#top-btn-2 : 4`]),
+ "Tabbing order is visible for the top level target after clicking the checkbox"
+ );
+
+ const iframeTabingOrderHighlighterData =
+ await iframeHighlighterTestFront.getTabbingOrderHighlighterData(
+ iframeAccessibilityFrontActorID
+ );
+
+ is(
+ JSON.stringify(iframeTabingOrderHighlighterData),
+ JSON.stringify([`button#iframe-btn-1 : 2`, `button#iframe-btn-2 : 3`]),
+ "Tabbing order is visible for the top level target after clicking the checkbox"
+ );
+ } else {
+ is(
+ JSON.stringify(tabbingOrderHighlighterData),
+ JSON.stringify([
+ `button#top-btn-1 : 1`,
+ `html : 2`,
+ `button#iframe-btn-1 : 3`,
+ `button#iframe-btn-2 : 4`,
+ `button#top-btn-2 : 5`,
+ ]),
+ "Tabbing order is visible for the top level target after clicking the checkbox"
+ );
+ }
+
+ info("Select the iframe in the iframe picker");
+ // Get the iframe picker items
+ const menuList = toolbox.doc.getElementById("toolbox-frame-menu");
+
+ if (isFissionEnabled() && !isEveryFrameTargetEnabled()) {
+ is(
+ menuList,
+ null,
+ "iframe picker does not show remote frames when Fission is enabled and EFT is disabled"
+ );
+ return;
+ }
+
+ const frames = Array.from(menuList.querySelectorAll(".command"));
+
+ let onInitialized = win.once(win.EVENTS.INITIALIZED);
+ frames[1].click();
+ await onInitialized;
+ await waitUntilState(
+ store,
+ state => state.ui.tabbingOrderDisplayed === false
+ );
+
+ is(
+ tabbingOrderCheckbox.checked,
+ false,
+ "Checkbox is unchecked after selecting an iframe"
+ );
+
+ tabbingOrderHighlighterData =
+ await topLevelFrameHighlighterTestFront.getTabbingOrderHighlighterData(
+ topLevelAccessibilityFrontActorID
+ );
+ is(
+ tabbingOrderHighlighterData.length,
+ 0,
+ "Tabbing order is not visible anymore"
+ );
+
+ info(
+ `Click on "Show Tabbing Order" checkbox and check that highlighter is only displayed for selected frame`
+ );
+ tabbingOrderCheckbox.click();
+ await waitUntilState(store, state => state.ui.tabbingOrderDisplayed === true);
+
+ tabbingOrderHighlighterData =
+ await topLevelFrameHighlighterTestFront.getTabbingOrderHighlighterData(
+ topLevelAccessibilityFrontActorID
+ );
+ if (isFissionEnabled() || isEveryFrameTargetEnabled()) {
+ is(
+ tabbingOrderHighlighterData.length,
+ 0,
+ "There's no highlighter displayed on the top level target when focused on specific iframe"
+ );
+
+ const iframeTabingOrderHighlighterData =
+ await iframeHighlighterTestFront.getTabbingOrderHighlighterData(
+ iframeAccessibilityFrontActorID
+ );
+
+ is(
+ JSON.stringify(iframeTabingOrderHighlighterData),
+ JSON.stringify([`button#iframe-btn-1 : 1`, `button#iframe-btn-2 : 2`]),
+ "Tabbing order has expected data when a specific iframe is selected"
+ );
+ } else {
+ // When Fission/EFT are not enabled, the highlighter is displayed from the top-level
+ // target, but only for the iframe
+ is(
+ JSON.stringify(tabbingOrderHighlighterData),
+ JSON.stringify([`button#iframe-btn-1 : 1`, `button#iframe-btn-2 : 2`]),
+ "Tabbing order has expected data when a specific iframe is selected"
+ );
+ }
+
+ info("Select the top level document back");
+ onInitialized = win.once(win.EVENTS.INITIALIZED);
+ toolbox.doc.querySelector("#toolbox-frame-menu .command").click();
+ await onInitialized;
+
+ is(
+ tabbingOrderCheckbox.checked,
+ false,
+ "Checkbox is unchecked after selecting the top level frame"
+ );
+ await waitUntilState(
+ store,
+ state => state.ui.tabbingOrderDisplayed === false
+ );
+
+ tabbingOrderHighlighterData =
+ await topLevelFrameHighlighterTestFront.getTabbingOrderHighlighterData(
+ topLevelAccessibilityFrontActorID
+ );
+
+ if (isFissionEnabled() || isEveryFrameTargetEnabled()) {
+ const iframeTabingOrderHighlighterData =
+ await iframeHighlighterTestFront.getTabbingOrderHighlighterData(
+ iframeAccessibilityFrontActorID
+ );
+
+ is(
+ iframeTabingOrderHighlighterData.length,
+ 0,
+ "Highlighter is hidden on the frame after selecting back the top level target"
+ );
+ }
+
+ await closeTabToolboxAccessibility(env.tab);
+});
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tree.js b/devtools/client/accessibility/test/browser/browser_accessibility_tree.js
new file mode 100644
index 0000000000..d1be57ee2d
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1>Top level header</h1>
+ <p>This is a paragraph.</p>
+ </body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Test the initial accessibility tree state.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Expand first tree node.",
+ setup: async ({ doc }) => toggleRow(doc, 0),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "paragraph",
+ name: `""`,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Collapse first tree node.",
+ setup: async ({ doc }) => toggleRow(doc, 0),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ ],
+ },
+ },
+];
+
+/**
+ * Simple test that checks content of the Accessibility panel tree.
+ */
+addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel tree.");
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit.js b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit.js
new file mode 100644
index 0000000000..b321fafba2
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit.js
@@ -0,0 +1,137 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global toggleRow, toggleMenuItem, TREE_FILTERS_MENU_ID */
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1 style="color:rgba(255,0,0,0.1); background-color:rgba(255,255,255,1);">
+ Top level header
+ </h1>
+ <h2 style="color:rgba(0,255,0,0.1); background-color:rgba(255,255,255,1);">
+ Second level header
+ </h2>
+ </body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Expand first and second tree nodes.",
+ setup: async ({ doc }) => {
+ await toggleRow(doc, 0);
+ await toggleRow(doc, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ level: 1,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ level: 2,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ level: 3,
+ },
+ {
+ role: "heading",
+ name: `"Second level header"`,
+ level: 2,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Click on the all filter.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ level: 1,
+ },
+ {
+ role: "text leaf",
+ name: `"Second level header "contrast`,
+ badges: ["contrast"],
+ selected: true,
+ level: 1,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Click on the all filter again.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ level: 1,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ level: 2,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ level: 3,
+ },
+ {
+ role: "heading",
+ name: `"Second level header"`,
+ level: 2,
+ },
+ {
+ role: "text leaf",
+ name: `"Second level header "contrast`,
+ badges: ["contrast"],
+ selected: true,
+ level: 3,
+ },
+ ],
+ },
+ },
+];
+
+/**
+ * Simple test that checks content of the Accessibility panel tree when one of
+ * the tree rows has a "contrast" badge and auditing is activated via toolbar
+ * filter.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel tree with contrast badge present."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_long.js b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_long.js
new file mode 100644
index 0000000000..a8e295c504
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_long.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global toggleMenuItem, TREE_FILTERS_MENU_ID */
+
+const header =
+ '<h1 style="color:rgba(255,0,0,0.1); ' +
+ 'background-color:rgba(255,255,255,1);">header</h1>';
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ ${header.repeat(20)}
+ </body>
+</html>`;
+
+const docRow = {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+};
+const headingRow = {
+ role: "heading",
+ name: `"header"`,
+};
+const textLeafRow = {
+ role: "text leaf",
+ name: `"header"contrast`,
+ badges: ["contrast"],
+};
+const audit = new Array(20).fill(textLeafRow);
+
+const auditInitial = audit.map(check => ({ ...check }));
+auditInitial[0].selected = true;
+
+const auditSecondLastSelected = audit.map(check => ({ ...check }));
+auditSecondLastSelected[19].selected = true;
+
+const resetAfterAudit = [docRow];
+for (let i = 0; i < 20; i++) {
+ resetAfterAudit.push(headingRow);
+ resetAfterAudit.push({ ...textLeafRow, selected: i === 19 });
+}
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Check initial state.",
+ expected: {
+ tree: [{ ...docRow, selected: true }],
+ },
+ },
+ {
+ desc: "Run an audit from a11y panel toolbar by activating a filter.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: auditInitial,
+ },
+ },
+ {
+ desc: "Select a row that is guaranteed to have to be scrolled into view.",
+ setup: async ({ doc }) => {
+ selectRow(doc, 0);
+ EventUtils.synthesizeKey("VK_END", {}, doc.defaultView);
+ },
+ expected: {
+ tree: auditSecondLastSelected,
+ },
+ },
+ {
+ desc: "Click on the filter again.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: resetAfterAudit,
+ },
+ },
+];
+
+/**
+ * Simple test that checks content of the Accessibility panel tree when the
+ * audit is activated via the panel's toolbar and the selection persists when
+ * the filter is toggled off.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel tree with persistent selected row."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_reset.js b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_reset.js
new file mode 100644
index 0000000000..084a46dc89
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_reset.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global toggleMenuItem, selectAccessibleForNode, TREE_FILTERS_MENU_ID */
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1 style="color:rgba(255,0,0,0.1); background-color:rgba(255,255,255,1);">
+ Top level header
+ </h1>
+ <h2 style="color:rgba(0,255,0,0.1); background-color:rgba(255,255,255,1);">
+ Second level header
+ </h2>
+ </body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Check initial state.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ selected: true,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Run an audit from a11y panel toolbar by activating a filter.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ selected: true,
+ },
+ {
+ role: "text leaf",
+ name: `"Second level header "contrast`,
+ badges: ["contrast"],
+ },
+ ],
+ },
+ },
+ {
+ desc: "Select an accessible object.",
+ setup: async env => {
+ await selectAccessibleForNode(env, "h1");
+ },
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ selected: true,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ },
+ {
+ role: "heading",
+ name: `"Second level header"`,
+ },
+ {
+ role: "text leaf",
+ name: `"Second level header "contrast`,
+ badges: ["contrast"],
+ },
+ ],
+ },
+ },
+];
+
+/**
+ * Simple test that checks content of the Accessibility panel tree when the
+ * audit is activated via the panel's toolbar.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel tree with contrast filter audit activation."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_toolbar.js b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_toolbar.js
new file mode 100644
index 0000000000..9a1b9ba4c7
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_toolbar.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global toggleMenuItem, TREE_FILTERS_MENU_ID */
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1 style="color:rgba(255,0,0,0.1); background-color:rgba(255,255,255,1);">
+ Top level header
+ </h1>
+ <h2 style="color:rgba(0,255,0,0.1); background-color:rgba(255,255,255,1);">
+ Second level header
+ </h2>
+ </body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Check initial state.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ selected: true,
+ },
+ ],
+ activeToolbarFilters: [true, false, false, false, false],
+ },
+ },
+ {
+ desc: "Run an audit (all) from a11y panel toolbar by activating a filter.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ selected: true,
+ },
+ {
+ role: "text leaf",
+ name: `"Second level header "contrast`,
+ badges: ["contrast"],
+ },
+ ],
+ activeToolbarFilters: [false, true, true, true, true],
+ },
+ },
+ {
+ desc: "Click on the filter again.",
+ setup: async ({ doc, toolbox }) => {
+ await toggleMenuItem(doc, toolbox.doc, TREE_FILTERS_MENU_ID, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ selected: true,
+ },
+ {
+ role: "heading",
+ name: `"Second level header"`,
+ },
+ {
+ role: "text leaf",
+ name: `"Second level header "contrast`,
+ badges: ["contrast"],
+ },
+ ],
+ activeToolbarFilters: [true, false, false, false, false],
+ },
+ },
+];
+
+/**
+ * Simple test that checks content of the Accessibility panel tree when the
+ * audit is activated via the panel's toolbar.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel tree with 'all' filter audit activation."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tree_contrast.js b/devtools/client/accessibility/test/browser/browser_accessibility_tree_contrast.js
new file mode 100644
index 0000000000..2abfabd324
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_contrast.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1 style="color:rgba(255,0,0,0.1); background-color:rgba(255,255,255,1);">
+ Top level header
+ </h1>
+ </body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Expand first and second tree nodes.",
+ setup: async ({ doc }) => {
+ await toggleRow(doc, 0);
+ await toggleRow(doc, 1);
+ },
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header "contrast`,
+ badges: ["contrast"],
+ },
+ ],
+ },
+ },
+];
+
+/**
+ * Simple test that checks content of the Accessibility panel tree when one of
+ * the tree rows has a "contrast" badge.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel tree with contrast badge."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tree_iframe_picker.js b/devtools/client/accessibility/test/browser/browser_accessibility_tree_iframe_picker.js
new file mode 100644
index 0000000000..dc146a32d5
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_iframe_picker.js
@@ -0,0 +1,121 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that the accessibility panel works as expected when using the iframe picker
+
+const TEST_URI = `data:text/html,<meta charset=utf8>
+ <head>
+ <title>TopLevel</title>
+ <style>h1 { color: lightgrey; }</style>
+ </head>
+ <body>
+ <h1>Top level header</h1>
+ <p>This is a paragraph.</p>
+ <iframe src="data:text/html,<meta charset=utf8>
+ <head>
+ <title>iframe</title>
+ <style>h2 { color: aliceblue }</style>
+ </head>
+ <body>
+ <h2>Iframe header</h2>
+
+ <iframe src='data:text/html,<meta charset=utf8>
+ <head>
+ <title>nested iframe</title>
+ <style>h2 { color: lightpink }</style>
+ </head>
+ <body>
+ <h2>Nested Iframe header</h2>
+ </body>
+ '></iframe>
+
+ </body>
+ "></iframe>`;
+
+add_task(async () => {
+ const env = await addTestTab(TEST_URI);
+ const { doc, toolbox, win } = env;
+
+ await checkTree(env, [
+ {
+ role: "document",
+ name: `"TopLevel"`,
+ },
+ ]);
+
+ info("Select the iframe in the iframe picker");
+ // Get the iframe picker items
+ const menuList = toolbox.doc.getElementById("toolbox-frame-menu");
+ const frames = Array.from(menuList.querySelectorAll(".command"));
+
+ let onInitialized = win.once(win.EVENTS.INITIALIZED);
+ frames[1].click();
+ await onInitialized;
+
+ await checkTree(env, [
+ {
+ role: "document",
+ name: `"iframe"`,
+ },
+ ]);
+
+ info(
+ "Run a constrast audit to check only issues from selected iframe tree are displayed"
+ );
+ const CONTRAST_MENU_ITEM_INDEX = 2;
+ const onUpdated = win.once(win.EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED);
+ await toggleMenuItem(
+ doc,
+ toolbox.doc,
+ TREE_FILTERS_MENU_ID,
+ CONTRAST_MENU_ITEM_INDEX
+ );
+ await onUpdated;
+ // wait until the tree is filtered (i.e. the audit is done and only nodes with issues
+ // should be displayed)
+ await waitFor(() => doc.querySelector(".treeTable.filtered"));
+
+ await checkTree(env, [
+ {
+ role: "text leaf",
+ name: `"Iframe header"contrast`,
+ badges: ["contrast"],
+ level: 1,
+ },
+ {
+ role: "text leaf",
+ name: `"Nested Iframe header"contrast`,
+ badges: ["contrast"],
+ level: 1,
+ },
+ ]);
+
+ info("Select the top level document back");
+ onInitialized = win.once(win.EVENTS.INITIALIZED);
+ frames[0].click();
+ await onInitialized;
+
+ await checkTree(env, [
+ {
+ role: "document",
+ name: `"TopLevel"`,
+ },
+ ]);
+
+ await closeTabToolboxAccessibility(env.tab);
+});
+
+function checkTree(env, tree) {
+ return runA11yPanelTests(
+ [
+ {
+ expected: {
+ tree,
+ },
+ },
+ ],
+ env
+ );
+}
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tree_navigation.js b/devtools/client/accessibility/test/browser/browser_accessibility_tree_navigation.js
new file mode 100644
index 0000000000..d98c3d8e35
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_navigation.js
@@ -0,0 +1,186 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Panel Test</title>
+ </head>
+ <body>
+ <h1>Top level header</h1>
+ <p>This is a paragraph.</p>
+ </body>
+</html>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Test the initial accessibility tree and sidebar states.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ ],
+ sidebar: {
+ name: "Accessibility Panel Test",
+ role: "document",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 2,
+ indexInParent: 0,
+ states: [
+ // The focused state is an outdated state, since the toolbox should now
+ // have the focus and not the content page. See Bug 1702709.
+ "focused",
+ "readonly",
+ "focusable",
+ "opaque",
+ "enabled",
+ "sensitive",
+ ],
+ },
+ },
+ },
+ {
+ desc: "Expand first tree node.",
+ setup: async ({ doc }) => toggleRow(doc, 0),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "paragraph",
+ name: `""`,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Expand second tree node.",
+ setup: async ({ doc }) => toggleRow(doc, 1),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header"`,
+ },
+ {
+ role: "paragraph",
+ name: `""`,
+ },
+ ],
+ sidebar: {
+ name: "Top level header",
+ role: "heading",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 1,
+ indexInParent: 0,
+ states: ["selectable text", "opaque", "enabled", "sensitive"],
+ },
+ },
+ },
+ {
+ desc: "Select third tree node.",
+ setup: ({ doc }) => selectRow(doc, 2),
+ expected: {
+ sidebar: {
+ name: "Top level header",
+ role: "text leaf",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 0,
+ indexInParent: 0,
+ states: ["opaque", "enabled", "sensitive"],
+ },
+ },
+ },
+ {
+ desc: "Collapse first tree node.",
+ setup: async ({ doc }) => toggleRow(doc, 0),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ ],
+ sidebar: {
+ name: "Accessibility Panel Test",
+ role: "document",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 2,
+ indexInParent: 0,
+ states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
+ },
+ },
+ },
+ {
+ desc: "Expand first tree node again.",
+ setup: async ({ doc }) => toggleRow(doc, 0),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `"Accessibility Panel Test"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "text leaf",
+ name: `"Top level header"`,
+ },
+ {
+ role: "paragraph",
+ name: `""`,
+ },
+ ],
+ },
+ },
+];
+
+/**
+ * Check navigation within the tree.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel tree navigation."
+);
diff --git a/devtools/client/accessibility/test/browser/browser_accessibility_tree_navigation_oop.js b/devtools/client/accessibility/test/browser/browser_accessibility_tree_navigation_oop.js
new file mode 100644
index 0000000000..dc710c82e4
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_navigation_oop.js
@@ -0,0 +1,149 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<h1>Top level header</h1><p>This is a paragraph.</p>`;
+
+/**
+ * Test data 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.
+ * }
+ */
+const tests = [
+ {
+ desc: "Test the initial accessibility tree and sidebar states.",
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+ },
+ ],
+ sidebar: {
+ name: "",
+ role: "document",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 1,
+ indexInParent: 0,
+ states: [
+ // The focused state is an outdated state, since the toolbox should now
+ // have the focus and not the content page. See Bug 1702709.
+ "focused",
+ "readonly",
+ "focusable",
+ "opaque",
+ "enabled",
+ "sensitive",
+ ],
+ },
+ },
+ },
+ {
+ desc: "Expand first tree node.",
+ setup: ({ doc }) => toggleRow(doc, 0),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+ },
+ {
+ role: "internal frame",
+ name: `"Accessibility Panel Test (OOP)"`,
+ },
+ ],
+ },
+ },
+ {
+ desc: "Expand second tree node. Display OOP document.",
+ setup: ({ doc }) => toggleRow(doc, 1),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+ },
+ {
+ role: "internal frame",
+ name: `"Accessibility Panel Test (OOP)"`,
+ },
+ {
+ role: "document",
+ name: `"Accessibility Panel Test (OOP)"`,
+ },
+ ],
+ sidebar: {
+ name: "Accessibility Panel Test (OOP)",
+ role: "internal frame",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 1,
+ indexInParent: 0,
+ states: ["focusable", "opaque", "enabled", "sensitive"],
+ },
+ },
+ },
+ {
+ desc: "Expand third tree node. Display OOP frame content.",
+ setup: ({ doc }) => toggleRow(doc, 2),
+ expected: {
+ tree: [
+ {
+ role: "document",
+ name: `""text label`,
+ badges: ["text label"],
+ },
+ {
+ role: "internal frame",
+ name: `"Accessibility Panel Test (OOP)"`,
+ },
+ {
+ role: "document",
+ name: `"Accessibility Panel Test (OOP)"`,
+ },
+ {
+ role: "heading",
+ name: `"Top level header"`,
+ },
+ {
+ role: "paragraph",
+ name: `""`,
+ },
+ ],
+ sidebar: {
+ name: "Accessibility Panel Test (OOP)",
+ role: "document",
+ actions: [],
+ value: "",
+ description: "",
+ keyboardShortcut: "",
+ childCount: 2,
+ indexInParent: 0,
+ states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
+ },
+ },
+ },
+];
+
+/**
+ * Check navigation within the tree.
+ */
+addA11yPanelTestsTask(
+ tests,
+ TEST_URI,
+ "Test Accessibility panel tree navigation with OOP frame.",
+ { remoteIframe: true }
+);
diff --git a/devtools/client/accessibility/test/browser/head.js b/devtools/client/accessibility/test/browser/head.js
new file mode 100644
index 0000000000..1a94c723e0
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/head.js
@@ -0,0 +1,823 @@
+/* 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;
+
+ const enableButton = doc.getElementById("accessibility-enable-button");
+ // If enable button is not found, asume the tool is already enabled.
+ if (enableButton) {
+ EventUtils.sendMouseEvent({ type: "click" }, enableButton, win);
+ }
+
+ 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 test URL
+ * @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(buildURL(uri, options));
+ await task(env);
+ await closeTabToolboxAccessibility(env.tab);
+ });
+}