Repository navigation
fix(ios): refuse scene-hosted remote content in bridge snapshots - #3319
Conversation
On iOS 26 a share or action extension presented over its host app reaches the host's accessibility tree as one AXRemoteElement leaf under a _UISceneHostingView; its controls live in the extension's process. The single-process bridge published that leaf as if the screen were complete, so a snapshot of Photos with a share extension up returned one `other "Photos"` node. Count such leaves as opaque remote content, as web-hosted leaves already are (callstack#2484), so the route serves the XCTest runner, which resolves them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Reply to a comment to ask cubic a question or push back. It learns from your replies.
View guided diff | Re-trigger cubic
|
The PR is ready at a32d5e0. The change looks correct, CI is green with no failing checks, and there are no conflicts, so nothing is left to do before merge. Not blocking: the README at apple/snapshot-bridge/README.md still describes remote-content-boundary as WebContent-only (lines 54-57), so it could name the scene-hosting host next to the ADR text, and tree.test.ts could add one assertion that a _UISceneHostingView with ordinary children, or a remote element that has children, decodes with opaqueRemoteElements 0, so the "does not refuse ordinary trees" half is also covered for that host. Take or leave both. On the open thread from another reviewer: the P3 thread on the scene-host test root class still applies (#3319 (comment)). It is also non-blocking. I did not run the tests or the live simulator flow. The live result is author-reported against a patched 0.21.22 dist, not the PR head build. I could not check whether other _UISceneHostingView plus AXRemoteElement screens (share sheet, SFSafariViewController, other extensions) are now refused too. Such a screen would take the XCTest fallback, so it would be slower but still correct. The raw capture is not in the PR as a fixture, so the test shape is hand-built from the PR description. |
The scene-hosted test root carried no window, so its viewport was missing and every positive-area leaf refused through the no-viewport fallback. Shape it as the bridge captured it: an untyped app root over a UIWindow that reports the viewport. Add the off-screen, crossed-boundary and ordinary-children cases, and name the scene-hosting host in the snapshot-bridge README. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks @thymikee. I took both suggestions and covered the evidence gap. Everything is in dc3955f.
|
|
I reviewed dc3955f and found no problems in the code. The last review (#3319 (comment)) was also clean at a32d5e0. Since then the tree.test.ts fixture has gained a UIWindow viewport root and asserts viewport.kind 'reported'. The refusal case and the off-screen and zero-area negative cases now run through the reported-viewport branch of isOpaqueRemoteLeaf. The only change in this update touches tree.test.ts and the README, with no production code. The one production change in the PR is still the earlier tree.ts decode. The one cubic-dev-ai thread on the test fixture is fixed at this head, so please resolve it: #3319 (comment) I did not run the tests or the live simulator flow. The live result on the PR head is as you reported it. The fixture is hand-built from the PR description and the captured ancestry, and the raw capture is not committed. I also did not check whether other _UISceneHostingView screens, such as SFSafariViewController, now refuse. As you note, a refusal there would only cost the slower XCTest fallback. Smoke Tests is still running and no check has failed. I expect it to pass, because an ordinary tree has no _UISceneHostingView leaf and so should not hit the new refusal. Once Smoke Tests finishes green, I see nothing else in the way of merge. |
Problem
On an iOS 26 simulator, a snapshot taken while a share extension is presented over its host app returns one node:
The extension is scene-hosted. Its controls live in the extension's process and reach Photos' tree as one leaf, which the single-process host AX bridge cannot cross. From
snapshot --raw(agent-device 0.21.22, iPhone 17 / iOS 26.5):decodeSnapshotBridgeTreealready refuses this shape under aWebView(remote-content-boundary, #2484) so the route serves the XCTest runner. ADR 0004 left remote elements under any other host unclassified because "no capture has shown one". This is one.Change
tree.tsalso counts anAXRemoteElementleaf under a_UISceneHostingViewas opaque remote content, with the same frame rules as the web case (zero-area or off-screen leaves are published; frameless ones refuse). The refusal, generation circuit and XCTest fallback are unchanged. Doc comments intree.ts,types.ts,adapter.tsand ADR 0004 now name both hosts.Verification
New
tree.test.tscase built from the captured shape: fails without the change and passes with it.packages/platform-apple/src/snapshot-source: 109/109.pnpm check:affected --run: all runnable checks passed.pnpm lint,pnpm typecheck, andformat:checkpass.pnpm test:unit: 13,007 passed, 1 failed. The failure isscripts/__tests__/apple-ci-impact.test.ts("a shallow PR merge still yields a known change set…"), which fails the same way on unmodifiedmainon this machine.Live, iOS 26.5 simulator, with the change applied to the 0.21.22 dist. The same screen now reads:
An
@e2e-dev/mobiletest that goes Photos → Share → extension → Enshrine, then asserts in the host app, passes using only locators. Before the change it failed at the first extension locator.Not covered: a physical device (the bridge is simulator-only), Android, and Apple's own scene-hosted services (none captured).
🤖 Generated with Claude Code