Title: [288963] trunk/Source/WebCore
Revision
288963
Author
[email protected]
Date
2022-02-02 10:42:28 -0800 (Wed, 02 Feb 2022)

Log Message

Make modifications to the isolated Tree atomic.
https://bugs.webkit.org/show_bug.cgi?id=235683
<rdar://problem/88108412>

Reviewed by Chris Fleizach.

Covered by existing tests.

The isolated tree is currently updated in a non-atomic fashion, i.e.,
nodes are added or removed while clients are accessing the tree in an
inconsistent state. The idea of this patch is to make all necessary
changes in the tree in one atomic operation while the client thread,
or AX secondary thread, is blocked. To accomplish this and still be
responsive to client's requests on the AX thread, it is necessary that
all the processing in preparation for the update of the isolated tree
happens on the main thread without blocking the AX thread, and then do
the update of the tree in one atomic operation.

* accessibility/AXLogger.cpp:
(WebCore::operator<<):
Rearranged the properties of the AXCoreObject to log most relevant on top.
* accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::computeAccessibilityIsIgnored const):
Removed AXTRACE from this method since it is called so often that it is
just noise in the log.
* accessibility/isolatedtree/AXIsolatedTree.cpp:
(WebCore::AXIsolatedTree::generateSubtree):
It now collects all the changes to be perform and queues them in one
operation under lock.
(WebCore::AXIsolatedTree::nodeChangeForObject):
(WebCore::AXIsolatedTree::queueChange):
Queues one single change, must be called under lock.
(WebCore::AXIsolatedTree::queueChangesAndRemovals):
Queues all necessary changes to the isolated tree, additions and removals.
(WebCore::AXIsolatedTree::collectNodeChangesForSubtree):
Collects on the main thread all the additions/changes to be done to the
isolated tree.
(WebCore::AXIsolatedTree::updateNode):
(WebCore::AXIsolatedTree::nodeAncestryChanges):
Connects a new isolated node to the existing hierarchy.
(WebCore::AXIsolatedTree::updateChildren):
(WebCore::AXIsolatedTree::removeSubtreeFromNodeMap):
(WebCore::AXIsolatedTree::updateChildrenIDs): Deleted.
(WebCore::AXIsolatedTree::createSubtree): Deleted.
Replaced with collectNodeChanges and queueChangesAndRemovals.
(WebCore::AXIsolatedTree::updateSubtree): Deleted, not used.
(WebCore::AXIsolatedTree::removeSubtree): Deleted, not used.
* accessibility/isolatedtree/AXIsolatedTree.h:
(WebCore::AXIsolatedTree::queueChangesAndRemovals):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (288962 => 288963)


--- trunk/Source/WebCore/ChangeLog	2022-02-02 18:32:06 UTC (rev 288962)
+++ trunk/Source/WebCore/ChangeLog	2022-02-02 18:42:28 UTC (rev 288963)
@@ -1,3 +1,55 @@
+2022-02-02  Andres Gonzalez  <[email protected]>
+
+        Make modifications to the isolated Tree atomic.
+        https://bugs.webkit.org/show_bug.cgi?id=235683
+        <rdar://problem/88108412>
+
+        Reviewed by Chris Fleizach.
+
+        Covered by existing tests.
+
+        The isolated tree is currently updated in a non-atomic fashion, i.e.,
+        nodes are added or removed while clients are accessing the tree in an
+        inconsistent state. The idea of this patch is to make all necessary
+        changes in the tree in one atomic operation while the client thread,
+        or AX secondary thread, is blocked. To accomplish this and still be
+        responsive to client's requests on the AX thread, it is necessary that
+        all the processing in preparation for the update of the isolated tree
+        happens on the main thread without blocking the AX thread, and then do
+        the update of the tree in one atomic operation.
+
+        * accessibility/AXLogger.cpp:
+        (WebCore::operator<<):
+        Rearranged the properties of the AXCoreObject to log most relevant on top.
+        * accessibility/AccessibilityRenderObject.cpp:
+        (WebCore::AccessibilityRenderObject::computeAccessibilityIsIgnored const):
+        Removed AXTRACE from this method since it is called so often that it is
+        just noise in the log.
+        * accessibility/isolatedtree/AXIsolatedTree.cpp:
+        (WebCore::AXIsolatedTree::generateSubtree):
+        It now collects all the changes to be perform and queues them in one
+        operation under lock.
+        (WebCore::AXIsolatedTree::nodeChangeForObject):
+        (WebCore::AXIsolatedTree::queueChange):
+        Queues one single change, must be called under lock.
+        (WebCore::AXIsolatedTree::queueChangesAndRemovals):
+        Queues all necessary changes to the isolated tree, additions and removals.
+        (WebCore::AXIsolatedTree::collectNodeChangesForSubtree):
+        Collects on the main thread all the additions/changes to be done to the
+        isolated tree.
+        (WebCore::AXIsolatedTree::updateNode):
+        (WebCore::AXIsolatedTree::nodeAncestryChanges):
+        Connects a new isolated node to the existing hierarchy.
+        (WebCore::AXIsolatedTree::updateChildren):
+        (WebCore::AXIsolatedTree::removeSubtreeFromNodeMap):
+        (WebCore::AXIsolatedTree::updateChildrenIDs): Deleted.
+        (WebCore::AXIsolatedTree::createSubtree): Deleted.
+        Replaced with collectNodeChanges and queueChangesAndRemovals.
+        (WebCore::AXIsolatedTree::updateSubtree): Deleted, not used.
+        (WebCore::AXIsolatedTree::removeSubtree): Deleted, not used.
+        * accessibility/isolatedtree/AXIsolatedTree.h:
+        (WebCore::AXIsolatedTree::queueChangesAndRemovals):
+
 2022-02-02  Gabriel Nava Marino  <[email protected]>
 
         null ptr deref in Editor::pasteWithPasteboard

Modified: trunk/Source/WebCore/accessibility/AXLogger.cpp (288962 => 288963)


--- trunk/Source/WebCore/accessibility/AXLogger.cpp	2022-02-02 18:32:06 UTC (rev 288962)
+++ trunk/Source/WebCore/accessibility/AXLogger.cpp	2022-02-02 18:42:28 UTC (rev 288963)
@@ -517,13 +517,16 @@
 {
     TextStream::GroupScope groupScope(stream);
     stream << "objectID " << object.objectID();
-    stream.dumpProperty("identifierAttribute", object.identifierAttribute());
     auto role = object.roleValue();
     stream.dumpProperty("roleValue", role);
 
+    auto* parent = object.parentObjectUnignored();
+    stream.dumpProperty("parentObject", parent ? parent->objectID() : AXID());
+
+    stream.dumpProperty("identifierAttribute", object.identifierAttribute());
+
     auto* objectWithInterestingHTML = role == AccessibilityRole::Button ? // Add here other roles of interest.
         &object : nullptr;
-    auto* parent = object.parentObject();
     if (role == AccessibilityRole::StaticText && parent)
         objectWithInterestingHTML = parent;
     if (objectWithInterestingHTML)
@@ -532,8 +535,6 @@
     stream.dumpProperty("address", &object);
     stream.dumpProperty("wrapper", object.wrapper());
 
-    stream.dumpProperty("parentObject", parent ? parent->objectID() : AXID());
-
     return stream;
 }
 

Modified: trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp (288962 => 288963)


--- trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp	2022-02-02 18:32:06 UTC (rev 288962)
+++ trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp	2022-02-02 18:42:28 UTC (rev 288963)
@@ -1266,7 +1266,6 @@
     
 bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const
 {
-    AXTRACE("AccessibilityRenderObject::computeAccessibilityIsIgnored");
 #ifndef NDEBUG
     ASSERT(m_initialized);
 #endif

Modified: trunk/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp (288962 => 288963)


--- trunk/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp	2022-02-02 18:32:06 UTC (rev 288962)
+++ trunk/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp	2022-02-02 18:42:28 UTC (rev 288963)
@@ -176,17 +176,6 @@
     });
 }
 
