Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: 30446586d80c473d54cc344b85404af37e1f536c
https://github.com/WebKit/WebKit/commit/30446586d80c473d54cc344b85404af37e1f536c
Author: Tyler Wilcock <[email protected]>
Date: 2026-08-14 (Fri, 14 Aug 2026)
Changed paths:
M Source/WTF/wtf/SystemTracing.h
M Source/WebCore/accessibility/AXCoreObject.cpp
M Source/WebCore/accessibility/AXObjectCache.cpp
M Source/WebCore/accessibility/AXObjectCache.h
M Source/WebCore/accessibility/AXTreeStore.cpp
M Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
M Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp
M Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h
Log Message:
-----------
AX: Atomically transfer pending tree updates from main thread to AX thread
https://bugs.webkit.org/show_bug.cgi?id=316148
rdar://178573374
Reviewed by Andres Gonzalez.
Before this change, every main-thread writer acquired m_changeLogLock briefly
to mutate m_pendingChanges directly. Because the lock was acquired and released
per-mutation rather than once per logical work cycle, the AX thread could take
a snapshot mid-update-cycle and observe partial state. As an example,
performDeferredCacheUpdate calls tree->setFocusedNodeID(X) (publishing focus
immediately), but X's append into m_pendingChanges->appends is still queued in
m_unresolvedPendingAppends and only materialized later by
processQueuedNodeUpdates. If the AX thread takes the snapshot between these two
writes,
it observes focusedNodeID = X with no corresponding appends entry and reports
focus on a node that isn't in m_readerThreadNodeMap.
This commit reshapes main-thread writes so the AX thread can never observe a
partial update. Introduce a two-buffer model:
- m_workingChanges: main-thread-only, no lock. Writes accumulate here via
markDirtyAndGetWorkingChanges().
- m_committedChanges: cross-thread, guarded by m_changeLogLock. The AX thread
reads only this buffer, via takeCommittedChangesLocked().
commitWorkingChanges() merges working into committed in a single critical
section via PendingChanges::merge(), so a logical work cycle becomes one
indivisible state change. Field-specific merge semantics mirror how
applyCommittedChanges consumes each field. Vectors concatenate, sets and maps
union, and last-write-wins fields only overwrite when the source wrote this
cycle.
The one-shot scheduleQueuedNodeUpdateProcessing() timer drops from 100ms to
17ms, so a
batch is processed roughly one frame after its first change.
The eager-apply dedup flag (m_appliedOrApplyingCommittedChanges) becomes a
plain bool
guarded by m_changeLogLock, and is modified inside the same lock-section that
commits
the working changes.
* Source/WTF/wtf/SystemTracing.h:
* Source/WebCore/accessibility/AXCoreObject.cpp:
(WebCore::AXCoreObject::unignoredChildren):
* Source/WebCore/accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::onAccessibilityPaintFinished):
(WebCore::AXObjectCache::dirtyIsolatedTreeRelations):
(WebCore::AXObjectCache::startUpdateTreeSnapshotTimer):
(WebCore::AXObjectCache::updateTreeSnapshotTimerFired):
* Source/WebCore/accessibility/AXObjectCache.h:
* Source/WebCore/accessibility/AXTreeStore.cpp:
(WebCore::AXTreeStore<AXIsolatedTree>::applyPendingChangesForAllIsolatedTrees):
* Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp:
(WebCore::AXIsolatedObject::approximateHitTest const):
* Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp:
(WebCore::AXIsolatedTree::createEmpty):
(WebCore::AXIsolatedTree::createEmptyContent):
(WebCore::AXIsolatedTree::create):
(WebCore::AXIsolatedTree::storeTree):
(WebCore::AXIsolatedTree::removeTreeForFrameID):
(WebCore::AXIsolatedTree::queueChange):
(WebCore::AXIsolatedTree::addUnconnectedNode):
(WebCore::AXIsolatedTree::queueRemovals):
(WebCore::AXIsolatedTree::queueAppendsAndRemovals):
(WebCore::AXIsolatedTree::updateNode):
(WebCore::AXIsolatedTree::updateNodeProperties):
(WebCore::AXIsolatedTree::overrideNodeProperties):
(WebCore::AXIsolatedTree::pendingRootNodeID):
(WebCore::AXIsolatedTree::setPendingRootNodeID):
(WebCore::AXIsolatedTree::setFocusedNodeID):
(WebCore::AXIsolatedTree::updateRelations):
(WebCore::AXIsolatedTree::setSelectedTextMarkerRange):
(WebCore::AXIsolatedTree::setFrameGeometry):
(WebCore::AXIsolatedTree::updateFrame):
(WebCore::AXIsolatedTree::applyPendingChanges):
(WebCore::AXIsolatedTree::applyPendingChangesUnlessQueuedForDestruction):
(WebCore::AXIsolatedTree::applyPendingChangesOrTearDown):
(WebCore::AXIsolatedTree::clearTreeContentsLocked):
(WebCore::AXIsolatedTree::commitWorkingChanges):
(WebCore::AXIsolatedTree::takeCommittedChangesLocked):
(WebCore::AXIsolatedTree::applyCommittedChanges):
(WebCore::AXIsolatedTree::sortedLiveRegionsDidChange):
(WebCore::AXIsolatedTree::sortedNonRootWebAreasDidChange):
(WebCore::AXIsolatedTree::queueNodeUpdate):
(WebCore::AXIsolatedTree::queueNodeRemoval):
(WebCore::AXIsolatedTree::processQueuedNodeUpdates):
(WebCore::AXIsolatedTree::queueRemovalsLocked): Deleted.
(WebCore::AXIsolatedTree::setPendingRootNodeIDLocked): Deleted.
(WebCore::AXIsolatedTree::takePendingChangesLocked): Deleted.
(WebCore::AXIsolatedTree::applyPendingChangesFromSnapshot): Deleted.
* Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h:
(WebCore::AXIsolatedTree::markRelationsDirty):
(WebCore::AXIsolatedTree::markMostRecentlyPaintedTextDirty):
(WebCore::AXIsolatedTree::PendingChanges::setFocusedNodeID):
(WebCore::AXIsolatedTree::markDirtyAndGetWorkingChanges):
(WebCore::AXIsolatedTree::PendingChangesAccessor::PendingChangesAccessor):
Deleted.
(WebCore::AXIsolatedTree::PendingChangesAccessor::~PendingChangesAccessor):
Deleted.
(WebCore::AXIsolatedTree::PendingChangesAccessor::operator->): Deleted.
(WebCore::AXIsolatedTree::WTF_REQUIRES_LOCK): Deleted.
Canonical link: https://commits.webkit.org/319200@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications