← WebKit — 2026-W36

1f30924d488bff2288561f2832c5f5395e4529eb  AX: Frames sharing a Web process are unreachable or loop during VoiceOver navigation

severity medium class CrossOrigin confidence 0.6 signal score 9.06 Source/WebCore/accessibility patch-now
Primitive: Accessibility search escaping an iframe subtree to reach the main frame root
Triage note: AXSearchManager::findMatchingObjectsInternalAsStream now requires startObject->crossFrameIsDescendantOfObject(*anchorObject) before matching when walking backwards; the comment states that otherwise a start element outside the anchor's subtree lets the walk escape the iframe and return the main frame's root scroll area (cross-frame AX boundary escape / info disclosure).
Structural signals: smartptr_member_added (+6.47), null_guard (+2.59)

Commit message

AX: Frames sharing a Web process are unreachable or loop during VoiceOver navigation
https://bugs.webkit.org/show_bug.cgi?id=322236
rdar://184955716

Reviewed by Tyler Wilcock.

When several cross-site frames land in one Web process, each is a root
frame of the page and needs its own mock accessibility element.
This plus the following fixes were all needed to make shared process
site isolation mode to work with accessibility.

accessibilityRemoteObjectForFrame() fell back to the page-level
element for a frame the UI process had not bound yet.  Elements are
now created on demand, keyed by frame, so their identity does not
depend on registration order. registerRemoteFrameAccessibilityTokens()
goes through that same accessor on both platforms, which also makes
re-binding a frame update the element in place instead of replacing
one an isolated tree already references as its remote parent.

iOS registered every frame against the single page-level element, so
the frame that registered last took over and the rest were unreachable
from their parents.

AccessibilityScrollView::computeIsIgnored() ignored every scroll view
hosting a remote frame: the check for it came after the non-root case,
and a hosting scroll view is never the root. The frame was left with
no representation owned by its own process, so parent and child
disagreed about the tree and a search could not anchor beside the
iframe.

A backward search whose start was explicitly the anchor was treated as
having no start at all and answered with the anchor's children, the
same as a forward search, which stalled VoiceOver on an element
hosting an out-of-process frame. Separately, a backward search whose
start lay outside the anchor's subtree began its walk above the anchor
and matched the anchor's own ancestors, so walking up out of an iframe
returned the main frame's root scroll area -- an element the client
was already above.

Tests: http/wpt/site-isolation/accessibility/two-iframes-shared-process.sub.html

* LayoutTests/TestExpectations:
* LayoutTests/http/tests/site-isolation/accessibility/cross-process-search-traversal-expected.txt:
* LayoutTests/http/wpt/site-isolation/accessibility/resources/frame-with-heading.html: Added.
* LayoutTests/http/wpt/site-isolation/accessibility/two-iframes-shared-process.sub-expected.txt: Added.
* LayoutTests/http/wpt/site-isolation/accessibility/two-iframes-shared-process.sub.html: Added.
* LayoutTests/platform/mac/TestExpectations:
* Source/WebCore/accessibility/AXSearchManager.cpp:
(WebCore::AXSearchManager::findMatchingObjectsInternalAsStream):
* Source/WebCore/accessibility/AccessibilityScrollView.cpp:
(WebCore::AccessibilityScrollView::computeIsIgnored const):
* Source/WebKit/WebProcess/WebCoreSupport/cocoa/WebLocalFrameLoaderClientCocoa.mm:
(WebKit::WebLocalFrameLoaderClient::accessibilityRemoteObject):
(WebKit::WebLocalFrameLoaderClient::setIsolatedTree):
(WebKit::WebLocalFrameLoaderClient::isolatedTree const):
* Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm:
(WebKit::WebPage::bindRemoteAccessibilityFrames):
(WebKit::WebPage::ensureRemoteFrameAccessibilityElement):
(WebKit::WebPage::accessibilityRemoteObjectForFrame):
* Source/WebKit/WebProcess/WebPage/WebPage.h:
* Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::registerRemoteFrameAccessibilityTokens):
(WebKit::WebPage::createMockAccessibilityElementWithPresenter):
(WebKit::WebPage::createMockAccessibilityElement):
* Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm:
(WebKit::WebPage::createMockAccessibilityElementWithPresenter):
(WebKit::WebPage::createMockAccessibilityElement):
(WebKit::WebPage::accessibilityRemoteTokenDataForFrame const):
(WebKit::WebPage::registerRemoteFrameAccessibilityTokens):
(WebKit::WebPage::setIsolatedTreeForFrame):
(WebKit::WebPage::isolatedTreeForFrame):
(WebKit::WebPage::setIsolatedTree): Deleted.
(WebKit::WebPage::isolatedTree const): Deleted.
* Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementClientMac.h:
* Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementClientMac.mm:
(WTR::axElementsAreEqual):
(WTR::AccessibilityUIElementClientMac::isEqual):
* Tools/WebKitTestRunner/TestController.cpp:
* Tools/WebKitTestRunner/TestController.h:

Canonical link: https://commits.webkit.org/320133@main

Before / after

Loading diff…