-void AXIsolatedTree::updateChildrenIDs(AXID axID, Vector<AXID>&& childrenIDs)
-{
-    ASSERT(isMainThread());
-    ASSERT(m_changeLogLock.isLocked());
-
-    if (axID.isValid()) {
-        m_nodeMap.set(axID, childrenIDs);
-        m_pendingChildrenUpdates.append(std::make_pair(axID, WTFMove(childrenIDs)));
-    }
-}
-
 void AXIsolatedTree::generateSubtree(AXCoreObject& axObject, AXCoreObject* axParent, bool attachWrapper)
 {
     AXTRACE("AXIsolatedTree::generateSubtree");
@@ -195,15 +184,13 @@
     if (!axObject.objectID().isValid())
         return;
 
-    auto object = createSubtree(axObject, axParent ? axParent->objectID() : AXID(), attachWrapper);
-    Locker locker { m_changeLogLock };
-    if (!axParent)
-        setRootNode(object.ptr());
-    else if (axParent->objectID().isValid()) // Need to check for the objectID of axParent again because it may have been detached while traversing the tree.
-        updateChildrenIDs(axParent->objectID(), axParent->childrenIDs());
+    AXID parentID = axParent ? axParent->objectID() : AXID();
+    Vector<NodeChange> changes;
+    collectNodeChangesForSubtree(axObject, parentID, attachWrapper, changes);
+    queueChangesAndRemovals(changes);
 }
 
-AXIsolatedTree::NodeChange AXIsolatedTree::nodeChangeForObject(AXCoreObject& axObject, AXID parentID, bool attachWrapper)
+AXIsolatedTree::NodeChange AXIsolatedTree::nodeChangeForObject(AXCoreObject& axObject, AXID parentID, bool attachWrapper, bool updateNodeMap)
 {
     ASSERT(isMainThread());
 
@@ -224,40 +211,72 @@
         nodeChange.wrapper = axObject.wrapper();
     }
 
+    if (updateNodeMap)
+        m_nodeMap.set(axObject.objectID(), axObject.childrenIDs());
+
+    if (!parentID.isValid()) {
+        Locker locker { m_changeLogLock };
+        setRootNode(nodeChange.isolatedObject.ptr());
+    }
+
     return nodeChange;
 }
 
-void AXIsolatedTree::queueChanges(const NodeChange& nodeChange, Vector<AXID>&& childrenIDs)
+void AXIsolatedTree::queueChange(const NodeChange& nodeChange)
 {
     ASSERT(isMainThread());
+    ASSERT(m_changeLogLock.isLocked());
 
-    Locker locker { m_changeLogLock };
     m_pendingAppends.append(nodeChange);
-    updateChildrenIDs(nodeChange.isolatedObject->objectID(), WTFMove(childrenIDs));
+
+    AXID parentID = nodeChange.isolatedObject->parent();
+    if (parentID.isValid()) {
+        ASSERT(m_nodeMap.contains(parentID));
+        auto siblingsIDs = m_nodeMap.get(parentID);
+        m_pendingChildrenUpdates.append({ parentID, WTFMove(siblingsIDs) });
+    }
+
+    AXID objectID = nodeChange.isolatedObject->objectID();
+    ASSERT(objectID != parentID);
+    ASSERT(m_nodeMap.contains(objectID));
+    auto childrenIDs = m_nodeMap.get(objectID);
+    m_pendingChildrenUpdates.append({ objectID, WTFMove(childrenIDs) });
 }
 
