Repository navigation
[web] Scope the mobile semantics placeholder to its view - #190856
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors accessibility placeholder management in the Flutter Web engine to support multi-view applications. On desktop, a single page-level placeholder is shared across views and attached to the document body, while on mobile, individual placeholders are attached to each view root. Feedback identifies two issues: first, addPlaceholder() is evaluated in DesktopSemanticsEnabler even if domDocument.body is null, which incorrectly tracks a placeholder; second, semanticsActivationTimer in MobileSemanticsEnabler should be explicitly cancelled during disposal to prevent post-dispose callbacks.
| /// | ||
| /// This is used when semantics is enabled programmatically and therefore the | ||
| /// placehodler is no longer needed. | ||
| /// placeholder is no longer needed. |
There was a problem hiding this comment.
| /// placeholder is no longer needed. | |
| /// placeholders are no longer needed. |
| @@ -131,12 +178,46 @@ abstract class SemanticsEnabler { | |||
| /// and enables semantics. | |||
| @visibleForTesting | |||
| class DesktopSemanticsEnabler extends SemanticsEnabler { | |||
There was a problem hiding this comment.
not related to this pr, but none of the method in this class should call its own dispose(). Only the owner that initalize a instance of this class should call dispose.
A instance shouldn't self destroy as a side effect of calling other method.
There was a problem hiding this comment.
If the intention is to clear out placeholder list, refactor it to a separate clean up method.
| /// | ||
| /// See https://github.com/flutter/flutter/issues/152838 | ||
| @override | ||
| void addPlaceholderForView(DomElement viewRoot) { |
There was a problem hiding this comment.
is there a reason desktop and mobile can't share the same placeholder insert and remove logic?
There was a problem hiding this comment.
@flutter-zl so on desktop, we always have a single placeholder that covers the entire screen? That may not be ideal for multi-view mode where the screen contains other non-Flutter content.
Does it make sense to match the mobile behavior (have a placeholder for each view)?
There was a problem hiding this comment.
Good suggestion and it's shared now.
@mdebbar on coverage: the desktop placeholder doesn't cover anything. The two form factors use opposite CSS.
// mobile: fills its containing block
..left = '0' ..top = '0' ..right = '0' ..bottom = '0';
// desktop: 1x1, offscreen
..left = '-1px' ..top = '-1px' ..width = '1px' ..height = '1px';
| /// For Webkit browser engine the placeholders are removed upon the next | ||
| /// "touchend" event. This is to prevent Safari from swallowing the event | ||
| /// that happens on an element that's being removed. Blink doesn't have | ||
| /// this issue. |
There was a problem hiding this comment.
does it work if we handle it the same way for Blink? that way we don't need to maintain two behavior
There was a problem hiding this comment.
This PR doesn't change that behavior. The only edits in this block are pluralizing "placeholder is removed" to "placeholders are removed".
| void dispose() { | ||
| _semanticsPlaceholder?.remove(); | ||
| _semanticsPlaceholder = null; | ||
| super.dispose(); |
There was a problem hiding this comment.
dispose should be the last,
dispose should be the reverse order of init
| /// Only touches [placeholders]. The caller attaches the element and keeps | ||
| /// its own bookkeeping in sync, so this is for [addPlaceholderForView] | ||
| /// implementations rather than outside callers. | ||
| DomElement addPlaceholder() { |
There was a problem hiding this comment.
If this is only meant for internal use (as the comment says), let's make it private. If it's needed only by tests, then let's add @visibleForTesting.
There was a problem hiding this comment.
addPlaceholder() is gone.
| /// | ||
| /// See https://github.com/flutter/flutter/issues/152838 | ||
| @override | ||
| void addPlaceholderForView(DomElement viewRoot) { |
There was a problem hiding this comment.
@flutter-zl so on desktop, we always have a single placeholder that covers the entire screen? That may not be ideal for multi-view mode where the screen contains other non-Flutter content.
Does it make sense to match the mobile behavior (have a placeholder for each view)?
|
autosubmit label was removed for flutter/flutter/190856, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR. |

Fixes #152838
Problem
Flutter Web puts an invisible "Enable accessibility" button on the page for screen reader users. On mobile it covered the whole page instead of just the Flutter app, swallowing taps meant for the surrounding HTML until about 20 taps made the engine remove it.
Desktop was never affected: its button is one pixel and sits offscreen.
Fix
The button now lives inside
<flutter-view>rather than<body>, one per view, so it can only cover the Flutter app. The CSS is unchanged, only the parent element, and it goes in as the first child so platform views keep their own events.Demo
Before: https://flutter-demo-59-before.web.app
After: https://flutter-demo-59-after.web.app
Open either on a phone and tap the HTML links before touching the Flutter app.
Desktop browsers cannot show the difference, since they use the 1x1 placeholder on both builds.