-
Notifications
You must be signed in to change notification settings - Fork 6k
Add a11y support for embedded iOS platform view #8156
Conversation
shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
Outdated
Show resolved
Hide resolved
| // We first check if the child is a semantic node for a platform view. | ||
| // If so, we add the platform view as accessibilityElements of the child. | ||
| shell::FlutterPlatformViewsController *flutterPlatformViewsController = _bridge.get()->flutter_platform_views_controller(); | ||
| if (child.node.platformViewId > -1 && flutterPlatformViewsController) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if child at a later point no longer has a platfromViewId? How do you remove the native a11y elements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can clean it up when it happens, but I guess it might require some ugly tracking logic or some ugly type check. Is it even possible that the child object's platformViewId got changed but the child still exists in the tree?
| if (child.node.platformViewId > -1 && flutterPlatformViewsController) { | ||
| NSObject<FlutterPlatformView> *platformViewProtocolObject = flutterPlatformViewsController->GetPlatformViewByID(child.node.platformViewId); | ||
| UIView *platformView = [platformViewProtocolObject view]; | ||
| child.accessibilityElements = @[platformView]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about accessibilityContainer on platfromView? Doesn't that have to link back?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don' think we need to set the accessibilityContainer on the platform view since it is already an UIView.
So our a11y tree finds this node for platform views, and we set its accessibilityElements as the platform view. Now the platform view will handle the a11y on however it wants, but in the end, because the child here is still part of our tree, it will return when the accessibilityElements is done traversing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is - and this may be incorrect - that in the iOS tree on iOS each node needs to know its parent (the accessibilityContainer) and it's children, if it has any. In this case, the platformView doesn't know what its a11y parent is. This may be a problem if the platform view is contained within a Flutter Scroll view: if you put a11y focus on a node within the platform view, iOS cannot walk up the hierarchy to find the scrolling a11y node and dispatch the scroll event there. At least, that how I think that would work....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are correct if traverse the accessibility dynamically. (Using the a11y protocol methods etc.) My understanding is that If we directly set the accessibilityElements property, iOS would handle the traversal and we don't need to worry about the parents at all. I might be wrong, but when I test with google map view inside a list view it works fine (it goes into the google map then goes out). Is there something else you can think of that might fail if parent is not set? I can try them out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I tried to switch the code to make the accessibility dynamically traverse through the platform view. It indeed won't pick up the parent as you mentioned. And we'd have to access the platform view's code to actually do it. Now the platform view is completely controlled by the author of the individual plugin so I think we should not touch it at all since they might want to do something else within their accessibility. I guess the safer way to do it would just be statically assign the accessibilityElements, and let iOS work its magic.
amirh
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
please wait for @goderbauer's approval as well.
| // We enforce in the framework that no other useful semantics are merged with these nodes. | ||
| if ([self node].HasFlag(blink::SemanticsFlags::kScopesRoute)) | ||
| return false; | ||
| // If the node is the placeholder for a platform view. We know it should be a container. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you adding the asserts for this in the framework?
I would probably remove the comment and just add this case to this if above. It's just anther node that doesn't get semantics merged in.
shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
Outdated
Show resolved
Hide resolved
| child.accessibilityElements = @[ platformView ]; | ||
| return child; | ||
| } else if (child.accessibilityElements.count > 0 && | ||
| [child.accessibilityElements.firstObject isKindOfClass:[UIView class]]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to do the clearing in two different places? It looks like if you do the clearing in the other place this will always be cleared here already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, forgot to move this testing code out. going to remove it.
|
cc @cbracken |
|
Thanks -- will take a pass over this tonight! |
cbracken
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very quick first pass. Grabbing dinner then I'll finish up!
shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h
Show resolved
Hide resolved
|
|
||
| fml::WeakPtr<AccessibilityBridge> GetWeakPtr(); | ||
|
|
||
| FlutterPlatformViewsController* flutter_platform_views_controller() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... GetPlatformViewsController() const for consistency with the rest of the engine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| // We first check if the child is a semantic node for a platform view. | ||
| // If so, we add the platform view as accessibilityElements of the child. | ||
| shell::FlutterPlatformViewsController* flutterPlatformViewsController = | ||
| _bridge.get()->flutter_platform_views_controller(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider naming it controller for readability. See: Long names are long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
|
||
| UIView* view_; | ||
| PlatformViewIOS* platform_view_; | ||
| FlutterPlatformViewsController* flutter_platform_views_controller_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
platform_views_controller for consistency with elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
|
||
| // We enforce in the framework that no other useful semantics are merged with these nodes. | ||
| if ([self node].HasFlag(blink::SemanticsFlags::kScopesRoute)) | ||
| if ([self node].HasFlag(blink::SemanticsFlags::kScopesRoute) || [self node].platformViewId > -1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
>= kMinPlatformViewId (is there a better name?) and define as constexpr above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, also added a function IsPlatformViewNode() in SemanticsNode to make it more readable.
| // If so, we add the platform view as accessibilityElements of the child. | ||
| shell::FlutterPlatformViewsController* flutterPlatformViewsController = | ||
| _bridge.get()->flutter_platform_views_controller(); | ||
| if (child.node.platformViewId > -1 && flutterPlatformViewsController) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment here, replace -1 with a named constexpr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
|
||
| // This 'if' block handles adding accessibility support for the embedded platform view. | ||
| // We first check if the child is a semantic node for a platform view. | ||
| // If so, we add the platform view as accessibilityElements of the child. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid documenting the how, and instead focus on the what/why. Maybe something like:
// If the child is a semantic node for a platform view, inject the platform view into the iOS accessibility tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| _bridge.get()->flutter_platform_views_controller(); | ||
| if (child.node.platformViewId > -1 && flutterPlatformViewsController) { | ||
| NSObject<FlutterPlatformView>* platformViewProtocolObject = | ||
| flutterPlatformViewsController->GetPlatformViewByID(child.node.platformViewId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a shorter name?
| if (child.node.platformViewId > -1 && flutterPlatformViewsController) { | ||
| NSObject<FlutterPlatformView>* platformViewProtocolObject = | ||
| flutterPlatformViewsController->GetPlatformViewByID(child.node.platformViewId); | ||
| UIView* platformView = [platformViewProtocolObject view]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it safe to assume platformViewProtocolObject and [platformViewProtocolObject view] will never be nil? If not, put a guard around the next line since nil is disallowed in NSArray.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Done.
| } | ||
|
|
||
| BOOL isPlatformViewNode = node.platformViewId > -1; | ||
| BOOL wasPlatformViewNode = object.node.platformViewId > -1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment -- avoid the -1 here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
goderbauer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for a11y
Please wait for @cbracken's LGTM for ObjC stuff.
lib/ui/semantics.dart
Outdated
| /// specified, `childrenInHitTestOrder` and `childrenInTraversalOrder` must be | ||
| /// empty, and other semantic configurations will be ignored. | ||
| /// empty, and other semantic configurations will be ignored. The ignored configurations | ||
| /// include:[flags], [actions], [textSelectionBase], [textSelectionExtent], [scrollChildren], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: space missing after :
lib/ui/semantics.dart
Outdated
| /// include:[flags], [actions], [textSelectionBase], [textSelectionExtent], [scrollChildren], | ||
| /// [scrollIndex], [scrollPosition], [scrollExtentMax], [scrollExtentMin], [elevation], | ||
| /// [thickness], [label], [hint], [value], [increasedValue], [decreasedValue], [textDirection], | ||
| /// [transform], [childrenInTraversalOrder], [childrenInHitTestOrder], [additionalActions] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised to see that transform is ignored. What if the PlatForm viewed is scaled up or down?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we current support transforming in platform view. @amirh
lib/ui/semantics.dart
Outdated
| /// empty, and other semantic configurations will be ignored. The ignored configurations | ||
| /// include:[flags], [actions], [textSelectionBase], [textSelectionExtent], [scrollChildren], | ||
| /// [scrollIndex], [scrollPosition], [scrollExtentMax], [scrollExtentMin], [elevation], | ||
| /// [thickness], [label], [hint], [value], [increasedValue], [decreasedValue], [textDirection], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This explicit list seems hard to maintain. Nobody will remember to add a property here if we add a new one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point, we also have the same problem in the framework semantics code. I think one problem could be that we have a very long list of properties inside these semantics class, this can lead to maintain hell.
There are ways I can think of.
- We can refactor SemanticNode, SemanticConfiguration(in framework), SemanticData(in framework), SemanticProperty(in framework) classes, group this long list of properties to make them shorter. For example, the semanticNode can have
scrollingConfig,textConfig,actionConfig,transformConfig, etc and each contains reasonable amount of properties. This requires a lot of refactoring work and regression test, and it is also a breaking change. But I feel like it would be a better long term solution since it can be easily scaled up. - A non breaking way: We can have a different semantic node class to represent a node for platform view.
Or maybe we can do both 1 and 2?
Do you have a different way to do it? Also copying @cbracken on this. Maybe we should open a new issue to track this effort?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- would probably be nice, but since it is a breaking change I am not sure if it is currently worth doing. It also doesn't fix the problem. You'd still have to keep a (now shorter) list upto date.
I do not understand what you mean by 2.
Instead of listing all these properties, I'd probably just explain what it means on the platform side to set a paltformViewId.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After offline discussion with @goderbauer, I am going to restructure the platform view subtree.
|
Going to rework the tree structure for platform view node to make it more maintainable. I am going to close this PR for now and reopen when the work is done. |
flutter/engine@0d83a2e...3a3f707 git log 0d83a2e..3a3f707 --no-merges --oneline 3a3f707 Reland "Allow specification of std::functions as native entrypoints from Dart code." (flutter/engine#8329) d4275d9 Roll src/third_party/skia 576eb87a2d2d..99ccc0ca87e6 (5 commits) (flutter/engine#8328) 3a415c4 Map glfw into third_party, and roll buildroot (flutter/engine#8308) ce9ea58 [fuchsia] Disable FML_TRACE_COUNTER events to unblock roll (flutter/engine#8325) 4849658 Roll src/third_party/dart 8a92d2a8d9..991c9da720 (2 commits) 20c241a Roll src/third_party/skia c476e5da4fef..576eb87a2d2d (7 commits) (flutter/engine#8324) cadcf1c Roll src/third_party/dart f3fd1943fc..8a92d2a8d9 (16 commits) 66141eb Roll src/third_party/skia d1c271bd39f0..c476e5da4fef (2 commits) (flutter/engine#8322) e5b31cd Roll src/third_party/skia cec20280b3fb..d1c271bd39f0 (3 commits) (flutter/engine#8321) 1daff5c Roll src/third_party/skia bf341ae88c83..cec20280b3fb (3 commits) (flutter/engine#8320) 87db585 Roll src/third_party/skia 45d5f702133e..bf341ae88c83 (2 commits) (flutter/engine#8316) fd7d7fa Add a11y support for embedded iOS platform view (flutter/engine#8156) f64ee01 Roll src/third_party/skia d2ca31218bc4..45d5f702133e (11 commits) (flutter/engine#8315) f521df3 Fixed service isolate not being initialized correctly due to bad name (flutter/engine#8251) 80b825c Roll src/third_party/dart 093c2909fe..f3fd1943fc (13 commits) (flutter/engine#8310) 7e77d5c Revert "Allow specification of std::functions as native entrypoints from Dart code. (#8309)" (flutter/engine#8312) 400a86a Allow specification of std::functions as native entrypoints from Dart code. (flutter/engine#8309) 51f23fe [vulkan] Add FUCHSIA external sem/mem extensions 78de8dc Enable lambda like native callbacks in tests and add support for custom entrypoints. (flutter/engine#8299) 2812c7d Roll src/third_party/skia 62fd6c411622..d2ca31218bc4 (9 commits) (flutter/engine#8307) 95f9134 Roll src/third_party/skia d90004516a63..62fd6c411622 (4 commits) (flutter/engine#8306) 358273b Roll src/third_party/skia 33211b2c7a02..d90004516a63 (1 commits) (flutter/engine#8305) 2804057 Roll src/third_party/skia 80dd599e91d0..33211b2c7a02 (1 commits) (flutter/engine#8303) a673bbf Roll src/third_party/skia c5ee499f2c59..80dd599e91d0 (5 commits) (flutter/engine#8301) d88037d Roll src/third_party/dart fa74184b7a..093c2909fe (71 commits) The AutoRoll server is located here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+/master/autoroll/README.md If the roll is causing failures, please contact the current sheriff ([email protected]), and stop the roller if necessary.
Follow up the framework change in flutter/flutter#29304. Inject the accessibility element tree in the semantic node if the node is for platform views. flutter/flutter#29302
This reverts commit 47a9c76.
This reverts commit 47a9c76.
Follow up the framework change in flutter/flutter#29304.
Inject the accessibility element tree in the semantic node if the node is for platform views.
flutter/flutter#29302