-Ref<AXIsolatedObject> AXIsolatedTree::createSubtree(AXCoreObject& axObject, AXID parentID, bool attachWrapper)
+void AXIsolatedTree::queueChangesAndRemovals(const Vector<NodeChange>& changes, const Vector<AXID>& subtreeRemovals)
 {
-    AXTRACE("AXIsolatedTree::createSubtree");
     ASSERT(isMainThread());
 
+    Locker locker { m_changeLogLock };
+
+    for (const auto& axID : subtreeRemovals)
+        m_pendingSubtreeRemovals.append(axID);
+
+    for (const auto& change : changes)
+        queueChange(change);
+}
+
+void AXIsolatedTree::collectNodeChangesForSubtree(AXCoreObject& axObject, AXID parentID, bool attachWrapper, Vector<NodeChange>& changes)
+{
+    AXTRACE("AXIsolatedTree::collectNodeChangesForSubtree");
+    ASSERT(isMainThread());
+
     if (!m_creatingSubtree)
         axObjectCache()->processDeferredChildrenChangedList();
     SetForScope<bool> creatingSubtree(m_creatingSubtree, true);
 
     auto nodeChange = nodeChangeForObject(axObject, parentID, attachWrapper);
+    changes.append(nodeChange);
 
     auto axChildren = axObject.children();
-    Vector<AXID> childrenIDs;
-    childrenIDs.reserveCapacity(axChildren.size());
+    Vector<AXID> axChildrenIDs;
+    axChildrenIDs.reserveCapacity(axChildren.size());
     for (const auto& axChild : axChildren) {
-        auto child = createSubtree(*axChild, axObject.objectID(), attachWrapper);
-        childrenIDs.uncheckedAppend(child->objectID());
+        collectNodeChangesForSubtree(*axChild, axObject.objectID(), attachWrapper, changes);
+        axChildrenIDs.uncheckedAppend(axChild->objectID());
     }
 
-    queueChanges(nodeChange, WTFMove(childrenIDs));
-
-    return nodeChange.isolatedObject;
+    m_nodeMap.set(axObject.objectID(), axChildrenIDs);
 }
 
 void AXIsolatedTree::updateNode(AXCoreObject& axObject)
@@ -267,16 +286,15 @@
     ASSERT(isMainThread());
 
     AXID axID = axObject.objectID();
-    auto* axParent = axObject.parentObject();
+    auto* axParent = axObject.parentObjectUnignored();
     AXID parentID = axParent ? axParent->objectID() : AXID();
 
-    auto newObject = AXIsolatedObject::create(axObject, this, parentID);
-    newObject->m_childrenIDs = axObject.childrenIDs();
+    auto change = nodeChangeForObject(axObject, parentID, true);
 
+    // Remove the old object and set the new one to be updated on the AX thread.
     Locker locker { m_changeLogLock };
-    // Remove the old object and set the new one to be updated on the AX thread.
     m_pendingNodeRemovals.append(axID);
-    m_pendingAppends.append({ newObject, axObject.wrapper() });
+    queueChange(change);
 }
 
 void AXIsolatedTree::updateNodeProperty(const AXCoreObject& axObject, AXPropertyName property)
@@ -309,14 +327,33 @@
     m_pendingPropertyChanges.append({ axObject.objectID(), propertyMap });
 }
 
-void AXIsolatedTree::updateSubtree(AXCoreObject& axObject)
+Vector<AXIsolatedTree::NodeChange> AXIsolatedTree::nodeAncestryChanges(AXCoreObject& axCoreObject)
 {
-    AXTRACE("AXIsolatedTree::updateSubtree");
-    AXLOG(&axObject);
-    ASSERT(isMainThread());
+    Vector<NodeChange> changes;
 
-    removeSubtree(axObject.objectID());
-    generateSubtree(axObject, axObject.parentObject(), false);
+    auto* axObject = &axCoreObject;
+    while (axObject && !m_nodeMap.contains(axObject->objectID())) {
+        // axObject has no node in the isolated tree, thus add it.
+        AXLOG("axObject not in the isolated tree:");
+        AXLOG(axObject);
+        auto* axParent = axObject->parentObjectUnignored();
+        AXLOG("axParent:");
+        AXLOG(axParent);
+        AXID axParentID = axParent ? axParent->objectID() : AXID();
+
+        // If axObject is the original object for which we are adding its ancestry, don't update the nodeMap since updateChildren needs to compare its previous children with the new ones.
+        bool updateNodeMap = axObject != &axCoreObject;
+        changes.append(nodeChangeForObject(*axObject, axParentID, true, updateNodeMap));
+
+        if (axParent)
+            m_nodeMap.set(axParent->objectID(), axParent->childrenIDs());
+
+        axObject = axParent;
+    }
+
+    // Since the NodeChanges are added to the changes vector in a child -> parent traversal instead of a parent -> child traversal, we need to reverse changes.
+    changes.reverse();
+    return changes;
 }
 
 void AXIsolatedTree::updateChildren(AXCoreObject& axObject)
