- Revision
- 291570
- Author
- tyle...@apple.com
- Date
- 2022-03-21 11:48:25 -0700 (Mon, 21 Mar 2022)
Log Message
AX: Include display: contents elements in the AX tree
https://bugs.webkit.org/show_bug.cgi?id=237834
Reviewed by Chris Fleizach.
Source/WebCore:
Because display: contents intentionally prevents a render object from being
generated for the element it's applied to, we don't add it to the AX tree as
part of our normal render tree walk, making these elements inaccessible.
This patch includes these elements as part of the DOM walk that
addHiddenChildren (now renamed to addNodeOnlyChildren) already does.
Also, because display: contents moves the affected element's children up a
level in the render tree, this patch also special cases:
1. AccessibilityRenderObject::parentObject and similar methods to
return their display: contents parent instead of their render tree
parent
2. AccessibilityObject::insertChild to only insert display: contents
children to their display: contents parent, rather than their render
tree parent
Test: accessibility/display-contents-element-roles.html, accessibility/aria-hidden-display-contents-element.html
* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::getOrCreate):
Allow creation of AX objects from display: contents `Node`s.
Also, don't create an object for a renderer that is in the process of
being destroyed (prevents display-contents-element-roles.html from crashing in ITM)
* accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::displayContentsParent const):
(WebCore::AccessibilityObject::insertChild):
If an object has a display: contents parent, and that parent isn't
`this`, return early.
* accessibility/AccessibilityObject.h:
* accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::parentObjectIfExists const):
(WebCore::AccessibilityRenderObject::parentObject const):
(WebCore::AccessibilityRenderObject::parentObjectUnignored const):
If an object has a display: contents parent, return that instead of
its render tree parent.
(WebCore::AccessibilityRenderObject::addNodeOnlyChildren):
Renamed from addHiddenChildren.
(WebCore::AccessibilityRenderObject::addChildren):
Don't clear m_subtreeDirty until after all children have been added.
Necessary to make aria-hidden-display-contents-element.html pass, as
we were clearing this too early, causing display: contents subtrees to
not be updated after aria-hidden changes.
(WebCore::AccessibilityRenderObject::addHiddenChildren):
Renamed to addNodeOnlyChildren.
* accessibility/AccessibilityRenderObject.h:
LayoutTests:
* accessibility/aria-hidden-display-contents-element-expected.txt: Added.
* accessibility/aria-hidden-display-contents-element.html: Added.
* accessibility/display-contents-element-roles-expected.txt: Added.
* accessibility/display-contents-element-roles.html: Added.
* platform/glib/accessibility/aria-hidden-display-contents-element-expected.txt: Added.
* platform/glib/accessibility/display-contents-element-roles-expected.txt: Added.
* platform/ios/TestExpectations: Enable new tests.
* platform/ios/accessibility/aria-hidden-display-contents-element-expected.txt: Added.
* platform/ios/accessibility/display-contents-element-roles-expected.txt: Added.
* platform/win/TestExpectations: Skip display-contents-element-roles.html.
* platform/win/accessibility/aria-hidden-display-contents-element-expected.txt: Added.
Modified Paths
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (291569 => 291570)
--- trunk/LayoutTests/ChangeLog 2022-03-21 18:01:29 UTC (rev 291569)
+++ trunk/LayoutTests/ChangeLog 2022-03-21 18:48:25 UTC (rev 291570)
@@ -1,3 +1,22 @@
+2022-03-21 Tyler Wilcock <tyle...@apple.com>
+
+ AX: Include display: contents elements in the AX tree
+ https://bugs.webkit.org/show_bug.cgi?id=237834
+
+ Reviewed by Chris Fleizach.
+
+ * accessibility/aria-hidden-display-contents-element-expected.txt: Added.
+ * accessibility/aria-hidden-display-contents-element.html: Added.
+ * accessibility/display-contents-element-roles-expected.txt: Added.
+ * accessibility/display-contents-element-roles.html: Added.
+ * platform/glib/accessibility/aria-hidden-display-contents-element-expected.txt: Added.
+ * platform/glib/accessibility/display-contents-element-roles-expected.txt: Added.
+ * platform/ios/TestExpectations: Enable new tests.
+ * platform/ios/accessibility/aria-hidden-display-contents-element-expected.txt: Added.
+ * platform/ios/accessibility/display-contents-element-roles-expected.txt: Added.
+ * platform/win/TestExpectations: Skip display-contents-element-roles.html.
+ * platform/win/accessibility/aria-hidden-display-contents-element-expected.txt: Added.
+
2022-03-21 Ziran Sun <z...@igalia.com>
[selection] HTMLTextFormControlElement::subtreeHasChanged() shouldn't be called in setRangeText
Added: trunk/LayoutTests/accessibility/aria-hidden-display-contents-element-expected.txt (0 => 291570)
--- trunk/LayoutTests/accessibility/aria-hidden-display-contents-element-expected.txt (rev 0)
+++ trunk/LayoutTests/accessibility/aria-hidden-display-contents-element-expected.txt 2022-03-21 18:48:25 UTC (rev 291570)
@@ -0,0 +1,23 @@
+This test ensures that an aria-hidden display:contents element behaves as expected.
+
+Testing element #main
+AXRole: AXGroup
+computedRoleString: main
+AXSubrole: AXLandmarkMain
+
+Testing element #list
+AXRole: AXList
+computedRoleString: list
+AXSubrole: AXContentList
+
+Testing element #paragraph
+AXRole: AXGroup
+computedRoleString: paragraph
+
+Hiding #main with aria-hidden='true'.
+PASS: All elements are now hidden.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
Added: trunk/LayoutTests/accessibility/aria-hidden-display-contents-element.html (0 => 291570)
--- trunk/LayoutTests/accessibility/aria-hidden-display-contents-element.html (rev 0)
+++ trunk/LayoutTests/accessibility/aria-hidden-display-contents-element.html 2022-03-21 18:48:25 UTC (rev 291570)
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body id="body">
+
+<main id="main" style="display: contents">
+ <ol id="list">
+ <li>List item one</li>
+ <li>List item two</li>
+ </ol>
+ <p id="paragraph">Content inside main > p tag.<p>
+</main>
+
+<script>
+ var testOutput = "This test ensures that an aria-hidden display:contents element behaves as expected.";
+
+ function debugElement(id) {
+ testOutput += `\n\nTesting element #${id}\n`;
+ const axElement = accessibilityController.accessibleElementById(id);
+ if (!axElement) {
+ debug(`\nFAIL: Couldn't get AX element for #${id}.`);
+ return;
+ }
+
+ testOutput += axElement.role;
+ const computedRoleString = axElement.computedRoleString;
+ if (computedRoleString)
+ testOutput += `\ncomputedRoleString: ${computedRoleString}`;
+
+ let subrole = axElement.subrole;
+ if (subrole.replace("AXSubrole: ", ""))
+ testOutput += `\n${subrole}`;
+ }
+
+ if (window.accessibilityController) {
+ window.jsTestIsAsync = true;
+
+ debugElement("main");
+ debugElement("list");
+ debugElement("paragraph");
+
+ testOutput += "\n\nHiding #main with aria-hidden='true'.";
+ document.getElementById("main").ariaHidden = "true";
+ setTimeout(async function() {
+ await waitFor(() => {
+ // Wait for the main to be hidden.
+ return !accessibilityController.accessibleElementById("main");
+ });
+ const list = accessibilityController.accessibleElementById("list");
+ const paragraph = accessibilityController.accessibleElementById("paragraph");
+ if (!list && !paragraph)
+ testOutput += "\nPASS: All elements are now hidden.";
+ else {
+ if (list)
+ testOutput += "\nFAIL: <ol> was not hidden by aria-hidden.";
+ if (paragraph)
+ testOutput += "\nFAIL: <p> was not hidden by aria-hidden.";
+ }
+
+ document.getElementById("main").style.visibility = "hidden";
+ debug(testOutput);
+ finishJSTest();
+ }, 0);
+ }
+</script>
+</body>
+</html>
+
Added: trunk/LayoutTests/accessibility/display-contents-element-roles-expected.txt (0 => 291570)
--- trunk/LayoutTests/accessibility/display-contents-element-roles-expected.txt (rev 0)
+++ trunk/LayoutTests/accessibility/display-contents-element-roles-expected.txt 2022-03-21 18:48:25 UTC (rev 291570)
@@ -0,0 +1,166 @@
+This test ensures elements with CSS display: contents have the correct role.
+
+<a href="" id="link" class="testcase"></a>
+ AXRole: AXLink
+ computedRoleString: link
+
+<article class="testcase" id="article"></article>
+ AXRole: AXGroup
+ computedRoleString: article
+ AXSubrole: AXDocumentArticle
+
+<aside class="testcase" id="aside"></aside>
+ AXRole: AXGroup
+ computedRoleString: complementary
+ AXSubrole: AXLandmarkComplementary
+
+<blockquote class="testcase" id="blockquote"></blockquote>
+ AXRole: AXGroup
+ computedRoleString: blockquote
+
+<button class="testcase" id="button"></button>
+ AXRole: AXButton
+ computedRoleString: button
+
+<code class="testcase" id="code"></code>
+ AXRole: AXGroup
+ AXSubrole: AXCodeStyleGroup
+
+<del class="testcase" id="del"></del>
+ AXRole: AXGroup
+ computedRoleString: deletion
+ AXSubrole: AXDeleteStyleGroup
+
+<details class="testcase" id="details"></details>
+ AXRole: AXGroup
+ AXSubrole: AXDetails
+
+<summary class="testcase" id="summary"></summary>
+ AXRole: AXButton
+ AXSubrole: AXSummary
+
+<dfn class="testcase" id="dfn"></dfn>
+ AXRole: AXGroup
+ computedRoleString: definition
+ AXSubrole: AXDefinition
+
+<div class="testcase" id="div"></div>
+ AXRole: AXGroup
+
+<dl class="testcase" id="dl"></dl>
+ AXRole: AXList
+
+<dt class="testcase" id="dt"></dt>
+ AXRole: AXGroup
+ AXSubrole: AXTerm
+
+<dd class="testcase" id="dd"></dd>
+ AXRole: AXGroup
+ AXSubrole: AXDescription
+
+<legend class="testcase" id="legend"></legend>
+ AXRole: AXGroup
+
+<figure class="testcase" id="figure"></figure>
+ AXRole: AXGroup
+ computedRoleString: figure
+ AXSubrole: AXEmptyGroup
+
+<form class="testcase" id="form"></form>
+ AXRole: AXGroup
+ computedRoleString: form
+ AXSubrole: AXEmptyGroup
+
+<h2 class="testcase" id="h2"></h2>
+ AXRole: AXHeading
+ computedRoleString: heading
+
+<hr class="testcase" id="hr">
+ AXRole: AXSplitter
+ computedRoleString: separator
+ AXSubrole: AXContentSeparator
+
+<ins class="testcase" id="ins"></ins>
+ AXRole: AXGroup
+ computedRoleString: insertion
+ AXSubrole: AXInsertStyleGroup
+
+<label class="testcase" id="label"></label>
+ AXRole: AXGroup
+
+<main class="testcase" id="main"></main>
+ AXRole: AXGroup
+ computedRoleString: main
+ AXSubrole: AXLandmarkMain
+
+<mark class="testcase" id="mark"></mark>
+ AXRole: AXGroup
+
+<menu class="testcase" type="toolbar" id="menu-toolbar"></menu>
+ AXRole: AXToolbar
+ computedRoleString: toolbar
+
+<nav class="testcase" id="nav"></nav>
+ AXRole: AXGroup
+ computedRoleString: navigation
+ AXSubrole: AXLandmarkNavigation
+
+<ol class="testcase" id="ol"></ol>
+ AXRole: AXList
+ computedRoleString: list
+
+<li class="testcase" id="ol-li-element"></li>
+ AXRole: AXGroup
+ computedRoleString: listitem
+
+<output class="testcase" id="output"></output>
+ AXRole: AXGroup
+ computedRoleString: status
+ AXSubrole: AXApplicationStatus
+
+<p class="testcase" id="p"></p>
+ AXRole: AXGroup
+ computedRoleString: paragraph
+
+<pre class="testcase" id="pre"></pre>
+ AXRole: AXGroup
+ AXSubrole: AXPreformattedStyleGroup
+
+<section class="testcase" id="section-with-name" aria-label="Section name"></section>
+ AXRole: AXGroup
+ computedRoleString: region
+ AXSubrole: AXLandmarkRegion
+
+<section class="testcase" id="section-without-name"></section>
+ AXRole: AXGroup
+
+<sub class="testcase" id="sub"></sub>
+ AXRole: AXGroup
+ computedRoleString: subscript
+ AXSubrole: AXSubscriptStyleGroup
+
+<sup class="testcase" id="sup"></sup>
+ AXRole: AXGroup
+ computedRoleString: superscript
+ AXSubrole: AXSuperscriptStyleGroup
+
+<time class="testcase" id="time"></time>
+ AXRole: AXGroup
+ computedRoleString: time
+ AXSubrole: AXEmptyGroup
+
+<ul class="testcase" id="ul"></ul>
+ AXRole: AXList
+ computedRoleString: list
+
+<li class="testcase" id="ul-li-element"></li>
+ AXRole: AXGroup
+ computedRoleString: listitem
+
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
+
Added: trunk/LayoutTests/accessibility/display-contents-element-roles.html (0 => 291570)
--- trunk/LayoutTests/accessibility/display-contents-element-roles.html (rev 0)
+++ trunk/LayoutTests/accessibility/display-contents-element-roles.html 2022-03-21 18:48:25 UTC (rev 291570)
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+<script src=""
+<style>
+.testcase { display: contents };
+</style>
+</head>
+<body>
+
+<div id="content">
+ <a href="" id="link" class="testcase">apple.com</a>
+
+ <article class="testcase" id="article">Article content</article>
+
+ <aside class="testcase" id="aside">Aside content</aside>
+
+ <blockquote class="testcase" id="blockquote">To be or not to be, that is the question</blockquote>
+
+ <button class="testcase" id="button">Click me</button>
+
+ <code class="testcase" id="code">Hello world</code>
+
+ <del class="testcase" id="del">Hello world</del>
+
+ <details class="testcase" id="details">
+ <summary class="testcase" id="summary">Summary text</summary>
+ </details>
+
+ <dfn class="testcase" id="dfn">Some definition</dfn>
+
+ <div class="testcase" id="div">Some text</div>
+
+ <dl class="testcase" id="dl">
+ <dt class="testcase" id="dt">dt element</dt>
+ <dd class="testcase" id="dd">dd element</dd>
+ </dl>
+
+ <fieldset>
+ <legend class="testcase" id="legend">Choose your favorite monster</legend>
+
+ <input type="radio" id="radio-button" name="monster">
+ <label for=""
+
+ <input type="radio" id="sasquatch" name="monster">
+ <label for=""
+ </fieldset>
+
+ <figure class="testcase" id="figure"></figure>
+
+ <form class="testcase" id="form"></form>
+
+ <h2 class="testcase" id="h2">Hello world</h2>
+
+ <hr class="testcase" id="hr"></hr>
+
+ <ins class="testcase" id="ins">Hello world</ins>
+
+ <label class="testcase" id="label">Label</label>
+
+ <main class="testcase" id="main">Main content</main>
+
+ <mark class="testcase" id="mark">Marked text</mark>
+
+ <menu class="testcase" type="toolbar" id="menu-toolbar"></menu>
+
+ <nav class="testcase" id="nav">Nav</nav>
+
+ <ol class="testcase" id="ol">
+ <li class="testcase" id="ol-li-element">Hello world</li>
+ </ol>
+
+ <output class="testcase" id="output">Output</output>
+
+ <p class="testcase" id="p">Paragraph</p>
+
+ <pre class="testcase" id="pre">Pre-text</pre>
+
+ <section class="testcase" id="section-with-name" aria-label="Section name">Section</section>
+
+ <section class="testcase" id="section-without-name">Section</section>
+
+ <sub class="testcase" id="sub">Hello world</sub>
+
+ <sup class="testcase" id="sup">Hello world</sup>
+
+ <time class="testcase" id="time"></time>
+
+ <ul class="testcase" id="ul">
+ <li class="testcase" id="ul-li-element">Hello world</li>
+ </ul>
+</div>
+
+<script>
+ // Buffer test output and dump it at the end to make the test run faster vs. individual `debug` calls.
+ var testOutput = "This test ensures elements with CSS display: contents have the correct role.\n\n";
+
+ var axElement;
+ function verifyRoles() {
+ const testcases = document.getElementsByClassName("testcase");
+ for (const domElement of testcases) {
+ const outerHTML = escapeHTML(domElement.cloneNode().outerHTML);
+ axElement = accessibilityController.accessibleElementById(domElement.id);
+ if (!axElement) {
+ testOutput += `FAIL. Couldn't get AX element for #${domElement.id}: ${outerHTML}\n`;
+ return;
+ }
+ testOutput += `${outerHTML}\n`;
+ testOutput += ` ${axElement.role}`;
+
+ let computedRoleString = axElement.computedRoleString;
+ if (computedRoleString)
+ testOutput += `\n computedRoleString: ${computedRoleString}`;
+
+ let subrole = axElement.subrole;
+ if (subrole.replace("AXSubrole: ", ""))
+ testOutput += `\n ${subrole}`;
+ testOutput += `\n\n`;
+ }
+ }
+
+ if (window.accessibilityController) {
+ verifyRoles();
+ document.getElementById("content").style.visibility = "hidden";
+ debug(testOutput);
+ }
+</script>
+</body>
Added: trunk/LayoutTests/platform/glib/accessibility/aria-hidden-display-contents-element-expected.txt (0 => 291570)
--- trunk/LayoutTests/platform/glib/accessibility/aria-hidden-display-contents-element-expected.txt (rev 0)
+++ trunk/LayoutTests/platform/glib/accessibility/aria-hidden-display-contents-element-expected.txt 2022-03-21 18:48:25 UTC (rev 291570)
@@ -0,0 +1,21 @@
+This test ensures that an aria-hidden display:contents element behaves as expected.
+
+Testing element #main
+AXRole: AXLandmarkMain
+computedRoleString: main
+
+Testing element #list
+AXRole: AXList
+computedRoleString: list
+
+Testing element #paragraph
+AXRole: AXParagraph
+computedRoleString: paragraph
+
+Hiding #main with aria-hidden='true'.
+PASS: All elements are now hidden.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
Added: trunk/LayoutTests/platform/glib/accessibility/display-contents-element-roles-expected.txt (0 => 291570)
--- trunk/LayoutTests/platform/glib/accessibility/display-contents-element-roles-expected.txt (rev 0)
+++ trunk/LayoutTests/platform/glib/accessibility/display-contents-element-roles-expected.txt 2022-03-21 18:48:25 UTC (rev 291570)
@@ -0,0 +1,145 @@
+This test ensures elements with CSS display: contents have the correct role.
+
+<a href="" id="link" class="testcase"></a>
+ AXRole: AXLink
+ computedRoleString: link
+
+<article class="testcase" id="article"></article>
+ AXRole: AXArticle
+ computedRoleString: article
+
+<aside class="testcase" id="aside"></aside>
+ AXRole: AXLandmarkComplementary
+ computedRoleString: complementary
+
+<blockquote class="testcase" id="blockquote"></blockquote>
+ AXRole: AXBlockquote
+ computedRoleString: blockquote
+
+<button class="testcase" id="button"></button>
+ AXRole: AXButton
+ computedRoleString: button
+
+<code class="testcase" id="code"></code>
+ AXRole: AXSection
+
+<del class="testcase" id="del"></del>
+ AXRole: AXDeletion
+ computedRoleString: deletion
+
+<details class="testcase" id="details"></details>
+ AXRole: AXUnknown
+
+<summary class="testcase" id="summary"></summary>
+ AXRole: AXUnknown
+
+<dfn class="testcase" id="dfn"></dfn>
+ AXRole: AXDefinition
+ computedRoleString: definition
+
+<div class="testcase" id="div"></div>
+ AXRole: AXSection
+
+<dl class="testcase" id="dl"></dl>
+ AXRole: AXDescriptionList
+
+<dt class="testcase" id="dt"></dt>
+ AXRole: AXDescriptionTerm
+
+<dd class="testcase" id="dd"></dd>
+ AXRole: AXDescriptionValue
+
+<legend class="testcase" id="legend"></legend>
+ AXRole: AXLabel
+
+<figure class="testcase" id="figure"></figure>
+ AXRole: AXGroup
+ computedRoleString: figure
+
+<form class="testcase" id="form"></form>
+ AXRole: AXForm
+ computedRoleString: form
+
+<h2 class="testcase" id="h2"></h2>
+ AXRole: AXHeading
+ computedRoleString: heading
+
+<hr class="testcase" id="hr">
+ AXRole: AXSeparator
+ computedRoleString: separator
+
+<ins class="testcase" id="ins"></ins>
+ AXRole: AXInsertion
+ computedRoleString: insertion
+
+<label class="testcase" id="label"></label>
+ AXRole: AXLabel
+
+<main class="testcase" id="main"></main>
+ AXRole: AXLandmarkMain
+ computedRoleString: main
+
+<mark class="testcase" id="mark"></mark>
+
+
+<menu class="testcase" type="toolbar" id="menu-toolbar"></menu>
+ AXRole: AXToolbar
+ computedRoleString: toolbar
+
+<nav class="testcase" id="nav"></nav>
+ AXRole: AXLandmarkNavigation
+ computedRoleString: navigation
+
+<ol class="testcase" id="ol"></ol>
+ AXRole: AXList
+ computedRoleString: list
+
+<li class="testcase" id="ol-li-element"></li>
+ AXRole: AXListItem
+ computedRoleString: listitem
+
+<output class="testcase" id="output"></output>
+ AXRole: AXStatusBar
+ computedRoleString: status
+
+<p class="testcase" id="p"></p>
+ AXRole: AXParagraph
+ computedRoleString: paragraph
+
+<pre class="testcase" id="pre"></pre>
+ AXRole: AXSection
+
+<section class="testcase" id="section-with-name" aria-label="Section name"></section>
+ AXRole: AXLandmarkRegion
+ computedRoleString: region
+
+<section class="testcase" id="section-without-name"></section>
+ AXRole: AXSection
+
+<sub class="testcase" id="sub"></sub>
+ AXRole: AXSubscript
+ computedRoleString: subscript
+
+<sup class="testcase" id="sup"></sup>
+ AXRole: AXSuperscript
+ computedRoleString: superscript
+
+<time class="testcase" id="time"></time>
+ AXRole: AXStatic
+ computedRoleString: time
+
+<ul class="testcase" id="ul"></ul>
+ AXRole: AXList
+ computedRoleString: list
+
+<li class="testcase" id="ul-li-element"></li>
+ AXRole: AXListItem
+ computedRoleString: listitem
+
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
+
Modified: trunk/LayoutTests/platform/ios/TestExpectations (291569 => 291570)
--- trunk/LayoutTests/platform/ios/TestExpectations 2022-03-21 18:01:29 UTC (rev 291569)
+++ trunk/LayoutTests/platform/ios/TestExpectations 2022-03-21 18:48:25 UTC (rev 291570)
@@ -2088,6 +2088,8 @@
# Enable "phone number linkifying" test for iOS
fast/dom/linkify-phone-numbers.html [ Pass ]
+accessibility/aria-hidden-display-contents-element.html [ Pass ]
+accessibility/display-contents-element-roles.html [ Pass ]
accessibility/video-element-url-attribute.html [ Pass ]
accessibility/visible-character-range.html [ Pass ]
accessibility/ancestor-computation.html [ Pass ]
Added: trunk/LayoutTests/platform/ios/accessibility/aria-hidden-display-contents-element-expected.txt (0 => 291570)
--- trunk/LayoutTests/platform/ios/accessibility/aria-hidden-display-contents-element-expected.txt (rev 0)
+++ trunk/LayoutTests/platform/ios/accessibility/aria-hidden-display-contents-element-expected.txt 2022-03-21 18:48:25 UTC (rev 291570)
@@ -0,0 +1,18 @@
+This test ensures that an aria-hidden display:contents element behaves as expected.
+
+Testing element #main
+LandmarkMain
+
+Testing element #list
+List
+
+Testing element #paragraph
+Paragraph
+
+Hiding #main with aria-hidden='true'.
+PASS: All elements are now hidden.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
Added: trunk/LayoutTests/platform/ios/accessibility/display-contents-element-roles-expected.txt (0 => 291570)
--- trunk/LayoutTests/platform/ios/accessibility/display-contents-element-roles-expected.txt (rev 0)
+++ trunk/LayoutTests/platform/ios/accessibility/display-contents-element-roles-expected.txt 2022-03-21 18:48:25 UTC (rev 291570)
@@ -0,0 +1,120 @@
+This test ensures elements with CSS display: contents have the correct role.
+
+<a href="" id="link" class="testcase"></a>
+ WebCoreLink
+
+<article class="testcase" id="article"></article>
+ DocumentArticle
+
+<aside class="testcase" id="aside"></aside>
+ LandmarkComplementary
+
+<blockquote class="testcase" id="blockquote"></blockquote>
+ Blockquote
+
+<button class="testcase" id="button"></button>
+ Button
+
+<code class="testcase" id="code"></code>
+ TextGroup
+
+<del class="testcase" id="del"></del>
+ Deletion
+
+<details class="testcase" id="details"></details>
+ Details
+
+<summary class="testcase" id="summary"></summary>
+ Summary
+
+<dfn class="testcase" id="dfn"></dfn>
+ Definition
+
+<div class="testcase" id="div"></div>
+ Div
+
+<dl class="testcase" id="dl"></dl>
+ DescriptionList
+
+<dt class="testcase" id="dt"></dt>
+ DescriptionListTerm
+
+<dd class="testcase" id="dd"></dd>
+ DescriptionListDetail
+
+<legend class="testcase" id="legend"></legend>
+ Legend
+
+<figure class="testcase" id="figure"></figure>
+ Figure
+
+<form class="testcase" id="form"></form>
+ Form
+
+<h2 class="testcase" id="h2"></h2>
+ Heading
+
+<hr class="testcase" id="hr">
+ HorizontalRule
+
+<ins class="testcase" id="ins"></ins>
+ Insertion
+
+<label class="testcase" id="label"></label>
+ Label
+
+<main class="testcase" id="main"></main>
+ LandmarkMain
+
+<mark class="testcase" id="mark"></mark>
+ Mark
+
+<menu class="testcase" type="toolbar" id="menu-toolbar"></menu>
+ Toolbar
+
+<nav class="testcase" id="nav"></nav>
+ LandmarkNavigation
+
+<ol class="testcase" id="ol"></ol>
+ List
+
+<li class="testcase" id="ol-li-element"></li>
+ ListItem
+
+<output class="testcase" id="output"></output>
+ ApplicationStatus
+
+<p class="testcase" id="p"></p>
+ Paragraph
+
+<pre class="testcase" id="pre"></pre>
+ TextGroup
+
+<section class="testcase" id="section-with-name" aria-label="Section name"></section>
+ LandmarkRegion
+
+<section class="testcase" id="section-without-name"></section>
+ TextGroup
+
+<sub class="testcase" id="sub"></sub>
+ Subscript
+
+<sup class="testcase" id="sup"></sup>
+ Superscript
+
+<time class="testcase" id="time"></time>
+ Time
+
+<ul class="testcase" id="ul"></ul>
+ List
+
+<li class="testcase" id="ul-li-element"></li>
+ ListItem
+
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
+
Modified: trunk/LayoutTests/platform/win/TestExpectations (291569 => 291570)
--- trunk/LayoutTests/platform/win/TestExpectations 2022-03-21 18:01:29 UTC (rev 291569)
+++ trunk/LayoutTests/platform/win/TestExpectations 2022-03-21 18:48:25 UTC (rev 291570)
@@ -1540,6 +1540,8 @@
################## Accessibility Issues ####################
################################################################################
+accessibility/display-contents-element-roles.html [ Skip ]
+
# Missing 'press' support
accessibility/axpress-on-aria-button.html [ Skip ]
accessibility/svg-element-press.html [ Skip ]
Added: trunk/LayoutTests/platform/win/accessibility/aria-hidden-display-contents-element-expected.txt (0 => 291570)
--- trunk/LayoutTests/platform/win/accessibility/aria-hidden-display-contents-element-expected.txt (rev 0)
+++ trunk/LayoutTests/platform/win/accessibility/aria-hidden-display-contents-element-expected.txt 2022-03-21 18:48:25 UTC (rev 291570)
@@ -0,0 +1,18 @@
+This test ensures that an aria-hidden display:contents element behaves as expected.
+
+Testing element #main
+AXRole: AXGroup
+
+Testing element #list
+AXRole: AXList
+
+Testing element #paragraph
+AXRole: AXGroup
+
+Hiding #main with aria-hidden='true'.
+PASS: All elements are now hidden.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
Modified: trunk/Source/WebCore/ChangeLog (291569 => 291570)
--- trunk/Source/WebCore/ChangeLog 2022-03-21 18:01:29 UTC (rev 291569)
+++ trunk/Source/WebCore/ChangeLog 2022-03-21 18:48:25 UTC (rev 291570)
@@ -1,3 +1,57 @@
+2022-03-21 Tyler Wilcock <tyle...@apple.com>
+
+ AX: Include display: contents elements in the AX tree
+ https://bugs.webkit.org/show_bug.cgi?id=237834
+
+ Reviewed by Chris Fleizach.
+
+ Because display: contents intentionally prevents a render object from being
+ generated for the element it's applied to, we don't add it to the AX tree as
+ part of our normal render tree walk, making these elements inaccessible.
+
+ This patch includes these elements as part of the DOM walk that
+ addHiddenChildren (now renamed to addNodeOnlyChildren) already does.
+
+ Also, because display: contents moves the affected element's children up a
+ level in the render tree, this patch also special cases:
+
+ 1. AccessibilityRenderObject::parentObject and similar methods to
+ return their display: contents parent instead of their render tree
+ parent
+ 2. AccessibilityObject::insertChild to only insert display: contents
+ children to their display: contents parent, rather than their render
+ tree parent
+
+ Test: accessibility/display-contents-element-roles.html, accessibility/aria-hidden-display-contents-element.html
+
+ * accessibility/AXObjectCache.cpp:
+ (WebCore::AXObjectCache::getOrCreate):
+ Allow creation of AX objects from display: contents `Node`s.
+ Also, don't create an object for a renderer that is in the process of
+ being destroyed (prevents display-contents-element-roles.html from crashing in ITM)
+ * accessibility/AccessibilityObject.cpp:
+ (WebCore::AccessibilityObject::displayContentsParent const):
+ (WebCore::AccessibilityObject::insertChild):
+ If an object has a display: contents parent, and that parent isn't
+ `this`, return early.
+ * accessibility/AccessibilityObject.h:
+ * accessibility/AccessibilityRenderObject.cpp:
+ (WebCore::AccessibilityRenderObject::parentObjectIfExists const):
+ (WebCore::AccessibilityRenderObject::parentObject const):
+ (WebCore::AccessibilityRenderObject::parentObjectUnignored const):
+ If an object has a display: contents parent, return that instead of
+ its render tree parent.
+ (WebCore::AccessibilityRenderObject::addNodeOnlyChildren):
+ Renamed from addHiddenChildren.
+ (WebCore::AccessibilityRenderObject::addChildren):
+ Don't clear m_subtreeDirty until after all children have been added.
+ Necessary to make aria-hidden-display-contents-element.html pass, as
+ we were clearing this too early, causing display: contents subtrees to
+ not be updated after aria-hidden changes.
+ (WebCore::AccessibilityRenderObject::addHiddenChildren):
+ Renamed to addNodeOnlyChildren.
+ * accessibility/AccessibilityRenderObject.h:
+
2022-03-21 Oriol Brufau <obru...@igalia.com>
[css-cascade] Don't defer applying text decoration properties
Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (291569 => 291570)
--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2022-03-21 18:01:29 UTC (rev 291569)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2022-03-21 18:48:25 UTC (rev 291570)
@@ -720,14 +720,10 @@
return object.get();
}
- // It's only allowed to create an AccessibilityObject from a Node if it's in a canvas subtree.
- // Or if it's a hidden element, but we still want to expose it because of other ARIA attributes.
bool inCanvasSubtree = lineageOfType<HTMLCanvasElement>(*node->parentElement()).first();
- bool isHidden = isNodeAriaVisible(node);
-
bool insideMeterElement = is<HTMLMeterElement>(*node->parentElement());
-
- if (!inCanvasSubtree && !isHidden && !insideMeterElement)
+ bool hasDisplayContents = is<Element>(*node) && downcast<Element>(*node).hasDisplayContents();
+ if (!inCanvasSubtree && !insideMeterElement && !hasDisplayContents && !isNodeAriaVisible(node))
return nullptr;
Ref protectedNode { *node };
@@ -760,6 +756,9 @@
if (AccessibilityObject* obj = get(renderer))
return obj;
+ // Don't create an object for this renderer if it's being destroyed.
+ if (renderer->beingDestroyed())
+ return nullptr;
RefPtr<AccessibilityObject> newObj = createFromRenderer(renderer);
// Will crash later if we have two objects for the same renderer.
Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.cpp (291569 => 291570)
--- trunk/Source/WebCore/accessibility/AccessibilityObject.cpp 2022-03-21 18:01:29 UTC (rev 291569)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.cpp 2022-03-21 18:48:25 UTC (rev 291570)
@@ -448,6 +448,16 @@
});
}
+AccessibilityObject* AccessibilityObject::displayContentsParent() const
+{
+ auto* parentNode = node() ? node()->parentNode() : nullptr;
+ if (!is<Element>(parentNode) || !downcast<Element>(parentNode)->hasDisplayContents())
+ return nullptr;
+
+ auto* cache = axObjectCache();
+ return cache ? cache->getOrCreate(parentNode) : nullptr;
+}
+
AccessibilityObject* AccessibilityObject::previousSiblingUnignored(int limit) const
{
AccessibilityObject* previous;
@@ -575,7 +585,7 @@
{
if (!newChild)
return;
-
+
ASSERT(is<AccessibilityObject>(newChild));
if (!is<AccessibilityObject>(newChild))
return;
@@ -600,7 +610,12 @@
}
}
}
-
+
+ auto* displayContentsParent = child->displayContentsParent();
+ // To avoid double-inserting a child of a `display: contents` element, only insert if `this` is the rightful parent.
+ if (displayContentsParent && displayContentsParent != this)
+ return;
+
auto thisAncestorFlags = computeAncestorFlags();
child->initializeAncestorFlags(thisAncestorFlags);
setIsIgnoredFromParentDataForChild(child);
@@ -625,7 +640,7 @@
} else {
// Table component child-parent relationships often don't line up properly, hence the need for methods
// like parentTable() and parentRow(). Exclude them from this ASSERT.
- ASSERT((!isTableComponent(*child) && !isTableComponent(*this)) ? child->parentObject() == this : true);
+ ASSERT(isTableComponent(*child) || isTableComponent(*this) || child->parentObject() == this);
m_children.insert(index, child);
}
Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.h (291569 => 291570)
--- trunk/Source/WebCore/accessibility/AccessibilityObject.h 2022-03-21 18:01:29 UTC (rev 291569)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.h 2022-03-21 18:48:25 UTC (rev 291570)
@@ -373,6 +373,7 @@
AccessibilityObject* nextSiblingUnignored(int limit) const override;
AccessibilityObject* previousSiblingUnignored(int limit) const override;
AccessibilityObject* parentObject() const override { return nullptr; }
+ AccessibilityObject* displayContentsParent() const;
AXCoreObject* parentObjectUnignored() const override;
AccessibilityObject* parentObjectIfExists() const override { return nullptr; }
static AccessibilityObject* firstAccessibleObjectFromNode(const Node*);
Modified: trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp (291569 => 291570)
--- trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp 2022-03-21 18:01:29 UTC (rev 291569)
+++ trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp 2022-03-21 18:48:25 UTC (rev 291570)
@@ -491,11 +491,17 @@
if (m_renderer && isWebArea())
return cache->get(&m_renderer->view().frameView());
+ if (auto* displayContentsParent = this->displayContentsParent())
+ return displayContentsParent;
+
return cache->get(renderParentObject());
}
AccessibilityObject* AccessibilityRenderObject::parentObject() const
{
+ if (auto* displayContentsParent = this->displayContentsParent())
+ return displayContentsParent;
+
if (!m_renderer)
return nullptr;
@@ -508,15 +514,14 @@
if (parent)
return parent;
}
-
+
AXObjectCache* cache = axObjectCache();
if (!cache)
return nullptr;
+
+ if (auto* parentObject = renderParentObject())
+ return cache->getOrCreate(parentObject);
- RenderObject* parentObj = renderParentObject();
- if (parentObj)
- return cache->getOrCreate(parentObj);
-
// WebArea's parent should be the scroll view containing it.
if (isWebArea())
return cache->getOrCreate(&m_renderer->view().frameView());
@@ -537,7 +542,7 @@
}
#endif
- return AccessibilityNodeObject::parentObjectUnignored();
+ return AccessibilityObject::parentObjectUnignored();
}
bool AccessibilityRenderObject::isAttachment() const
@@ -3198,29 +3203,36 @@
}
#endif
-// Hidden children are those that are not rendered or visible, but are specifically marked as aria-hidden=false,
-// meaning that they should be exposed to the AX hierarchy.
-void AccessibilityRenderObject::addHiddenChildren()
+// Some elements don't have an associated render object, meaning they won't be picked up by a walk of the render tree.
+// For example, nodes that are `aria-hidden="false"` and `hidden`, or elements with `display: contents`.
+// This function will find and add these elements to the AX tree.
+void AccessibilityRenderObject::addNodeOnlyChildren()
{
Node* node = this->node();
if (!node)
return;
-
- // First do a quick run through to determine if we have any hidden nodes (most often we will not).
- // If we do have hidden nodes, we need to determine where to insert them so they match DOM order as close as possible.
- bool shouldInsertHiddenNodes = false;
+
+ auto nodeHasDisplayContents = [] (Node& node) {
+ return is<Element>(node) && downcast<Element>(node).hasDisplayContents();
+ };
+ // First do a quick run through to determine if we have any interesting nodes (most often we will not).
+ // If we do have any interesting nodes, we need to determine where to insert them so they match DOM order as close as possible.
+ bool hasNodeOnlyChildren = false;
for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
- if (!child->renderer() && isNodeAriaVisible(child)) {
- shouldInsertHiddenNodes = true;
+ if (child->renderer())
+ continue;
+
+ if (nodeHasDisplayContents(*child) || isNodeAriaVisible(child)) {
+ hasNodeOnlyChildren = true;
break;
}
}
- if (!shouldInsertHiddenNodes)
+ if (!hasNodeOnlyChildren)
return;
// Iterate through all of the children, including those that may have already been added, and
- // try to insert hidden nodes in the correct place in the DOM order.
+ // try to insert the nodes in the correct place in the DOM order.
unsigned insertionIndex = 0;
for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
if (child->renderer()) {
@@ -3239,7 +3251,7 @@
continue;
}
- if (!isNodeAriaVisible(child))
+ if (!nodeHasDisplayContents(*child) && !isNodeAriaVisible(child))
continue;
unsigned previousSize = m_children.size();
@@ -3311,9 +3323,7 @@
for (RefPtr<AccessibilityObject> object = firstChild(); object; object = object->nextSibling())
addChildIfNeeded(*object);
- m_subtreeDirty = false;
-
- addHiddenChildren();
+ addNodeOnlyChildren();
addAttachmentChildren();
addImageMapChildren();
addTextFieldChildren();
@@ -3322,11 +3332,11 @@
#if USE(ATSPI)
addListItemMarker();
#endif
-
#if PLATFORM(COCOA)
updateAttachmentViewParents();
#endif
-
+
+ m_subtreeDirty = false;
updateRoleAfterChildrenCreation();
}
Modified: trunk/Source/WebCore/accessibility/AccessibilityRenderObject.h (291569 => 291570)
--- trunk/Source/WebCore/accessibility/AccessibilityRenderObject.h 2022-03-21 18:01:29 UTC (rev 291569)
+++ trunk/Source/WebCore/accessibility/AccessibilityRenderObject.h 2022-03-21 18:48:25 UTC (rev 291570)
@@ -248,7 +248,7 @@
void offsetBoundingBoxForRemoteSVGElement(LayoutRect&) const;
bool supportsPath() const override;
- void addHiddenChildren();
+ void addNodeOnlyChildren();
void addTextFieldChildren();
void addImageMapChildren();
void addCanvasChildren();