C.W.K.
Stream
Lesson 03 of 05 · published

The Apps That Hide Their Selection

~11 min · chromium, electron, opaque-controls, boundary

Level 0Cold Flint
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"The clean read works everywhere — until it doesn't. Knowing exactly where it stops is more useful than pretending it never does."

The Clean Read Has an Edge

The last two lessons made AX reading look uniform and elegant: one tree, a few attributes, the selection falls out. That's true for apps that implement Accessibility fully — native editors, Apple's apps, most Cocoa text fields. But a large slice of the software people actually live in does not: Chromium-based browsers, and the many Electron apps built on the same engine — chat clients, editors, note tools. In those, the clean read hits a wall. Understanding that wall precisely is the difference between a tool that works 'usually' and one that works everywhere.

What the Opaque Apps Don't Give You

Chromium and Electron draw much of their interface themselves instead of using native controls, and their Accessibility tree is only partially populated. Concretely: kAXFocusedUIElementAttribute may return nothing usable, or the focused element it returns doesn't expose kAXSelectedTextAttribute — the web content's selection simply isn't published as the standard attribute. It isn't malice and it isn't a lock you can pick; the information is just not there to read. From Flint's side, a real, visible selection comes back as nothing.

native editor:     focused element -> selected text -> "..."   (works)
Chromium/Electron: focused element -> (missing)     -> nil     (the hole)

Same API call. In one app it returns the selection;
in the other it returns nothing, even though text IS selected.

Detecting the Hole

The dangerous mistake is to treat a nil AX read as 'nothing is selected.' In a native app that's true; in an opaque app it's a false negative — there is a selection, AX just can't see it. So Flint distinguishes the two: an empty selection from a fully-cooperating element is genuinely empty, but a failed read from a known-opaque app (or a focused element that won't yield selected text) is a signal to switch strategies, not to give up. That detection is what routes the strike to the fallback probe in the next lesson.

Every uniform mechanism has apps that don't play along — find the edge on purpose. A clean abstraction that works across most of a platform will always have a minority that implements it partially or not at all. The mature move is not to assume the abstraction is total, nor to abandon it, but to detect precisely where it fails and have a second path ready for exactly those cases. Map the hole; don't fall into it.
A nil read is not proof of an empty selection. Conflating 'AX returned nothing' with 'the user selected nothing' is the bug that makes a tool feel broken in exactly the apps people use most. Before concluding there's no input, ask whether the target is one of the opaque ones — and if so, reach for the probe instead of reporting an empty selection.

Code

Distinguish 'genuinely empty' from 'opaque, needs probing'·swift
enum SelectionRead {
    case text(String, range: CFRange?)   // AX gave us the selection
    case genuinelyEmpty                   // cooperating element, nothing selected
    case opaque                           // can't read via AX; must probe
}

func classifyRead(app: NSRunningApplication,
                  axResult: (text: String?, element: AXUIElement)?) -> SelectionRead {
    guard let r = axResult, let element = r.element as AXUIElement? else {
        // No focused element at all in an app that should have one -> opaque.
        return isKnownOpaque(app) ? .opaque : .genuinelyEmpty
    }
    if let t = r.text, !t.isEmpty { return .text(t, range: nil) }
    // Element exists but yields no selected text. In an opaque app that's the
    // hole, not an empty selection.
    return isKnownOpaque(app) ? .opaque : .genuinelyEmpty
}

External links

Exercise

You strike a macro over selected text in a chat app built on Electron and your AX read returns nil. List every conclusion you must NOT jump to, then describe the one signal that tells you this is the opaque hole rather than a genuinely empty selection. Why is misclassifying this case the difference between a tool that feels reliable and one that feels broken?
Hint
Do not conclude 'nothing selected,' 'the user made a mistake,' or 'AX is down.' The distinguishing signal is the target app itself — a known Chromium/Electron app returning nothing where a selection clearly exists is the hole. Misclassifying it means the tool silently does nothing in the exact apps people use most, which reads as 'broken everywhere,' while correctly detecting it routes you to the probe and the tool keeps working.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.