Skip to content
Browse files
AX: AXIsolatedTree::updateChildren sometimes fails to update isolated…
… subtrees when the given live object is ignored

Reviewed by Andres Gonzalez.

Our current algorithm in AXIsolatedTree::updateChildren is:

  1. If the object we got an AXChildrenChanged notification
  for is in the isolated tree, update its isolated children

  2. Otherwise, ascend the ancestry to the nearest
  in-isolated-tree ancestor, and update the children of that

This is not always adequate when the object passed to updateChildren
is ignored, as in some cases the ancestor has no children changes
but the subtrees of the ignored object do.

This patch fixes this by also checking the live-children of the ignored
object for any that have unitialized children. If so, we call
AXIsolatedTree::updateChildren on those too.

* Source/WebCore/accessibility/AccessibilityObject.h:
(WebCore::AccessibilityObject::childrenInitialized const):
Move from private to public.
* Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp:

Canonical link:
git-svn-id: 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
twilco committed Jun 23, 2022
1 parent 4a761ea commit 15ff72753700f9df018d1762c8c0f8f63a1610f0
Showing 3 changed files with 41 additions and 14 deletions.
@@ -497,6 +497,7 @@ class AccessibilityObject : public AXCoreObject {
void decrement() override { }

virtual void updateRole() { }
bool childrenInitialized() const { return m_childrenInitialized; }
const AccessibilityChildrenVector& children(bool updateChildrenIfNeeded = true) override;
virtual void addChildren() { }
enum class DescendIfIgnored : uint8_t { No, Yes };
@@ -827,7 +828,6 @@ class AccessibilityObject : public AXCoreObject {

protected: // FIXME: Make the data members private.
bool childrenInitialized() const { return m_childrenInitialized; }
AccessibilityChildrenVector m_children;
mutable bool m_childrenInitialized { false };
AccessibilityRole m_role { AccessibilityRole::Unknown };
@@ -209,20 +209,20 @@ void AXIsolatedTree::generateSubtree(AXCoreObject& axObject)
queueRemovalsAndUnresolvedChanges({ });

static bool shouldCreateNodeChange(AXCoreObject& axObject)
// We should never create an isolated object from an ignored object or one with an invalid ID.
return !axObject.accessibilityIsIgnored() && axObject.objectID().isValid();

std::optional<AXIsolatedTree::NodeChange> AXIsolatedTree::nodeChangeForObject(Ref<AXCoreObject> axObject, AttachWrapper attachWrapper)

// We should never create an isolated object from an ignored object.
if (axObject->accessibilityIsIgnored())
if (!shouldCreateNodeChange(axObject.get()))
return std::nullopt;

if (!axObject->objectID().isValid()) {
// Either the axObject has an invalid ID or something else went terribly wrong. Don't bother doing anything else.
return std::nullopt;

auto object = AXIsolatedObject::create(axObject, this);
NodeChange nodeChange { object, nullptr };

@@ -458,7 +458,7 @@ void AXIsolatedTree::updateNodeAndDependentProperties(AXCoreObject& axObject)
updateNodeProperty(*treeAncestor, AXPropertyName::ARIATreeRows);

void AXIsolatedTree::updateChildren(AXCoreObject& axObject)
void AXIsolatedTree::updateChildren(AXCoreObject& axObject, ResolveNodeChanges resolveNodeChanges)
AXLOG("For AXObject:");
@@ -489,12 +489,34 @@ void AXIsolatedTree::updateChildren(AXCoreObject& axObject)

#ifndef NDEBUG
if (axAncestor != &axObject) {
AXLOG(makeString("Original object with ID ", axObject.objectID().loggingString(), " wasn't in the isolated tree, so instead updating the closest in-isolated-tree ancestor:"));
for (auto& child : axObject.children()) {
auto* liveChild = dynamicDowncast<AccessibilityObject>(child.get());
if (!liveChild || liveChild->childrenInitialized())

if (!m_nodeMap.contains(liveChild->objectID())) {
if (!shouldCreateNodeChange(*liveChild))

// This child should be added to the isolated tree but hasn't been yet.
// Add it to the nodemap so the recursive call to updateChildren below properly builds the subtree for this object.
auto* parent = liveChild->parentObjectUnignored();
m_nodeMap.set(liveChild->objectID(), ParentChildrenIDs { parent ? parent->objectID() : AXID(), liveChild->childrenIDs() });
m_unresolvedPendingAppends.set(liveChild->objectID(), AttachWrapper::OnMainThread);

"Child ID ", liveChild->objectID().loggingString(), " of original object ID ", axObject.objectID().loggingString(), " was found in the isolated tree with uninitialized live children. Updating its isolated children."
// Don't immediately resolve node changes in these recursive calls to updateChildren. This avoids duplicate node change creation in this scenario:
// 1. Some subtree is updated in the below call to updateChildren.
// 2. Later in this function, when updating axAncestor, we update some higher subtree that includes the updated subtree from step 1.
updateChildren(*liveChild, ResolveNodeChanges::No);

auto oldIDs = m_nodeMap.get(axAncestor->objectID());
auto& oldChildrenIDs = oldIDs.childrenIDs;
@@ -528,7 +550,11 @@ void AXIsolatedTree::updateChildren(AXCoreObject& axObject)
if (axID.isValid())
removeSubtreeFromNodeMap(axID, axAncestor);

if (resolveNodeChanges == ResolveNodeChanges::Yes)

// Also queue updates to the target node itself and any properties that depend on children().
@@ -346,7 +346,8 @@ class AXIsolatedTree : public ThreadSafeRefCounted<AXIsolatedTree> {

void generateSubtree(AXCoreObject&);
void updateNode(AXCoreObject&);
void updateChildren(AXCoreObject&);
enum class ResolveNodeChanges : bool { No, Yes };
void updateChildren(AXCoreObject&, ResolveNodeChanges = ResolveNodeChanges::Yes);
void updateNodeProperty(AXCoreObject&, AXPropertyName);
void updateNodeAndDependentProperties(AXCoreObject&);

0 comments on commit 15ff727

Please sign in to comment.