Resilient a11y-tree navigation for Firefox address field and Thunderbird recipients#21
Open
akj wants to merge 2 commits into
Open
Resilient a11y-tree navigation for Firefox address field and Thunderbird recipients#21akj wants to merge 2 commits into
akj wants to merge 2 commits into
Conversation
Mozilla restructures Firefox's a11y tree between releases; FF151 inserted a .urlbar-input-container wrapper that broke the rigid direct-child path behind NVDA+A (Address not found), and also silently broke the notification domain-tagging path in event_alert. - B: add findInSubtree(anchor, predicate) + byIA2Attribute() to shared -- an anchored descendant search that tolerates wrapper elements between a stable anchor and the target, where searchObject's direct-child path could not. - A: extract addressField()/getURL() in firefox.py as the single home for locating the URL-bearing element. FF>=133 unified to anchor #urlbar then findInSubtree(#urlbar-input), subsuming both .urlbar-input-box (133-150) and .urlbar-input-container (151+). Rewire script_url AND event_alert to it. - C: searchObject now logs which milestone missed and the ids/classes present at that level, so the next restructure self-reports from a user's NVDA log. Establish a tests/ harness (plain pytest, NVDA runtime faked via sys.modules) with FF151/pre-151/pre-133 fixtures, anchored by a regression test for the exact FF151 break. Add CONTEXT.md with 'address field' as the first term.
The Thunderbird message-header address fields had the same brittleness as the Firefox urlbar: a rigid sender path up to five levels deep (headerSenderToolbarContainer -> expandedfromRow -> multi-recipient-row -> recipients-list -> fromRecipient0) plus 3-level To/CC paths, all matched direct-child-only -- one inserted wrapper away from breaking exactly like FF151. Extract messageHeaderRecipients() in thunderbird.py: anchor on the message header landmark, find #fromRecipient0 at any depth, and each recipient row by id then its .recipients-list within -- the second adapter for findInSubtree, which is what proves the shared seam. Add byIA2Class() (CSS class-token membership) to shared, since recipient lists carry multiple class tokens that a whole-string match would miss. Tests cover sender/To/CC location, survival of an inserted wrapper (the Thunderbird analogue of the FF151 break), class-token matching, and the no-CC / sender-only headers. Document 'message header recipients' in CONTEXT.md as the second adapter.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Mozilla restructures Firefox's and Thunderbird's IAccessible2 trees between releases, and the add-on navigated them with
searchObject(path)— a rigid sequence of(attr, value)milestones matched against direct children only, returning on the first miss. The moment Mozilla inserts a wrapper element, the path silently breaks and the feature dies with a generic "not found".FF151 did exactly this: it wrapped
#urlbar-inputin a new.urlbar-input-container, so NVDA+A ("read address bar") started saying "Address not found". The same change also silently broke the notification domain-tagging path inevent_alert.What
A resilient subtree-search primitive (
shared):findInSubtree(anchor, predicate)— an anchored descendant search that, from a stable anchor, finds the first matching descendant at any depth, tolerating wrapper elements that the rigid path could not. Predicate helpersbyIA2Attribute(key, value)(exact match) andbyIA2Class(value)(CSS class-token membership).Firefox address field (
firefox.py): extractedaddressField(foreground, ffVersion)+ thingetURL()as the single home for locating the URL-bearing element. FF≥133 is unified to anchor#urlbarthenfindInSubtree(#urlbar-input), which subsumes both.urlbar-input-box(133–150) and.urlbar-input-container(151+). Genuinely-different pre-133 trees keep their explicit version paths. Bothscript_urland the previously-brokenevent_alertnow go through it (the knowledge had drifted into two places; FF151 broke both).Thunderbird recipients (
thunderbird.py):messageHeaderRecipients()replaces rigid paths up to five levels deep (sender) with the same anchored search — the second adapter that proves the shared seam.Observable failures (
shared): whensearchObjectmisses a milestone it now logs which milestone failed and the ids/classes actually present at that level, so the next Mozilla restructure self-reports from a user's NVDA log instead of needing a hand-injected probe.Tests
Establishes a
tests/harness runnable with plainpytest(no NVDA runtime — it's faked viasys.modules, and the search helpers are pure tree walks over fake NVDAObjects). 20 tests, including a regression anchored on the exact FF151 break and the Thunderbird analogue (survival of an inserted wrapper).Adds
CONTEXT.mdwith address field and message header recipients as glossary terms.