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 };
#endif
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 };
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)
{
ASSERT(isMainThread());
+ ASSERT(axObject->objectID().isValid());
- // 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.
- ASSERT_NOT_REACHED();
- return std::nullopt;
- }
-
auto object = AXIsolatedObject::create(axObject, this);
NodeChange nodeChange { object, nullptr };
updateNodeProperty(*treeAncestor, AXPropertyName::ARIATreeRows);
}
-void AXIsolatedTree::updateChildren(AXCoreObject& axObject)
+void AXIsolatedTree::updateChildren(AXCoreObject& axObject, ResolveNodeChanges resolveNodeChanges)
{
AXTRACE("AXIsolatedTree::updateChildren"_s);
AXLOG("For AXObject:");
return;
}
-#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:"));
AXLOG(axAncestor);
+ for (auto& child : axObject.children()) {
+ auto* liveChild = dynamicDowncast<AccessibilityObject>(child.get());
+ if (!liveChild || liveChild->childrenInitialized())
+ continue;
+
+ if (!m_nodeMap.contains(liveChild->objectID())) {
+ if (!shouldCreateNodeChange(*liveChild))
+ continue;
+
+ // 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);
+ }
+
+ AXLOG(makeString(
+ "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);
+ }
}
-#endif
auto oldIDs = m_nodeMap.get(axAncestor->objectID());
auto& oldChildrenIDs = oldIDs.childrenIDs;
if (axID.isValid())
removeSubtreeFromNodeMap(axID, axAncestor);
}
- queueRemovalsAndUnresolvedChanges(oldChildrenIDs);
+
+ if (resolveNodeChanges == ResolveNodeChanges::Yes)
+ queueRemovalsAndUnresolvedChanges(oldChildrenIDs);
+ else
+ queueRemovals(oldChildrenIDs);
// Also queue updates to the target node itself and any properties that depend on children().
updateNodeAndDependentProperties(*axAncestor);