Bug 240750

Summary: AccessibilityTable::m_isExposable is never recomputed after AccessibilityTable::init
Product: WebKit Reporter: Tyler Wilcock <tyler_w>
Component: AccessibilityAssignee: Tyler Wilcock <tyler_w>
Status: RESOLVED FIXED    
Severity: Normal CC: aboxhall, andresg_22, apinheiro, cdumez, cfleizach, changseok, dmazzoni, esprehn+autocc, ews-watchlist, gyuyoung.kim, jcraig, jdiggs, samuel_white, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
Attachments:
Description Flags
Patch
none
Patch
none
Patch
none
Patch
none
Patch
none
Patch
none
Patch
none
Patch none

Tyler Wilcock
Reported 2022-05-20 18:50:48 PDT
This can cause it to become out of date after dynamic page changes (e.g. ARIA attribute changes).
Attachments
Patch (22.00 KB, patch)
2022-05-20 18:58 PDT, Tyler Wilcock
no flags
Patch (22.28 KB, patch)
2022-05-20 19:14 PDT, Tyler Wilcock
no flags
Patch (23.48 KB, patch)
2022-05-23 13:35 PDT, Tyler Wilcock
no flags
Patch (24.33 KB, patch)
2022-05-23 18:42 PDT, Tyler Wilcock
no flags
Patch (25.19 KB, patch)
2022-05-23 22:11 PDT, Tyler Wilcock
no flags
Patch (26.36 KB, patch)
2022-05-24 09:16 PDT, Tyler Wilcock
no flags
Patch (29.55 KB, patch)
2022-05-24 22:25 PDT, Tyler Wilcock
no flags
Patch (29.24 KB, patch)
2022-05-25 10:46 PDT, Tyler Wilcock
no flags
Radar WebKit Bug Importer
Comment 1 2022-05-20 18:50:56 PDT
Tyler Wilcock
Comment 2 2022-05-20 18:58:48 PDT
Tyler Wilcock
Comment 3 2022-05-20 19:14:27 PDT
chris fleizach
Comment 4 2022-05-22 23:03:15 PDT
Comment on attachment 459633 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=459633&action=review > Source/WebCore/accessibility/AXObjectCache.cpp:1302 > + if (auto* axObject = get(node); is<AccessibilityTable>(axObject)) is this a style in WebKit now to have two conditions in one if block?
Andres Gonzalez
Comment 5 2022-05-23 05:37:58 PDT
(In reply to chris fleizach from comment #4) > Comment on attachment 459633 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=459633&action=review > > > Source/WebCore/accessibility/AXObjectCache.cpp:1302 > > + if (auto* axObject = get(node); is<AccessibilityTable>(axObject)) > > is this a style in WebKit now to have two conditions in one if block? It is not two conditions, the first statement auto* axObject = get(node); it is just an initialization. The is only one condition is<AccessibilityTable>(axObject). I personally don't like this because if you wrote if (auto* axObject = get(node)) Then that would be an initialization and a condition at the same time. But if you add a second expression like in the case above, the first one becomes just an initialization and the second one is the real condition. I've seen it used more in WebKit, but I'm not sure that is the recommended way in most cases.
Andres Gonzalez
Comment 6 2022-05-23 06:22:00 PDT
(In reply to Tyler Wilcock from comment #3) > Created attachment 459633 [details] > Patch --- a/Source/WebCore/accessibility/AXObjectCache.cpp +++ a/Source/WebCore/accessibility/AXObjectCache.cpp +void AXObjectCache::handleRowCountChanged(Node* node) + postNotification(node, AXObjectCache::AXRowCountChanged); Don't need to qualify AXNotifications with AXObjectCache:: inside the AXObjectCache implementation. +void AXObjectCache::handleRowCountChanged(AXCoreObject* axObject, Document* document) Why do we need two overloads for this? At least one should call the other one, but I don't see why we need two. + postNotification(axObject, document, AXObjectCache::AXRowCountChanged); Don't need to qualify AXNotifications with AXObjectCache:: inside the AXObjectCache implementation. @@ -3414,6 +3438,14 @@ void AXObjectCache::updateIsolatedTree(const Vector<std::pair<RefPtr<AXCoreObjec + auto updateNode = [&] (RefPtr<AXCoreObject> axObject) { + auto updatedFields = updatedObjects.get(axObject->objectID()); + if (!updatedFields.node) { + updatedObjects.set(axObject->objectID(), UpdatedFields { updatedFields.children, true }); + tree->updateNode(*axObject); + } + }; + This whole thing should be done in AXIsolatedTree::updateNode, not here.
Tyler Wilcock
Comment 7 2022-05-23 13:35:19 PDT
Tyler Wilcock
Comment 8 2022-05-23 13:41:04 PDT
> +void AXObjectCache::handleRowCountChanged(AXCoreObject* axObject, Document* > document) > > Why do we need two overloads for this? At least one should call the other > one, but I don't see why we need two. This mirrors the overloads for postNotification: * postNotification(Node*, AXNotification, PostTarget) * postNotification(AXCoreObject*, Document*, AXNotification, PostTarget) The behavior for each is different enough that I think we need to match these overloads, unless you have a different idea. > @@ -3414,6 +3438,14 @@ void AXObjectCache::updateIsolatedTree(const > Vector<std::pair<RefPtr<AXCoreObjec > > + auto updateNode = [&] (RefPtr<AXCoreObject> axObject) { > + auto updatedFields = updatedObjects.get(axObject->objectID()); > + if (!updatedFields.node) { > + updatedObjects.set(axObject->objectID(), UpdatedFields { > updatedFields.children, true }); > + tree->updateNode(*axObject); > + } > + }; > + > This whole thing should be done in AXIsolatedTree::updateNode, not here. The code here in AXObjectCache::updateIsolatedTree(const Vector<std::pair<RefPtr<AXCoreObject>, AXNotification>>& notifications) filters notifications such that we don't perform the same work over and over (e.g. multiple calls to AXIsolatedTree::updateNode for the same object). This feels different than what AXIsolatedTree::updateNode does and should do, doesn't it? But maybe I don't totally understand your suggestion.
Tyler Wilcock
Comment 9 2022-05-23 18:42:47 PDT
Tyler Wilcock
Comment 10 2022-05-23 22:11:56 PDT
Tyler Wilcock
Comment 11 2022-05-24 09:16:23 PDT
Andres Gonzalez
Comment 12 2022-05-24 10:52:52 PDT
(In reply to Tyler Wilcock from comment #10) > Created attachment 459696 [details] > Patch --- a/Source/WebCore/accessibility/AXObjectCache.cpp +++ a/Source/WebCore/accessibility/AXObjectCache.cpp + if (auto* tableSectionElement = dynamicDowncast<HTMLTableSectionElement>(object.element())) { + auto* parentTable = tableSectionElement->findParentTable().get(); + if (auto* axTable = dynamicDowncast<AccessibilityTable>(getOrCreate(const_cast<HTMLTableElement*>(parentTable)))) + axTable->recomputeIsExposable(); We shouldn't need to go back to the Element at this point in the cache. Can we instead schedule some event for the table when we actually get the notification from the DOM in childrenChanged(Node* node, Node* changedChild) or the equivalent RenderObject overload. void AXObjectCache::updateIsolatedTree(const Vector<std::pair<RefPtr<AXCoreObjec + case AXRowCountChanged: + if (notification.first->isTable()) + updateNode(notification.first); + FALLTHROUGH; Do we need to check if (notification.first->isTable()) here? I'd think we can just update if we receive that notification like with the others, e.g., CheckedStateChanged.
Andres Gonzalez
Comment 13 2022-05-24 11:02:52 PDT
(In reply to Tyler Wilcock from comment #8) > > +void AXObjectCache::handleRowCountChanged(AXCoreObject* axObject, Document* > > document) > > > > Why do we need two overloads for this? At least one should call the other > > one, but I don't see why we need two. > This mirrors the overloads for postNotification: > > * postNotification(Node*, AXNotification, PostTarget) > * postNotification(AXCoreObject*, Document*, AXNotification, PostTarget) > > The behavior for each is different enough that I think we need to match > these overloads, unless you have a different idea. Can we do + else if (attrName == aria_rowcountAttr) + handleRowCountChanged(get(element), ...); If so, we just need one. The Document parameter doesn't even need to be passed I believe, but you can get it from the cache when needed. > > > @@ -3414,6 +3438,14 @@ void AXObjectCache::updateIsolatedTree(const > > Vector<std::pair<RefPtr<AXCoreObjec > > > > + auto updateNode = [&] (RefPtr<AXCoreObject> axObject) { > > + auto updatedFields = updatedObjects.get(axObject->objectID()); > > + if (!updatedFields.node) { > > + updatedObjects.set(axObject->objectID(), UpdatedFields { > > updatedFields.children, true }); > > + tree->updateNode(*axObject); > > + } > > + }; > > + > > This whole thing should be done in AXIsolatedTree::updateNode, not here. > The code here in AXObjectCache::updateIsolatedTree(const > Vector<std::pair<RefPtr<AXCoreObject>, AXNotification>>& notifications) > filters notifications such that we don't perform the same work over and over > (e.g. multiple calls to AXIsolatedTree::updateNode for the same object). > This feels different than what AXIsolatedTree::updateNode does and should > do, doesn't it? But maybe I don't totally understand your suggestion. oh right, I misread this change.
Tyler Wilcock
Comment 14 2022-05-24 22:25:41 PDT
Tyler Wilcock
Comment 15 2022-05-24 22:28:17 PDT
> --- a/Source/WebCore/accessibility/AXObjectCache.cpp > +++ a/Source/WebCore/accessibility/AXObjectCache.cpp > > + if (auto* tableSectionElement = > dynamicDowncast<HTMLTableSectionElement>(object.element())) { > + auto* parentTable = tableSectionElement->findParentTable().get(); > + if (auto* axTable = > dynamicDowncast<AccessibilityTable>(getOrCreate(const_cast<HTMLTableElement*> > (parentTable)))) > + axTable->recomputeIsExposable(); > > We shouldn't need to go back to the Element at this point in the cache. Can > we instead schedule some event for the table when we actually get the notification from the DOM in > childrenChanged(Node* node, Node* changedChild) > or the equivalent RenderObject overload. OK, fixed this by adding m_deferredRecomputeTableIsExposedList. > void AXObjectCache::updateIsolatedTree(const > Vector<std::pair<RefPtr<AXCoreObjec > > + case AXRowCountChanged: > + if (notification.first->isTable()) > + updateNode(notification.first); > + FALLTHROUGH; > > Do we need to check if (notification.first->isTable()) here? I'd think we > can just update if we receive that notification like with the others, e.g., > CheckedStateChanged. I did this because the only time AXRowCountChanged actually needs to update properties is for tables. This guards against authoring errors where they change aria-rowcount on non-tables, having no practical effect but causing us to `updateNode`, which is expensive. But I might be overthinking it, so I'd be fine removing the check if you'd prefer. > > The behavior for each is different enough that I think we need to match > > these overloads, unless you have a different idea. > > Can we do > > + else if (attrName == aria_rowcountAttr) > + handleRowCountChanged(get(element), ...); > > If so, we just need one. The Document parameter doesn't even need to be > passed I believe, but you can get it from the cache when needed. I considered this too. But the behavior of postNotification(Node*, AXNotification) is more complicated than that, doing traversal if necessary: RefPtr<AccessibilityObject> object = get(node); while (!object && node) { node = node->parentNode(); object = get(node); } But maybe we can get away with not doing that in this case? Not sure...what do you think, better to match the behavior that other consumers of postNotification(Node*) uses, or do the simpler, one-less-overload approach?
Andres Gonzalez
Comment 16 2022-05-25 08:43:50 PDT
(In reply to Tyler Wilcock from comment #14) > Created attachment 459744 [details] > Patch --- a/Source/WebCore/accessibility/AXObjectCache.cpp +++ a/Source/WebCore/accessibility/AXObjectCache.cpp @@ -1140,6 +1143,12 @@ void AXObjectCache::childrenChanged(AccessibilityObject* object) + if (auto* tableSectionElement = dynamicDowncast<HTMLTableSectionElement>(object->element())) { + if (auto* parentTable = tableSectionElement->findParentTable().get()) + m_deferredRecomputeTableIsExposedList.add(*parentTable); + } Can we do this in childrenChanged(Node* node, Node* changedChild) or childrenChanged(RenderObject* renderer, RenderObject* changedChild), whichever is called in this case. That way we don't have to go from AXObject back to Element. This would make the path more straightforward, i.e., we receive a notification with a Node or a RenderObject, then for some special case like being a TableSection, we storage the containing table Element and process the update of the corresponding AX table update later.
Andres Gonzalez
Comment 17 2022-05-25 08:58:49 PDT
(In reply to Tyler Wilcock from comment #15) > > --- a/Source/WebCore/accessibility/AXObjectCache.cpp > > +++ a/Source/WebCore/accessibility/AXObjectCache.cpp > > > > + if (auto* tableSectionElement = > > dynamicDowncast<HTMLTableSectionElement>(object.element())) { > > + auto* parentTable = tableSectionElement->findParentTable().get(); > > + if (auto* axTable = > > dynamicDowncast<AccessibilityTable>(getOrCreate(const_cast<HTMLTableElement*> > > (parentTable)))) > > + axTable->recomputeIsExposable(); > > > > We shouldn't need to go back to the Element at this point in the cache. Can > > we instead schedule some event for the table when we actually get the notification from the DOM in > > childrenChanged(Node* node, Node* changedChild) > > or the equivalent RenderObject overload. > OK, fixed this by adding m_deferredRecomputeTableIsExposedList. See my comment above concerning this. > > > void AXObjectCache::updateIsolatedTree(const > > Vector<std::pair<RefPtr<AXCoreObjec > > > > + case AXRowCountChanged: > > + if (notification.first->isTable()) > > + updateNode(notification.first); > > + FALLTHROUGH; > > > > Do we need to check if (notification.first->isTable()) here? I'd think we > > can just update if we receive that notification like with the others, e.g., > > CheckedStateChanged. > I did this because the only time AXRowCountChanged actually needs to update > properties is for tables. This guards against authoring errors where they > change aria-rowcount on non-tables, having no practical effect but causing > us to `updateNode`, which is expensive. But I might be overthinking it, so > I'd be fine removing the check if you'd prefer. Maybe doing the check in the IsolatedTree::updateNode would be more appropriate, i.e., the cache doesn't need to know all of this, but IsolatedTree::updateNode should know whether it needs to update something or not. > > > > The behavior for each is different enough that I think we need to match > > > these overloads, unless you have a different idea. > > > > Can we do > > > > + else if (attrName == aria_rowcountAttr) > > + handleRowCountChanged(get(element), ...); > > > > If so, we just need one. The Document parameter doesn't even need to be > > passed I believe, but you can get it from the cache when needed. > I considered this too. But the behavior of postNotification(Node*, > AXNotification) is more complicated than that, doing traversal if necessary: > > RefPtr<AccessibilityObject> object = get(node); > while (!object && node) { > node = node->parentNode(); > object = get(node); > } > > But maybe we can get away with not doing that in this case? Not sure...what > do you think, better to match the behavior that other consumers of > postNotification(Node*) uses, or do the simpler, one-less-overload approach? I think the walk up is done for the case that we don't have an AX object for which to post a platform notification. I don't think that applies in this case.
Tyler Wilcock
Comment 18 2022-05-25 10:46:31 PDT
EWS
Comment 19 2022-05-26 04:40:19 PDT
Committed r294875 (251005@main): <https://commits.webkit.org/251005@main> All reviewed patches have been landed. Closing bug and clearing flags on attachment 459762 [details].
Note You need to log in before you can comment on or make changes to this bug.