diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/BUILD.gn b/shell/platform/fuchsia/flutter/tests/integration/touch-input/BUILD.gn index 83075b1a5cd54..051fc40bbf33b 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/touch-input/BUILD.gn +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/BUILD.gn @@ -46,6 +46,7 @@ executable("touch-input-test-bin") { "$fuchsia_sdk_root/pkg:scenic_cpp", "$fuchsia_sdk_root/pkg:sys_component_cpp_testing", "$fuchsia_sdk_root/pkg:zx", + "embedding-flutter-view:package", "touch-input-view:package", "//build/fuchsia/fidl:fuchsia.ui.gfx", "//flutter/fml", diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/README.md b/shell/platform/fuchsia/flutter/tests/integration/touch-input/README.md index 828db8566346d..006236a21c8ab 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/touch-input/README.md +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/README.md @@ -1,8 +1,9 @@ # touch-input `touch-input-test` exercises touch through a child view (in this case, the `touch-input-view` Dart component) and asserting -the precise location of the touch event. We do this by attaching the child view, injecting touch, and validating that the view -reports the touch event back with the correct coordinates. +the precise location of the touch event. We validate a touch event as valid through two ways: +- By attaching the child view, injecting touch, and validating that the view reports the touch event back with the correct coordinates. +- By embedding a child view into a parent view, injecting touch into both views, and validating that each view reports its touch event back with the correct coordinates. ```shell Injecting the tap event diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/BUILD.gn b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/BUILD.gn new file mode 100644 index 0000000000000..665050687076d --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/BUILD.gn @@ -0,0 +1,40 @@ +# 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("//build/fuchsia/sdk.gni") +import("//flutter/tools/fuchsia/dart/dart_library.gni") +import("//flutter/tools/fuchsia/flutter/flutter_component.gni") +import("//flutter/tools/fuchsia/gn-sdk/component.gni") +import("//flutter/tools/fuchsia/gn-sdk/package.gni") + +dart_library("lib") { + package_name = "embedding-flutter-view" + sources = [ "embedding-flutter-view.dart" ] + + deps = [ + "//flutter/tools/fuchsia/dart:fuchsia_services", + "//flutter/tools/fuchsia/dart:zircon", + "//flutter/tools/fuchsia/fidl:fuchsia.ui.app", + "//flutter/tools/fuchsia/fidl:fuchsia.ui.scenic", + "//flutter/tools/fuchsia/fidl:fuchsia.ui.test.input", + "//flutter/tools/fuchsia/fidl:fuchsia.ui.views", + ] +} + +flutter_component("component") { + testonly = true + component_name = "embedding-flutter-view" + manifest = rebase_path("meta/embedding-flutter-view.cml") + main_package = "embedding-flutter-view" + main_dart = "embedding-flutter-view.dart" + + deps = [ ":lib" ] +} + +fuchsia_package("package") { + testonly = true + package_name = "embedding-flutter-view" + + deps = [ ":component" ] +} diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/lib/embedding-flutter-view.dart b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/lib/embedding-flutter-view.dart new file mode 100644 index 0000000000000..745556bccc20b --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/lib/embedding-flutter-view.dart @@ -0,0 +1,175 @@ +// 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:typed_data'; +import 'dart:io'; +import 'dart:ui'; + +import 'package:fidl_fuchsia_ui_app/fidl_async.dart'; +import 'package:fidl_fuchsia_ui_views/fidl_async.dart'; +import 'package:fidl_fuchsia_ui_test_input/fidl_async.dart' as test_touch; +import 'package:fuchsia_services/services.dart'; +import 'package:zircon/zircon.dart'; + +void main(List args) { + print('Launching embedding-flutter-view'); + TestApp app = TestApp(ChildView.gfx(_launchGfxChildView())); + app.run(); +} + +class TestApp { + static const _black = Color.fromARGB(255, 0, 0, 0); + static const _blue = Color.fromARGB(255, 0, 0, 255); + + final ChildView childView; + final _responseListener = test_touch.TouchInputListenerProxy(); + + Color _backgroundColor = _blue; + + TestApp(this.childView) {} + + void run() { + childView.create((ByteData reply) { + // Set up window callbacks. + window.onPointerDataPacket = (PointerDataPacket packet) { + this.pointerDataPacket(packet); + }; + window.onMetricsChanged = () { + window.scheduleFrame(); + }; + window.onBeginFrame = (Duration duration) { + this.beginFrame(duration); + }; + + // The child view should be attached to Scenic now. + // Ready to build the scene. + window.scheduleFrame(); + }); + } + + void beginFrame(Duration duration) { + // Convert physical screen size of device to values + final pixelRatio = window.devicePixelRatio; + final size = window.physicalSize / pixelRatio; + final physicalBounds = Offset.zero & window.physicalSize; + final windowBounds = Offset.zero & size; + // Set up a Canvas that uses the screen size + final recorder = PictureRecorder(); + final canvas = Canvas(recorder, physicalBounds); + canvas.scale(pixelRatio); + // Draw something + final paint = Paint()..color = this._backgroundColor; + canvas.drawRect(windowBounds, paint); + final picture = recorder.endRecording(); + // Build the scene + final sceneBuilder = SceneBuilder() + ..pushClipRect(physicalBounds) + ..addPicture(Offset.zero, picture); + // Child view should take up half the screen + final childPhysicalSize = window.physicalSize * 0.5; + sceneBuilder + ..addPlatformView(childView.viewId, + width: childPhysicalSize.width, + height: size.height) + ..pop(); + sceneBuilder.pop(); + window.render(sceneBuilder.build()); + } + + void pointerDataPacket(PointerDataPacket packet) async { + int nowNanos = System.clockGetMonotonic(); + + for (PointerData data in packet.data) { + print('embedding-flutter-view received tap: ${data.toStringFull()}'); + + if (data.change == PointerChange.down) { + this._backgroundColor = _black; + } + + if (data.change == PointerChange.down || data.change == PointerChange.move) { + Incoming.fromSvcPath() + ..connectToService(_responseListener) + ..close(); + + _respond(test_touch.TouchInputListenerReportTouchInputRequest( + localX: data.physicalX, + localY: data.physicalY, + timeReceived: nowNanos, + componentName: 'embedding-flutter-view', + )); + } + } + + window.scheduleFrame(); + } + + void _respond(test_touch.TouchInputListenerReportTouchInputRequest request) async { + print('embedding-flutter-view reporting touch input to TouchInputListener'); + await _responseListener.reportTouchInput(request); + } +} + +class ChildView { + final ViewHolderToken viewHolderToken; + final ViewportCreationToken viewportCreationToken; + final int viewId; + + ChildView(this.viewportCreationToken) : viewHolderToken = null, viewId = viewportCreationToken.value.handle.handle { + assert(viewId != null); + } + + ChildView.gfx(this.viewHolderToken) : viewportCreationToken = null, viewId = viewHolderToken.value.handle.handle { + assert(viewId != null); + } + + void create(PlatformMessageResponseCallback callback) { + // Construct the dart:ui platform message to create the view, and when the + // return callback is invoked, build the scene. At that point, it is safe + // to embed the child view in the scene. + final viewOcclusionHint = Rect.zero; + final Map args = { + 'viewId': viewId, + 'hitTestable': true, + 'focusable': true, + 'viewOcclusionHintLTRB': [ + viewOcclusionHint.left, + viewOcclusionHint.top, + viewOcclusionHint.right, + viewOcclusionHint.bottom + ], + }; + + final ByteData createViewMessage = utf8.encoder.convert( + json.encode({ + 'method': 'View.create', + 'args': args, + }) + ).buffer.asByteData(); + + final platformViewsChannel = 'flutter/platform_views'; + + PlatformDispatcher.instance.sendPlatformMessage( + platformViewsChannel, + createViewMessage, + callback); + } +} + +ViewHolderToken _launchGfxChildView() { + ViewProviderProxy viewProvider = ViewProviderProxy(); + Incoming.fromSvcPath() + ..connectToService(viewProvider) + ..close(); + + final viewTokens = EventPairPair(); + assert(viewTokens.status == ZX.OK); + final viewHolderToken = ViewHolderToken(value: viewTokens.first); + final viewToken = ViewToken(value: viewTokens.second); + + viewProvider.createView(viewToken.value, null, null); + viewProvider.ctrl.close(); + + return viewHolderToken; +} diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/meta/embedding-flutter-view.cml b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/meta/embedding-flutter-view.cml new file mode 100644 index 0000000000000..f55ac3507b2c8 --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/meta/embedding-flutter-view.cml @@ -0,0 +1,38 @@ +// 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: [ "syslog/client.shard.cml" ], + program: { + data: "data/embedding-flutter-view", + + // Always use the jit runner for now. + // TODO(fxbug.dev/106577): Implement manifest merging build rules for V2 components. + runner: "flutter_jit_runner", + }, + capabilities: [ + { + protocol: [ "fuchsia.ui.app.ViewProvider" ], + }, + ], + expose: [ + { + protocol: [ "fuchsia.ui.app.ViewProvider" ], + from: "self", + }, + ], + use: [ + { + protocol: [ + "fuchsia.ui.app.ViewProvider", + "fuchsia.ui.scenic.Scenic", + "fuchsia.ui.test.input.TouchInputListener", + ] + }, + { + directory: "config-data", + rights: [ "r*" ], + path: "/config/data", + }, + ] +} diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/pubspec.yaml b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/pubspec.yaml new file mode 100644 index 0000000000000..fcd78ccdc8d9a --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/pubspec.yaml @@ -0,0 +1,8 @@ +# 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. + +name: embedding-flutter-view + +environment: + sdk: '>=2.18.0 <3.0.0' diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-test.cc b/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-test.cc index 78137ac0f49fd..9ebf909be4437 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-test.cc +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-test.cc @@ -126,14 +126,24 @@ using RealmBuilder = component_testing::RealmBuilder; // Max timeout in failure cases. // Set this as low as you can that still works across all test platforms. -constexpr zx::duration kTimeout = zx::min(5); +constexpr zx::duration kTimeout = zx::min(1); + +constexpr auto kTestUIStackUrl = + "fuchsia-pkg://fuchsia.com/gfx-root-presenter-test-ui-stack#meta/" + "test-ui-stack.cm"; constexpr auto kMockTouchInputListener = "touch_input_listener"; constexpr auto kMockTouchInputListenerRef = ChildRef{kMockTouchInputListener}; + constexpr auto kTouchInputView = "touch-input-view"; constexpr auto kTouchInputViewRef = ChildRef{kTouchInputView}; constexpr auto kTouchInputViewUrl = "fuchsia-pkg://fuchsia.com/touch-input-view#meta/touch-input-view.cm"; +constexpr auto kEmbeddingFlutterView = "embedding-flutter-view"; +constexpr auto kEmbeddingFlutterViewRef = ChildRef{kEmbeddingFlutterView}; +constexpr auto kEmbeddingFlutterViewUrl = + "fuchsia-pkg://fuchsia.com/embedding-flutter-view#meta/" + "embedding-flutter-view.cm"; bool CompareDouble(double f0, double f1, double epsilon) { return std::abs(f0 - f1) <= epsilon; @@ -195,11 +205,11 @@ class TouchInputListenerServer events_received_; }; -class FlutterTapTest : public PortableUITest, - public ::testing::Test, - public ::testing::WithParamInterface { +class FlutterTapTestBase : public PortableUITest, + public ::testing::Test, + public ::testing::WithParamInterface { protected: - ~FlutterTapTest() override { + ~FlutterTapTestBase() override { FML_CHECK(touch_injection_request_count() > 0) << "Injection expected but didn't happen."; } @@ -251,24 +261,41 @@ class FlutterTapTest : public PortableUITest, auto actual_x = pixel_scale * last_event.local_x(); auto actual_y = pixel_scale * last_event.local_y(); + auto actual_component = last_event.component_name(); + + bool last_event_matches = + CompareDouble(actual_x, expected_x, pixel_scale) && + CompareDouble(actual_y, expected_y, pixel_scale) && + last_event.component_name() == component_name; + + if (last_event_matches) { + FML_LOG(INFO) << "Received event for component " << component_name + << " at (" << expected_x << ", " << expected_y << ")"; + } else { + FML_LOG(WARNING) << "Expecting event for component " << component_name + << " at (" << expected_x << ", " << expected_y << "). " + << "Instead received event for component " + << actual_component << " at (" << actual_x << ", " + << actual_y << "), accounting for pixel scale of " + << pixel_scale; + } - FML_LOG(INFO) << "Expecting event for component " << component_name - << " at (" << expected_x << ", " << expected_y << ")"; - FML_LOG(INFO) << "Received event for component " << component_name - << " at (" << actual_x << ", " << actual_y - << "), accounting for pixel scale of " << pixel_scale; - - return CompareDouble(actual_x, expected_x, pixel_scale) && - CompareDouble(actual_y, expected_y, pixel_scale) && - last_event.component_name() == component_name; + return last_event_matches; } // Guaranteed to be initialized after SetUp(). uint32_t display_width() const { return display_width_; } uint32_t display_height() const { return display_height_; } + ParamType GetTestUIStackUrl() override { return GetParam(); }; + + std::unique_ptr touch_input_listener_server_; +}; + +class FlutterTapTest : public FlutterTapTestBase { private: void ExtendRealm() override { + FML_LOG(INFO) << "Extending realm"; // Key part of service setup: have this test component vend the // |TouchInputListener| service in the constructed realm. touch_input_listener_server_ = @@ -276,6 +303,7 @@ class FlutterTapTest : public PortableUITest, realm_builder()->AddLocalChild(kMockTouchInputListener, touch_input_listener_server_.get()); + // Add touch-input-view to the Realm realm_builder()->AddChild(kTouchInputView, kTouchInputViewUrl, component_testing::ChildOptions{ .environment = kFlutterRunnerEnvironment, @@ -293,26 +321,57 @@ class FlutterTapTest : public PortableUITest, .source = kTouchInputViewRef, .targets = {ParentRef()}}); } +}; - ParamType GetTestUIStackUrl() override { return GetParam(); }; +class FlutterEmbedTapTest : public FlutterTapTestBase { + private: + void ExtendRealm() override { + FML_LOG(INFO) << "Extending realm"; + // Key part of service setup: have this test component vend the + // |TouchInputListener| service in the constructed realm. + touch_input_listener_server_ = + std::make_unique(dispatcher()); + realm_builder()->AddLocalChild(kMockTouchInputListener, + touch_input_listener_server_.get()); - std::unique_ptr touch_input_listener_server_; + // Add touch-input-view to the Realm + realm_builder()->AddChild(kTouchInputView, kTouchInputViewUrl, + component_testing::ChildOptions{ + .environment = kFlutterRunnerEnvironment, + }); + // Add embedding-flutter-view to the Realm + // This component will embed touch-input-view as a child view + realm_builder()->AddChild(kEmbeddingFlutterView, kEmbeddingFlutterViewUrl, + component_testing::ChildOptions{ + .environment = kFlutterRunnerEnvironment, + }); + + // Route the TouchInput protocol capability to the Dart component + realm_builder()->AddRoute( + Route{.capabilities = {Protocol{ + fuchsia::ui::test::input::TouchInputListener::Name_}}, + .source = kMockTouchInputListenerRef, + .targets = {kFlutterJitRunnerRef, kTouchInputViewRef, + kEmbeddingFlutterViewRef}}); - fuchsia::ui::scenic::ScenicPtr scenic_; - uint32_t display_width_ = 0; - uint32_t display_height_ = 0; + realm_builder()->AddRoute( + Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}}, + .source = kEmbeddingFlutterViewRef, + .targets = {ParentRef()}}); + realm_builder()->AddRoute( + Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}}, + .source = kTouchInputViewRef, + .targets = {kEmbeddingFlutterViewRef}}); + } }; // Makes use of gtest's parameterized testing, allowing us // to test different combinations of test-ui-stack + runners. Currently, there // is just one combination. Documentation: // http://go/gunitadvanced#value-parameterized-tests -INSTANTIATE_TEST_SUITE_P( - FlutterTapTestParameterized, - FlutterTapTest, - ::testing::Values( - "fuchsia-pkg://fuchsia.com/gfx-root-presenter-test-ui-stack#meta/" - "test-ui-stack.cm")); +INSTANTIATE_TEST_SUITE_P(FlutterTapTestParameterized, + FlutterTapTest, + ::testing::Values(kTestUIStackUrl)); TEST_P(FlutterTapTest, FlutterTap) { // Launch client view, and wait until it's rendering to proceed with the test. @@ -335,5 +394,43 @@ TEST_P(FlutterTapTest, FlutterTap) { }); } +INSTANTIATE_TEST_SUITE_P(FlutterEmbedTapTestParameterized, + FlutterEmbedTapTest, + ::testing::Values(kTestUIStackUrl)); + +TEST_P(FlutterEmbedTapTest, FlutterEmbedTap) { + // Launch view + FML_LOG(INFO) << "Initializing scene"; + LaunchClientWithEmbeddedView(); + FML_LOG(INFO) << "Client launched"; + + { + // Embedded child view takes up the left side of the screen + // Expect a response from the child view if we inject a tap there + InjectTap(-500, -500); + RunLoopUntil([this] { + return LastEventReceivedMatches( + /*expected_x=*/static_cast(display_width() / 4.0f), + /*expected_y=*/static_cast(display_height() / 4.0f), + /*component_name=*/"touch-input-view"); + }); + } + + { + // Parent view takes up the right side of the screen + // Validate that parent can still receive taps + InjectTap(500, 500); + RunLoopUntil([this] { + return LastEventReceivedMatches( + /*expected_x=*/static_cast(display_width() / (4.0f / 3.0f)), + /*expected_y=*/static_cast(display_height() / (4.0f / 3.0f)), + /*component_name=*/"embedding-flutter-view"); + }); + } + + // There should be 2 injected taps + ASSERT_EQ(touch_injection_request_count(), 2); +} + } // namespace } // namespace touch_input_test::testing diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/lib/touch-input-view.dart b/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/lib/touch-input-view.dart index 3578ae93a6a05..58c1ac98edbad 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/lib/touch-input-view.dart +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/lib/touch-input-view.dart @@ -11,30 +11,17 @@ import 'package:fuchsia_services/services.dart'; import 'package:zircon/zircon.dart'; void main() { - print('Launching two-flutter view'); - MyApp app = MyApp(); + print('Launching touch-input-view'); + TestApp app = TestApp(); app.run(); } -class MyApp { - static const _red = Color.fromARGB(255, 244, 67, 54); - static const _orange = Color.fromARGB(255, 255, 152, 0); - static const _yellow = Color.fromARGB(255, 255, 235, 59); - static const _green = Color.fromARGB(255, 76, 175, 80); - static const _blue = Color.fromARGB(255, 33, 150, 143); - static const _purple = Color.fromARGB(255, 156, 39, 176); +class TestApp { + static const _yellow = Color.fromARGB(255, 255, 255, 0); + static const _pink = Color.fromARGB(255, 255, 0, 255); - final List _colors = [ - _red, - _orange, - _yellow, - _green, - _blue, - _purple, - ]; + Color _backgroundColor = _pink; - // Each tap will increment the counter, we then determine what color to choose - int _touchCounter = 0; final _responseListener = test_touch.TouchInputListenerProxy(); void run() { @@ -59,15 +46,14 @@ class MyApp { final pixelRatio = window.devicePixelRatio; final size = window.physicalSize / pixelRatio; final physicalBounds = Offset.zero & size * pixelRatio; - // Set up Canvas that uses the screen size + final windowBounds = Offset.zero & size; + // Set up a Canvas that uses the screen size final recorder = PictureRecorder(); final canvas = Canvas(recorder, physicalBounds); canvas.scale(pixelRatio, pixelRatio); // Draw something - // Color of the screen is set initially to the first value in _colors - // Incrementing _touchCounter will change screen color - final paint = Paint()..color = _colors[_touchCounter % _colors.length]; - canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); + final paint = Paint()..color = this._backgroundColor; + canvas.drawRect(windowBounds, paint); // Build the scene final picture = recorder.endRecording(); final sceneBuilder = SceneBuilder() @@ -83,10 +69,6 @@ class MyApp { for (PointerData data in packet.data) { print('touch-input-view received tap: ${data.toStringFull()}'); - if (data.change == PointerChange.down) { - _touchCounter++; - } - if (data.change == PointerChange.down || data.change == PointerChange.move) { Incoming.fromSvcPath() ..connectToService(_responseListener) diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/meta/touch-input-view.cml b/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/meta/touch-input-view.cml index bdd76ea1b30ee..0210fbd527a55 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/meta/touch-input-view.cml +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/meta/touch-input-view.cml @@ -24,11 +24,7 @@ use: [ { protocol: [ - "fuchsia.sysmem.Allocator", - "fuchsia.tracing.provider.Registry", - "fuchsia.ui.scenic.Scenic", "fuchsia.ui.test.input.TouchInputListener", - "fuchsia.vulkan.loader.Loader", ] } ] diff --git a/shell/platform/fuchsia/flutter/tests/integration/utils/portable_ui_test.cc b/shell/platform/fuchsia/flutter/tests/integration/utils/portable_ui_test.cc index 80aa700945294..74085ba945248 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/utils/portable_ui_test.cc +++ b/shell/platform/fuchsia/flutter/tests/integration/utils/portable_ui_test.cc @@ -31,9 +31,7 @@ using fuchsia_test_utils::CheckViewExistsInSnapshot; void PortableUITest::SetUp() { SetUpRealmBase(); - ExtendRealm(); - realm_ = std::make_unique(realm_builder_.Build()); } @@ -142,6 +140,7 @@ void PortableUITest::LaunchClient() { FML_LOG(ERROR) << "Error from test scene provider: " << &zx_status_get_string; }); + fuchsia::ui::test::scene::ControllerAttachClientViewRequest request; request.set_view_provider(realm_->Connect()); scene_provider_->RegisterViewTreeWatcher(view_tree_watcher_.NewRequest(), @@ -157,11 +156,55 @@ void PortableUITest::LaunchClient() { WatchViewGeometry(); FML_LOG(INFO) << "Waiting for client view to connect"; + // Wait for the client view to get attached to the view tree. RunLoopUntil( [this] { return HasViewConnected(*client_root_view_ref_koid_); }); FML_LOG(INFO) << "Client view has rendered"; } +void PortableUITest::LaunchClientWithEmbeddedView() { + LaunchClient(); + // At this point, the parent view must have rendered, so we just need to wait + // for the embedded view. + RunLoopUntil([this] { + if (!last_view_tree_snapshot_.has_value() || + !last_view_tree_snapshot_->has_views()) { + return false; + } + + if (!client_root_view_ref_koid_.has_value()) { + return false; + } + + for (const auto& view : last_view_tree_snapshot_->views()) { + if (!view.has_view_ref_koid() || + view.view_ref_koid() != *client_root_view_ref_koid_) { + continue; + } + + if (view.children().empty()) { + return false; + } + + // NOTE: We can't rely on the presence of the child view in + // `view.children()` to guarantee that it has rendered. The child view + // also needs to be present in `last_view_tree_snapshot_->views`. + return std::count_if( + last_view_tree_snapshot_->views().begin(), + last_view_tree_snapshot_->views().end(), + [view_to_find = + view.children().back()](const auto& view_to_check) { + return view_to_check.has_view_ref_koid() && + view_to_check.view_ref_koid() == view_to_find; + }) > 0; + } + + return false; + }); + + FML_LOG(INFO) << "Embedded view has rendered"; +} + void PortableUITest::RegisterTouchScreen() { FML_LOG(INFO) << "Registering fake touch screen"; input_registry_ = realm_->Connect(); diff --git a/shell/platform/fuchsia/flutter/tests/integration/utils/portable_ui_test.h b/shell/platform/fuchsia/flutter/tests/integration/utils/portable_ui_test.h index ad39c125f120a..42178a2ccfb54 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/utils/portable_ui_test.h +++ b/shell/platform/fuchsia/flutter/tests/integration/utils/portable_ui_test.h @@ -48,6 +48,9 @@ class PortableUITest : public ::loop_fixture::RealLoop { // Attaches a client view to the scene, and waits for it to render. void LaunchClient(); + // Attaches a view with an embedded child view to the scene, and waits for it + // to render. + void LaunchClientWithEmbeddedView(); // Returns true when the specified view is fully connected to the scene AND // has presented at least one frame of content. @@ -86,6 +89,10 @@ class PortableUITest : public ::loop_fixture::RealLoop { component_testing::RealmBuilder* realm_builder() { return &realm_builder_; } component_testing::RealmRoot* realm_root() { return realm_.get(); } + fuchsia::ui::scenic::ScenicPtr scenic_; + uint32_t display_width_ = 0; + uint32_t display_height_ = 0; + int touch_injection_request_count() const { return touch_injection_request_count_; } diff --git a/testing/fuchsia/test_suites.yaml b/testing/fuchsia/test_suites.yaml index fa33a1503bbc3..11301b96d4ab2 100644 --- a/testing/fuchsia/test_suites.yaml +++ b/testing/fuchsia/test_suites.yaml @@ -53,6 +53,7 @@ - touch-input-test-0.far - oot_flutter_jit_runner-0.far - gen/flutter/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/touch-input-view/touch-input-view.far + - gen/flutter/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/embedding-flutter-view/embedding-flutter-view.far - test_command: run-test-suite fuchsia-pkg://fuchsia.com/mouse-input-test#meta/mouse-input-test.cm packages: - mouse-input-test-0.far diff --git a/tools/fuchsia/devshell/run_integration_test.sh b/tools/fuchsia/devshell/run_integration_test.sh index f3b86ad9130e7..7b31a3a9f45af 100755 --- a/tools/fuchsia/devshell/run_integration_test.sh +++ b/tools/fuchsia/devshell/run_integration_test.sh @@ -60,7 +60,7 @@ case $test_name in test_packages=("text-input-test-0.far" "text-input-view.far") ;; touch-input) - test_packages=("touch-input-test-0.far" "touch-input-view.far") + test_packages=("touch-input-test-0.far" "touch-input-view.far" "embedding-flutter-view.far") ;; *) engine-error "Unknown test name $test_name. You may need to add it to $0"