Package limn.testing

Class RecordingAccessibilityBridge

java.lang.Object
limn.testing.RecordingAccessibilityBridge
All Implemented Interfaces:
AccessibilityBridge

public final class RecordingAccessibilityBridge extends Object implements AccessibilityBridge
A bridge that keeps what it is handed instead of talking to a platform: the double every headless accessibility test is written against.

It keeps an element registry of its own — a set of node identifiers — because two of the obligations the real bridges have are about exactly that and are otherwise untestable without a platform: an event budget that collapses has to leave a bridge able to release everything that went away, and a scene bound over a live window has to leave it holding nothing at all. A double that only counted calls could not fail either.

  • Field Details

    • listening

      public boolean listening
      What isListening() answers; a test turns the platform on and off with it.
    • needsPriming

      public boolean needsPriming
      What needsPrimingPublish() answers.
    • needsRootAtBind

      public boolean needsRootAtBind
    • published

      public final List<AccessibleTree> published
      Every tree handed over, newest last.
    • reentrant

      public final List<Boolean> reentrant
      Whether each of those was handed over with the platform on the stack.
    • events

      public final List<AccessibleEvent> events
      Every event handed over, in order.
    • elements

      public final Set<Long> elements
      The identifiers this bridge would be holding platform objects for.
    • attachments

      public int attachments
      How many hosts it has been given, and the one it holds.
    • host

      public @Nullable AccessibilityBridge.Host host
  • Constructor Details

    • RecordingAccessibilityBridge

      public RecordingAccessibilityBridge()
  • Method Details

    • listening

      public static RecordingAccessibilityBridge listening()
      A bridge that is listening from the start: what a component test always wants, since it is asking what an assistive technology would be told. The scene's own gate, that a quiet frame costs nothing when nothing is listening, is the scene package's to prove, with the plain constructor.
      Returns:
      a recording bridge whose platform is on
    • isListening

      public boolean isListening()
      Description copied from interface: AccessibilityBridge
      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.

      Specified by:
      isListening in interface AccessibilityBridge
      Returns:
      whether anything is reading this window
    • needsPrimingPublish

      public boolean needsPrimingPublish()
      Description copied from interface: AccessibilityBridge
      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.

      Specified by:
      needsPrimingPublish in interface AccessibilityBridge
      Returns:
      whether this bridge is owed one tree on the first frame regardless
    • needsRootBeforeTheFirstFrame

      public boolean needsRootBeforeTheFirstFrame()
      Description copied from interface: AccessibilityBridge
      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 0 answer 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.

      Specified by:
      needsRootBeforeTheFirstFrame in interface AccessibilityBridge
      Returns:
      whether this bridge is owed the window's own node the moment a scene binds
    • publish

      public void publish(AccessibleTree tree, boolean isReentrant)
      Description copied from interface: AccessibilityBridge
      Hands over a fresh whole-window snapshot. User-interface thread.

      reentrant is 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.

      Specified by:
      publish in interface AccessibilityBridge
      Parameters:
      tree - the window as it now stands
      isReentrant - whether the platform is on the stack
    • emit

      public void emit(AccessibleEvent event)
      Description copied from interface: AccessibilityBridge
      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.

      Specified by:
      emit in interface AccessibilityBridge
      Parameters:
      event - what happened
    • attach

      public void attach(AccessibilityBridge.Host newHost)
      Description copied from interface: AccessibilityBridge
      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.

      Specified by:
      attach in interface AccessibilityBridge
      Parameters:
      newHost - what the scene answers with
    • detach

      public void detach()
      Description copied from interface: AccessibilityBridge
      The window is going away: raises a window-closed, then empties whatever it was holding.
      Specified by:
      detach in interface AccessibilityBridge
    • clear

      public void clear()
      Forgets every tree and event recorded so far, so a test can assert on what follows.
    • tree

      public AccessibleTree tree()
      Returns:
      the most recently published tree, or the empty one
    • eventsOf

      public List<AccessibleEvent> eventsOf(AccessibleEvent.Type type)
      Parameters:
      type - the kind to look for
      Returns:
      every event of that kind, in the order handed over
    • countOf

      public long countOf(AccessibleEvent.Type type)
      Parameters:
      type - the kind to count
      Returns:
      how many events of that kind have been handed over
    • first

      public @Nullable AccessibleEvent first(AccessibleEvent.Type type)
      Parameters:
      type - the kind to look for
      Returns:
      the first event of that kind, or null