diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2f76b8683b20b..be32f3594ccf8 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -928,6 +928,7 @@ FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/vmservice/empty.dart FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/vmservice/meta/vmservice.cmx FILE: ../../../flutter/shell/platform/fuchsia/flutter/accessibility_bridge.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/accessibility_bridge.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/collect_traces.dart FILE: ../../../flutter/shell/platform/fuchsia/flutter/component.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/component.h @@ -935,6 +936,7 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/compositor_context.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/compositor_context.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/engine.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/engine.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/flutter_runner_fakes.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/isolate_configurator.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/isolate_configurator.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/extract_far.dart @@ -955,9 +957,9 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/jit_product_runtime FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/jit_runtime FILE: ../../../flutter/shell/platform/fuchsia/flutter/platform_view.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/platform_view.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/platform_view_unittest.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/sample_unittests.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/session_connection.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/session_connection.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/surface.cc diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn index 2baf8ce33701a..de9cfa561e708 100644 --- a/shell/platform/fuchsia/flutter/BUILD.gn +++ b/shell/platform/fuchsia/flutter/BUILD.gn @@ -247,14 +247,39 @@ executable("flutter_runner_unittests") { output_name = "flutter_runner_tests" sources = [ - "sample_unittests.cc", + "accessibility_bridge.cc", + "accessibility_bridge.h", + "accessibility_bridge_unittest.cc", + "flutter_runner_fakes.h", + "logging.h", + "platform_view.cc", + "platform_view.h", + "platform_view_unittest.cc", + "surface.cc", + "surface.h", + "vsync_recorder.cc", + "vsync_recorder.h", + "vsync_waiter.cc", + "vsync_waiter.h", ] # This is needed for //third_party/googletest for linking zircon symbols. libs = [ "//fuchsia/sdk/$host_os/arch/$target_cpu/sysroot/lib/libzircon.so" ] deps = [ + ":aot", + "//build/fuchsia/fidl:fuchsia.accessibility.semantics", + "//build/fuchsia/fidl:fuchsia.modular", + "//build/fuchsia/pkg:async-loop-cpp", + "//build/fuchsia/pkg:async-loop-default", + "//build/fuchsia/pkg:scenic_cpp", + "//build/fuchsia/pkg:sys_cpp_testing", + "//flutter/lib/ui", + "//flutter/shell/common", + "//flutter/shell/platform/fuchsia/runtime/dart/utils", "//flutter/testing", + "//third_party/dart/runtime:libdart_jit", + "//third_party/dart/runtime/platform:libdart_platform_jit", ] } diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge.cc b/shell/platform/fuchsia/flutter/accessibility_bridge.cc index 2e0c338c35afb..8f3e28104964a 100644 --- a/shell/platform/fuchsia/flutter/accessibility_bridge.cc +++ b/shell/platform/fuchsia/flutter/accessibility_bridge.cc @@ -14,9 +14,10 @@ namespace flutter_runner { AccessibilityBridge::AccessibilityBridge( + Delegate& delegate, const std::shared_ptr services, fuchsia::ui::views::ViewRef view_ref) - : binding_(this) { + : delegate_(delegate), binding_(this) { services->Connect(fuchsia::accessibility::semantics::SemanticsManager::Name_, fuchsia_semantics_manager_.NewRequest().TakeChannel()); fuchsia_semantics_manager_.set_error_handler([](zx_status_t status) { @@ -240,4 +241,11 @@ void AccessibilityBridge::HitTest( fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback) {} +// |fuchsia::accessibility::semantics::SemanticListener| +void AccessibilityBridge::OnSemanticsModeChanged( + bool enabled, + OnSemanticsModeChangedCallback callback) { + delegate_.SetSemanticsEnabled(enabled); +} + } // namespace flutter_runner diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge.h b/shell/platform/fuchsia/flutter/accessibility_bridge.h index ac124c985bcb6..f81f995526323 100644 --- a/shell/platform/fuchsia/flutter/accessibility_bridge.h +++ b/shell/platform/fuchsia/flutter/accessibility_bridge.h @@ -36,6 +36,12 @@ namespace flutter_runner { class AccessibilityBridge : public fuchsia::accessibility::semantics::SemanticListener { public: + // A delegate to call when semantics are enabled or disabled. + class Delegate { + public: + virtual void SetSemanticsEnabled(bool enabled) = 0; + }; + // TODO(MI4-2531, FIDL-718): Remove this. We shouldn't be worried about // batching messages at this level. // FIDL may encode a C++ struct as larger than the sizeof the C++ struct. @@ -55,7 +61,8 @@ class AccessibilityBridge "flutter::SemanticsNode::id and " "fuchsia::accessibility::semantics::Node::node_id differ in size."); - AccessibilityBridge(const std::shared_ptr services, + AccessibilityBridge(Delegate& delegate, + const std::shared_ptr services, fuchsia::ui::views::ViewRef view_ref); // Returns true if accessible navigation is enabled. @@ -75,6 +82,8 @@ class AccessibilityBridge zx_status_t OnHoverMove(double x, double y); private: + AccessibilityBridge::Delegate& delegate_; + static constexpr int32_t kRootNodeId = 0; fidl::Binding binding_; fuchsia::accessibility::semantics::SemanticsManagerPtr @@ -128,9 +137,8 @@ class AccessibilityBridge callback) override; // |fuchsia::accessibility::semantics::SemanticListener| - void OnSemanticsModeChanged( - bool enabled, - OnSemanticsModeChangedCallback callback) override {} + void OnSemanticsModeChanged(bool enabled, + OnSemanticsModeChangedCallback callback) override; FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge); }; diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc b/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc new file mode 100644 index 0000000000000..d432acc7ddaed --- /dev/null +++ b/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc @@ -0,0 +1,297 @@ +// 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/shell/platform/fuchsia/flutter/accessibility_bridge.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "flutter/lib/ui/semantics/semantics_node.h" +#include "flutter/shell/platform/fuchsia/flutter/flutter_runner_fakes.h" + +namespace flutter_runner_test { + +class AccessibilityBridgeTestDelegate + : public flutter_runner::AccessibilityBridge::Delegate { + public: + void SetSemanticsEnabled(bool enabled) override { enabled_ = enabled; } + bool enabled() { return enabled_; } + + private: + bool enabled_; +}; + +class AccessibilityBridgeTest : public testing::Test { + public: + AccessibilityBridgeTest() + : loop_(&kAsyncLoopConfigAttachToCurrentThread), + services_provider_(loop_.dispatcher()) { + services_provider_.AddService( + semantics_manager_.GetHandler(loop_.dispatcher()), + SemanticsManager::Name_); + } + + void RunLoopUntilIdle() { + loop_.RunUntilIdle(); + loop_.ResetQuit(); + } + + protected: + void SetUp() override { + zx_status_t status = zx::eventpair::create( + /*flags*/ 0u, &view_ref_control_.reference, &view_ref_.reference); + EXPECT_EQ(status, ZX_OK); + + accessibility_bridge_ = + std::make_unique( + accessibility_delegate_, services_provider_.service_directory(), + std::move(view_ref_)); + RunLoopUntilIdle(); + } + + void TearDown() override { semantics_manager_.ResetTree(); } + + fuchsia::ui::views::ViewRefControl view_ref_control_; + fuchsia::ui::views::ViewRef view_ref_; + MockSemanticsManager semantics_manager_; + AccessibilityBridgeTestDelegate accessibility_delegate_; + std::unique_ptr accessibility_bridge_; + + private: + async::Loop loop_; + sys::testing::ServiceDirectoryProvider services_provider_; +}; + +TEST_F(AccessibilityBridgeTest, RegistersViewRef) { + EXPECT_TRUE(semantics_manager_.RegisterViewCalled()); +} + +TEST_F(AccessibilityBridgeTest, EnableDisable) { + EXPECT_FALSE(accessibility_delegate_.enabled()); + std::unique_ptr listener( + accessibility_bridge_.release()); + listener->OnSemanticsModeChanged(true, nullptr); + EXPECT_TRUE(accessibility_delegate_.enabled()); +} + +TEST_F(AccessibilityBridgeTest, DeletesChildrenTransitively) { + // Test that when a node is deleted, so are its transitive children. + flutter::SemanticsNode node2; + node2.id = 2; + + flutter::SemanticsNode node1; + node1.id = 1; + node1.childrenInTraversalOrder = {2}; + + flutter::SemanticsNode node0; + node0.id = 0; + node0.childrenInTraversalOrder = {1}; + + accessibility_bridge_->AddSemanticsNodeUpdate({ + {0, node0}, + {1, node1}, + {2, node2}, + }); + RunLoopUntilIdle(); + + EXPECT_EQ(0, semantics_manager_.DeleteCount()); + EXPECT_EQ(1, semantics_manager_.UpdateCount()); + EXPECT_EQ(1, semantics_manager_.CommitCount()); + EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size()); + EXPECT_EQ(0U, semantics_manager_.LastDeletedNodeIds().size()); + EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); + EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); + + // Remove the children + node0.childrenInTraversalOrder.clear(); + accessibility_bridge_->AddSemanticsNodeUpdate({ + {0, node0}, + }); + RunLoopUntilIdle(); + + EXPECT_EQ(1, semantics_manager_.DeleteCount()); + EXPECT_EQ(2, semantics_manager_.UpdateCount()); + EXPECT_EQ(2, semantics_manager_.CommitCount()); + EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); + ASSERT_EQ(std::vector({1, 2}), + semantics_manager_.LastDeletedNodeIds()); + EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); + EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); +} + +TEST_F(AccessibilityBridgeTest, TruncatesLargeLabel) { + // Test that labels which are too long are truncated. + flutter::SemanticsNode node0; + node0.id = 0; + + flutter::SemanticsNode node1; + node1.id = 1; + + flutter::SemanticsNode bad_node; + bad_node.id = 2; + bad_node.label = + std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE + 1, '2'); + + node0.childrenInTraversalOrder = {1, 2}; + + accessibility_bridge_->AddSemanticsNodeUpdate({ + {0, node0}, + {1, node1}, + {2, bad_node}, + }); + RunLoopUntilIdle(); + + // Nothing to delete, but we should have broken + EXPECT_EQ(0, semantics_manager_.DeleteCount()); + EXPECT_EQ(1, semantics_manager_.UpdateCount()); + EXPECT_EQ(1, semantics_manager_.CommitCount()); + EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size()); + auto trimmed_node = + std::find_if(semantics_manager_.LastUpdatedNodes().begin(), + semantics_manager_.LastUpdatedNodes().end(), + [id = static_cast(bad_node.id)]( + fuchsia::accessibility::semantics::Node const& node) { + return node.node_id() == id; + }); + ASSERT_NE(trimmed_node, semantics_manager_.LastUpdatedNodes().end()); + ASSERT_TRUE(trimmed_node->has_attributes()); + EXPECT_EQ( + trimmed_node->attributes().label(), + std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '2')); + EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); + EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); +} + +TEST_F(AccessibilityBridgeTest, SplitsLargeUpdates) { + // Test that labels which are too long are truncated. + flutter::SemanticsNode node0; + node0.id = 0; + + flutter::SemanticsNode node1; + node1.id = 1; + node1.label = + std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '1'); + + flutter::SemanticsNode node2; + node2.id = 2; + node2.label = "2"; + + flutter::SemanticsNode node3; + node3.id = 3; + node3.label = "3"; + + flutter::SemanticsNode node4; + node4.id = 4; + node4.label = + std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '4'); + + node0.childrenInTraversalOrder = {1, 2}; + node1.childrenInTraversalOrder = {3, 4}; + + accessibility_bridge_->AddSemanticsNodeUpdate({ + {0, node0}, + {1, node1}, + {2, node2}, + {3, node3}, + {4, node4}, + }); + RunLoopUntilIdle(); + + // Nothing to delete, but we should have broken into groups (4, 3, 2), (1, 0) + EXPECT_EQ(0, semantics_manager_.DeleteCount()); + EXPECT_EQ(2, semantics_manager_.UpdateCount()); + EXPECT_EQ(1, semantics_manager_.CommitCount()); + EXPECT_EQ(2U, semantics_manager_.LastUpdatedNodes().size()); + EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); + EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); +} + +TEST_F(AccessibilityBridgeTest, HandlesCycles) { + // Test that cycles don't cause fatal error. + flutter::SemanticsNode node0; + node0.id = 0; + node0.childrenInTraversalOrder.push_back(0); + accessibility_bridge_->AddSemanticsNodeUpdate({ + {0, node0}, + }); + RunLoopUntilIdle(); + + EXPECT_EQ(0, semantics_manager_.DeleteCount()); + EXPECT_EQ(1, semantics_manager_.UpdateCount()); + EXPECT_EQ(1, semantics_manager_.CommitCount()); + EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); + EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); + + node0.childrenInTraversalOrder = {0, 1}; + flutter::SemanticsNode node1; + node1.id = 1; + node1.childrenInTraversalOrder = {0}; + accessibility_bridge_->AddSemanticsNodeUpdate({ + {0, node0}, + {1, node1}, + }); + RunLoopUntilIdle(); + + EXPECT_EQ(0, semantics_manager_.DeleteCount()); + EXPECT_EQ(2, semantics_manager_.UpdateCount()); + EXPECT_EQ(2, semantics_manager_.CommitCount()); + EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); + EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); +} + +TEST_F(AccessibilityBridgeTest, BatchesLargeMessages) { + // Tests that messages get batched appropriately. + flutter::SemanticsNode node0; + node0.id = 0; + + flutter::SemanticsNodeUpdates update; + + const int32_t child_nodes = fuchsia::accessibility::semantics::MAX_FAN_OUT; + const int32_t leaf_nodes = fuchsia::accessibility::semantics::MAX_FAN_OUT; + for (int32_t i = 1; i < child_nodes + 1; i++) { + flutter::SemanticsNode node; + node.id = i; + node0.childrenInTraversalOrder.push_back(i); + for (int32_t j = 0; j < leaf_nodes; j++) { + flutter::SemanticsNode leaf_node; + int id = (i * child_nodes) + ((j + 1) * leaf_nodes); + leaf_node.id = id; + leaf_node.label = "A relatively simple label"; + node.childrenInTraversalOrder.push_back(id); + update.insert(std::make_pair(id, std::move(leaf_node))); + } + update.insert(std::make_pair(i, std::move(node))); + } + + update.insert(std::make_pair(0, std::move(node0))); + accessibility_bridge_->AddSemanticsNodeUpdate(update); + RunLoopUntilIdle(); + + EXPECT_EQ(0, semantics_manager_.DeleteCount()); + EXPECT_EQ(13, semantics_manager_.UpdateCount()); + EXPECT_EQ(1, semantics_manager_.CommitCount()); + EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); + EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); + + // Remove the children + node0.childrenInTraversalOrder.clear(); + accessibility_bridge_->AddSemanticsNodeUpdate({ + {0, node0}, + }); + RunLoopUntilIdle(); + + EXPECT_EQ(1, semantics_manager_.DeleteCount()); + EXPECT_EQ(14, semantics_manager_.UpdateCount()); + EXPECT_EQ(2, semantics_manager_.CommitCount()); + EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); + EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); +} +} // namespace flutter_runner_test diff --git a/shell/platform/fuchsia/flutter/flutter_runner_fakes.h b/shell/platform/fuchsia/flutter/flutter_runner_fakes.h new file mode 100644 index 0000000000000..4516d00f26c15 --- /dev/null +++ b/shell/platform/fuchsia/flutter/flutter_runner_fakes.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef TOPAZ_RUNTIME_FLUTTER_RUNNER_PLATFORM_VIEW_FAKES_H_ +#define TOPAZ_RUNTIME_FLUTTER_RUNNER_PLATFORM_VIEW_FAKES_H_ + +#include + +namespace flutter_runner_test { +using fuchsia::accessibility::semantics::SemanticsManager; + +class MockSemanticsManager + : public SemanticsManager, + public fuchsia::accessibility::semantics::SemanticTree { + public: + MockSemanticsManager() : tree_binding_(this) {} + + // |fuchsia::accessibility::semantics::SemanticsManager|: + void RegisterViewForSemantics( + fuchsia::ui::views::ViewRef view_ref, + fidl::InterfaceHandle + handle, + fidl::InterfaceRequest + semantic_tree) override { + tree_binding_.Bind(std::move(semantic_tree)); + has_view_ref_ = true; + } + + fidl::InterfaceRequestHandler GetHandler( + async_dispatcher_t* dispatcher) { + return bindings_.GetHandler(this, dispatcher); + } + + bool RegisterViewCalled() { return has_view_ref_; } + + void ResetTree() { + update_count_ = 0; + delete_count_ = 0; + commit_count_ = 0; + last_updated_nodes_.clear(); + last_deleted_node_ids_.clear(); + delete_overflowed_ = false; + update_overflowed_ = false; + } + + void UpdateSemanticNodes( + std::vector nodes) override { + update_count_++; + if (!update_overflowed_) { + size_t size = 0; + for (const auto& node : nodes) { + size += sizeof(node); + size += sizeof(node.attributes().label().size()); + } + update_overflowed_ = size > ZX_CHANNEL_MAX_MSG_BYTES; + } + last_updated_nodes_ = std::move(nodes); + } + + void DeleteSemanticNodes(std::vector node_ids) override { + delete_count_++; + if (!delete_overflowed_) { + size_t size = + sizeof(node_ids) + + (node_ids.size() * flutter_runner::AccessibilityBridge::kNodeIdSize); + delete_overflowed_ = size > ZX_CHANNEL_MAX_MSG_BYTES; + } + last_deleted_node_ids_ = std::move(node_ids); + } + + const std::vector& LastDeletedNodeIds() const { + return last_deleted_node_ids_; + } + + int DeleteCount() const { return delete_count_; } + bool DeleteOverflowed() const { return delete_overflowed_; } + + int UpdateCount() const { return update_count_; } + bool UpdateOverflowed() const { return update_overflowed_; } + + int CommitCount() const { return commit_count_; } + + const std::vector& LastUpdatedNodes() + const { + return last_updated_nodes_; + } + + void CommitUpdates(CommitUpdatesCallback callback) override { + commit_count_++; + } + + private: + bool has_view_ref_ = false; + fidl::BindingSet bindings_; + fidl::Binding tree_binding_; + + std::vector last_updated_nodes_; + bool update_overflowed_; + int update_count_; + int delete_count_; + bool delete_overflowed_; + std::vector last_deleted_node_ids_; + int commit_count_; +}; + +} // namespace flutter_runner_test + +#endif // TOPAZ_RUNTIME_FLUTTER_RUNNER_PLATFORM_VIEW_FAKES_H_ diff --git a/shell/platform/fuchsia/flutter/meta/flutter_runner_tests.cmx b/shell/platform/fuchsia/flutter/meta/flutter_runner_tests.cmx index 0fff3a5984cfd..fedcb77867acb 100644 --- a/shell/platform/fuchsia/flutter/meta/flutter_runner_tests.cmx +++ b/shell/platform/fuchsia/flutter/meta/flutter_runner_tests.cmx @@ -8,7 +8,6 @@ "deprecated-ambient-replace-as-executable" ], "services": [ - "fuchsia.accessibility.SettingsManager", "fuchsia.accessibility.semantics.SemanticsManager", "fuchsia.sys.Launcher" ] diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index 9646ba3d709a4..38832a539ab28 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -11,6 +11,7 @@ #include "flutter/fml/logging.h" #include "flutter/lib/ui/compositing/scene_host.h" #include "flutter/lib/ui/window/pointer_data.h" +#include "flutter/lib/ui/window/window.h" #include "logging.h" #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" @@ -77,7 +78,7 @@ void SetInterfaceErrorHandler(fidl::Binding& binding, std::string name) { } PlatformView::PlatformView( - PlatformView::Delegate& delegate, + flutter::PlatformView::Delegate& delegate, std::string debug_label, fuchsia::ui::views::ViewRefControl view_ref_control, fuchsia::ui::views::ViewRef view_ref, @@ -129,12 +130,7 @@ PlatformView::PlatformView( fuchsia::ui::views::ViewRef accessibility_view_ref; view_ref_.Clone(&accessibility_view_ref); accessibility_bridge_ = std::make_unique( - runner_services, std::move(accessibility_view_ref)); - - // TODO(SCN-975): Re-enable. Likely that Engine should clone the ViewToken - // and pass the clone in here. - // view_->GetToken(std::bind(&PlatformView::ConnectSemanticsProvider, this, - // std::placeholders::_1)); + *this, runner_services, std::move(accessibility_view_ref)); } PlatformView::~PlatformView() = default; @@ -601,9 +597,15 @@ void PlatformView::HandlePlatformMessage( } // |flutter::PlatformView| +// |flutter_runner::AccessibilityBridge::Delegate| void PlatformView::SetSemanticsEnabled(bool enabled) { - accessibility_bridge_->SetSemanticsEnabled(enabled); flutter::PlatformView::SetSemanticsEnabled(enabled); + if (enabled) { + SetAccessibilityFeatures(static_cast( + flutter::AccessibilityFeatureFlag::kAccessibleNavigation)); + } else { + SetAccessibilityFeatures(0); + } } // |flutter::PlatformView| diff --git a/shell/platform/fuchsia/flutter/platform_view.h b/shell/platform/fuchsia/flutter/platform_view.h index d142d6682dd6d..6317c75c48f1f 100644 --- a/shell/platform/fuchsia/flutter/platform_view.h +++ b/shell/platform/fuchsia/flutter/platform_view.h @@ -38,9 +38,10 @@ using OnEnableWireframe = fit::function; // thread. class PlatformView final : public flutter::PlatformView, private fuchsia::ui::scenic::SessionListener, - public fuchsia::ui::input::InputMethodEditorClient { + public fuchsia::ui::input::InputMethodEditorClient, + public AccessibilityBridge::Delegate { public: - PlatformView(PlatformView::Delegate& delegate, + PlatformView(flutter::PlatformView::Delegate& delegate, std::string debug_label, fuchsia::ui::views::ViewRefControl view_ref_control, fuchsia::ui::views::ViewRef view_ref, @@ -55,7 +56,7 @@ class PlatformView final : public flutter::PlatformView, OnSizeChangeHint session_size_change_hint_callback, OnEnableWireframe wireframe_enabled_callback, zx_handle_t vsync_event_handle); - PlatformView(PlatformView::Delegate& delegate, + PlatformView(flutter::PlatformView::Delegate& delegate, std::string debug_label, flutter::TaskRunners task_runners, fidl::InterfaceHandle @@ -66,6 +67,10 @@ class PlatformView final : public flutter::PlatformView, void UpdateViewportMetrics(const fuchsia::ui::gfx::Metrics& metrics); + // |flutter::PlatformView| + // |flutter_runner::AccessibilityBridge::Delegate| + void SetSemanticsEnabled(bool enabled) override; + private: const std::string debug_label_; // TODO(MI4-2490): remove once ViewRefControl is passed to Scenic and kept @@ -153,9 +158,6 @@ class PlatformView final : public flutter::PlatformView, void HandlePlatformMessage( fml::RefPtr message) override; - // |flutter::PlatformView| - void SetSemanticsEnabled(bool enabled) override; - // |flutter::PlatformView| void UpdateSemantics( flutter::SemanticsNodeUpdates update, diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc new file mode 100644 index 0000000000000..5bbca9e4718ff --- /dev/null +++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc @@ -0,0 +1,202 @@ +// 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/shell/platform/fuchsia/flutter/platform_view.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "flutter/lib/ui/window/platform_message.h" +#include "flutter/lib/ui/window/window.h" +#include "fuchsia/ui/views/cpp/fidl.h" +#include "googletest/googletest/include/gtest/gtest.h" + +namespace flutter_runner_test::flutter_runner_a11y_test { + +class PlatformViewTests : public testing::Test { + protected: + PlatformViewTests() : loop_(&kAsyncLoopConfigAttachToCurrentThread) {} + + async_dispatcher_t* dispatcher() { return loop_.dispatcher(); } + + void RunLoopUntilIdle() { + loop_.RunUntilIdle(); + loop_.ResetQuit(); + } + + private: + async::Loop loop_; + + FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewTests); +}; + +class MockPlatformViewDelegate : public flutter::PlatformView::Delegate { + public: + // |flutter::PlatformView::Delegate| + void OnPlatformViewCreated(std::unique_ptr surface) {} + // |flutter::PlatformView::Delegate| + void OnPlatformViewDestroyed() {} + // |flutter::PlatformView::Delegate| + void OnPlatformViewSetNextFrameCallback(fml::closure closure) {} + // |flutter::PlatformView::Delegate| + void OnPlatformViewSetViewportMetrics( + const flutter::ViewportMetrics& metrics) {} + // |flutter::PlatformView::Delegate| + void OnPlatformViewDispatchPlatformMessage( + fml::RefPtr message) {} + // |flutter::PlatformView::Delegate| + void OnPlatformViewDispatchPointerDataPacket( + std::unique_ptr packet) {} + // |flutter::PlatformView::Delegate| + void OnPlatformViewDispatchSemanticsAction(int32_t id, + flutter::SemanticsAction action, + std::vector args) {} + // |flutter::PlatformView::Delegate| + void OnPlatformViewSetSemanticsEnabled(bool enabled) { + semantics_enabled_ = enabled; + } + // |flutter::PlatformView::Delegate| + void OnPlatformViewSetAccessibilityFeatures(int32_t flags) { + semantics_features_ = flags; + } + // |flutter::PlatformView::Delegate| + void OnPlatformViewRegisterTexture( + std::shared_ptr texture) {} + // |flutter::PlatformView::Delegate| + void OnPlatformViewUnregisterTexture(int64_t texture_id) {} + // |flutter::PlatformView::Delegate| + void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) {} + + bool SemanticsEnabled() const { return semantics_enabled_; } + int32_t SemanticsFeatures() const { return semantics_features_; } + + private: + bool semantics_enabled_ = false; + int32_t semantics_features_ = 0; +}; + +TEST_F(PlatformViewTests, ChangesAccessibilitySettings) { + sys::testing::ServiceDirectoryProvider services_provider(dispatcher()); + + MockPlatformViewDelegate delegate; + zx::eventpair a, b; + zx::eventpair::create(/* flags */ 0u, &a, &b); + auto view_ref = fuchsia::ui::views::ViewRef({ + .reference = std::move(a), + }); + auto view_ref_control = fuchsia::ui::views::ViewRefControl({ + .reference = std::move(b), + }); + flutter::TaskRunners task_runners = + flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr); + + EXPECT_FALSE(delegate.SemanticsEnabled()); + EXPECT_EQ(delegate.SemanticsFeatures(), 0); + + auto platform_view = flutter_runner::PlatformView( + delegate, // delegate + "test_platform_view", // label + std::move(view_ref_control), // view_ref_control + std::move(view_ref), // view_ref + std::move(task_runners), // task_runners + services_provider.service_directory(), // runner_services + nullptr, // parent_environment_service_provider_handle + nullptr, // session_listener_request + nullptr, // on_session_listener_error_callback + nullptr, // session_metrics_did_change_callback + nullptr, // session_size_change_hint_callback + nullptr, // on_enable_wireframe_callback, + 0u // vsync_event_handle + ); + + RunLoopUntilIdle(); + + platform_view.SetSemanticsEnabled(true); + + EXPECT_TRUE(delegate.SemanticsEnabled()); + EXPECT_EQ(delegate.SemanticsFeatures(), + static_cast( + flutter::AccessibilityFeatureFlag::kAccessibleNavigation)); + + platform_view.SetSemanticsEnabled(false); + + EXPECT_FALSE(delegate.SemanticsEnabled()); + EXPECT_EQ(delegate.SemanticsFeatures(), 0); +} + +// Test to make sure that PlatformView correctly registers messages sent on +// the "flutter/platform_views" channel, correctly parses the JSON it receives +// and calls the EnableWireframeCallback with the appropriate args. +TEST_F(PlatformViewTests, EnableWireframeTest) { + sys::testing::ServiceDirectoryProvider services_provider(dispatcher()); + MockPlatformViewDelegate delegate; + zx::eventpair a, b; + zx::eventpair::create(/* flags */ 0u, &a, &b); + auto view_ref = fuchsia::ui::views::ViewRef({ + .reference = std::move(a), + }); + auto view_ref_control = fuchsia::ui::views::ViewRefControl({ + .reference = std::move(b), + }); + flutter::TaskRunners task_runners = + flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr); + + // Test wireframe callback function. If the message sent to the platform view + // was properly handled and parsed, this function should be called, setting + // |wireframe_enabled| to true. + bool wireframe_enabled = false; + auto EnableWireframeCallback = [&wireframe_enabled](bool should_enable) { + wireframe_enabled = should_enable; + }; + + auto platform_view = flutter_runner::PlatformView( + delegate, // delegate + "test_platform_view", // label + std::move(view_ref_control), // view_ref_control + std::move(view_ref), // view_refs + std::move(task_runners), // task_runners + services_provider.service_directory(), // runner_services + nullptr, // parent_environment_service_provider_handle + nullptr, // session_listener_request + nullptr, // on_session_listener_error_callback + nullptr, // session_metrics_did_change_callback + nullptr, // session_size_change_hint_callback + EnableWireframeCallback, // on_enable_wireframe_callback, + 0u // vsync_event_handle + ); + + // Cast platform_view to its base view so we can have access to the public + // "HandlePlatformMessage" function. + auto base_view = dynamic_cast(&platform_view); + EXPECT_TRUE(base_view); + + // JSON for the message to be passed into the PlatformView. + const uint8_t txt[] = + "{" + " \"method\":\"View.enableWireframe\"," + " \"args\": {" + " \"enable\":true" + " }" + "}"; + + fml::RefPtr message = + fml::MakeRefCounted( + "flutter/platform_views", + std::vector(txt, txt + sizeof(txt)), + fml::RefPtr()); + base_view->HandlePlatformMessage(message); + + RunLoopUntilIdle(); + + EXPECT_TRUE(wireframe_enabled); +} + +} // namespace flutter_runner_test::flutter_runner_a11y_test diff --git a/shell/platform/fuchsia/flutter/sample_unittests.cc b/shell/platform/fuchsia/flutter/sample_unittests.cc deleted file mode 100644 index fd796edb3b804..0000000000000 --- a/shell/platform/fuchsia/flutter/sample_unittests.cc +++ /dev/null @@ -1,15 +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. - -#include "gtest/gtest.h" - -namespace flutter { -namespace testing { - -TEST(FlutterRunnerSampleTest, Sample) { - ASSERT_TRUE(true); -} - -} // namespace testing -} // namespace flutter