Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: e30fb3e304f3ec7eefa43a791d52f3ee2bc91676
https://github.com/WebKit/WebKit/commit/e30fb3e304f3ec7eefa43a791d52f3ee2bc91676
Author: Tyler Wilcock <[email protected]>
Date: 2026-05-14 (Thu, 14 May 2026)
Changed paths:
A
LayoutTests/accessibility/dynamic-subtree-replacement-with-stale-parent-children-expected.txt
A
LayoutTests/accessibility/dynamic-subtree-replacement-with-stale-parent-children.html
M Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp
Log Message:
-----------
AX: nodeChangeForObject overwrites childrenIDs in the nodeMap, sometimes
causing content to be missing in the isolated tree
https://bugs.webkit.org/show_bug.cgi?id=314773
rdar://176891627
Reviewed by Dominic Mazzoni.
With this sequence, content can become missing from the isolated tree:
1. An attribute change (e.g. role) on parent X is posted as a notification
into AXObjectCache's m_notificationsToPost, which schedules
notificationPostTimer.
2. X's children are dynamically replaced (innerHTML, etc.), so live
children differ from what's stored in m_nodeMap[X].childrenIDs. The
children-changed events sit in AXObjectCache's
m_deferredChildrenChangedList
waiting for performDeferredCacheUpdate to drain them into
AXIsolatedTree's m_needsUpdateChildren.
3. performCacheUpdate fires before notificationPostTimer, but bails
because layout is dirty. m_deferredChildrenChangedList stays untouched,
so AXIsolatedTree's m_needsUpdateChildren stays empty for X.
4. notificationPostTimer fires. It (a) queues a full node update for X
into m_needsUpdateNode via updateIsolatedTree, and (b) calls
processQueuedIsolatedNodeUpdates synchronously inside
postPlatformNotification (so the AT sees an up-to-date tree when
it responds to the notification).
5. processQueuedNodeUpdates runs with m_needsUpdateNode={X} and
m_needsUpdateChildren={} (still empty from step 3). It calls
resolveAppends -> nodeChangeForObject(X), which sees live children
= NEW and stored children = OLD, and overwrites m_nodeMap[X].childrenIDs
with the NEW IDs without registering any of them in m_nodeMap.
6. Later, performCacheUpdate runs successfully and queues
updateChildren(X) into m_needsUpdateChildren. The snapshot timer
fires processQueuedNodeUpdates, which calls updateChildren(X).
It reads oldChildrenIDs from the polluted m_nodeMap (= NEW IDs)
and compares against newChildrenIDs from live (= the same NEW IDs).
old == new, so it never calls collectNodeChangesForSubtree on the
new children, and thus they never get isolated objects created for them.
7. AXIsolatedObject::children resolves the stored childrenIDs against
tree().objectForID(...). The unregistered NEW IDs return null and
get silently dropped via WTF::compactMap, leaving them invisible
to AT clients.
Fix this in the same way we fixed a similar problem for
collectNodeChangesForSubtree
in https://commits.webkit.org/285352@main. If the object is already in the
nodemap,
we leave the existing childrenIDs alone. Arguably we should do this even if the
object isn't in the nodemap, but rethinking this is saved for a later commit
since
this change alone fixes the bug on the webpage.
*
LayoutTests/accessibility/dynamic-subtree-replacement-with-stale-parent-children-expected.txt:
Added.
*
LayoutTests/accessibility/dynamic-subtree-replacement-with-stale-parent-children.html:
Added.
* Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp:
(WebCore::AXIsolatedTree::nodeChangeForObject):
Canonical link: https://commits.webkit.org/313241@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications