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

Rewire ios accessibility bridge message channel to receive semantics generating event #56691

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

chunhtai
Copy link
Contributor

@chunhtai chunhtai commented Nov 18, 2024

fixes flutter/flutter#158399

previously the only correct way to enable semantics is that ios embedding receive signal from native OS, it call SetSemanticsEnabled to shell and then to dart to enable semantics tree generation.

If for some reason framework decide to enable semantics first, e.g. through SemanticsBinding.instance.ensureSemantics(typically due to integration test or ci that wants to test semantics), the update will be dropped in shell. Even if it later on receive signal from native OS to turn on semantics, it can't construct the complete accessibility tree in embedding because the updatesemantics sends diff update and previous updates are gone forever. It will end up in a broken state.

This pr changes so that the only source of truth will be in the framework side. When framework starts generating the the semantics tree, it will send a GenertingSemanticsTreeSemanticsEvent to the embedding, and the embedding needs to prepare itself to accept semantics update after receiving the message.

This however require some refactoring on iOS embedding because it will only start listen to a11y message channel after semantics is enabled. I moved a11y channel listener logic to platform_view_ios instead.

framework change flutter/flutter#159163

I also need to do the Android part

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 and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

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

/**
* Gets the accessibility bridge created in this platform view.
*/
AccessibilityBridge* GetAccessibilityBridge() { return accessibility_bridge_.get(); }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are public for testing only, i am not sure how to best test the change without exposing these

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 fine for now; refactoring this class is on my to do list for a rainy day, but please add a note pointing out this is only for testing so we can clean it up in the refactor.

@@ -113,35 +128,18 @@ class PlatformViewIOS final : public PlatformView {
id<NSObject> observer_ = nil;
};

