Skip to content

Revamp A11y for Web platform#3016

Draft
ApoloApps wants to merge 13 commits into
JetBrains:jb-mainfrom
ApoloApps:moreWebSemantics
Draft

Revamp A11y for Web platform#3016
ApoloApps wants to merge 13 commits into
JetBrains:jb-mainfrom
ApoloApps:moreWebSemantics

Conversation

@ApoloApps

@ApoloApps ApoloApps commented Apr 24, 2026

Copy link
Copy Markdown

Redone the whole A11y for Web platform. Separated SemanticsOwnerListener from WebSemanticOwner and WebSemanticNode.
Compose can have several SemanticOwners (the main one + all the popups and dialogs) so thats why now, SemanticsOwnerListener holds a list of the owners. The callbacks signaling semantic changes filter them by the SemanticNode they are referring to and then invoke the WebSemanticOwner specific functions. Each WebSemanticOwner has its own Channels for tracking invalidation (this way it is much easier to have a more fine grained control over what we dispose, create, update, etc...).
Then there is WebSemanticNode (which is a wrapper around SemanticNode) that contains the backing html element it is referring to and more info related to its handling.
When WebSemanticOwner receives an invalidation there is some debounce (same logic as before) and if it syncs, instead of recreating the whole accessibility dom tree, I update positions (if needed) in the html elements, its data and its layout info (like Bounds, position, etc... only if changed!!). This makes the whole synchronization way more performant as it also prevents html layout reflows (which causes major slowdowns).
Attributes are moved to js interop blocks to avoid the constant wasm2js boundary crossing for objects that don't need it (eg: attribute names, attributes setter groups, especially string related crossings). Now, when syncing, values are compared to previous ones to check whether we need an update to the node or not.

I have also fixed several issues of missing features: a11y nodes that were not working as expected, added more information to html elements, added a couple of roles in web (+fixed some issue due to the timing on when the role was being specified, which caused certain roles to change to Button role even though they had a more specific role); the tree is now correctly ordered by indexTraversal and by its geometric position; scrolling with VoiceOver works as expected

I have tried to mimic what other Compose platforms do, especially IOS. There might still be some bugs regarding missing data or the way nodes are merged but overall, I have added most of the a11y features

Fixes CMP-8612, CMP-8614, CMP-9368, CMP-8628, CMP-8624, CMP-8619, CMP-8621, CMP-8627
Before
Screenshot 2026-04-23 232609
Screenshot

After
Screenshot 2026-04-23 233615
Screenshot 2026-04-24 133913
Screenshot 2026-04-24 134637

ScreenRecording_04-24-2026.14-44-50_1.MP4
trim.B9DD9CAE-6312-4FDB-BAE8-94F10EDEDB49.MOV

Testing

Manually, existing tests pass

This should be tested by QA

Release Notes

Fixes - Web

  • Fixes several A11y problems and enhances its capabilities and performance

ApoloApps and others added 9 commits April 24, 2026 18:50
…eral problems with popups and allows for better separation of concerns
…. The tree is not redo on each invalidation, instead we track changes and sync periodically. This allows for better performance and more accurate a11y since browser can diff the tree better. Fixed issues with how certain roles were not being applied.
…nd bounds items so that ScreenReader can traverse elements by focusing them. Fixes https://youtrack.jetbrains.com/issue/CMP-8627/Web-A11y.-Voice-over.-Scroll-doesnt-work (succesfully tested on my iphone 15)
val webSemanticsRoot: HTMLElement,
) : PlatformContext.SemanticsOwnerListener {

private val invalidationChannel =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a good part of this file moved to the new ComposeWebSemanticsOwner.

Do you think it's possible to convert this ComposeWebSemanticsListener into ComposeWebSemanticsOwner? To rename the file and amend it, so the git diff would become more granular to preserve the history of this file and so it's easier to see what changed and what not?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to: with ComposeWebSemanticsListener old file (before PR) to be renamed to ComposeWebSemanticsOwner and add all of the PR's ComposeWebSemanticsOwner new content into it. Then create a new ComposeWebSemanticsListener that implements the listener??? This way all most code change can be seen in the renamed file and what has changed so that it is easier to track vs ComposeWebSemanticsListener having so low code that it is better create a new file for it because it is easier to track changes???

@eymar

Copy link
Copy Markdown
Member

Hi ApoloApps ! Thanks for keeping your branch up-to-date with jb-main branch.
Could you please merge it once again? I just fixed the flakiness in CfWA11yTests (#3058) and we'll see if tests pass here

@ApoloApps

Copy link
Copy Markdown
Author

Hi ApoloApps ! Thanks for keeping your branch up-to-date with jb-main branch. Could you please merge it once again? I just fixed the flakiness in CfWA11yTests (#3058) and we'll see if tests pass here

I'm still doing changes to fix to rough edges with the PR and I might open a new one replacing this. making it draft

@ApoloApps ApoloApps marked this pull request as draft May 27, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants