Bug 240750 - AccessibilityTable::m_isExposable is never recomputed after AccessibilityTable::init
Summary: AccessibilityTable::m_isExposable is never recomputed after AccessibilityTabl...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Accessibility (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Tyler Wilcock
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2022-05-20 18:50 PDT by Tyler Wilcock
Modified: 2022-05-26 04:40 PDT (History)
14 users (show)

See Also:


Attachments
Patch (22.00 KB, patch)
2022-05-20 18:58 PDT, Tyler Wilcock
no flags Details | Formatted Diff | Diff
Patch (22.28 KB, patch)
2022-05-20 19:14 PDT, Tyler Wilcock
no flags Details | Formatted Diff | Diff
Patch (23.48 KB, patch)
2022-05-23 13:35 PDT, Tyler Wilcock
no flags Details | Formatted Diff | Diff
Patch (24.33 KB, patch)
2022-05-23 18:42 PDT, Tyler Wilcock
no flags Details | Formatted Diff | Diff
Patch (25.19 KB, patch)
2022-05-23 22:11 PDT, Tyler Wilcock
no flags Details | Formatted Diff | Diff
Patch (26.36 KB, patch)
2022-05-24 09:16 PDT, Tyler Wilcock
no flags Details | Formatted Diff | Diff
Patch (29.55 KB, patch)
2022-05-24 22:25 PDT, Tyler Wilcock
no flags Details | Formatted Diff | Diff
Patch (29.24 KB, patch)
2022-05-25 10:46 PDT, Tyler Wilcock
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Tyler Wilcock 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).
Comment 1 Radar WebKit Bug Importer 2022-05-20 18:50:56 PDT
<rdar://problem/93690148>
Comment 2 Tyler Wilcock 2022-05-20 18:58:48 PDT
Created attachment 459632 [details]
Patch
Comment 3 Tyler Wilcock 2022-05-20 19:14:27 PDT
Created attachment 459633 [details]
Patch
Comment 4 chris fleizach 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?
Comment 5 Andres Gonzalez 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.
Comment 6 Andres Gonzalez 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.
Comment 7 Tyler Wilcock 2022-05-23 13:35:19 PDT
Created attachment 459687 [details]
Patch
Comment 8 Tyler Wilcock 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.
Comment 9 Tyler Wilcock 2022-05-23 18:42:47 PDT
Created attachment 459695 [details]
Patch
Comment 10 Tyler Wilcock 2022-05-23 22:11:56 PDT
Created attachment 459696 [details]
Patch
Comment 11 Tyler Wilcock 2022-05-24 09:16:23 PDT
Created attachment 459726 [details]
Patch
Comment 12 Andres Gonzalez 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.
Comment 13 Andres Gonzalez 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.
Comment 14 Tyler Wilcock 2022-05-24 22:25:41 PDT
Created attachment 459744 [details]
Patch
Comment 15 Tyler Wilcock 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?
Comment 16 Andres Gonzalez 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.
Comment 17 Andres Gonzalez 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.
Comment 18 Tyler Wilcock 2022-05-25 10:46:31 PDT
Created attachment 459762 [details]
Patch
Comment 19 EWS 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].