Interface AccessibilityBridge
- All Known Implementing Classes:
HeadlessWindow.CapturingBridge,RecordingAccessibilityBridge
The whole seam is twelve members and one direction each. Outbound, the scene tells a
bridge things and asks it three questions, and nothing in the toolkit calls a bridge in any other
way. Inbound, everything a platform asks of the toolkit arrives through the four members of
AccessibilityBridge.Host. Nothing in a bridge ever touches a widget, a scene or a window: it is handed an
immutable tree and it answers from that.
That is not tidiness, it is what the three platforms measured out. On one of them the platform calls a provider from several threads of its own while the user-interface thread sleeps, holding no lock on anyone's behalf. On another the platform never calls at all and wants a whole tree in one message. On the third the platform's thread is the user-interface thread. A snapshot published through a single volatile write is the only shape that serves all three, and a bridge that read live state off a foreign thread would get a silent data race rather than an exception: the toolkit's thread confinement covers property setters, tree mutation, focus and overlays, and not the getters, the layout, the damage or the observer registrars that a bridge is tempted by.
Every member has a default, and NONE takes all of them. A window with no
accessibility — a test double, an embedded surface, a backend on its first day — gets exactly
zero cost by implementing nothing.
-
Nested Class Summary
Nested ClassesModifier and TypeInterfaceDescriptionstatic interfaceWhat the scene gives a bridge: the three ways to ask for a tree, and the one way to act. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final AccessibilityBridgeNo accessibility: every question answered with a constant, every statement discarded. -
Method Summary
Modifier and TypeMethodDescriptiondefault voidHands the bridge the scene-side half, replacing any host it already held.default voiddetach()The window is going away: raises a window-closed, then empties whatever it was holding.default voidemit(AccessibleEvent event) One coalesced event, naming a node in the tree just published.default voidThe frame's accessibility step is over: everything this frame had to say has been emitted.default booleanWhether any assistive technology is listening.default booleanWhether this bridge needs one tree on the scene's first frame even though nothing is listening yet, because its own listening gate cannot open until it has elements to offer.default booleanWhether this bridge is owed the window's own node the moment a scene binds, because it can be asked whether this window has accessibility at all before the first frame has run.default voidpublish(AccessibleTree tree, boolean reentrant) Hands over a fresh whole-window snapshot.
-
Field Details
-
NONE
No accessibility: every question answered with a constant, every statement discarded.
-
-
Method Details
-
isListening
default boolean isListening()Whether any assistive technology is listening.Asked once per frame on the publish path, and on the two rare paths that have to know whether spending a frame is worth it at all. It must therefore be cheap: one platform call, or a flag the bridge already keeps.
It is never "did a client ask us something recently." A gate of that shape inverts the contract on all three platforms, because a platform event is a push a client waits on: one of the three screen readers registers for a focus change and then calls nothing at all until it fires. Tab twice quickly with such a gate and the toolkit goes silent — the client only asks after an event, and the event only fires after the client asks.
- Returns:
- whether anything is reading this window
-
needsPrimingPublish
default boolean needsPrimingPublish()Whether this bridge needs one tree on the scene's first frame even though nothing is listening yet, because its own listening gate cannot open until it has elements to offer.Asked once, when a scene binds. True where the honest gate is "someone has asked", which cannot open before the platform has been handed something to ask about: macOS, which has no equivalent of "is anyone listening" at all, and Windows, whose process-wide flag is only reliable in the negative and whose per-window half needs an element for a client to reach. False on Linux, which can ask its desktop directly and so pays no walk for a window an assistive technology never touches.
- Returns:
- whether this bridge is owed one tree on the first frame regardless
-
needsRootBeforeTheFirstFrame
default boolean needsRootBeforeTheFirstFrame()Whether this bridge is owed the window's own node the moment a scene binds, because it can be asked whether this window has accessibility at all before the first frame has run.Asked once, when a scene binds, and true on one platform. There a client asks the window itself, in a message, and a window that answers "nothing here" is not asked again: the client subscribes to no events, and every event raised for the rest of that window's life is delivered to nobody while the window keeps answering every question it is asked. Measured on the Windows 11 guest, 2026-09-16: the reader's first ask reaches the window 36-116 ms after the bridge goes in front of the window procedure and is answered 50-141 ms in, its second follows within milliseconds, and the first frame publishes 305-523 ms later — so ten runs of one build split four spoken and six silent on nothing but whether a second ask happened to arrive after that first publish (it did in two runs, ~200 ms late, and in two more the reader simply never asked twice). One
0answer before the first real one and the reader speaks; two and it is silent, in ten runs out of ten (readings/phase5-windows-diagnosis/evidence-table.txt).What it is owed is the window node alone, and not the scene. No layout has run at a bind, so every widget is a zero-size rectangle at the origin and a tree of those is worse than none — it reads perfectly and hit-tests nowhere. The window's role, its title and the size it already has are true at that instant, and the identifier they are published under is the one every later walk reuses, so the element a client subscribes to here is the element the contents arrive under.
- Returns:
- whether this bridge is owed the window's own node the moment a scene binds
-
publish
Hands over a fresh whole-window snapshot. User-interface thread.reentrantis true when the scene built this tree from inside the platform's own callback, with the platform on the stack holding elements this bridge vended. A reentrant publish stores the tree and answers from it, and does nothing else: it must not destroy or release anything in its element registry, must not re-push the top of its tree, and must not drain its event queue — each of those would act on the objects the caller is standing on. Everything it defers is owed by the next ordinary frame, which the scene has already asked for.- Parameters:
tree- the window as it now standsreentrant- whether the platform is on the stack
-
emit
One coalesced event, naming a node in the tree just published. User-interface thread.Enqueue it; do not raise it. A difference between two frames of a scrolling list or a dragged slider can be hundreds of nodes wide, and every raise is a cross-process call: a bridge that raised them here would spend the frame budget inside the platform, which is precisely the stall that makes a window unreadable to the user it is being read to.
- Parameters:
event- what happened
-
frameEnded
default void frameEnded()The frame's accessibility step is over: everything this frame had to say has been emitted. User-interface thread, once per frame, whether or not the frame published — a frame that only drained announcements, or one that follows a reentrant publish and finds nothing left to walk, ends too.It is the one moment a bridge that posts on the user-interface thread can post. A queue drained at the top of the next publish instead holds every event of a change until the tree changes again: on a still window the last thing that happened is never told at all, and everything else is told one change late, about a tree that has already moved on. The obligations a reentrant publish deferred are owed here too, because the frame that reentrant publish asked for need not publish anything.
Never called from inside a reentrant publish, nor from anywhere the platform is on the stack: it runs at the end of the frame step, where the ordinary publish runs.
It is also the publish boundary a bridge that raises elsewhere reads: a bridge draining on a thread of its own cannot see where one frame's events end and the next frame's begin, and the order it owes an assistive technology after a collapse — the shape of the tree first, then where the user is — is exactly that boundary. Such a bridge hands the marker to its own thread rather than acting here. The no-op default is for a bridge with neither obligation.
-
attach
Hands the bridge the scene-side half, replacing any host it already held.A scene can be bound over a live window and the outgoing one never learns it was, so the bridge is the only object that knows there was a tree to close. That is why the window pair is raised here rather than by the caller: an implementation empties its element registry and raises a window-closed for the host it is replacing, if there was one, then raises a window-opened for the incoming one. On a first attach only the second is raised.
It builds and pushes no tree, on any platform. At this moment the scene has never laid out, so every box in it would be a zero-size rectangle at the origin.
- Parameters:
host- what the scene answers with
-
detach
default void detach()The window is going away: raises a window-closed, then empties whatever it was holding.
-