본문 바로가기
C.W.K.
Stream
Lesson 03 of 05 · published

긁어놓은 걸 안 보여주는 앱들

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

Level 0식은 부싯돌
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"깨끗한 방법은 어디서나 통해. 안 통하는 데를 만나기 전까진. 어디서 멈추는지 정확히 아는 게 안 멈추는 척하는 것보다 훨씬 쓸모 있어."

깨끗한 읽기에도 끝이 있어

지난 두 레슨은 AX 로 읽는 걸 아주 반듯하게 보여줬지. 나무 하나, 질문 몇 개, 글이 툭 떨어지고. 접근성을 제대로 구현한 앱에선 맞는 얘기야. 네이티브 에디터, 애플 앱, 대부분의 Cocoa 입력 칸. 그런데 사람들이 실제로 하루 종일 머무는 소프트웨어 중 상당수가 안 그래. Chromium 으로 만든 브라우저들, 그리고 같은 엔진 위에 얹은 수많은 Electron 앱들. 채팅 클라이언트, 에디터, 메모 도구. 거기선 깨끗한 읽기가 벽에 부딪혀. 그 벽이 정확히 어디 있는지 아는 게 '대개는 되는' 도구랑 어디서나 되는 도구를 가르고.

불투명한 앱이 안 내주는 것

Chromium 이랑 Electron 은 화면 대부분을 네이티브 컨트롤 대신 자기가 직접 그려. 그래서 접근성 나무가 절반만 채워져 있어. 구체적으로는 kAXFocusedUIElementAttribute 가 쓸 만한 걸 안 돌려주거나, 돌려준 요소가 kAXSelectedTextAttribute 를 안 갖고 있지. 웹 내용에서 긁은 글이 표준 속성으로 아예 안 실려 나오는 거야. 악의도 아니고 따고 들어갈 자물쇠도 아니야. 읽을 정보가 그냥 없는 거지. Flint 쪽에서 보면 눈에 뻔히 보이는 하이라이트가 빈손으로 돌아와.

네이티브 에디터:    포커스된 요소 -> 긁어놓은 글 -> "..."   (된다)
Chromium/Electron: 포커스된 요소 -> (없음)      -> nil     (구멍)

같은 호출인데 한쪽은 글을 돌려주고, 다른 쪽은
분명히 긁혀 있는데도 빈손으로 온다.

구멍인 걸 알아채기

위험한 실수는 AX 가 빈손으로 왔다고 '아무것도 안 긁혔네' 로 넘기는 거야. 네이티브 앱에선 맞는 말이지만 불투명한 앱에선 틀린 결론이거든. 긁혀 있는데 AX 가 못 볼 뿐이니까. 그래서 Flint 는 둘을 갈라. 제대로 협조하는 요소가 빈손이면 진짜로 빈 거고, 불투명한 걸로 알려진 앱에서 못 읽었으면 포기하라는 게 아니라 방법을 바꾸라는 신호야. 그 판단이 다음 레슨의 찔러보기로 데려가고.

어디서나 통하는 방법에도 안 맞춰주는 앱은 있어. 그 끝을 일부러 찾아둬. 플랫폼 대부분에서 통하는 깨끗한 방식엔 언제나 절반만 구현했거나 아예 안 한 소수가 있어. 어른스러운 대응은 그 방식이 전부 통한다고 믿는 것도, 통째로 버리는 것도 아니야. 어디서 실패하는지 정확히 알아채고 딱 그 경우를 위한 두 번째 길을 준비해두는 거지. 구멍은 지도에 그려둬. 빠지지 말고.
빈손으로 왔다고 안 긁힌 건 아니야. 'AX 가 아무것도 안 줬다' 를 '사용자가 아무것도 안 긁었다' 로 뭉개는 게, 사람들이 제일 많이 쓰는 바로 그 앱에서 도구를 고장 난 물건처럼 보이게 만드는 버그야. 입력이 없다고 결론 내리기 전에 지금 대상이 불투명한 쪽인지부터 물어봐. 그렇다면 빈손이라고 보고할 게 아니라 찔러보기로 넘어가야 하고.

Code

'진짜 빈 것' 과 '불투명해서 못 읽은 것' 을 갈라내기·swift
enum SelectionRead {
    case text(String, range: CFRange?)   // AX 가 글을 줬다
    case genuinelyEmpty                   // 협조하는 요소인데 긁힌 게 없다
    case opaque                           // AX 로는 못 읽는다. 찔러봐야 한다
}

func classifyRead(app: NSRunningApplication,
                  axResult: (text: String?, element: AXUIElement)?) -> SelectionRead {
    guard let r = axResult, let element = r.element as AXUIElement? else {
        // 있어야 할 앱인데 포커스된 요소가 아예 없다 -> 불투명 쪽이다.
        return isKnownOpaque(app) ? .opaque : .genuinelyEmpty
    }
    if let t = r.text, !t.isEmpty { return .text(t, range: nil) }
    // 요소는 있는데 긁어놓은 글을 안 내준다. 불투명 앱이면 빈 게 아니라
    // 구멍이다.
    return isKnownOpaque(app) ? .opaque : .genuinelyEmpty
}

External links

Exercise

Electron 으로 만든 채팅 앱에서 글을 긁어놓고 매크로를 쳤는데 AX 읽기가 빈손으로 왔어. 여기서 절대 뛰어들면 안 되는 결론들을 먼저 적어봐. 그다음 이게 진짜로 빈 게 아니라 불투명한 구멍이라는 걸 알려주는 신호 하나를 짚어보고. 이 상황을 잘못 판단하는 게 왜 '믿을 만한 도구' 랑 '고장 난 도구' 를 가르는지도.
Hint
'아무것도 안 긁혔다', '사용자가 뭘 잘못했다', 'AX 가 죽었다' 로 뛰어들면 안 돼. 갈라주는 신호는 대상 앱 자체야. 분명히 긁혀 있는데 빈손으로 오는 게 Chromium/Electron 계열이면 그게 구멍이지. 잘못 판단하면 사람들이 제일 많이 쓰는 앱에서 도구가 조용히 아무것도 안 하게 되고, 그게 '어디서나 고장' 으로 읽혀. 제대로 알아채면 찔러보기로 넘어가서 계속 굴러가고.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.