@@ -337,63 +374,36 @@
     // updateChildren may be called as the result of a children changed
     // notification for an axObject that has no associated isolated object.
     // An example of this is when an empty element such as a <canvas> or <div>
-    // is added a new child. So find the closest ancestor of axObject that has
-    // an associated isolated object and update its children.
-    auto iterator = m_nodeMap.end();
-    auto* axAncestor = Accessibility::findAncestor(axObject, true, [&iterator, this] (auto& ancestor) {
-        auto it = m_nodeMap.find(ancestor.objectID());
-        if (it != m_nodeMap.end()) {
-            iterator = it;
-            return true;
-        }
+    // is added a new child. Thus add the ancestry that connects the associated
+    // isolated object to the closest existing ancestor.
+    Vector<NodeChange> changes = nodeAncestryChanges(axObject);
 
-        // ancestor has no node in the isolated tree, thus add it.
-        auto* axParent = ancestor.parentObject();
-        AXID axParentID = axParent ? axParent->objectID() : AXID();
-        auto nodeChange = nodeChangeForObject(ancestor, axParentID, true);
-        queueChanges(nodeChange, ancestor.childrenIDs());
+    auto oldChildrenIDs = m_nodeMap.get(axObject.objectID());
 
-        return false;
-    });
-    if (!axAncestor || !axAncestor->objectID().isValid() || iterator == m_nodeMap.end()) {
-        // This update triggered before the isolated tree has been repopulated.
-        // Return here since there is nothing to update.
-        return;
-    }
+    const auto& axChildren = axObject.children();
+    auto newChildrenIDs = axObject.childrenIDs(false);
 
-    // iterator is pointing to the m_nodeMap entry corresponding to axAncestor->objectID().
-    ASSERT(iterator->key == axAncestor->objectID());
-    auto removals = iterator->value;
-
-    const auto& axChildren = axAncestor->children();
-    auto axChildrenIDs = axAncestor->childrenIDs(false);
-
-    bool updatedChild = false; // Set to true if at least one child's subtree is updated.
-    for (size_t i = 0; i < axChildren.size() && i < axChildrenIDs.size(); ++i) {
-        ASSERT(axChildren[i]->objectID() == axChildrenIDs[i]);
-        ASSERT(axChildrenIDs[i].isValid());
-        size_t index = removals.find(axChildrenIDs[i]);
+    for (size_t i = 0; i < axChildren.size(); ++i) {
+        ASSERT(axChildren[i]->objectID() == newChildrenIDs[i]);
+        ASSERT(newChildrenIDs[i].isValid());
+        size_t index = oldChildrenIDs.find(newChildrenIDs[i]);
         if (index != notFound)
-            removals.remove(index);
+            oldChildrenIDs.remove(index);
         else {
             // This is a new child, add it to the tree.
             AXLOG("Adding a new child for:");
             AXLOG(axChildren[i]);
-            generateSubtree(*axChildren[i], axAncestor, true);
-            updatedChild = true;
+            collectNodeChangesForSubtree(*axChildren[i], axObject.objectID(), true, changes);
         }
     }
 
-    // What is left in removals are the IDs that are no longer children of
-    // axObject. Thus, remove them from the tree.
-    for (const AXID& childID : removals)
-        removeSubtree(childID);
+    m_nodeMap.set(axObject.objectID(), newChildrenIDs);
 
-    if (updatedChild || removals.size()) {
-        // Make the children IDs of the isolated object to be the same as the AXObject's.
-        Locker locker { m_changeLogLock };
-        updateChildrenIDs(axAncestor->objectID(), WTFMove(axChildrenIDs));
-    }
+    // What is left in oldChildrenIDs are the IDs that are no longer children of axObject.
+    // Thus, remove them from m_nodeMap and queue them to be removed from the tree.
+    for (AXID& axID : oldChildrenIDs)
+        removeSubtreeFromNodeMap(axID);
+    queueChangesAndRemovals(changes, oldChildrenIDs);
 }
 
 RefPtr<AXIsolatedObject> AXIsolatedTree::focusedNode()
