diff --git a/examples/glfw/FlutterEmbedderGLFW.cc b/examples/glfw/FlutterEmbedderGLFW.cc index ae7fad5dec2ba..a5e5e63643809 100644 --- a/examples/glfw/FlutterEmbedderGLFW.cc +++ b/examples/glfw/FlutterEmbedderGLFW.cc @@ -81,6 +81,9 @@ void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { event.width = width * g_pixelRatio; event.height = height * g_pixelRatio; event.pixel_ratio = g_pixelRatio; + // This example only supports a single window, therefore we assume the event + // occurred in the only view, the implicit view. + event.view_id = kImplicitViewId; FlutterEngineSendWindowMetricsEvent( reinterpret_cast(glfwGetWindowUserPointer(window)), &event); diff --git a/examples/glfw_drm/FlutterEmbedderGLFW.cc b/examples/glfw_drm/FlutterEmbedderGLFW.cc index f4a9ce8ead473..79caba2dc2fef 100644 --- a/examples/glfw_drm/FlutterEmbedderGLFW.cc +++ b/examples/glfw_drm/FlutterEmbedderGLFW.cc @@ -104,6 +104,9 @@ void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { event.width = width * g_pixelRatio; event.height = height * g_pixelRatio; event.pixel_ratio = g_pixelRatio; + // This example only supports a single window, therefore we assume the event + // occurred in the only view, the implicit view. + event.view_id = kImplicitViewId; FlutterEngineSendWindowMetricsEvent( reinterpret_cast(glfwGetWindowUserPointer(window)), &event); diff --git a/examples/vulkan_glfw/src/main.cc b/examples/vulkan_glfw/src/main.cc index c6fb9d591d496..ebd55dec02e9d 100644 --- a/examples/vulkan_glfw/src/main.cc +++ b/examples/vulkan_glfw/src/main.cc @@ -32,6 +32,7 @@ static const size_t kInitialWindowHeight = 600; // `VK_PRESENT_MODE_MAILBOX_KHR` for continual swap without horizontal tearing, // or `VK_PRESENT_MODE_IMMEDIATE_KHR` for no vsync. static const VkPresentModeKHR kPreferredPresentMode = VK_PRESENT_MODE_FIFO_KHR; +static constexpr FlutterViewId kImplicitViewId = 0; static_assert(FLUTTER_ENGINE_VERSION == 1, "This Flutter Embedder was authored against the stable Flutter " @@ -86,6 +87,9 @@ void GLFWcursorPositionCallbackAtPhase(GLFWwindow* window, std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch()) .count(); + // This example only supports a single window, therefore we assume the event + // occurred in the only view, the implicit view. + event.view_id = kImplicitViewId; FlutterEngineSendPointerEvent(g_state.engine, &event, 1); } @@ -130,6 +134,9 @@ void GLFWframebufferSizeCallback(GLFWwindow* window, int width, int height) { event.width = width; event.height = height; event.pixel_ratio = g_pixelRatio; + // This example only supports a single window, therefore we assume the event + // occurred in the only view, the implicit view. + event.view_id = kImplicitViewId; FlutterEngineSendWindowMetricsEvent(g_state.engine, &event); } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index e836cedd5920b..7b0174695533f 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -898,12 +898,6 @@ - (nonnull NSString*)executableName { } - (void)updateWindowMetricsForViewController:(FlutterViewController*)viewController { - if (viewController.viewId != kFlutterImplicitViewId) { - // TODO(dkwingsmt): The embedder API only supports single-view for now. As - // embedder APIs are converted to multi-view, this method should support any - // views. - return; - } if (!_engine || !viewController || !viewController.viewLoaded) { return; } @@ -922,6 +916,7 @@ - (void)updateWindowMetricsForViewController:(FlutterViewController*)viewControl .left = static_cast(scaledBounds.origin.x), .top = static_cast(scaledBounds.origin.y), .display_id = static_cast(displayId), + .view_id = viewController.viewId, }; _embedderAPI.SendWindowMetricsEvent(_engine, &windowMetricsEvent); } diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 4633fcad552d2..81bbfa1c2deea 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -2143,8 +2143,8 @@ FlutterEngineResult FlutterEngineSendWindowMetricsEvent( if (engine == nullptr || flutter_metrics == nullptr) { return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); } - // TODO(dkwingsmt): Use a real view ID when multiview is supported. - int64_t view_id = kFlutterImplicitViewId; + FlutterViewId view_id = + SAFE_ACCESS(flutter_metrics, view_id, kFlutterImplicitViewId); flutter::ViewportMetrics metrics; diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index d8adf5e77834c..e01b9453ffe32 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -859,6 +859,8 @@ typedef struct { double physical_view_inset_left; /// The identifier of the display the view is rendering on. FlutterEngineDisplayId display_id; + /// The view that this event is describing. + int64_t view_id; } FlutterWindowMetricsEvent; /// The phase of the pointer event. diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index dffa430fbb520..7270526b830e1 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -1334,6 +1334,42 @@ void pointer_data_packet_view_id() { signalNativeTest(); } +Map _getAllViewSizes() { + final Map result = {}; + for (final FlutterView view in PlatformDispatcher.instance.views) { + result[view.viewId] = view.physicalSize; + } + return result; +} + +List _findDifferences(Map a, Map b) { + final Set result = {}; + a.forEach((int viewId, Size sizeA) { + if (!b.containsKey(viewId) || b[viewId] != sizeA) { + result.add(viewId); + } + }); + b.forEach((int viewId, Size sizeB) { + if (!a.containsKey(viewId)) { + result.add(viewId); + } + }); + return result.toList()..sort(); +} + +@pragma('vm:entry-point') +void window_metrics_event_view_id() { + Map sizes = _getAllViewSizes(); + PlatformDispatcher.instance.onMetricsChanged = () { + final Map newSizes = _getAllViewSizes(); + final List differences = _findDifferences(sizes, newSizes); + sizes = newSizes; + signalNativeMessage('Changed: $differences'); + }; + + signalNativeTest(); +} + @pragma('vm:entry-point') Future channel_listener_response() async { channelBuffers.setListener('test/listen', diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index da76770763d34..a33c133ecb024 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -2726,7 +2726,7 @@ TEST_F(EmbedderTest, CanSendPointer) { /// Send a pointer event to Dart and wait until the Dart code echos with the /// view ID. -TEST_F(EmbedderTest, CanSendPointerWithViewId) { +TEST_F(EmbedderTest, CanSendPointerEventWithViewId) { auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); @@ -2766,6 +2766,103 @@ TEST_F(EmbedderTest, CanSendPointerWithViewId) { message_latch.Wait(); } +TEST_F(EmbedderTest, WindowMetricsEventDefaultsToImplicitView) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); + builder.SetDartEntrypoint("window_metrics_event_view_id"); + + fml::AutoResetWaitableEvent ready_latch, message_latch; + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY( + [&ready_latch](Dart_NativeArguments args) { ready_latch.Signal(); })); + context.AddNativeCallback( + "SignalNativeMessage", + CREATE_NATIVE_ENTRY([&message_latch](Dart_NativeArguments args) { + auto message = tonic::DartConverter::FromDart( + Dart_GetNativeArgument(args, 0)); + ASSERT_EQ("Changed: [0]", message); + message_latch.Signal(); + })); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + ready_latch.Wait(); + + FlutterWindowMetricsEvent event = {}; + // Simulate an event that comes from an old version of embedder.h that doesn't + // have the view_id field. + event.struct_size = offsetof(FlutterWindowMetricsEvent, view_id); + event.width = 200; + event.height = 300; + event.pixel_ratio = 1.5; + // Skip assigning event.view_id here to test the default behavior. + + FlutterEngineResult result = + FlutterEngineSendWindowMetricsEvent(engine.get(), &event); + ASSERT_EQ(result, kSuccess); + + message_latch.Wait(); +} + +TEST_F(EmbedderTest, IgnoresWindowMetricsEventForUnknownView) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); + builder.SetDartEntrypoint("window_metrics_event_view_id"); + + fml::AutoResetWaitableEvent ready_latch, message_latch; + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY( + [&ready_latch](Dart_NativeArguments args) { ready_latch.Signal(); })); + + context.AddNativeCallback( + "SignalNativeMessage", + CREATE_NATIVE_ENTRY([&message_latch](Dart_NativeArguments args) { + auto message = tonic::DartConverter::FromDart( + Dart_GetNativeArgument(args, 0)); + // Message latch should only be signaled once as the bad + // view metric should be dropped by the engine. + ASSERT_FALSE(message_latch.IsSignaledForTest()); + ASSERT_EQ("Changed: [0]", message); + message_latch.Signal(); + })); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + ready_latch.Wait(); + + // Send a window metric for a nonexistent view, which should be dropped by the + // engine. + FlutterWindowMetricsEvent bad_event = {}; + bad_event.struct_size = sizeof(FlutterWindowMetricsEvent); + bad_event.width = 200; + bad_event.height = 300; + bad_event.pixel_ratio = 1.5; + bad_event.view_id = 100; + + FlutterEngineResult result = + FlutterEngineSendWindowMetricsEvent(engine.get(), &bad_event); + ASSERT_EQ(result, kSuccess); + + // Send a window metric for a valid view. The engine notifies the Dart app. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(FlutterWindowMetricsEvent); + event.width = 200; + event.height = 300; + event.pixel_ratio = 1.5; + event.view_id = 0; + + result = FlutterEngineSendWindowMetricsEvent(engine.get(), &event); + ASSERT_EQ(result, kSuccess); + + message_latch.Wait(); +} + TEST_F(EmbedderTest, RegisterChannelListener) { auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index 266a97bec58cc..b00bab8cedbca 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -297,6 +297,9 @@ static void SendWindowMetrics(FlutterDesktopWindowControllerState* controller, } else { event.pixel_ratio = controller->window_wrapper->pixel_ratio_override; } + // The GLFW embedder doesn't support multiple views. We assume all pointer + // events come from the only view, the implicit view. + event.view_id = flutter::kFlutterImplicitViewId; FlutterEngineSendWindowMetricsEvent(controller->engine->flutter_engine, &event); } diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index 3069cd2b39710..f2c8040aa41d2 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -760,6 +760,10 @@ void fl_engine_send_window_metrics_event(FlEngine* self, event.width = width; event.height = height; event.pixel_ratio = pixel_ratio; + // TODO(dkwingsmt): Assign the correct view ID once the Linux embedder + // supports multiple views. + // https://github.com/flutter/flutter/issues/138178 + event.view_id = flutter::kFlutterImplicitViewId; self->embedder_api.SendWindowMetricsEvent(self->engine, &event); } diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 996a0da3d9a5d..f8a87c825979f 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -352,6 +352,7 @@ void FlutterWindowsView::SendWindowMetrics(size_t width, event.width = width; event.height = height; event.pixel_ratio = dpiScale; + event.view_id = view_id_; engine_->SendWindowMetricsEvent(event); } @@ -588,10 +589,7 @@ void FlutterWindowsView::SendPointerEventWithData( event.device_kind = state->device_kind; event.device = state->pointer_id; event.buttons = state->buttons; - // TODO(dkwingsmt): Use the correct view ID for pointer events once the - // Windows embedder supports multiple views. - // https://github.com/flutter/flutter/issues/138179 - event.view_id = flutter::kFlutterImplicitViewId; + event.view_id = view_id_; // Set metadata that's always the same regardless of the event. event.struct_size = sizeof(event);