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/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..381dc6e8ddd94 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. + /// 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. @@ -393,6 +392,26 @@ class FlutterView { @Native)>(symbol: 'PlatformConfigurationNativeApi::UpdateSemantics') external static void _updateSemantics(_NativeSemanticsUpdate update); + /// 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') + 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..43b1e3dfdfc65 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -621,6 +621,14 @@ 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..9e71d347add3f 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/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, diff --git a/lib/web_ui/lib/window.dart b/lib/web_ui/lib/window.dart index d42bbf7854395..106444fad689b 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 { 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(); 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/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/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..96fe426274a6b 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -440,11 +440,17 @@ void RuntimeController::CheckIfAllViewsRendered() { // |PlatformConfigurationClient| void RuntimeController::UpdateSemantics(SemanticsUpdate* update) { - if (platform_data_.semantics_enabled) { + if (semantics_tree_enabled_) { client_.UpdateSemantics(update->takeNodes(), update->takeActions()); } } +// |PlatformConfigurationClient| +void RuntimeController::SetSemanticsTreeEnabled(bool enabled) { + semantics_tree_enabled_ = enabled; + client_.SetSemanticsTreeEnabled(enabled); +} + // |PlatformConfigurationClient| void RuntimeController::HandlePlatformMessage( std::unique_ptr message) { diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 3ed05aa945461..6e6b79ce4e972 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 @@ -700,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. // @@ -752,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; @@ -779,6 +787,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..70c3123292786 --- /dev/null +++ b/runtime/runtime_controller_unittests.cc @@ -0,0 +1,151 @@ +// 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 SetSemanticsTreeEnabled(bool enabled) override {} + + 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 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()); + } + + private: + MockRuntimeDelegate delegate_; + UIDartState::Context& context_; + RuntimeController runtime_controller_; +}; + +TEST_F(RuntimeControllerTest, CanUpdateSemanticsWhenSetSemanticsTreeEnabled) { + // 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 + 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->CanUpdateSemanticsWhenSetSemanticsTreeEnabled(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/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..898bc54487067 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -159,6 +159,20 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { SemanticsNodeUpdates updates, CustomAccessibilityActionUpdates actions) = 0; + //-------------------------------------------------------------------------- + /// @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 + /// 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 +1004,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/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/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index 4e1da467d22bd..266d1322a5db1 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -87,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_; } @@ -98,6 +101,11 @@ class PlatformViewIOS final : public PlatformView { return platform_message_handler_; } + /** + * Gets the accessibility bridge created in this platform view. + */ + AccessibilityBridge* GetAccessibilityBridge() { return accessibility_bridge_.get(); } + private: /// Smart pointer for use with objective-c observers. /// This guarantees we remove the observer. @@ -113,24 +121,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,7 +128,7 @@ 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_; diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index b726fd8fc1ee4..0d834e3e2233a 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,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} if (ios_surface_ || !owner_controller) { NotifyDestroyed(); ios_surface_.reset(); - accessibility_bridge_.Clear(); + accessibility_bridge_.reset(); } owner_controller_ = owner_controller; @@ -97,7 +73,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 +96,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); } } @@ -165,22 +141,10 @@ 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| +// |PlatformView| void PlatformViewIOS::SetAccessibilityFeatures(int32_t flags) { PlatformView::SetAccessibilityFeatures(flags); } @@ -189,6 +153,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 @@ -196,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 new file mode 100644 index 0000000000000..2fa3080501c76 --- /dev/null +++ b/shell/platform/darwin/ios/platform_view_ios_test.mm @@ -0,0 +1,104 @@ +// 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)testSetSemanticsTreeEnabled { + 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->SetSemanticsTreeEnabled(true); + XCTAssertTrue(platform_view->GetAccessibilityBridge()); + platform_view->SetSemanticsTreeEnabled(false); + XCTAssertFalse(platform_view->GetAccessibilityBridge()); + latch.Signal(); + }); + latch.Wait(); + + [engine stopMocking]; +} + +@end 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(); } 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); }