Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes IgnorePointer and AbsorbPointer to only block user interactions… #120619

Merged
merged 3 commits into from Apr 7, 2023

Conversation

chunhtai
Copy link
Contributor

@chunhtai chunhtai commented Feb 13, 2023

… in a11y

Fixes #59402
Fixes #37162

migration guide: flutter/website#8312

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) a: text input Entering text in a text field or keyboard related problems f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels. labels Feb 13, 2023
/// Setting this true prevents user from activating pointer related
/// [SemanticsAction]s, such as [SemanticsAction.tap] or
/// [SemanticsAction.longPress].
bool get isUserInteractionsBlocked => _isUserInteractionsBlocked;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

was curious if there is a better name

Copy link
Member

Choose a reason for hiding this comment

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

Maybe: blocksUserActions or blocksSemanticsActions (in general, maybe we should stick with the "actions" terminology throughout this PR to make it clear that this relates to SemanticsActions)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree to change this to "Actions". but I may keep it past tense since it is already a done deal in SemanticsNode level. Present tense kinda implies it will do something that it hasn't done.

const IgnorePointer({
super.key,
this.ignoring = true,
this.ignoringSemantics,
this.ignoringSemantics = false,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the ignoringSemantics was a workaround to provide people a way to get the semanitcs back if ignoring = true, but I am not sure.

If so, should we deprecate this and just ask developer to use ExcludeSemantics?

@goderbauer
Copy link
Member

Looks like this is failing a lot of checks.

@chunhtai
Copy link
Contributor Author

Sorry for the test failure. I fixed the test, this is ready for review

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

Thanks for looking into this long-standing issue!

packages/flutter/lib/src/semantics/semantics.dart Outdated Show resolved Hide resolved
packages/flutter/test/widgets/basic_test.dart Outdated Show resolved Hide resolved
testWidgets('ignores user interactions', (WidgetTester tester) async {
final UniqueKey key = UniqueKey();
await tester.pumpWidget(
MaterialApp(
Copy link
Member

Choose a reason for hiding this comment

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

same

packages/flutter/test/widgets/absorb_pointer_test.dart Outdated Show resolved Hide resolved
Comment on lines 4671 to 4690
if (child.isBlockingUserInteractions) {
child._actions.forEach((SemanticsAction key, SemanticsActionHandler value) {
if (_kSystemInteractions & key.index > 0) {
_actions[key] = value;
}
});
} else {
_actions.addAll(child._actions);
}
Copy link
Member

Choose a reason for hiding this comment

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

for my own understanding: could we skip this filtering? Just adjusting the _actionsAsBits should be enough to determine whether an action is supported or not, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added this filtering because I was trying to support use case where merging child that has same action but blocked. I guess that is no longer the case.

Even without that requirement, why do we want to add a action to the map that will be blocked anyway?

Copy link
Member

Choose a reason for hiding this comment

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

It's fine to do that. I was just thinking if we don't have to do it what's the point of implementing all this extra logic?

packages/flutter/lib/src/rendering/object.dart Outdated Show resolved Hide resolved
/// Setting this true prevents user from activating pointer related
/// [SemanticsAction]s, such as [SemanticsAction.tap] or
/// [SemanticsAction.longPress].
bool get isUserInteractionsBlocked => _isUserInteractionsBlocked;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe: blocksUserActions or blocksSemanticsActions (in general, maybe we should stick with the "actions" terminology throughout this PR to make it clear that this relates to SemanticsActions)?

/// Setting this true prevents user from activating pointer related
/// [SemanticsAction]s, such as [SemanticsAction.tap] or
/// [SemanticsAction.longPress].
bool isBlockingUserInteractions = false;
Copy link
Member

Choose a reason for hiding this comment

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

Random thought: Is there ever a use case for only blocking certain actions? I wonder if this should just take a list of blocked actions (that we internally translate to a bitMask right away) and provide a const value containing all block-able actions that the use cases in this PR would use. Would be a little more flexible, but not sure how useful that is...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I haven't found use cases other than blocking pointer related action. We can add a new api to block a list instead of just hardcoded actions in the future if we have more use cases, and then we can reimplment this boolean to use that API under the hood.

I was suspecting this may be an one-off case, that is why I go with hardcoded actions.

@@ -7030,12 +7090,29 @@ class Semantics extends SingleChildRenderObjectWidget {
/// an [ExcludeSemantics] widget and then another [Semantics] widget.
final bool excludeSemantics;

/// Whether to block user interactions for the rendering subtree.
///
/// Setting this to true will prevent user from interacting with this
Copy link
Member

Choose a reason for hiding this comment

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

user -> users

/// rendering object and its subtree through pointer-related
/// [SemanticsAction]s in assistive technologies.
///
/// This [SemanticsNode] created from this rendering object is still focusable
Copy link
Member

Choose a reason for hiding this comment

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

This [SemanticsNode] -> The [SemanticsNode]

/// rendering object and its subtree through pointer-related
/// [SemanticsAction]s in assistive technologies.
///
/// This [SemanticsNode] created from this rendering object is still focusable
Copy link
Member

Choose a reason for hiding this comment

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

Also, this doc is on a widget, not rendering object.

Copy link
Member

Choose a reason for hiding this comment

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

Also below, where rendering object is mentioned again.

Comment on lines 7105 to 7106
/// are blocked. [SemanticsAction]s in the parent semantics node originated
/// from other sources are still allowed.
Copy link
Member

Choose a reason for hiding this comment

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

what does "originated from other sources" mean?

/// by assistive technologies. Only pointer-related [SemanticsAction]s, such as
/// [SemanticsAction.tap] or its friends, are blocked.
///
/// If this rendering object is merged into a parent semantics node, only the
Copy link
Member

Choose a reason for hiding this comment

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

I am actually not 100% clear on how merging works here. If I have a parent SemanticsConfig with a tap handler and there are two child semantics configs: One of them has a longpress handler and the other one sets blockUserInteractions. These will all get implicitly merged into a single node, right? What actions are now allowed on that node?

Also, if I force merge (using MergeSemantics) a SemanticsNode with blockUserInteractions set into another node with an action handler, will the resulting node have its action blocked or not?

Copy link
Contributor Author

@chunhtai chunhtai Feb 27, 2023

Choose a reason for hiding this comment

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

If I have a parent SemanticsConfig with a tap handler and there are two child semantics configs: One of them has a longpress handler and the other one sets blockUserInteractions. These will all get implicitly merged into a single node, right? What actions are now allowed on that node?

Only the action in blockUserInteractions child will be block and drop from the merging. So the onTap on parent and the onLongPress on the child will still be merged into the resulting node.

if I force merge (using MergeSemantics) a SemanticsNode with blockUserInteractions set into another node with an action handler, will the resulting node have its action blocked or not?

The resulting node will not have its actions blocked, the actions in the node with blockUserInteractions will be dropped.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, both behave the same here, that's good. Thanks for the clarification!

Copy link
Member

Choose a reason for hiding this comment

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

(I can't remember, do you have tests in place for both of these scenarios to make sure that this doesn't break?)

Copy link
Contributor Author

@chunhtai chunhtai Feb 27, 2023

Choose a reason for hiding this comment

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

yes, the tests in flutter/packages/flutter/test/widgets/semantics_test.dart

@goderbauer
Copy link
Member

Looks like some tests are still failing?

@chunhtai
Copy link
Contributor Author

chunhtai commented Mar 2, 2023

ah that is the semantics handle dispose issue, will fix

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

I believe the only thing left to figure out on this PR are the custom actions.

Comment on lines 649 to 653
' STALE\n'
' owner: null\n'
' Rect.fromLTRB(60.0, 20.0, 80.0, 50.0)\n'
' actions: didGainAccessibilityFocus, longPress🚫️, scrollUp🚫️,\n'
' showOnScreen🚫️\n'
Copy link
Member

Choose a reason for hiding this comment

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

unindent these lines to match line 648

@@ -6706,6 +6735,22 @@ class IgnorePointer extends SingleChildRenderObjectWidget {
/// ** See code in examples/api/lib/widgets/basic/absorb_pointer.0.dart **
/// {@end-tool}
///
/// ## Semantics
/// Using this widget may also affect how semantics subtree underneath this
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Using this widget may also affect how semantics subtree underneath this
/// Using this widget may also affect how the semantics subtree underneath this

@@ -6706,6 +6735,22 @@ class IgnorePointer extends SingleChildRenderObjectWidget {
/// ** See code in examples/api/lib/widgets/basic/absorb_pointer.0.dart **
/// {@end-tool}
///
/// ## Semantics
Copy link
Member

Choose a reason for hiding this comment

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

nit: blank line after this heading.

/// rendering object and its subtree through pointer-related
/// [SemanticsAction]s in assistive technologies.
///
/// This [SemanticsNode] created from this widget is still focusable
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// This [SemanticsNode] created from this widget is still focusable
/// The [SemanticsNode] created from this widget is still focusable

/// ```
///
/// The result semantics node will still have `_myTap`, but the `_myLongPress`
/// will be blocked.
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for adding the example!

/// [SemanticsAction.tap] or its friends, are blocked.
///
/// If this rendering object is merged into a parent semantics node, only the
/// [SemanticsAction]s from this rendering object and the rendering objects in
Copy link
Member

Choose a reason for hiding this comment

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

same

@@ -1800,6 +1804,22 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_markDirty();
}

/// Whether this user can interact with this node in assistive technologies.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Whether this user can interact with this node in assistive technologies.
/// Whether the user can interact with this node via assistive technologies.

@@ -1800,6 +1804,22 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_markDirty();
}

/// Whether this user can interact with this node in assistive technologies.
///
/// This node can still be receive accessibility focus even if this is true.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// This node can still be receive accessibility focus even if this is true.
/// This node can still receive accessibility focus even if this is true.

/// Whether this user can interact with this node in assistive technologies.
///
/// This node can still be receive accessibility focus even if this is true.
/// Setting this true prevents user from activating pointer related
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Setting this true prevents user from activating pointer related
/// Setting this true prevents the user from activating pointer related

@@ -68,6 +68,11 @@ typedef SemanticsUpdateCallback = void Function(ui.SemanticsUpdate update);
/// value.
typedef ChildSemanticsConfigurationsDelegate = ChildSemanticsConfigurationsResult Function(List<SemanticsConfiguration>);

final int _kSystemInteractions = SemanticsAction.didGainAccessibilityFocus.index
| SemanticsAction.didLoseAccessibilityFocus.index
| SemanticsAction.customAction.index;
Copy link
Member

Choose a reason for hiding this comment

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

This is the only remaining open question on the PR. For me, if I have an interface where I can delete a ListItem by swiping right/left on it and I expose this action as a custom action for a11y users, I would expect it gets blocked when I use an IgnorePointer. Everything else would be surprising. I can't think of a use case where I would still want custom actions to be allowed. Do you have one? If so, we may need to make this configurable if it can go both ways.

@chunhtai
Copy link
Contributor Author

some more updates, still working on resolving internal test failures

@chunhtai
Copy link
Contributor Author

some of the test failures are due to flutter/engine#40146, waiting for it to merge

@chunhtai chunhtai added the autosubmit Merge PR when tree becomes green via auto submit App label Apr 6, 2023
@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Apr 6, 2023
@auto-submit
Copy link
Contributor

auto-submit bot commented Apr 6, 2023

auto label is removed for flutter/flutter, pr: 120619, due to - The status or check suite Mac framework_tests_libraries has failed. Please fix the issues identified (or deflake) before re-applying this label.

@chunhtai chunhtai added the autosubmit Merge PR when tree becomes green via auto submit App label Apr 7, 2023
@auto-submit auto-submit bot merged commit 9884b5f into flutter:master Apr 7, 2023
72 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Apr 8, 2023
exaby73 pushed a commit to NevercodeHQ/flutter that referenced this pull request Apr 17, 2023
flutter#120619)

Fixes IgnorePointer and AbsorbPointer to only block user interactions�
@Hixie
Copy link
Contributor

Hixie commented Jul 25, 2023

I just ran into this deprecation in the documentation for SliverIgnorePointer, and it was quite confusing, because there's no context as to why I would have wanted to use this feature, why it's deprecated, or what it means to "Create a custom sliver ignore pointer widget instead" or why that would be better than having this feature. Since three quarters of the widget's documentation appears to be about this feature, it feels like the feature was quite important, and so its removal is surprising to the reader.

The migration guide doesn't really illuminate the reader, either. It doesn't provide any background regarding what the feature was, why it was normally used, or why you might no longer need it, other than "this workaround is no longer needed". However, if the workaround is no longer needed, one would assume the migration is just to remove the use of the property, whereas the migration guide says "consider using ExcludeSemantics instead" and "Consider creating your own custom widget", but there's no explanation as to why one would do either of these things.

It would be great if we could provide significantly more context in the dartdocs and the migration guide for this removal.

cc @chunhtai @goderbauer @sfshaza2

@chunhtai
Copy link
Contributor Author

Hi @Hixie

there's no context as to why I would have wanted to use this feature

I can't really think of a use case for ignoringSemantics, either for it to be true or false, if the ignorePointer/AbsorbPointer and its sliver counter parts handle of the semantics correctly.

To me, the ignoringSemantics was here to work around that the ignorepointer couldn't handle the semantics before.

Therefore, I didn't add too much context on why you would want to use this property but merely provide migration guide if developers did use it.

This migration guide https://docs.flutter.dev/release/breaking-changes/ignoringsemantics-migration provides only the code samples for IgnorePointer. I can add more samples for AbsorbPointer, SliverIgnorePointer. Will they be enough to address your concerns?

@Hixie
Copy link
Contributor

Hixie commented Jul 25, 2023

Well presumably we added this feature for a reason. :-)
That's what I would try to include in the explanations.

@goderbauer
Copy link
Member

I think we had that "feature" because we didn't know how to do the right thing, which is only selectively blocking semantics interactions and not all semantics. There was no real use case for it, it was just a stop-gap solution.

@Hixie
Copy link
Contributor

Hixie commented Jul 25, 2023

Stop-gap solution for what problem? That's what we should describe. That problem. And the better solution. :-)

@chunhtai
Copy link
Contributor Author

yes I can definitely explain this in the doc. I will create a pr.

auto-submit bot pushed a commit that referenced this pull request Aug 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) a: text input Entering text in a text field or keyboard related problems autosubmit Merge PR when tree becomes green via auto submit App f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

TextField semantics are gone when being disabled a11y: disabled textfield not announced as textfield
3 participants