RESOLVED FIXED Bug 237834
AX: Include display: contents elements in the AX tree
https://bugs.webkit.org/show_bug.cgi?id=237834
Summary AX: Include display: contents elements in the AX tree
Tyler Wilcock
Reported 2022-03-14 09:49:42 PDT
Because display: contents intentionally prevents a render object from being generated for the element it's applied to, we don't find it as part of our normal render tree walk. We should include these elements in the tree some other way.
Attachments
Patch (24.06 KB, patch)
2022-03-14 12:02 PDT, Tyler Wilcock
no flags
Patch (24.30 KB, patch)
2022-03-14 12:06 PDT, Tyler Wilcock
no flags
Patch (22.11 KB, patch)
2022-03-15 09:25 PDT, Tyler Wilcock
no flags
Patch (36.28 KB, patch)
2022-03-17 19:08 PDT, Tyler Wilcock
no flags
Patch (38.12 KB, patch)
2022-03-18 17:04 PDT, Tyler Wilcock
no flags
Patch (38.13 KB, patch)
2022-03-18 23:00 PDT, Tyler Wilcock
no flags
Radar WebKit Bug Importer
Comment 1 2022-03-14 09:49:52 PDT
Tyler Wilcock
Comment 2 2022-03-14 12:02:21 PDT
Tyler Wilcock
Comment 3 2022-03-14 12:06:12 PDT
chris fleizach
Comment 4 2022-03-14 12:55:18 PDT
Comment on attachment 454611 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=454611&action=review > Source/WebCore/accessibility/AccessibilityObject.cpp:454 > + if (!node() || !RuntimeEnabledFeatures::sharedFeatures().cssDisplayContentsAXSupportEnabled()) can we put the runtime enablement check first. maybe on it's own line. it feels substantively different than the code presumably we'll remove that check soon? or can we do it with this PR? > Source/WebCore/accessibility/AccessibilityObject.cpp:458 > + if (!is<Element>(parentNode) || !downcast<Element>(parentNode)->hasDisplayContents()) do we need to worry about a display contents parent who is aria-hidden=true or role=presentational?
Andres Gonzalez
Comment 5 2022-03-15 06:54:50 PDT
(In reply to Tyler Wilcock from comment #3) > Created attachment 454611 [details] > Patch --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ a/Source/WebCore/accessibility/AccessibilityObject.cpp +AccessibilityObject* AccessibilityObject::displayContentsParent() const +{ ... + if (auto* cache = axObjectCache()) + return cache->getOrCreate(parentNode); + + return nullptr; Much prefer the two line, clear and compact: auto* cache = axObjectCache(); return cache ? cache->getOrCreate(parentNode) : nullptr; @@ -575,12 +591,17 @@ void AccessibilityObject::insertChild(AXCoreObject* newChild, unsigned index, De + 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; + Why would it try to double insert a child in the first place? --- a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp +++ a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp +// 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() This makes me think that this belongs to AXNodeObject and not to AXRenderObject. Can we keep AXRenderObject for nodes that do have a renderer, and move this to AXNodeObject? --- a/LayoutTests/accessibility/display-contents-element-roles.html +++ a/LayoutTests/accessibility/display-contents-element-roles.html Can we have the elements in alphabetical order when possible? Similar to other tests, I think it makes it easier to find what we are missing later on. + function verifyRole(elementId) { + const outerHTML = escapeHTML(document.getElementById(elementId).cloneNode().outerHTML); Do we need to cloneNode to get the outerHTML? + if (window.accessibilityController) { + verifyRole("p"); + verifyRole("link"); ... Wondering if we should follow what we do in other tests where we get all elements at once and make verifyRole a visitor on each item of the array.
Tyler Wilcock
Comment 6 2022-03-15 09:25:25 PDT
Tyler Wilcock
Comment 7 2022-03-15 09:26:32 PDT
> + // To avoid double-inserting a child of a `display: contents` element, > only insert if `this` is the rightful parent. > + if (displayContentsParent && displayContentsParent != this) > + return; > + > > Why would it try to double insert a child in the first place? Because display:contents moves the element's children up a level in the render tree, making them accessible by the render tree parent of display:contents. That means both this grandparent, and the actual display:contents parent (in addNodeOnlyChildren) will try to insert these children. > --- a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp > +++ a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp > > +// 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() > > This makes me think that this belongs to AXNodeObject and not to > AXRenderObject. Can we keep AXRenderObject for nodes that do have a > renderer, and move this to AXNodeObject? Well, the default behavior of AXNodeObject is already to add node only children (that's what AccessibilityNodeObject::addChildren does). Only AXRenderObject needs to addNodeOnlyChildren in this special way, since its normal means of adding children is via the render tree. So I think it makes sense that this method exists on AXRenderObject, but maybe I'm misunderstanding the suggestion. > + function verifyRole(elementId) { > + const outerHTML = > escapeHTML(document.getElementById(elementId).cloneNode().outerHTML); > > Do we need to cloneNode to get the outerHTML? We do. Both outerHTML and innerHTML return an element's contents in the returned string (so outerHTML on <ol> would include the <li> elements). Performing a cloneNode first ensures we only capture the outermost node in the string (since cloneNode is shallow by default).
Tyler Wilcock
Comment 8 2022-03-15 09:28:53 PDT
> can we put the runtime enablement check first. maybe on it's own line. it > feels substantively different than the code > presumably we'll remove that check soon? or can we do it with this PR? I decided to just not use the flag for now. My original thought process was that exposing these elements without porting all functionality might provide a worse experience than status quo (these elements not existing at all in the AX tree), but I'm not so sure about that anymore. I'll remove the flag entirely in a later patch if we still feel we don't need it. > > Source/WebCore/accessibility/AccessibilityObject.cpp:458 > > + if (!is<Element>(parentNode) || !downcast<Element>(parentNode)->hasDisplayContents()) > > do we need to worry about a display contents parent who is > aria-hidden=true or role=presentational? I don't believe so, similar to how it's OK if we return an aria-hidden or presentational parent from AXCoreObject::parentObject (vs. parentObjectUnignored). Children can be inserted to an ignored display:contents parent, or we can return an ignored display:contents parent, and things will work as they do if the element weren't display:contents. But maybe I'm missing something -- do you see this being problematic?
chris fleizach
Comment 9 2022-03-15 10:32:18 PDT
(In reply to Tyler Wilcock from comment #8) > > can we put the runtime enablement check first. maybe on it's own line. it > > feels substantively different than the code > > presumably we'll remove that check soon? or can we do it with this PR? > I decided to just not use the flag for now. My original thought process was > that exposing these elements without porting all functionality might provide > a worse experience than status quo (these elements not existing at all in > the AX tree), but I'm not so sure about that anymore. I'll remove the flag > entirely in a later patch if we still feel we don't need it. > Ok > > > Source/WebCore/accessibility/AccessibilityObject.cpp:458 > > > + if (!is<Element>(parentNode) || !downcast<Element>(parentNode)->hasDisplayContents()) > > > > do we need to worry about a display contents parent who is > > aria-hidden=true or role=presentational? > I don't believe so, similar to how it's OK if we return an aria-hidden or > presentational parent from AXCoreObject::parentObject (vs. > parentObjectUnignored). Children can be inserted to an ignored > display:contents parent, or we can return an ignored display:contents > parent, and things will work as they do if the element weren't > display:contents. But maybe I'm missing something -- do you see this being > problematic? Maybe adding a test case to see what happens if an element is display contents and is aria hidden.
James Craig
Comment 10 2022-03-15 16:27:41 PDT
*** Bug 185679 has been marked as a duplicate of this bug. ***
James Craig
Comment 11 2022-03-15 16:28:21 PDT
test case from the dupe may still be useful https://bugs.webkit.org/attachment.cgi?id=353831
Tyler Wilcock
Comment 12 2022-03-17 19:08:39 PDT
Tyler Wilcock
Comment 13 2022-03-18 17:04:24 PDT
Tyler Wilcock
Comment 14 2022-03-18 23:00:30 PDT
Andres Gonzalez
Comment 15 2022-03-21 08:06:45 PDT
(In reply to Tyler Wilcock from comment #7) > > --- a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp > > +++ a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp > > > > +// 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() > > > > This makes me think that this belongs to AXNodeObject and not to > > AXRenderObject. Can we keep AXRenderObject for nodes that do have a > > renderer, and move this to AXNodeObject? > Well, the default behavior of AXNodeObject is already to add node only > children (that's what AccessibilityNodeObject::addChildren does). Only > AXRenderObject needs to addNodeOnlyChildren in this special way, since its > normal means of adding children is via the render tree. So I think it makes > sense that this method exists on AXRenderObject, but maybe I'm > misunderstanding the suggestion. What I'm saying is that it would make more sense that AXRenderObject::addChildren adds the children that have an associated RednerObject, and since an AXRenderObject is an AXNodeObject, then it adds the children from the Node's hierarchy if needed, like the display content elements. I.e., have AXRenderObject::addChildren would call AXNOdeObject::addChildren to add those children that are not part of the RenderObject hierarchy but are part of the Node hierarchy. I believe that would be a cleaner and more maintainable design than mingling the traversals of RenderObjects and Nodes in the AXRenderObject subclass.
Tyler Wilcock
Comment 16 2022-03-21 09:23:45 PDT
> What I'm saying is that it would make more sense that > AXRenderObject::addChildren adds the children that have an associated > RednerObject, and since an AXRenderObject is an AXNodeObject, then it adds > the children from the Node's hierarchy if needed, like the display content > elements. I.e., have AXRenderObject::addChildren would call > AXNOdeObject::addChildren to add those children that are not part of the > RenderObject hierarchy but are part of the Node hierarchy. I believe that > would be a cleaner and more maintainable design than mingling the traversals > of RenderObjects and Nodes in the AXRenderObject subclass. Ah, OK, I think I understand your suggestion better. Agreed, this would probably be better homed in AXNodeObject. However, doing that wouldn’t be as straightforward as changing AXNodeObject::addChildren around a bit to accommodate this usecase, as AXRenderObject::addNodeOnlyObjects does a lot of work to splice the child into the right place, which AXNodeObject::addChildren doesn’t (and shouldn’t) have to worry about. This mechanism is not new to this patch, so in the name of incremental improvement I propose we save that refactoring for later.
EWS
Comment 17 2022-03-21 11:49:37 PDT
Committed r291570 (248670@main): <https://commits.webkit.org/248670@main> All reviewed patches have been landed. Closing bug and clearing flags on attachment 455165 [details].
Note You need to log in before you can comment on or make changes to this bug.