/// Wrapper that guarantees we communicate clearing Accessibility
/// information to Dart.
class AccessibilityBridgeManager {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The sole purpose for this class is to call setSemanticsEnabled when a11y bridge is created. This is not needed even before the change because the a11y bridge will only be created when setSemanticsEnabled is called before the change.

I think this class is obsolete

Copy link
Member

Choose a reason for hiding this comment

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

Agreed, yeah this is ICantBelieveItsNotUniquePtr with one additional feature.

if (generating) {
if (!accessibility_bridge_) {
accessibility_bridge_ = std::make_unique<AccessibilityBridge>(owner_controller_, this,
platform_views_controller_);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can I assume platform_views_controller_ contains the same owner_controller_.platformViewsController. From the code I think yes, but not too sure.

@@ -36,7 +36,7 @@ namespace flutter {
* lifecycle. It's a long lived bridge owned by the `FlutterEngine` and can be attached and
* detached sequentially to multiple `FlutterViewController`s and `FlutterView`s.
*/
class PlatformViewIOS final : public PlatformView {
class PlatformViewIOS : public PlatformView {
Copy link
Member

Choose a reason for hiding this comment

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

Why was it necessary to remove the final?

@@ -171,14 +190,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {}
"PlatformViewIOS has no ViewController.";
return;
}
if (enabled && !accessibility_bridge_) {
Copy link
Member

Choose a reason for hiding this comment

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

GitHub won't let me comment on the line above, but I think you're safe to delete the if (!owner_controller_) { ... } block now, since it's no longer used here.

/**
* Handles accessibility message from accessibility channel.
*/
void handleAccessibilityMessage(__weak id message, FlutterReply reply);
Copy link
Member

@cbracken cbracken Nov 19, 2024

Choose a reason for hiding this comment

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

This is a C++ class, so C++ naming applies:

Suggested change
void handleAccessibilityMessage(__weak id message, FlutterReply reply);
void HandleAccessibilityMessage(__weak id message, FlutterReply reply);

Need to fix up the call sites though.

/**
* Send accessibility message to accessibility channel.
*/
void sendAccessibilityMessage(__weak id message);
Copy link
Member

@cbracken cbracken Nov 19, 2024

Choose a reason for hiding this comment

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

This is a C++ class, so C++ naming applies:

Suggested change
void sendAccessibilityMessage(__weak id message);
void SendAccessibilityMessage(__weak id message);

Need to fix up the call sites.

binaryMessenger:owner_controller.engine.binaryMessenger
codec:[FlutterStandardMessageCodec sharedInstance]];
[accessibility_channel_ setMessageHandler:^(id message, FlutterReply reply) {
handleAccessibilityMessage(message, reply);
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
handleAccessibilityMessage(message, reply);
HandleAccessibilityMessage(message, reply);

}
}

void PlatformViewIOS::sendAccessibilityMessage(__weak id message) {
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
void PlatformViewIOS::sendAccessibilityMessage(__weak id message) {
void PlatformViewIOS::SendAccessibilityMessage(__weak id message) {

@@ -74,7 +65,7 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification,

void AccessibilityBridge::AccessibilityObjectDidBecomeFocused(int32_t id) {
last_focused_semantics_object_id_ = id;
[accessibility_channel_ sendMessage:@{@"type" : @"didGainFocus", @"nodeId" : @(id)}];
platform_view_->sendAccessibilityMessage(@{@"type" : @"didGainFocus", @"nodeId" : @(id)});
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
platform_view_->sendAccessibilityMessage(@{@"type" : @"didGainFocus", @"nodeId" : @(id)});
platform_view_->SendAccessibilityMessage(@{@"type" : @"didGainFocus", @"nodeId" : @(id)});

if ([type isEqualToString:@"announce"]) {
NSString* message = annotatedEvent[@"data"][@"message"];
ios_delegate_->PostAccessibilityNotification(UIAccessibilityAnnouncementNotification, message);
NSString* messageToAnnounce = message[@"data"][@"message"];
Copy link
Member

Choose a reason for hiding this comment

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

This should be message_to_announce since we're in a C++ method. I'm not in love with it either.

https://google.github.io/styleguide/objcguide.html#objective-c


namespace flutter {

namespace testing {
Copy link
Member

Choose a reason for hiding this comment

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

Since everything in here is in flutter::testing, do:

namespace flutter::testing {

void SetNeedsReportTimings(bool value) override {};

std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocale(
const std::vector<std::string>& supported_locale_data) override {
Copy link
Member

Choose a reason for hiding this comment

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

Nothing to do in your patch, but I'm going to take a look at the base class. Really curious why we're handing back a std::unique_ptr<std::vector<std::string>> here rather than just a std::vector<std::string>. I doubt these arrays are ever more than 5-10 entries even in extreme cases.

return {};
}

void SendChannelUpdate(std::string name, bool listening) override {}
Copy link
Member

Choose a reason for hiding this comment

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

Similarly, nothing to do here, but we should almost certainly make this a const std::string& or a std::string_view in the base class.


MockRuntimeDelegate delegate_;
UIDartState::Context& context_;
RuntimeController runtime_controller_;
Copy link
Member

@cbracken cbracken Nov 19, 2024

Choose a reason for hiding this comment

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

Should these members be private:? If so, move CanUpdateSemantics up, since that almost certainly should be public :)

};

TEST_F(RuntimeControllerTest, CanUpdateSemantics) {
auto message_latch = std::make_shared<fml::AutoResetWaitableEvent>();
Copy link
Member

@cbracken cbracken Nov 19, 2024

Choose a reason for hiding this comment

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

This should just be:

fml::AutoResetWaitableEvent message_latch;


Settings settings = CreateSettingsForFixture();
AddNativeCallback("SemanticsUpdate",
CREATE_NATIVE_ENTRY(nativeSemanticsUpdate));
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
CREATE_NATIVE_ENTRY(nativeSemanticsUpdate));
CREATE_NATIVE_ENTRY(native_semantics_update));

UIDartState::Context context(task_runners);
RuntimeControllerTester tester(context);

auto nativeSemanticsUpdate = [&tester,
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
auto nativeSemanticsUpdate = [&tester,
auto native_semantics_update = [&tester,

Copy link
Member

@cbracken cbracken left a comment

Choose a reason for hiding this comment

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

lgtm modulo nits. Looks like a locale test failing though?

@chunhtai
Copy link
Contributor Author

all comment addressed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SemanticsBinding.instance.ensureSemantics causes inconsistent state for mobile
2 participants