From 4519ab4716a72acb59581cab9348f381d5b528a3 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 25 Nov 2024 14:37:17 -0800 Subject: [PATCH 01/16] Rewire ios accessibility bridge message channel to receive semantics generating event --- ci/licenses_golden/excluded_files | 1 + ci/licenses_golden/licenses_flutter | 2 + runtime/BUILD.gn | 2 + runtime/fixtures/runtime_test.dart | 99 ++++++++++++ runtime/runtime_controller.cc | 4 +- runtime/runtime_controller.h | 5 + runtime/runtime_controller_unittests.cc | 143 ++++++++++++++++++ shell/platform/darwin/ios/BUILD.gn | 1 + .../framework/Source/accessibility_bridge.h | 3 +- .../framework/Source/accessibility_bridge.mm | 27 ++-- .../Source/accessibility_bridge_test.mm | 51 +------ shell/platform/darwin/ios/platform_view_ios.h | 36 +++-- .../platform/darwin/ios/platform_view_ios.mm | 90 ++++++----- .../darwin/ios/platform_view_ios_test.mm | 106 +++++++++++++ 14 files changed, 440 insertions(+), 130 deletions(-) create mode 100644 runtime/runtime_controller_unittests.cc create mode 100644 shell/platform/darwin/ios/platform_view_ios_test.mm diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index fc87ccf46c344..4e544308b8d4d 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -274,6 +274,7 @@ ../../../flutter/runtime/fixtures ../../../flutter/runtime/no_dart_plugin_registrant_unittests.cc ../../../flutter/runtime/platform_isolate_manager_unittests.cc +../../../flutter/runtime/runtime_controller_unittests.cc ../../../flutter/runtime/type_conversions_unittests.cc ../../../flutter/shell/common/animator_unittests.cc ../../../flutter/shell/common/base64_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 6bffc72ff49e3..43a3f4ddad450 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -44693,6 +44693,7 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/platform_message_handler_ios. ORIGIN: ../../../flutter/shell/platform/darwin/ios/platform_message_handler_ios_test.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/platform_view_ios_test.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h + ../../../flutter/LICENSE @@ -47646,6 +47647,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/platform_message_handler_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/platform_message_handler_ios_test.mm FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm +FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios_test.mm FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index 5c75d2f4cb2bc..701f7f802dca9 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -140,6 +140,7 @@ if (enable_unittests) { "dart_service_isolate_unittests.cc", "dart_vm_unittests.cc", "platform_isolate_manager_unittests.cc", + "runtime_controller_unittests.cc", "type_conversions_unittests.cc", ] @@ -153,6 +154,7 @@ if (enable_unittests) { "//flutter/common", "//flutter/fml", "//flutter/lib/snapshot", + "//flutter/shell/common:shell_test_fixture_sources", "//flutter/skia", "//flutter/testing", "//flutter/testing:dart", diff --git a/runtime/fixtures/runtime_test.dart b/runtime/fixtures/runtime_test.dart index 435c00430a1af..0c62b040c03cf 100644 --- a/runtime/fixtures/runtime_test.dart +++ b/runtime/fixtures/runtime_test.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'dart:isolate'; +import 'dart:typed_data'; +import 'dart:ui'; import 'split_lib_test.dart' deferred as splitlib; @@ -219,3 +221,100 @@ Function createEntryPointForPlatIsoSendAndRecvTest() { void mainForPlatformIsolatesThrowError() { throw AssertionError('Error from platform isolate'); } + +@pragma('vm:entry-point') +void sendSemanticsUpdate() { + final SemanticsUpdateBuilder builder = SemanticsUpdateBuilder(); + const String identifier = 'identifier'; + const String label = 'label'; + final List labelAttributes = [ + SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)), + ]; + + const String value = 'value'; + final List valueAttributes = [ + SpellOutStringAttribute(range: const TextRange(start: 2, end: 3)), + ]; + + const String increasedValue = 'increasedValue'; + final List increasedValueAttributes = [ + SpellOutStringAttribute(range: const TextRange(start: 4, end: 5)), + ]; + + const String decreasedValue = 'decreasedValue'; + final List decreasedValueAttributes = [ + SpellOutStringAttribute(range: const TextRange(start: 5, end: 6)), + ]; + + const String hint = 'hint'; + final List hintAttributes = [ + LocaleStringAttribute( + locale: const Locale('en', 'MX'), range: const TextRange(start: 0, end: 1), + ), + ]; + + const String tooltip = 'tooltip'; + + final Float64List transform = Float64List(16); + final Int32List childrenInTraversalOrder = Int32List(0); + final Int32List childrenInHitTestOrder = Int32List(0); + final Int32List additionalActions = Int32List(0); + transform[0] = 1; + transform[1] = 0; + transform[2] = 0; + transform[3] = 0; + + transform[4] = 0; + transform[5] = 1; + transform[6] = 0; + transform[7] = 0; + + transform[8] = 0; + transform[9] = 0; + transform[10] = 1; + transform[11] = 0; + + transform[12] = 0; + transform[13] = 0; + transform[14] = 0; + transform[15] = 0; + builder.updateNode( + id: 0, + flags: 0, + actions: 0, + maxValueLength: 0, + currentValueLength: 0, + textSelectionBase: -1, + textSelectionExtent: -1, + platformViewId: -1, + scrollChildren: 0, + scrollIndex: 0, + scrollPosition: 0, + scrollExtentMax: 0, + scrollExtentMin: 0, + rect: const Rect.fromLTRB(0, 0, 10, 10), + elevation: 0, + thickness: 0, + identifier: identifier, + label: label, + labelAttributes: labelAttributes, + value: value, + valueAttributes: valueAttributes, + increasedValue: increasedValue, + increasedValueAttributes: increasedValueAttributes, + decreasedValue: decreasedValue, + decreasedValueAttributes: decreasedValueAttributes, + hint: hint, + hintAttributes: hintAttributes, + tooltip: tooltip, + textDirection: TextDirection.ltr, + transform: transform, + childrenInTraversalOrder: childrenInTraversalOrder, + childrenInHitTestOrder: childrenInHitTestOrder, + additionalActions: additionalActions, + ); + _semanticsUpdate(builder.build()); +} + +@pragma('vm:external-name', 'SemanticsUpdate') +external void _semanticsUpdate(SemanticsUpdate update); diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 27193cdf73db3..d1161ffbfe7af 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -440,9 +440,7 @@ void RuntimeController::CheckIfAllViewsRendered() { // |PlatformConfigurationClient| void RuntimeController::UpdateSemantics(SemanticsUpdate* update) { - if (platform_data_.semantics_enabled) { - client_.UpdateSemantics(update->takeNodes(), update->takeActions()); - } + client_.UpdateSemantics(update->takeNodes(), update->takeActions()); } // |PlatformConfigurationClient| diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 3ed05aa945461..e83c38bdabd23 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -31,6 +31,10 @@ class RuntimeDelegate; class View; class Window; +namespace testing { +class RuntimeControllerTester; +} + //------------------------------------------------------------------------------ /// Represents an instance of a running root isolate with window bindings. In /// normal operation, a single instance of this object is owned by the engine @@ -779,6 +783,7 @@ class RuntimeController : public PlatformConfigurationClient, double GetScaledFontSize(double unscaled_font_size, int configuration_id) const override; + friend class testing::RuntimeControllerTester; FML_DISALLOW_COPY_AND_ASSIGN(RuntimeController); }; diff --git a/runtime/runtime_controller_unittests.cc b/runtime/runtime_controller_unittests.cc new file mode 100644 index 0000000000000..d056412e7b867 --- /dev/null +++ b/runtime/runtime_controller_unittests.cc @@ -0,0 +1,143 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/runtime/runtime_controller.h" +#include "flutter/runtime/runtime_delegate.h" + +#include "flutter/lib/ui/semantics/semantics_update.h" +#include "flutter/shell/common/shell_test.h" +#include "flutter/testing/testing.h" + +namespace flutter::testing { + +fml::AutoResetWaitableEvent message_latch; +using RuntimeControllerTest = ShellTest; + +class MockRuntimeDelegate : public RuntimeDelegate { + public: + FontCollection font; + std::vector updates; + std::vector actions; + std::string DefaultRouteName() override { return ""; } + + void ScheduleFrame(bool regenerate_layer_trees = true) override {} + + void OnAllViewsRendered() override {} + + void Render(int64_t view_id, + std::unique_ptr layer_tree, + float device_pixel_ratio) override {} + + void UpdateSemantics(SemanticsNodeUpdates update, + CustomAccessibilityActionUpdates actions) override { + this->updates.push_back(update); + this->actions.push_back(actions); + } + + void HandlePlatformMessage( + std::unique_ptr message) override {} + + FontCollection& GetFontCollection() override { return font; } + + std::shared_ptr GetAssetManager() override { return nullptr; } + + void OnRootIsolateCreated() override {}; + + void UpdateIsolateDescription(const std::string isolate_name, + int64_t isolate_port) override {}; + + void SetNeedsReportTimings(bool value) override {}; + + std::unique_ptr> ComputePlatformResolvedLocale( + const std::vector& supported_locale_data) override { + return nullptr; + } + + void RequestDartDeferredLibrary(intptr_t loading_unit_id) override {} + + std::weak_ptr GetPlatformMessageHandler() + const override { + return {}; + } + + void SendChannelUpdate(std::string name, bool listening) override {} + + double GetScaledFontSize(double unscaled_font_size, + int configuration_id) const override { + return 0.0; + } +}; + +class RuntimeControllerTester { + public: + explicit RuntimeControllerTester(UIDartState::Context& context) + : context_(context), + runtime_controller_(delegate_, + nullptr, + {}, + {}, + {}, + {}, + {}, + nullptr, + context_) {} + + void CanUpdateSemantics(SemanticsUpdate* update) { + ASSERT_TRUE(delegate_.updates.empty()); + ASSERT_TRUE(delegate_.actions.empty()); + runtime_controller_.UpdateSemantics(update); + ASSERT_FALSE(delegate_.updates.empty()); + ASSERT_FALSE(delegate_.actions.empty()); + } + + private: + MockRuntimeDelegate delegate_; + UIDartState::Context& context_; + RuntimeController runtime_controller_; +}; + +TEST_F(RuntimeControllerTest, CanUpdateSemantics) { + // The test is mostly setup code to get a SemanticsUpdate object. + // The real test is in RuntimeControllerTester::CanUpdateSemantics. + TaskRunners task_runners("test", // label + GetCurrentTaskRunner(), // platform + CreateNewThread(), // raster + CreateNewThread(), // ui + CreateNewThread() // io + ); + UIDartState::Context context(task_runners); + std::shared_ptr tester = + std::make_shared(context); + + auto native_semantics_update = [tester](Dart_NativeArguments args) { + auto handle = Dart_GetNativeArgument(args, 0); + intptr_t peer = 0; + Dart_Handle result = Dart_GetNativeInstanceField( + handle, tonic::DartWrappable::kPeerIndex, &peer); + ASSERT_FALSE(Dart_IsError(result)); + SemanticsUpdate* update = reinterpret_cast(peer); + + tester->CanUpdateSemantics(update); + message_latch.Signal(); + }; + + Settings settings = CreateSettingsForFixture(); + AddNativeCallback("SemanticsUpdate", + CREATE_NATIVE_ENTRY(native_semantics_update)); + + std::unique_ptr shell = CreateShell(settings, task_runners); + + ASSERT_TRUE(shell->IsSetup()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("sendSemanticsUpdate"); + + shell->RunEngine(std::move(configuration), [](auto result) { + ASSERT_EQ(result, Engine::RunStatus::Success); + }); + + message_latch.Wait(); + DestroyShell(std::move(shell), task_runners); +} + +} // namespace flutter::testing diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 6020365844afb..baf8ccb48694a 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -250,6 +250,7 @@ shared_library("ios_test_flutter") { "ios_context_noop_unittests.mm", "ios_surface_noop_unittests.mm", "platform_message_handler_ios_test.mm", + "platform_view_ios_test.mm", ] deps = [ ":flutter_framework", diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h index e808b88d20f8a..d1589c7538ca9 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h @@ -56,7 +56,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { void UpdateSemantics(flutter::SemanticsNodeUpdates nodes, const flutter::CustomAccessibilityActionUpdates& actions); - void HandleEvent(NSDictionary* annotatedEvent); + void HandleMessage(NSDictionary* message, FlutterReply reply); void DispatchSemanticsAction(int32_t id, flutter::SemanticsAction action) override; void DispatchSemanticsAction(int32_t id, flutter::SemanticsAction action, @@ -98,7 +98,6 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { int32_t last_focused_semantics_object_id_; NSMutableDictionary* objects_; - FlutterBasicMessageChannel* accessibility_channel_; int32_t previous_route_id_ = 0; std::unordered_map actions_; std::vector previous_routes_; diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm index daf6389834c81..98b3c6ed5cadd 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm @@ -52,18 +52,9 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification, previous_routes_({}), ios_delegate_(ios_delegate ? std::move(ios_delegate) : std::make_unique()), - weak_factory_(this) { - accessibility_channel_ = [[FlutterBasicMessageChannel alloc] - initWithName:@"flutter/accessibility" - binaryMessenger:platform_view->GetOwnerViewController().engine.binaryMessenger - codec:[FlutterStandardMessageCodec sharedInstance]]; - [accessibility_channel_ setMessageHandler:^(id message, FlutterReply reply) { - HandleEvent((NSDictionary*)message); - }]; -} + weak_factory_(this) {} AccessibilityBridge::~AccessibilityBridge() { - [accessibility_channel_ setMessageHandler:nil]; clearState(); view_controller_.viewIfLoaded.accessibilityElements = nil; } @@ -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)}); } void AccessibilityBridge::AccessibilityObjectDidLoseFocus(int32_t id) { @@ -354,16 +345,20 @@ static bool DidFlagChange(const flutter::SemanticsNode& oldNode, return nil; } -void AccessibilityBridge::HandleEvent(NSDictionary* annotatedEvent) { - NSString* type = annotatedEvent[@"type"]; +void AccessibilityBridge::HandleMessage(NSDictionary* message, FlutterReply reply) { + NSString* type = message[@"type"]; if ([type isEqualToString:@"announce"]) { - NSString* message = annotatedEvent[@"data"][@"message"]; - ios_delegate_->PostAccessibilityNotification(UIAccessibilityAnnouncementNotification, message); + NSString* message_to_announce = message[@"data"][@"message"]; + ios_delegate_->PostAccessibilityNotification(UIAccessibilityAnnouncementNotification, + message_to_announce); } if ([type isEqualToString:@"focus"]) { - SemanticsObject* node = objects_[annotatedEvent[@"nodeId"]]; + SemanticsObject* node = objects_[message[@"nodeId"]]; ios_delegate_->PostAccessibilityNotification(UIAccessibilityLayoutChangedNotification, node); } + if (reply) { + reply(nil); + } } fml::WeakPtr AccessibilityBridge::GetWeakPtr() { diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 343ed99eae983..960a5f85ddb98 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -1372,9 +1372,9 @@ - (void)testHandleEvent { /*platform_views_controller=*/nil, /*ios_delegate=*/std::move(ios_delegate)); - NSDictionary* annotatedEvent = @{@"type" : @"focus", @"nodeId" : @123}; + NSDictionary* message = @{@"type" : @"focus", @"nodeId" : @123}; - bridge->HandleEvent(annotatedEvent); + bridge->HandleMessage(message, nil); XCTAssertEqual([accessibility_notifications count], 1ul); XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue], @@ -2068,53 +2068,6 @@ - (void)testAnnouncesIgnoresScrollChangeWhenModal { XCTAssertEqual([accessibility_notifications count], 0ul); } -- (void)testAccessibilityMessageAfterDeletion { - flutter::MockDelegate mock_delegate; - auto thread = std::make_unique("AccessibilityBridgeTest"); - auto thread_task_runner = thread->GetTaskRunner(); - flutter::TaskRunners runners(/*label=*/self.name.UTF8String, - /*platform=*/thread_task_runner, - /*raster=*/thread_task_runner, - /*ui=*/thread_task_runner, - /*io=*/thread_task_runner); - id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); - id engine = OCMClassMock([FlutterEngine class]); - id flutterViewController = OCMClassMock([FlutterViewController class]); - - OCMStub([flutterViewController engine]).andReturn(engine); - OCMStub([engine binaryMessenger]).andReturn(messenger); - FlutterBinaryMessengerConnection connection = 123; - OCMStub([messenger setMessageHandlerOnChannel:@"flutter/accessibility" - binaryMessageHandler:[OCMArg any]]) - .andReturn(connection); - - auto platform_view = std::make_unique( - /*delegate=*/mock_delegate, - /*rendering_api=*/mock_delegate.settings_.enable_impeller - ? flutter::IOSRenderingAPI::kMetal - : flutter::IOSRenderingAPI::kSoftware, - /*platform_views_controller=*/nil, - /*task_runners=*/runners, - /*worker_task_runner=*/nil, - /*is_gpu_disabled_sync_switch=*/std::make_shared()); - fml::AutoResetWaitableEvent latch; - thread_task_runner->PostTask([&] { - platform_view->SetOwnerViewController(flutterViewController); - auto bridge = - std::make_unique(/*view=*/nil, - /*platform_view=*/platform_view.get(), - /*platform_views_controller=*/nil); - XCTAssertTrue(bridge.get()); - OCMVerify([messenger setMessageHandlerOnChannel:@"flutter/accessibility" - binaryMessageHandler:[OCMArg isNotNil]]); - bridge.reset(); - latch.Signal(); - }); - latch.Wait(); - OCMVerify([messenger cleanUpConnection:connection]); - [engine stopMocking]; -} - - (void)testFlutterSemanticsScrollViewManagedObjectLifecycleCorrectly { flutter::MockDelegate mock_delegate; auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest"); diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index 4e1da467d22bd..ae4dffc7ef536 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -66,6 +66,11 @@ class PlatformViewIOS final : public PlatformView { */ void SetOwnerViewController(__weak FlutterViewController* owner_controller); + /** + * Send accessibility message to accessibility channel. + */ + void SendAccessibilityMessage(__weak id message); + /** * Called one time per `FlutterViewController` when the `FlutterViewController`'s * UIView is first loaded. @@ -98,6 +103,16 @@ class PlatformViewIOS final : public PlatformView { return platform_message_handler_; } + /** + * Gets the accessibility bridge created in this platform view. + */ + AccessibilityBridge* GetAccessibilityBridge() { return accessibility_bridge_.get(); } + + /** + * Handles accessibility message from accessibility channel. + */ + void HandleAccessibilityMessage(__weak id message, FlutterReply reply); + private: /// Smart pointer for use with objective-c observers. /// This guarantees we remove the observer. @@ -113,24 +128,6 @@ class PlatformViewIOS final : public PlatformView { id observer_ = nil; }; - /// Wrapper that guarantees we communicate clearing Accessibility - /// information to Dart. - class AccessibilityBridgeManager { - public: - explicit AccessibilityBridgeManager(const std::function& set_semantics_enabled); - AccessibilityBridgeManager(const std::function& set_semantics_enabled, - AccessibilityBridge* bridge); - explicit operator bool() const noexcept { return static_cast(accessibility_bridge_); } - AccessibilityBridge* get() const noexcept { return accessibility_bridge_.get(); } - void Set(std::unique_ptr bridge); - void Clear(); - - private: - FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridgeManager); - std::unique_ptr accessibility_bridge_; - std::function set_semantics_enabled_; - }; - __weak FlutterViewController* owner_controller_; // Since the `ios_surface_` is created on the platform thread but // used on the raster thread we need to protect it with a mutex. @@ -138,10 +135,11 @@ class PlatformViewIOS final : public PlatformView { std::unique_ptr ios_surface_; std::shared_ptr ios_context_; __weak FlutterPlatformViewsController* platform_views_controller_; - AccessibilityBridgeManager accessibility_bridge_; + std::unique_ptr accessibility_bridge_; ScopedObserver dealloc_view_controller_observer_; std::vector platform_resolved_locale_; std::shared_ptr platform_message_handler_; + FlutterBasicMessageChannel* accessibility_channel_; // |PlatformView| void HandlePlatformMessage(std::unique_ptr message) override; diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index b726fd8fc1ee4..b9641c71776f0 100644 --- a/shell/platform/darwin/ios/platform_view_ios.mm +++ b/shell/platform/darwin/ios/platform_view_ios.mm @@ -18,29 +18,6 @@ namespace flutter { -PlatformViewIOS::AccessibilityBridgeManager::AccessibilityBridgeManager( - const std::function& set_semantics_enabled) - : AccessibilityBridgeManager(set_semantics_enabled, nullptr) {} - -PlatformViewIOS::AccessibilityBridgeManager::AccessibilityBridgeManager( - const std::function& set_semantics_enabled, - AccessibilityBridge* bridge) - : accessibility_bridge_(bridge), set_semantics_enabled_(set_semantics_enabled) { - if (bridge) { - set_semantics_enabled_(true); - } -} - -void PlatformViewIOS::AccessibilityBridgeManager::Set(std::unique_ptr bridge) { - accessibility_bridge_ = std::move(bridge); - set_semantics_enabled_(true); -} - -void PlatformViewIOS::AccessibilityBridgeManager::Clear() { - set_semantics_enabled_(false); - accessibility_bridge_.reset(); -} - PlatformViewIOS::PlatformViewIOS(PlatformView::Delegate& delegate, const std::shared_ptr& context, __weak FlutterPlatformViewsController* platform_views_controller, @@ -48,7 +25,6 @@ : PlatformView(delegate, task_runners), ios_context_(context), platform_views_controller_(platform_views_controller), - accessibility_bridge_([this](bool enabled) { PlatformView::SetSemanticsEnabled(enabled); }), platform_message_handler_( new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} @@ -85,7 +61,19 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} if (ios_surface_ || !owner_controller) { NotifyDestroyed(); ios_surface_.reset(); - accessibility_bridge_.Clear(); + accessibility_bridge_.reset(); + } + if (owner_controller) { + accessibility_channel_ = [[FlutterBasicMessageChannel alloc] + initWithName:@"flutter/accessibility" + binaryMessenger:owner_controller.engine.binaryMessenger + codec:[FlutterStandardMessageCodec sharedInstance]]; + [accessibility_channel_ setMessageHandler:^(id message, FlutterReply reply) { + HandleAccessibilityMessage(message, reply); + }]; + } else { + [accessibility_channel_ setMessageHandler:nil]; + accessibility_channel_ = nil; } owner_controller_ = owner_controller; @@ -97,7 +85,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* note) { // Implicit copy of 'this' is fine. - accessibility_bridge_.Clear(); + accessibility_bridge_.reset(); owner_controller_ = nil; }]); @@ -120,8 +108,8 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} FML_DCHECK(ios_surface_ != nullptr); if (accessibility_bridge_) { - accessibility_bridge_.Set(std::make_unique( - owner_controller_, this, owner_controller_.platformViewsController)); + accessibility_bridge_ = std::make_unique( + owner_controller_, this, owner_controller_.platformViewsController); } } @@ -131,6 +119,37 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} }; } +void PlatformViewIOS::HandleAccessibilityMessage(__weak id message, FlutterReply reply) { + if (!owner_controller_) { + FML_LOG(WARNING) << "Could not accept accessibility message, this " + "PlatformViewIOS has no ViewController."; + } + NSString* type = message[@"type"]; + if ([type isEqualToString:@"generatingSemanticsTree"]) { + BOOL generating = [message[@"data"][@"generating"] boolValue]; + if (generating) { + if (!accessibility_bridge_) { + accessibility_bridge_ = std::make_unique(owner_controller_, this, + platform_views_controller_); + } + } else { + accessibility_bridge_.reset(); + } + if (reply) { + reply(nil); + } + return; + } + + if (accessibility_bridge_) { + accessibility_bridge_->HandleMessage(message, reply); + } +} + +void PlatformViewIOS::SendAccessibilityMessage(__weak id message) { + [accessibility_channel_ sendMessage:message]; +} + void PlatformViewIOS::RegisterExternalTexture(int64_t texture_id, NSObject* texture) { RegisterTexture(ios_context_->CreateExternalTexture(texture_id, texture)); @@ -165,19 +184,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} // |PlatformView| void PlatformViewIOS::SetSemanticsEnabled(bool enabled) { - if (!owner_controller_) { - FML_LOG(WARNING) << "Could not set semantics to enabled, this " - "PlatformViewIOS has no ViewController."; - return; - } - if (enabled && !accessibility_bridge_) { - accessibility_bridge_.Set(std::make_unique( - owner_controller_, this, owner_controller_.platformViewsController)); - } else if (!enabled && accessibility_bridge_) { - accessibility_bridge_.Clear(); - } else { - PlatformView::SetSemanticsEnabled(enabled); - } + PlatformView::SetSemanticsEnabled(enabled); } // |shell:PlatformView| @@ -189,6 +196,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} void PlatformViewIOS::UpdateSemantics(flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { FML_DCHECK(owner_controller_); + FML_DCHECK(accessibility_bridge_); if (accessibility_bridge_) { accessibility_bridge_.get()->UpdateSemantics(std::move(update), actions); [[NSNotificationCenter defaultCenter] postNotificationName:FlutterSemanticsUpdateNotification diff --git a/shell/platform/darwin/ios/platform_view_ios_test.mm b/shell/platform/darwin/ios/platform_view_ios_test.mm new file mode 100644 index 0000000000000..97ab2a987ff1a --- /dev/null +++ b/shell/platform/darwin/ios/platform_view_ios_test.mm @@ -0,0 +1,106 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +#import "flutter/fml/thread.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" +#import "flutter/shell/platform/darwin/ios/platform_view_ios.h" + +FLUTTER_ASSERT_ARC + +namespace flutter { + +namespace { + +class MockDelegate : public PlatformView::Delegate { + public: + void OnPlatformViewCreated(std::unique_ptr surface) override {} + void OnPlatformViewDestroyed() override {} + void OnPlatformViewScheduleFrame() override {} + void OnPlatformViewAddView(int64_t view_id, + const ViewportMetrics& viewport_metrics, + AddViewCallback callback) override {} + void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {} + void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {} + void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {} + const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; } + void OnPlatformViewDispatchPlatformMessage(std::unique_ptr message) override {} + void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr packet) override { + } + void OnPlatformViewDispatchSemanticsAction(int32_t id, + SemanticsAction action, + fml::MallocMapping args) override {} + void OnPlatformViewSetSemanticsEnabled(bool enabled) override {} + void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {} + void OnPlatformViewRegisterTexture(std::shared_ptr texture) override {} + void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} + void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} + + void LoadDartDeferredLibrary(intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) override { + } + void LoadDartDeferredLibraryError(intptr_t loading_unit_id, + const std::string error_message, + bool transient) override {} + void UpdateAssetResolverByType(std::unique_ptr updated_asset_resolver, + flutter::AssetResolver::AssetResolverType type) override {} + + flutter::Settings settings_; +}; +} // namespace +} // namespace flutter + +@interface PlatformViewIOSTest : XCTestCase +@end + +@implementation PlatformViewIOSTest + +- (void)testCreate { + flutter::MockDelegate mock_delegate; + auto thread = std::make_unique("PlatformViewIOSTest"); + auto thread_task_runner = thread->GetTaskRunner(); + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/thread_task_runner, + /*raster=*/thread_task_runner, + /*ui=*/thread_task_runner, + /*io=*/thread_task_runner); + id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + id engine = OCMClassMock([FlutterEngine class]); + + id flutterViewController = OCMClassMock([FlutterViewController class]); + + OCMStub([flutterViewController isViewLoaded]).andReturn(NO); + OCMStub([flutterViewController engine]).andReturn(engine); + OCMStub([engine binaryMessenger]).andReturn(messenger); + + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/nil, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_sync_switch=*/std::make_shared()); + fml::AutoResetWaitableEvent latch; + thread_task_runner->PostTask([&] { + platform_view->SetOwnerViewController(flutterViewController); + XCTAssertFalse(platform_view->GetAccessibilityBridge()); + platform_view->HandleAccessibilityMessage( + @{@"type" : @"generatingSemanticsTree", @"data" : @{@"generating" : @(YES)}}, nil); + XCTAssertTrue(platform_view->GetAccessibilityBridge()); + platform_view->HandleAccessibilityMessage( + @{@"type" : @"generatingSemanticsTree", @"data" : @{@"generating" : @(NO)}}, nil); + XCTAssertFalse(platform_view->GetAccessibilityBridge()); + latch.Signal(); + }); + latch.Wait(); + + [engine stopMocking]; +} + +@end From 1421b5894dde29cc9864f0745f0a9f6b76798a35 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Tue, 26 Nov 2024 15:23:27 -0800 Subject: [PATCH 02/16] update --- testing/scenario_app/lib/main.dart | 8 + .../scenario_app/lib/src/standard_codec.dart | 286 ++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 testing/scenario_app/lib/src/standard_codec.dart diff --git a/testing/scenario_app/lib/main.dart b/testing/scenario_app/lib/main.dart index 636cd3b1521de..f6d34625194b6 100644 --- a/testing/scenario_app/lib/main.dart +++ b/testing/scenario_app/lib/main.dart @@ -7,6 +7,7 @@ import 'dart:typed_data'; import 'dart:ui'; import 'src/scenarios.dart'; +import 'src/standard_codec.dart'; void main() { // TODO(goderbauer): Create a window if embedder doesn't provide an implicit @@ -33,6 +34,13 @@ void main() { final ByteData data = ByteData(1); data.setUint8(0, 1); PlatformDispatcher.instance.sendPlatformMessage('waiting_for_status', data, null); + final Map enableSemantics = { + 'type': 'generatingSemanticsTree', + 'data': { + 'generating': true, + }, + }; + PlatformDispatcher.instance.sendPlatformMessage('flutter/accessibility', const StandardMessageCodec().encodeMessage(enableSemantics), null); } /// The FlutterView into which the [Scenario]s will be rendered. diff --git a/testing/scenario_app/lib/src/standard_codec.dart b/testing/scenario_app/lib/src/standard_codec.dart new file mode 100644 index 0000000000000..1e697ee38b23f --- /dev/null +++ b/testing/scenario_app/lib/src/standard_codec.dart @@ -0,0 +1,286 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:convert'; +import 'dart:math' as math; +import 'dart:typed_data'; + +const int __writeBufferStartCapacity = 64; + +/// This is mirroring the standard codec from framework +class StandardMessageCodec { + /// Creates a [MessageCodec] using the Flutter standard binary encoding. + const StandardMessageCodec(); + static const int _valueNull = 0; + static const int _valueTrue = 1; + static const int _valueFalse = 2; + static const int _valueInt32 = 3; + static const int _valueInt64 = 4; + // Unused + // static const int _valueLargeInt = 5; + static const int _valueFloat64 = 6; + static const int _valueString = 7; + static const int _valueUint8List = 8; + static const int _valueInt32List = 9; + static const int _valueInt64List = 10; + static const int _valueFloat64List = 11; + static const int _valueList = 12; + static const int _valueMap = 13; + static const int _valueFloat32List = 14; + + // Encode the message. + ByteData? encodeMessage(Object? message) { + if (message == null) { + return null; + } + final _WriteBuffer buffer = _WriteBuffer(startCapacity: __writeBufferStartCapacity); + _writeValue(buffer, message); + return buffer.done(); + } + + void _writeValue(_WriteBuffer buffer, Object? value) { + if (value == null) { + buffer.putUint8(_valueNull); + } else if (value is bool) { + buffer.putUint8(value ? _valueTrue : _valueFalse); + } else if (value is double) { // Double precedes int because in JS everything is a double. + // Therefore in JS, both `is int` and `is double` always + // return `true`. If we check int first, we'll end up treating + // all numbers as ints and attempt the int32/int64 conversion, + // which is wrong. This precedence rule is irrelevant when + // decoding because we use tags to detect the type of value. + buffer.putUint8(_valueFloat64); + buffer.putFloat64(value); + } else if (value is int) { // ignore: avoid_double_and_int_checks, JS code always goes through the `double` path above + if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) { + buffer.putUint8(_valueInt32); + buffer.putInt32(value); + } else { + buffer.putUint8(_valueInt64); + buffer.putInt64(value); + } + } else if (value is String) { + buffer.putUint8(_valueString); + final Uint8List asciiBytes = Uint8List(value.length); + Uint8List? utf8Bytes; + int utf8Offset = 0; + // Only do utf8 encoding if we encounter non-ascii characters. + for (int i = 0; i < value.length; i += 1) { + final int char = value.codeUnitAt(i); + if (char <= 0x7f) { + asciiBytes[i] = char; + } else { + utf8Bytes = utf8.encode(value.substring(i)); + utf8Offset = i; + break; + } + } + if (utf8Bytes != null) { + _writeSize(buffer, utf8Offset + utf8Bytes.length); + buffer.putUint8List(Uint8List.sublistView(asciiBytes, 0, utf8Offset)); + buffer.putUint8List(utf8Bytes); + } else { + _writeSize(buffer, asciiBytes.length); + buffer.putUint8List(asciiBytes); + } + } else if (value is Uint8List) { + buffer.putUint8(_valueUint8List); + _writeSize(buffer, value.length); + buffer.putUint8List(value); + } else if (value is Int32List) { + buffer.putUint8(_valueInt32List); + _writeSize(buffer, value.length); + buffer.putInt32List(value); + } else if (value is Int64List) { + buffer.putUint8(_valueInt64List); + _writeSize(buffer, value.length); + buffer.putInt64List(value); + } else if (value is Float32List) { + buffer.putUint8(_valueFloat32List); + _writeSize(buffer, value.length); + buffer.putFloat32List(value); + } else if (value is Float64List) { + buffer.putUint8(_valueFloat64List); + _writeSize(buffer, value.length); + buffer.putFloat64List(value); + } else if (value is List) { + buffer.putUint8(_valueList); + _writeSize(buffer, value.length); + for (final Object? item in value) { + _writeValue(buffer, item); + } + } else if (value is Map) { + buffer.putUint8(_valueMap); + _writeSize(buffer, value.length); + value.forEach((Object? key, Object? value) { + _writeValue(buffer, key); + _writeValue(buffer, value); + }); + } else { + throw ArgumentError.value(value); + } + } + + void _writeSize(_WriteBuffer buffer, int value) { + assert(0 <= value && value <= 0xffffffff); + if (value < 254) { + buffer.putUint8(value); + } else if (value <= 0xffff) { + buffer.putUint8(254); + buffer.putUint16(value); + } else { + buffer.putUint8(255); + buffer.putUint32(value); + } + } +} + + +class _WriteBuffer { + factory _WriteBuffer({int startCapacity = 8}) { + assert(startCapacity > 0); + final ByteData eightBytes = ByteData(8); + final Uint8List eightBytesAsList = eightBytes.buffer.asUint8List(); + return _WriteBuffer._(Uint8List(startCapacity), eightBytes, eightBytesAsList); + } + + _WriteBuffer._(this._buffer, this._eightBytes, this._eightBytesAsList); + + Uint8List _buffer; + int _currentSize = 0; + bool _isDone = false; + final ByteData _eightBytes; + final Uint8List _eightBytesAsList; + static final Uint8List _zeroBuffer = Uint8List(8); + + void _add(int byte) { + if (_currentSize == _buffer.length) { + _resize(); + } + _buffer[_currentSize] = byte; + _currentSize += 1; + } + + void _append(Uint8List other) { + final int newSize = _currentSize + other.length; + if (newSize >= _buffer.length) { + _resize(newSize); + } + _buffer.setRange(_currentSize, newSize, other); + _currentSize += other.length; + } + + void _addAll(Uint8List data, [int start = 0, int? end]) { + final int newEnd = end ?? _eightBytesAsList.length; + final int newSize = _currentSize + (newEnd - start); + if (newSize >= _buffer.length) { + _resize(newSize); + } + _buffer.setRange(_currentSize, newSize, data); + _currentSize = newSize; + } + + void _resize([int? requiredLength]) { + final int doubleLength = _buffer.length * 2; + final int newLength = math.max(requiredLength ?? 0, doubleLength); + final Uint8List newBuffer = Uint8List(newLength); + newBuffer.setRange(0, _buffer.length, _buffer); + _buffer = newBuffer; + } + + /// Write a Uint8 into the buffer. + void putUint8(int byte) { + assert(!_isDone); + _add(byte); + } + + /// Write a Uint16 into the buffer. + void putUint16(int value, {Endian? endian}) { + assert(!_isDone); + _eightBytes.setUint16(0, value, endian ?? Endian.host); + _addAll(_eightBytesAsList, 0, 2); + } + + /// Write a Uint32 into the buffer. + void putUint32(int value, {Endian? endian}) { + assert(!_isDone); + _eightBytes.setUint32(0, value, endian ?? Endian.host); + _addAll(_eightBytesAsList, 0, 4); + } + + /// Write an Int32 into the buffer. + void putInt32(int value, {Endian? endian}) { + assert(!_isDone); + _eightBytes.setInt32(0, value, endian ?? Endian.host); + _addAll(_eightBytesAsList, 0, 4); + } + + /// Write an Int64 into the buffer. + void putInt64(int value, {Endian? endian}) { + assert(!_isDone); + _eightBytes.setInt64(0, value, endian ?? Endian.host); + _addAll(_eightBytesAsList, 0, 8); + } + + /// Write an Float64 into the buffer. + void putFloat64(double value, {Endian? endian}) { + assert(!_isDone); + _alignTo(8); + _eightBytes.setFloat64(0, value, endian ?? Endian.host); + _addAll(_eightBytesAsList); + } + + /// Write all the values from a [Uint8List] into the buffer. + void putUint8List(Uint8List list) { + assert(!_isDone); + _append(list); + } + + /// Write all the values from an [Int32List] into the buffer. + void putInt32List(Int32List list) { + assert(!_isDone); + _alignTo(4); + _append(list.buffer.asUint8List(list.offsetInBytes, 4 * list.length)); + } + + /// Write all the values from an [Int64List] into the buffer. + void putInt64List(Int64List list) { + assert(!_isDone); + _alignTo(8); + _append(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length)); + } + + /// Write all the values from a [Float32List] into the buffer. + void putFloat32List(Float32List list) { + assert(!_isDone); + _alignTo(4); + _append(list.buffer.asUint8List(list.offsetInBytes, 4 * list.length)); + } + + /// Write all the values from a [Float64List] into the buffer. + void putFloat64List(Float64List list) { + assert(!_isDone); + _alignTo(8); + _append(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length)); + } + + void _alignTo(int alignment) { + assert(!_isDone); + final int mod = _currentSize % alignment; + if (mod != 0) { + _addAll(_zeroBuffer, 0, alignment - mod); + } + } + + /// Finalize and return the written [ByteData]. + ByteData done() { + if (_isDone) { + throw StateError('done() must not be called more than once on the same $runtimeType.'); + } + final ByteData result = _buffer.buffer.asByteData(0, _currentSize); + _buffer = Uint8List(0); + _isDone = true; + return result; + } +} From eca99854ccf4d8435d61ef3f0e311f40a729992d Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 27 Nov 2024 13:23:36 -0800 Subject: [PATCH 03/16] lint --- testing/scenario_app/lib/src/standard_codec.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/scenario_app/lib/src/standard_codec.dart b/testing/scenario_app/lib/src/standard_codec.dart index 1e697ee38b23f..09687e8535054 100644 --- a/testing/scenario_app/lib/src/standard_codec.dart +++ b/testing/scenario_app/lib/src/standard_codec.dart @@ -29,7 +29,7 @@ class StandardMessageCodec { static const int _valueMap = 13; static const int _valueFloat32List = 14; - // Encode the message. + /// Encode the message. ByteData? encodeMessage(Object? message) { if (message == null) { return null; From 836dbe38cacd2af49d2123354b7d7a12bec58ad9 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 09:47:42 -0800 Subject: [PATCH 04/16] update --- lib/ui/dart_ui.cc | 1 + lib/ui/window.dart | 9 + lib/ui/window/platform_configuration.cc | 6 + lib/ui/window/platform_configuration.h | 9 + runtime/dart_isolate_unittests.cc | 1 + runtime/runtime_controller.cc | 10 +- runtime/runtime_controller.h | 4 + runtime/runtime_controller_unittests.cc | 14 +- runtime/runtime_delegate.h | 1 + shell/common/engine.cc | 4 + shell/common/engine.h | 14 + shell/common/engine_animator_unittests.cc | 1 + shell/common/engine_unittests.cc | 2 + shell/common/platform_view.cc | 4 + shell/common/platform_view.h | 9 + shell/common/shell.cc | 14 + shell/common/shell.h | 3 + .../framework/Source/accessibility_bridge.h | 3 +- .../framework/Source/accessibility_bridge.mm | 27 +- .../Source/accessibility_bridge_test.mm | 51 +++- shell/platform/darwin/ios/platform_view_ios.h | 14 +- .../platform/darwin/ios/platform_view_ios.mm | 58 +--- .../darwin/ios/platform_view_ios_test.mm | 8 +- testing/scenario_app/lib/main.dart | 9 +- .../scenario_app/lib/src/standard_codec.dart | 286 ------------------ 25 files changed, 190 insertions(+), 372 deletions(-) delete mode 100644 testing/scenario_app/lib/src/standard_codec.dart diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index d8509e37e3a25..9849193f04ce0 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -99,6 +99,7 @@ typedef CanvasPath Path; V(PlatformConfigurationNativeApi::UpdateSemantics) \ V(PlatformConfigurationNativeApi::SetNeedsReportTimings) \ V(PlatformConfigurationNativeApi::SetIsolateDebugName) \ + V(PlatformConfigurationNativeApi::SetSemanticsTreeEnabled) \ V(PlatformConfigurationNativeApi::RequestDartPerformanceMode) \ V(PlatformConfigurationNativeApi::GetPersistentIsolateData) \ V(PlatformConfigurationNativeApi::ComputePlatformResolvedLocale) \ diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 229022b0fb017..0bc19ed40b4af 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -393,6 +393,15 @@ class FlutterView { @Native)>(symbol: 'PlatformConfigurationNativeApi::UpdateSemantics') external static void _updateSemantics(_NativeSemanticsUpdate update); + /// Sets whether framework starts generating semantics tree. + /// + /// This function lets platform to prepare or dispose the resource for accepting + /// semantics update sent through [updateSemantics]. + void setSemanticsTreeEnabled(bool enabled) => _setSemanticsTreeEnabled(enabled); + + @Native(symbol: 'PlatformConfigurationNativeApi::SetSemanticsTreeEnabled') + external static void _setSemanticsTreeEnabled(bool update); + @override String toString() => 'FlutterView(id: $viewId)'; } diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index a63d098726e5b..33c38ba8f7d7d 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -621,6 +621,12 @@ void PlatformConfigurationNativeApi::UpdateSemantics(SemanticsUpdate* update) { update); } +void PlatformConfigurationNativeApi::SetSemanticsTreeEnabled(bool enabled) { + UIDartState::ThrowIfUIOperationsProhibited(); + UIDartState::Current()->platform_configuration()->client()->SetSemanticsTreeEnabled( + enabled); +} + Dart_Handle PlatformConfigurationNativeApi::ComputePlatformResolvedLocale( Dart_Handle supportedLocalesHandle) { UIDartState::ThrowIfUIOperationsProhibited(); diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index 6d227ce347ed8..6b932c0adf209 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -95,6 +95,13 @@ class PlatformConfigurationClient { /// virtual void UpdateSemantics(SemanticsUpdate* update) = 0; + //-------------------------------------------------------------------------- + /// @brief Notifies whether Framework starts generating semantics tree. + /// + /// @param[in] enabled True if Framework starts generating semantics tree. + /// + virtual void SetSemanticsTreeEnabled(bool enabled) = 0; + //-------------------------------------------------------------------------- /// @brief When the Flutter application has a message to send to the /// underlying platform, the message needs to be forwarded to @@ -594,6 +601,8 @@ class PlatformConfigurationNativeApi { static void UpdateSemantics(SemanticsUpdate* update); + static void SetSemanticsTreeEnabled(bool enabled); + static void SetNeedsReportTimings(bool value); static Dart_Handle GetPersistentIsolateData(); diff --git a/runtime/dart_isolate_unittests.cc b/runtime/dart_isolate_unittests.cc index 9f7bb9a59d5da..feb111092a63a 100644 --- a/runtime/dart_isolate_unittests.cc +++ b/runtime/dart_isolate_unittests.cc @@ -712,6 +712,7 @@ class FakePlatformConfigurationClient : public PlatformConfigurationClient { double width, double height) override {} void UpdateSemantics(SemanticsUpdate* update) override {} + void SetSemanticsTreeEnabled(bool enabled) override {} void HandlePlatformMessage( std::unique_ptr message) override {} FontCollection& GetFontCollection() override { diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index d1161ffbfe7af..96fe426274a6b 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -440,7 +440,15 @@ void RuntimeController::CheckIfAllViewsRendered() { // |PlatformConfigurationClient| void RuntimeController::UpdateSemantics(SemanticsUpdate* update) { - client_.UpdateSemantics(update->takeNodes(), update->takeActions()); + if (semantics_tree_enabled_) { + client_.UpdateSemantics(update->takeNodes(), update->takeActions()); + } +} + +// |PlatformConfigurationClient| +void RuntimeController::SetSemanticsTreeEnabled(bool enabled) { + semantics_tree_enabled_ = enabled; + client_.SetSemanticsTreeEnabled(enabled); } // |PlatformConfigurationClient| diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index e83c38bdabd23..6e6b79ce4e972 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -704,6 +704,7 @@ class RuntimeController : public PlatformConfigurationClient, std::shared_ptr platform_isolate_manager_ = std::shared_ptr(new PlatformIsolateManager()); bool has_flushed_runtime_state_ = false; + bool semantics_tree_enabled_ = false; // Callbacks when `AddView` was called before the Dart isolate is launched. // @@ -756,6 +757,9 @@ class RuntimeController : public PlatformConfigurationClient, // |PlatformConfigurationClient| void UpdateSemantics(SemanticsUpdate* update) override; + // |PlatformConfigurationClient| + void SetSemanticsTreeEnabled(bool enabled) override; + // |PlatformConfigurationClient| void HandlePlatformMessage(std::unique_ptr message) override; diff --git a/runtime/runtime_controller_unittests.cc b/runtime/runtime_controller_unittests.cc index d056412e7b867..2ade7b7d4eb1b 100644 --- a/runtime/runtime_controller_unittests.cc +++ b/runtime/runtime_controller_unittests.cc @@ -35,6 +35,8 @@ class MockRuntimeDelegate : public RuntimeDelegate { this->actions.push_back(actions); } + void SetSemanticsTreeEnabled(bool enabled) override {} + void HandlePlatformMessage( std::unique_ptr message) override {} @@ -83,10 +85,16 @@ class RuntimeControllerTester { nullptr, context_) {} - void CanUpdateSemantics(SemanticsUpdate* update) { + void CanUpdateSemanticsWhenSetSemanticsTreeEnabled(SemanticsUpdate* update) { ASSERT_TRUE(delegate_.updates.empty()); ASSERT_TRUE(delegate_.actions.empty()); runtime_controller_.UpdateSemantics(update); + // Semantics tree is not yet enabled. + ASSERT_TRUE(delegate_.updates.empty()); + ASSERT_TRUE(delegate_.actions.empty()); + + runtime_controller_.SetSemanticsTreeEnabled(true); + runtime_controller_.UpdateSemantics(update); ASSERT_FALSE(delegate_.updates.empty()); ASSERT_FALSE(delegate_.actions.empty()); } @@ -97,7 +105,7 @@ class RuntimeControllerTester { RuntimeController runtime_controller_; }; -TEST_F(RuntimeControllerTest, CanUpdateSemantics) { +TEST_F(RuntimeControllerTest, CanUpdateSemanticsWhenSetSemanticsTreeEnabled) { // The test is mostly setup code to get a SemanticsUpdate object. // The real test is in RuntimeControllerTester::CanUpdateSemantics. TaskRunners task_runners("test", // label @@ -118,7 +126,7 @@ TEST_F(RuntimeControllerTest, CanUpdateSemantics) { ASSERT_FALSE(Dart_IsError(result)); SemanticsUpdate* update = reinterpret_cast(peer); - tester->CanUpdateSemantics(update); + tester->CanUpdateSemanticsWhenSetSemanticsTreeEnabled(update); message_latch.Signal(); }; diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index 7c031f1a53068..978362a802549 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -33,6 +33,7 @@ class RuntimeDelegate { virtual void UpdateSemantics(SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) = 0; + virtual void SetSemanticsTreeEnabled(bool enabled) = 0; virtual void HandlePlatformMessage( std::unique_ptr message) = 0; diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 770f778759925..85f070aa6e3b1 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -495,6 +495,10 @@ void Engine::UpdateSemantics(SemanticsNodeUpdates update, delegate_.OnEngineUpdateSemantics(std::move(update), std::move(actions)); } +void Engine::SetSemanticsTreeEnabled(bool enabled) { + delegate_.OnEngineSetSemanticsTreeEnabled(enabled); +} + void Engine::HandlePlatformMessage(std::unique_ptr message) { if (message->channel() == kAssetChannel) { HandleAssetPlatformMessage(std::move(message)); diff --git a/shell/common/engine.h b/shell/common/engine.h index 754539ee825e1..7dc322640b558 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -159,6 +159,18 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { SemanticsNodeUpdates updates, CustomAccessibilityActionUpdates actions) = 0; + //-------------------------------------------------------------------------- + /// @brief When the Framework starts or stop generating semantics tree, + /// this new information needs to be conveyed to the underlying + /// platform so that they can prepare to accept semantics update. + /// The engine delegates this task to the shell via this call. + /// + /// @see `OnEngineUpdateSemantics` + /// + /// @param[in] enabled whether Framework starts generating semantics tree. + /// + virtual void OnEngineSetSemanticsTreeEnabled(bool enabled) = 0; + //-------------------------------------------------------------------------- /// @brief When the Flutter application has a message to send to the /// underlying platform, the message needs to be forwarded to @@ -990,6 +1002,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { // |RuntimeDelegate| void UpdateSemantics(SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) override; + // |RuntimeDelegate| + void SetSemanticsTreeEnabled(bool enabled) override; // |RuntimeDelegate| void HandlePlatformMessage(std::unique_ptr message) override; diff --git a/shell/common/engine_animator_unittests.cc b/shell/common/engine_animator_unittests.cc index 8d8261bc02c08..91d4e5fa570da 100644 --- a/shell/common/engine_animator_unittests.cc +++ b/shell/common/engine_animator_unittests.cc @@ -56,6 +56,7 @@ class MockDelegate : public Engine::Delegate { OnEngineUpdateSemantics, (SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (override)); + MOCK_METHOD(void, OnEngineSetSemanticsTreeEnabled, (bool), (override)); MOCK_METHOD(void, OnEngineHandlePlatformMessage, (std::unique_ptr), diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index d549a6ccd5a73..8c89b81420f2b 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -64,6 +64,7 @@ class MockDelegate : public Engine::Delegate { OnEngineUpdateSemantics, (SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (override)); + MOCK_METHOD(void, OnEngineSetSemanticsTreeEnabled, (bool), (override)); MOCK_METHOD(void, OnEngineHandlePlatformMessage, (std::unique_ptr), @@ -111,6 +112,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { UpdateSemantics, (SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (override)); + MOCK_METHOD(void, SetSemanticsTreeEnabled, (bool), (override)); MOCK_METHOD(void, HandlePlatformMessage, (std::unique_ptr), diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index c6473c0ac7b42..36c347260580f 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -124,6 +124,10 @@ void PlatformView::UpdateSemantics( // NOLINTNEXTLINE(performance-unnecessary-value-param) CustomAccessibilityActionUpdates actions) {} +void PlatformView::SetSemanticsTreeEnabled( + bool enabled // NOLINT(performance-unnecessary-value-param) +) {} + void PlatformView::SendChannelUpdate(const std::string& name, bool listening) {} void PlatformView::HandlePlatformMessage( diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index dfe1b2eeb3885..bcf68989b94e9 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -502,6 +502,15 @@ class PlatformView { virtual void UpdateSemantics(SemanticsNodeUpdates updates, CustomAccessibilityActionUpdates actions); + //---------------------------------------------------------------------------- + /// @brief Used by the framework to tell the embedder to prepare or clear + /// resoruce for accepting semantics tree. + /// + /// @param[in] enabled whether framework starts or stops sending semantics + /// updates + /// + virtual void SetSemanticsTreeEnabled(bool enabled); + //---------------------------------------------------------------------------- /// @brief Used by the framework to tell the embedder that it has /// registered a listener on a given channel. diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 8bd6403fbda3c..593856156ee0b 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1324,6 +1324,20 @@ void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update, }); } +// |Engine::Delegate| +void Shell::OnEngineSetSemanticsTreeEnabled(bool enabled) { + FML_DCHECK(is_set_up_); + FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); + + task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask( + task_runners_.GetPlatformTaskRunner(), + [view = platform_view_->GetWeakPtr(), enabled] { + if (view) { + view->SetSemanticsTreeEnabled(enabled); + } + }); +} + // |Engine::Delegate| void Shell::OnEngineHandlePlatformMessage( std::unique_ptr message) { diff --git a/shell/common/shell.h b/shell/common/shell.h index 0ff7984b8e67d..44c13d9ebe22f 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -659,6 +659,9 @@ class Shell final : public PlatformView::Delegate, SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) override; + // |Engine::Delegate| + void OnEngineSetSemanticsTreeEnabled(bool enabled) override; + // |Engine::Delegate| void OnEngineHandlePlatformMessage( std::unique_ptr message) override; diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h index d1589c7538ca9..e808b88d20f8a 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h @@ -56,7 +56,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { void UpdateSemantics(flutter::SemanticsNodeUpdates nodes, const flutter::CustomAccessibilityActionUpdates& actions); - void HandleMessage(NSDictionary* message, FlutterReply reply); + void HandleEvent(NSDictionary* annotatedEvent); void DispatchSemanticsAction(int32_t id, flutter::SemanticsAction action) override; void DispatchSemanticsAction(int32_t id, flutter::SemanticsAction action, @@ -98,6 +98,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { int32_t last_focused_semantics_object_id_; NSMutableDictionary* objects_; + FlutterBasicMessageChannel* accessibility_channel_; int32_t previous_route_id_ = 0; std::unordered_map actions_; std::vector previous_routes_; diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm index 98b3c6ed5cadd..daf6389834c81 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm @@ -52,9 +52,18 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification, previous_routes_({}), ios_delegate_(ios_delegate ? std::move(ios_delegate) : std::make_unique()), - weak_factory_(this) {} + weak_factory_(this) { + accessibility_channel_ = [[FlutterBasicMessageChannel alloc] + initWithName:@"flutter/accessibility" + binaryMessenger:platform_view->GetOwnerViewController().engine.binaryMessenger + codec:[FlutterStandardMessageCodec sharedInstance]]; + [accessibility_channel_ setMessageHandler:^(id message, FlutterReply reply) { + HandleEvent((NSDictionary*)message); + }]; +} AccessibilityBridge::~AccessibilityBridge() { + [accessibility_channel_ setMessageHandler:nil]; clearState(); view_controller_.viewIfLoaded.accessibilityElements = nil; } @@ -65,7 +74,7 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification, void AccessibilityBridge::AccessibilityObjectDidBecomeFocused(int32_t id) { last_focused_semantics_object_id_ = id; - platform_view_->SendAccessibilityMessage(@{@"type" : @"didGainFocus", @"nodeId" : @(id)}); + [accessibility_channel_ sendMessage:@{@"type" : @"didGainFocus", @"nodeId" : @(id)}]; } void AccessibilityBridge::AccessibilityObjectDidLoseFocus(int32_t id) { @@ -345,20 +354,16 @@ static bool DidFlagChange(const flutter::SemanticsNode& oldNode, return nil; } -void AccessibilityBridge::HandleMessage(NSDictionary* message, FlutterReply reply) { - NSString* type = message[@"type"]; +void AccessibilityBridge::HandleEvent(NSDictionary* annotatedEvent) { + NSString* type = annotatedEvent[@"type"]; if ([type isEqualToString:@"announce"]) { - NSString* message_to_announce = message[@"data"][@"message"]; - ios_delegate_->PostAccessibilityNotification(UIAccessibilityAnnouncementNotification, - message_to_announce); + NSString* message = annotatedEvent[@"data"][@"message"]; + ios_delegate_->PostAccessibilityNotification(UIAccessibilityAnnouncementNotification, message); } if ([type isEqualToString:@"focus"]) { - SemanticsObject* node = objects_[message[@"nodeId"]]; + SemanticsObject* node = objects_[annotatedEvent[@"nodeId"]]; ios_delegate_->PostAccessibilityNotification(UIAccessibilityLayoutChangedNotification, node); } - if (reply) { - reply(nil); - } } fml::WeakPtr AccessibilityBridge::GetWeakPtr() { diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 960a5f85ddb98..343ed99eae983 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -1372,9 +1372,9 @@ - (void)testHandleEvent { /*platform_views_controller=*/nil, /*ios_delegate=*/std::move(ios_delegate)); - NSDictionary* message = @{@"type" : @"focus", @"nodeId" : @123}; + NSDictionary* annotatedEvent = @{@"type" : @"focus", @"nodeId" : @123}; - bridge->HandleMessage(message, nil); + bridge->HandleEvent(annotatedEvent); XCTAssertEqual([accessibility_notifications count], 1ul); XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue], @@ -2068,6 +2068,53 @@ - (void)testAnnouncesIgnoresScrollChangeWhenModal { XCTAssertEqual([accessibility_notifications count], 0ul); } +- (void)testAccessibilityMessageAfterDeletion { + flutter::MockDelegate mock_delegate; + auto thread = std::make_unique("AccessibilityBridgeTest"); + auto thread_task_runner = thread->GetTaskRunner(); + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/thread_task_runner, + /*raster=*/thread_task_runner, + /*ui=*/thread_task_runner, + /*io=*/thread_task_runner); + id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + id engine = OCMClassMock([FlutterEngine class]); + id flutterViewController = OCMClassMock([FlutterViewController class]); + + OCMStub([flutterViewController engine]).andReturn(engine); + OCMStub([engine binaryMessenger]).andReturn(messenger); + FlutterBinaryMessengerConnection connection = 123; + OCMStub([messenger setMessageHandlerOnChannel:@"flutter/accessibility" + binaryMessageHandler:[OCMArg any]]) + .andReturn(connection); + + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/nil, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_sync_switch=*/std::make_shared()); + fml::AutoResetWaitableEvent latch; + thread_task_runner->PostTask([&] { + platform_view->SetOwnerViewController(flutterViewController); + auto bridge = + std::make_unique(/*view=*/nil, + /*platform_view=*/platform_view.get(), + /*platform_views_controller=*/nil); + XCTAssertTrue(bridge.get()); + OCMVerify([messenger setMessageHandlerOnChannel:@"flutter/accessibility" + binaryMessageHandler:[OCMArg isNotNil]]); + bridge.reset(); + latch.Signal(); + }); + latch.Wait(); + OCMVerify([messenger cleanUpConnection:connection]); + [engine stopMocking]; +} + - (void)testFlutterSemanticsScrollViewManagedObjectLifecycleCorrectly { flutter::MockDelegate mock_delegate; auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest"); diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index ae4dffc7ef536..266d1322a5db1 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -66,11 +66,6 @@ class PlatformViewIOS final : public PlatformView { */ void SetOwnerViewController(__weak FlutterViewController* owner_controller); - /** - * Send accessibility message to accessibility channel. - */ - void SendAccessibilityMessage(__weak id message); - /** * Called one time per `FlutterViewController` when the `FlutterViewController`'s * UIView is first loaded. @@ -92,6 +87,9 @@ class PlatformViewIOS final : public PlatformView { // |PlatformView| void SetSemanticsEnabled(bool enabled) override; + // |PlatformView| + void SetSemanticsTreeEnabled(bool enabled) override; + /** Accessor for the `IOSContext` associated with the platform view. */ const std::shared_ptr& GetIosContext() { return ios_context_; } @@ -108,11 +106,6 @@ class PlatformViewIOS final : public PlatformView { */ AccessibilityBridge* GetAccessibilityBridge() { return accessibility_bridge_.get(); } - /** - * Handles accessibility message from accessibility channel. - */ - void HandleAccessibilityMessage(__weak id message, FlutterReply reply); - private: /// Smart pointer for use with objective-c observers. /// This guarantees we remove the observer. @@ -139,7 +132,6 @@ class PlatformViewIOS final : public PlatformView { ScopedObserver dealloc_view_controller_observer_; std::vector platform_resolved_locale_; std::shared_ptr platform_message_handler_; - FlutterBasicMessageChannel* accessibility_channel_; // |PlatformView| void HandlePlatformMessage(std::unique_ptr message) override; diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index b9641c71776f0..0d834e3e2233a 100644 --- a/shell/platform/darwin/ios/platform_view_ios.mm +++ b/shell/platform/darwin/ios/platform_view_ios.mm @@ -63,18 +63,6 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} ios_surface_.reset(); accessibility_bridge_.reset(); } - if (owner_controller) { - accessibility_channel_ = [[FlutterBasicMessageChannel alloc] - initWithName:@"flutter/accessibility" - binaryMessenger:owner_controller.engine.binaryMessenger - codec:[FlutterStandardMessageCodec sharedInstance]]; - [accessibility_channel_ setMessageHandler:^(id message, FlutterReply reply) { - HandleAccessibilityMessage(message, reply); - }]; - } else { - [accessibility_channel_ setMessageHandler:nil]; - accessibility_channel_ = nil; - } owner_controller_ = owner_controller; // Add an observer that will clear out the owner_controller_ ivar and @@ -119,37 +107,6 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} }; } -void PlatformViewIOS::HandleAccessibilityMessage(__weak id message, FlutterReply reply) { - if (!owner_controller_) { - FML_LOG(WARNING) << "Could not accept accessibility message, this " - "PlatformViewIOS has no ViewController."; - } - NSString* type = message[@"type"]; - if ([type isEqualToString:@"generatingSemanticsTree"]) { - BOOL generating = [message[@"data"][@"generating"] boolValue]; - if (generating) { - if (!accessibility_bridge_) { - accessibility_bridge_ = std::make_unique(owner_controller_, this, - platform_views_controller_); - } - } else { - accessibility_bridge_.reset(); - } - if (reply) { - reply(nil); - } - return; - } - - if (accessibility_bridge_) { - accessibility_bridge_->HandleMessage(message, reply); - } -} - -void PlatformViewIOS::SendAccessibilityMessage(__weak id message) { - [accessibility_channel_ sendMessage:message]; -} - void PlatformViewIOS::RegisterExternalTexture(int64_t texture_id, NSObject* texture) { RegisterTexture(ios_context_->CreateExternalTexture(texture_id, texture)); @@ -187,7 +144,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} PlatformView::SetSemanticsEnabled(enabled); } -// |shell:PlatformView| +// |PlatformView| void PlatformViewIOS::SetAccessibilityFeatures(int32_t flags) { PlatformView::SetAccessibilityFeatures(flags); } @@ -204,6 +161,19 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} } } +// |PlatformView| +void PlatformViewIOS::SetSemanticsTreeEnabled(bool enabled) { + FML_DCHECK(owner_controller_); + if (enabled) { + if (!accessibility_bridge_) { + accessibility_bridge_ = std::make_unique(owner_controller_, this, + platform_views_controller_); + } + } else { + accessibility_bridge_.reset(); + } +} + // |PlatformView| std::unique_ptr PlatformViewIOS::CreateVSyncWaiter() { return std::make_unique(task_runners_); diff --git a/shell/platform/darwin/ios/platform_view_ios_test.mm b/shell/platform/darwin/ios/platform_view_ios_test.mm index 97ab2a987ff1a..2fa3080501c76 100644 --- a/shell/platform/darwin/ios/platform_view_ios_test.mm +++ b/shell/platform/darwin/ios/platform_view_ios_test.mm @@ -59,7 +59,7 @@ @interface PlatformViewIOSTest : XCTestCase @implementation PlatformViewIOSTest -- (void)testCreate { +- (void)testSetSemanticsTreeEnabled { flutter::MockDelegate mock_delegate; auto thread = std::make_unique("PlatformViewIOSTest"); auto thread_task_runner = thread->GetTaskRunner(); @@ -90,11 +90,9 @@ - (void)testCreate { thread_task_runner->PostTask([&] { platform_view->SetOwnerViewController(flutterViewController); XCTAssertFalse(platform_view->GetAccessibilityBridge()); - platform_view->HandleAccessibilityMessage( - @{@"type" : @"generatingSemanticsTree", @"data" : @{@"generating" : @(YES)}}, nil); + platform_view->SetSemanticsTreeEnabled(true); XCTAssertTrue(platform_view->GetAccessibilityBridge()); - platform_view->HandleAccessibilityMessage( - @{@"type" : @"generatingSemanticsTree", @"data" : @{@"generating" : @(NO)}}, nil); + platform_view->SetSemanticsTreeEnabled(false); XCTAssertFalse(platform_view->GetAccessibilityBridge()); latch.Signal(); }); diff --git a/testing/scenario_app/lib/main.dart b/testing/scenario_app/lib/main.dart index f6d34625194b6..bb4807c2c6a09 100644 --- a/testing/scenario_app/lib/main.dart +++ b/testing/scenario_app/lib/main.dart @@ -7,7 +7,6 @@ import 'dart:typed_data'; import 'dart:ui'; import 'src/scenarios.dart'; -import 'src/standard_codec.dart'; void main() { // TODO(goderbauer): Create a window if embedder doesn't provide an implicit @@ -34,13 +33,7 @@ void main() { final ByteData data = ByteData(1); data.setUint8(0, 1); PlatformDispatcher.instance.sendPlatformMessage('waiting_for_status', data, null); - final Map enableSemantics = { - 'type': 'generatingSemanticsTree', - 'data': { - 'generating': true, - }, - }; - PlatformDispatcher.instance.sendPlatformMessage('flutter/accessibility', const StandardMessageCodec().encodeMessage(enableSemantics), null); + view.setSemanticsTreeEnabled(true); } /// The FlutterView into which the [Scenario]s will be rendered. diff --git a/testing/scenario_app/lib/src/standard_codec.dart b/testing/scenario_app/lib/src/standard_codec.dart deleted file mode 100644 index 09687e8535054..0000000000000 --- a/testing/scenario_app/lib/src/standard_codec.dart +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:convert'; -import 'dart:math' as math; -import 'dart:typed_data'; - -const int __writeBufferStartCapacity = 64; - -/// This is mirroring the standard codec from framework -class StandardMessageCodec { - /// Creates a [MessageCodec] using the Flutter standard binary encoding. - const StandardMessageCodec(); - static const int _valueNull = 0; - static const int _valueTrue = 1; - static const int _valueFalse = 2; - static const int _valueInt32 = 3; - static const int _valueInt64 = 4; - // Unused - // static const int _valueLargeInt = 5; - static const int _valueFloat64 = 6; - static const int _valueString = 7; - static const int _valueUint8List = 8; - static const int _valueInt32List = 9; - static const int _valueInt64List = 10; - static const int _valueFloat64List = 11; - static const int _valueList = 12; - static const int _valueMap = 13; - static const int _valueFloat32List = 14; - - /// Encode the message. - ByteData? encodeMessage(Object? message) { - if (message == null) { - return null; - } - final _WriteBuffer buffer = _WriteBuffer(startCapacity: __writeBufferStartCapacity); - _writeValue(buffer, message); - return buffer.done(); - } - - void _writeValue(_WriteBuffer buffer, Object? value) { - if (value == null) { - buffer.putUint8(_valueNull); - } else if (value is bool) { - buffer.putUint8(value ? _valueTrue : _valueFalse); - } else if (value is double) { // Double precedes int because in JS everything is a double. - // Therefore in JS, both `is int` and `is double` always - // return `true`. If we check int first, we'll end up treating - // all numbers as ints and attempt the int32/int64 conversion, - // which is wrong. This precedence rule is irrelevant when - // decoding because we use tags to detect the type of value. - buffer.putUint8(_valueFloat64); - buffer.putFloat64(value); - } else if (value is int) { // ignore: avoid_double_and_int_checks, JS code always goes through the `double` path above - if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) { - buffer.putUint8(_valueInt32); - buffer.putInt32(value); - } else { - buffer.putUint8(_valueInt64); - buffer.putInt64(value); - } - } else if (value is String) { - buffer.putUint8(_valueString); - final Uint8List asciiBytes = Uint8List(value.length); - Uint8List? utf8Bytes; - int utf8Offset = 0; - // Only do utf8 encoding if we encounter non-ascii characters. - for (int i = 0; i < value.length; i += 1) { - final int char = value.codeUnitAt(i); - if (char <= 0x7f) { - asciiBytes[i] = char; - } else { - utf8Bytes = utf8.encode(value.substring(i)); - utf8Offset = i; - break; - } - } - if (utf8Bytes != null) { - _writeSize(buffer, utf8Offset + utf8Bytes.length); - buffer.putUint8List(Uint8List.sublistView(asciiBytes, 0, utf8Offset)); - buffer.putUint8List(utf8Bytes); - } else { - _writeSize(buffer, asciiBytes.length); - buffer.putUint8List(asciiBytes); - } - } else if (value is Uint8List) { - buffer.putUint8(_valueUint8List); - _writeSize(buffer, value.length); - buffer.putUint8List(value); - } else if (value is Int32List) { - buffer.putUint8(_valueInt32List); - _writeSize(buffer, value.length); - buffer.putInt32List(value); - } else if (value is Int64List) { - buffer.putUint8(_valueInt64List); - _writeSize(buffer, value.length); - buffer.putInt64List(value); - } else if (value is Float32List) { - buffer.putUint8(_valueFloat32List); - _writeSize(buffer, value.length); - buffer.putFloat32List(value); - } else if (value is Float64List) { - buffer.putUint8(_valueFloat64List); - _writeSize(buffer, value.length); - buffer.putFloat64List(value); - } else if (value is List) { - buffer.putUint8(_valueList); - _writeSize(buffer, value.length); - for (final Object? item in value) { - _writeValue(buffer, item); - } - } else if (value is Map) { - buffer.putUint8(_valueMap); - _writeSize(buffer, value.length); - value.forEach((Object? key, Object? value) { - _writeValue(buffer, key); - _writeValue(buffer, value); - }); - } else { - throw ArgumentError.value(value); - } - } - - void _writeSize(_WriteBuffer buffer, int value) { - assert(0 <= value && value <= 0xffffffff); - if (value < 254) { - buffer.putUint8(value); - } else if (value <= 0xffff) { - buffer.putUint8(254); - buffer.putUint16(value); - } else { - buffer.putUint8(255); - buffer.putUint32(value); - } - } -} - - -class _WriteBuffer { - factory _WriteBuffer({int startCapacity = 8}) { - assert(startCapacity > 0); - final ByteData eightBytes = ByteData(8); - final Uint8List eightBytesAsList = eightBytes.buffer.asUint8List(); - return _WriteBuffer._(Uint8List(startCapacity), eightBytes, eightBytesAsList); - } - - _WriteBuffer._(this._buffer, this._eightBytes, this._eightBytesAsList); - - Uint8List _buffer; - int _currentSize = 0; - bool _isDone = false; - final ByteData _eightBytes; - final Uint8List _eightBytesAsList; - static final Uint8List _zeroBuffer = Uint8List(8); - - void _add(int byte) { - if (_currentSize == _buffer.length) { - _resize(); - } - _buffer[_currentSize] = byte; - _currentSize += 1; - } - - void _append(Uint8List other) { - final int newSize = _currentSize + other.length; - if (newSize >= _buffer.length) { - _resize(newSize); - } - _buffer.setRange(_currentSize, newSize, other); - _currentSize += other.length; - } - - void _addAll(Uint8List data, [int start = 0, int? end]) { - final int newEnd = end ?? _eightBytesAsList.length; - final int newSize = _currentSize + (newEnd - start); - if (newSize >= _buffer.length) { - _resize(newSize); - } - _buffer.setRange(_currentSize, newSize, data); - _currentSize = newSize; - } - - void _resize([int? requiredLength]) { - final int doubleLength = _buffer.length * 2; - final int newLength = math.max(requiredLength ?? 0, doubleLength); - final Uint8List newBuffer = Uint8List(newLength); - newBuffer.setRange(0, _buffer.length, _buffer); - _buffer = newBuffer; - } - - /// Write a Uint8 into the buffer. - void putUint8(int byte) { - assert(!_isDone); - _add(byte); - } - - /// Write a Uint16 into the buffer. - void putUint16(int value, {Endian? endian}) { - assert(!_isDone); - _eightBytes.setUint16(0, value, endian ?? Endian.host); - _addAll(_eightBytesAsList, 0, 2); - } - - /// Write a Uint32 into the buffer. - void putUint32(int value, {Endian? endian}) { - assert(!_isDone); - _eightBytes.setUint32(0, value, endian ?? Endian.host); - _addAll(_eightBytesAsList, 0, 4); - } - - /// Write an Int32 into the buffer. - void putInt32(int value, {Endian? endian}) { - assert(!_isDone); - _eightBytes.setInt32(0, value, endian ?? Endian.host); - _addAll(_eightBytesAsList, 0, 4); - } - - /// Write an Int64 into the buffer. - void putInt64(int value, {Endian? endian}) { - assert(!_isDone); - _eightBytes.setInt64(0, value, endian ?? Endian.host); - _addAll(_eightBytesAsList, 0, 8); - } - - /// Write an Float64 into the buffer. - void putFloat64(double value, {Endian? endian}) { - assert(!_isDone); - _alignTo(8); - _eightBytes.setFloat64(0, value, endian ?? Endian.host); - _addAll(_eightBytesAsList); - } - - /// Write all the values from a [Uint8List] into the buffer. - void putUint8List(Uint8List list) { - assert(!_isDone); - _append(list); - } - - /// Write all the values from an [Int32List] into the buffer. - void putInt32List(Int32List list) { - assert(!_isDone); - _alignTo(4); - _append(list.buffer.asUint8List(list.offsetInBytes, 4 * list.length)); - } - - /// Write all the values from an [Int64List] into the buffer. - void putInt64List(Int64List list) { - assert(!_isDone); - _alignTo(8); - _append(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length)); - } - - /// Write all the values from a [Float32List] into the buffer. - void putFloat32List(Float32List list) { - assert(!_isDone); - _alignTo(4); - _append(list.buffer.asUint8List(list.offsetInBytes, 4 * list.length)); - } - - /// Write all the values from a [Float64List] into the buffer. - void putFloat64List(Float64List list) { - assert(!_isDone); - _alignTo(8); - _append(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length)); - } - - void _alignTo(int alignment) { - assert(!_isDone); - final int mod = _currentSize % alignment; - if (mod != 0) { - _addAll(_zeroBuffer, 0, alignment - mod); - } - } - - /// Finalize and return the written [ByteData]. - ByteData done() { - if (_isDone) { - throw StateError('done() must not be called more than once on the same $runtimeType.'); - } - final ByteData result = _buffer.buffer.asByteData(0, _currentSize); - _buffer = Uint8List(0); - _isDone = true; - return result; - } -} From d59971c2141f8a5ed05e05f6e323b83762414499 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 09:51:05 -0800 Subject: [PATCH 05/16] update --- lib/ui/window/platform_configuration.cc | 6 ++++-- lib/ui/window/platform_configuration.h | 2 +- shell/common/engine.h | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index 33c38ba8f7d7d..43b1e3dfdfc65 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -623,8 +623,10 @@ void PlatformConfigurationNativeApi::UpdateSemantics(SemanticsUpdate* update) { void PlatformConfigurationNativeApi::SetSemanticsTreeEnabled(bool enabled) { UIDartState::ThrowIfUIOperationsProhibited(); - UIDartState::Current()->platform_configuration()->client()->SetSemanticsTreeEnabled( - enabled); + UIDartState::Current() + ->platform_configuration() + ->client() + ->SetSemanticsTreeEnabled(enabled); } Dart_Handle PlatformConfigurationNativeApi::ComputePlatformResolvedLocale( diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index 6b932c0adf209..9e71d347add3f 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -96,7 +96,7 @@ class PlatformConfigurationClient { virtual void UpdateSemantics(SemanticsUpdate* update) = 0; //-------------------------------------------------------------------------- - /// @brief Notifies whether Framework starts generating semantics tree. + /// @brief Notifies whether Framework starts generating semantics tree. /// /// @param[in] enabled True if Framework starts generating semantics tree. /// diff --git a/shell/common/engine.h b/shell/common/engine.h index 7dc322640b558..df76af968a975 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -162,8 +162,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { //-------------------------------------------------------------------------- /// @brief When the Framework starts or stop generating semantics tree, /// this new information needs to be conveyed to the underlying - /// platform so that they can prepare to accept semantics update. - /// The engine delegates this task to the shell via this call. + /// platform so that they can prepare to accept semantics + /// update. The engine delegates this task to the shell via this + /// call. /// /// @see `OnEngineUpdateSemantics` /// From 8b68a161d0686da24b3d38da08ce4fd4b24e490f Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 10:55:38 -0800 Subject: [PATCH 06/16] update --- runtime/runtime_controller_unittests.cc | 2 +- shell/common/engine.h | 2 +- shell/platform/embedder/fixtures/main.dart | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/runtime_controller_unittests.cc b/runtime/runtime_controller_unittests.cc index 2ade7b7d4eb1b..70c3123292786 100644 --- a/runtime/runtime_controller_unittests.cc +++ b/runtime/runtime_controller_unittests.cc @@ -106,7 +106,7 @@ class RuntimeControllerTester { }; TEST_F(RuntimeControllerTest, CanUpdateSemanticsWhenSetSemanticsTreeEnabled) { - // The test is mostly setup code to get a SemanticsUpdate object. + // The code in this test is mostly setup code to get a SemanticsUpdate object. // The real test is in RuntimeControllerTester::CanUpdateSemantics. TaskRunners task_runners("test", // label GetCurrentTaskRunner(), // platform diff --git a/shell/common/engine.h b/shell/common/engine.h index df76af968a975..ca4a6cb179e8f 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -160,7 +160,7 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { CustomAccessibilityActionUpdates actions) = 0; //-------------------------------------------------------------------------- - /// @brief When the Framework starts or stop generating semantics tree, + /// @brief When the Framework starts or stops generating semantics tree, /// this new information needs to be conveyed to the underlying /// platform so that they can prepare to accept semantics /// update. The engine delegates this task to the shell via this diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index dabbb1e96ab7e..7bb5ac51b07dc 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -292,7 +292,7 @@ Future a11y_main() async { label: 'Archive', hint: 'archive message', ); - + PlatformDispatcher.instance.views.first.setSemanticsTreeEnabled(true); PlatformDispatcher.instance.views.first.updateSemantics(builder.build()); signalNativeTest(); @@ -385,7 +385,7 @@ Future a11y_string_attributes() async { textDirection: TextDirection.ltr, additionalActions: Int32List(0), ); - + PlatformDispatcher.instance.views.first.setSemanticsTreeEnabled(true); PlatformDispatcher.instance.views.first.updateSemantics(builder.build()); signalNativeTest(); } From ff43b6c4aae48e9b3715a924c4e7e8cbd52e0344 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 10:56:13 -0800 Subject: [PATCH 07/16] update --- shell/common/engine.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/common/engine.h b/shell/common/engine.h index ca4a6cb179e8f..898bc54487067 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -160,7 +160,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { CustomAccessibilityActionUpdates actions) = 0; //-------------------------------------------------------------------------- - /// @brief When the Framework starts or stops generating semantics tree, + /// @brief When the Framework starts or stops generating semantics + /// tree, /// this new information needs to be conveyed to the underlying /// platform so that they can prepare to accept semantics /// update. The engine delegates this task to the shell via this From e868fd997867bcd36a9dc6fc5b35080cb1bd5dc5 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 11:30:49 -0800 Subject: [PATCH 08/16] update --- lib/web_ui/lib/window.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/web_ui/lib/window.dart b/lib/web_ui/lib/window.dart index d42bbf7854395..078d36c4d5088 100644 --- a/lib/web_ui/lib/window.dart +++ b/lib/web_ui/lib/window.dart @@ -26,6 +26,7 @@ abstract class FlutterView { Display get display; void render(Scene scene, {Size? size}); void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update); + void setSemanticsTreeEnabled(bool enabled) {}; } abstract class SingletonFlutterWindow extends FlutterView { From 7eab3c604cb16759d213b644cee551ce3b5fc2a6 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 11:30:58 -0800 Subject: [PATCH 09/16] update --- lib/web_ui/lib/window.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/lib/window.dart b/lib/web_ui/lib/window.dart index 078d36c4d5088..106444fad689b 100644 --- a/lib/web_ui/lib/window.dart +++ b/lib/web_ui/lib/window.dart @@ -26,7 +26,7 @@ abstract class FlutterView { Display get display; void render(Scene scene, {Size? size}); void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update); - void setSemanticsTreeEnabled(bool enabled) {}; + void setSemanticsTreeEnabled(bool enabled) {} } abstract class SingletonFlutterWindow extends FlutterView { From ab3fa7a12d7b34b39c554d67892cdcea7ac47c8b Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 11:50:05 -0800 Subject: [PATCH 10/16] update --- lib/web_ui/lib/src/engine/window.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 0281aa7b7c287..3ece39121af77 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -135,6 +135,14 @@ class EngineFlutterView implements ui.FlutterView { semantics.updateSemantics(update); } + @override + void setSemanticsTreeEnabled(bool enabled) { + assert(!isDisposed, 'Trying to update semantics on a disposed EngineFlutterView.'); + if (!enabled) { + semantics.reset(); + } + } + late final GlobalHtmlAttributes _globalHtmlAttributes = GlobalHtmlAttributes( rootElement: dom.rootElement, hostElement: embeddingStrategy.hostElement, From 0057b801d330b547fcf739dbe8a12ebe2feca96b Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 12:20:56 -0800 Subject: [PATCH 11/16] update --- lib/web_ui/test/engine/scene_view_test.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/web_ui/test/engine/scene_view_test.dart b/lib/web_ui/test/engine/scene_view_test.dart index 76e3b250d3dc3..4b0e8c8d2df67 100644 --- a/lib/web_ui/test/engine/scene_view_test.dart +++ b/lib/web_ui/test/engine/scene_view_test.dart @@ -93,6 +93,10 @@ class StubFlutterView implements EngineFlutterView { void updateSemantics(ui.SemanticsUpdate update) { } + @override + void setSemanticsTreeEnabled(bool enabled) { + } + @override int get viewId => throw UnimplementedError(); From a5a3f442622c7f32167d76696aef8cbedf678ba4 Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:23:41 -0800 Subject: [PATCH 12/16] Update lib/ui/window.dart Co-authored-by: Michael Goderbauer --- lib/ui/window.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 0bc19ed40b4af..e3a453f07f80d 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -393,10 +393,12 @@ class FlutterView { @Native)>(symbol: 'PlatformConfigurationNativeApi::UpdateSemantics') external static void _updateSemantics(_NativeSemanticsUpdate update); - /// Sets whether framework starts generating semantics tree. + /// Informs the engine whether the framework is generating a semantics tree. /// - /// This function lets platform to prepare or dispose the resource for accepting - /// semantics update sent through [updateSemantics]. + /// After this has been set to true, platforms are expected to prepare for accepting + /// semantics update sent via [updateSemantics]. When this is set to false, platforms + /// may dispose any resources associated with processing semantics as no further + /// semantics updates will be sent via [updateSemantics]. void setSemanticsTreeEnabled(bool enabled) => _setSemanticsTreeEnabled(enabled); @Native(symbol: 'PlatformConfigurationNativeApi::SetSemanticsTreeEnabled') From b4389119eed2717e4fa93a0e5a00d50926cea063 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 14:28:22 -0800 Subject: [PATCH 13/16] update doc --- lib/ui/window.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/ui/window.dart b/lib/ui/window.dart index e3a453f07f80d..ee2ccfed3c86b 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -382,9 +382,8 @@ class FlutterView { /// Change the retained semantics data about this [FlutterView]. /// - /// If [PlatformDispatcher.semanticsEnabled] is true, the user has requested that this function - /// be called whenever the semantic content of this [FlutterView] - /// changes. + /// This method should only be called after [setSemanticsTreeEnabled] is called with + /// true. /// /// This function disposes the given update, which means the semantics update /// cannot be used further. From c67e23dfcb381888852afd03ec17b3eacb62299b Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 9 Dec 2024 15:25:43 -0800 Subject: [PATCH 14/16] update --- testing/scenario_app/lib/main.dart | 1 - testing/scenario_app/lib/src/locale_initialization.dart | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/testing/scenario_app/lib/main.dart b/testing/scenario_app/lib/main.dart index bb4807c2c6a09..636cd3b1521de 100644 --- a/testing/scenario_app/lib/main.dart +++ b/testing/scenario_app/lib/main.dart @@ -33,7 +33,6 @@ void main() { final ByteData data = ByteData(1); data.setUint8(0, 1); PlatformDispatcher.instance.sendPlatformMessage('waiting_for_status', data, null); - view.setSemanticsTreeEnabled(true); } /// The FlutterView into which the [Scenario]s will be rendered. diff --git a/testing/scenario_app/lib/src/locale_initialization.dart b/testing/scenario_app/lib/src/locale_initialization.dart index 74eceaa53a526..d223065ce315e 100644 --- a/testing/scenario_app/lib/src/locale_initialization.dart +++ b/testing/scenario_app/lib/src/locale_initialization.dart @@ -81,7 +81,7 @@ class LocaleInitialization extends Scenario { ); final SemanticsUpdate semanticsUpdate = semanticsUpdateBuilder.build(); - + view.setSemanticsTreeEnabled(true); view.updateSemantics(semanticsUpdate); } From 14fd4ed58eb4b2d009f234698607df3738433070 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 11 Dec 2024 15:39:20 -0800 Subject: [PATCH 15/16] update --- lib/ui/window.dart | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/ui/window.dart b/lib/ui/window.dart index ee2ccfed3c86b..b8d92505e0742 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -382,8 +382,8 @@ class FlutterView { /// Change the retained semantics data about this [FlutterView]. /// - /// This method should only be called after [setSemanticsTreeEnabled] is called with - /// true. + /// Th [setSemanticsTreeEnabled] must be called with true before sending + /// update through this method. /// /// This function disposes the given update, which means the semantics update /// cannot be used further. @@ -394,10 +394,19 @@ class FlutterView { /// Informs the engine whether the framework is generating a semantics tree. /// + /// Only framework knows when semantics tree should be generated. It uses this + /// method to notify the engine such event. + /// + /// In the case where platforms want to enable semantics, e.g. when detected running + /// assitive technologies, it notifies framework through the + /// [PlatformDispatcher.onSemanticsEnabledChanged]. + /// /// After this has been set to true, platforms are expected to prepare for accepting /// semantics update sent via [updateSemantics]. When this is set to false, platforms /// may dispose any resources associated with processing semantics as no further /// semantics updates will be sent via [updateSemantics]. + /// + /// One must call this method with true before sending update through [updateSemantics]. void setSemanticsTreeEnabled(bool enabled) => _setSemanticsTreeEnabled(enabled); @Native(symbol: 'PlatformConfigurationNativeApi::SetSemanticsTreeEnabled') From f05255fabf41077b5dca8c85d67b378c6bd1301e Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 11 Dec 2024 15:41:04 -0800 Subject: [PATCH 16/16] format --- lib/ui/window.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui/window.dart b/lib/ui/window.dart index b8d92505e0742..381dc6e8ddd94 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -396,7 +396,7 @@ class FlutterView { /// /// Only framework knows when semantics tree should be generated. It uses this /// method to notify the engine such event. - /// + /// /// In the case where platforms want to enable semantics, e.g. when detected running /// assitive technologies, it notifies framework through the /// [PlatformDispatcher.onSemanticsEnabledChanged]. @@ -405,7 +405,7 @@ class FlutterView { /// semantics update sent via [updateSemantics]. When this is set to false, platforms /// may dispose any resources associated with processing semantics as no further /// semantics updates will be sent via [updateSemantics]. - /// + /// /// One must call this method with true before sending update through [updateSemantics]. void setSemanticsTreeEnabled(bool enabled) => _setSemanticsTreeEnabled(enabled);