@@ -461,7 +471,7 @@
     m_pendingNodeRemovals.append(axID);
 }
 
-void AXIsolatedTree::removeSubtree(AXID axID)
+void AXIsolatedTree::removeSubtreeFromNodeMap(AXID axID)
 {
     AXTRACE("AXIsolatedTree::removeSubtree");
     AXLOG(makeString("Removing subtree for axID ", axID.loggingString()));
@@ -479,9 +489,6 @@
             m_nodeMap.remove(axID);
         }
     }
-
-    Locker locker { m_changeLogLock };
-    m_pendingSubtreeRemovals.append(axID);
 }
 
 void AXIsolatedTree::applyPendingChanges()

Modified: trunk/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h (288962 => 288963)


--- trunk/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h	2022-02-02 18:32:06 UTC (rev 288962)
+++ trunk/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h	2022-02-02 18:42:28 UTC (rev 288963)
@@ -354,7 +354,6 @@
     void generateSubtree(AXCoreObject&, AXCoreObject*, bool attachWrapper);
     void updateNode(AXCoreObject&);
     void updateNodeProperty(const AXCoreObject&, AXPropertyName);
-    void updateSubtree(AXCoreObject&);
     void updateChildren(AXCoreObject&);
 
     double loadingProgress() { return m_loadingProgress; }
@@ -363,7 +362,7 @@
     // Removes the given node leaving all descendants alone.
     void removeNode(AXID);
     // Removes the given node and all its descendants.
-    void removeSubtree(AXID);
+    void removeSubtreeFromNodeMap(AXID);
 
     // Both setRootNodeID and setFocusedNodeID are called during the generation
     // of the IsolatedTree.
@@ -386,11 +385,11 @@
     static HashMap<PageIdentifier, Ref<AXIsolatedTree>>& treePageCache() WTF_REQUIRES_LOCK(s_cacheLock);
 
     // Called on main thread.
-    NodeChange nodeChangeForObject(AXCoreObject&, AXID parentID, bool attachWrapper);
-    void queueChanges(const NodeChange&, Vector<AXID>&& childrenIDs);
-    Ref<AXIsolatedObject> createSubtree(AXCoreObject&, AXID parentID, bool attachWrapper);
-    // Called on main thread to update both m_nodeMap and m_pendingChildrenUpdates.
-    void updateChildrenIDs(AXID parentID, Vector<AXID>&& childrenIDs) WTF_REQUIRES_LOCK(m_changeLogLock);
+    NodeChange nodeChangeForObject(AXCoreObject&, AXID parentID, bool attachWrapper = true, bool updateNodeMap = true);
+    void collectNodeChangesForSubtree(AXCoreObject&, AXID parentID, bool attachWrapper, Vector<NodeChange>&);
+    void queueChange(const NodeChange&) WTF_REQUIRES_LOCK(m_changeLogLock);
+    void queueChangesAndRemovals(const Vector<NodeChange>&, const Vector<AXID>& = { });
+    Vector<NodeChange> nodeAncestryChanges(AXCoreObject&);
 
     AXIsolatedTreeID m_treeID;
     AXObjectCache* m_axObjectCache { nullptr };
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to