Delay the window until the first frame is received from the Flutter engine (flutter/engine#54703)

Fixes https://github.com/flutter/flutter/issues/151098
This commit is contained in:
Robert Ancell 2024-09-17 10:48:43 +12:00 committed by GitHub
parent f64f4e4188
commit 3443af6a5c
3 changed files with 85 additions and 9 deletions

View File

@ -31,6 +31,16 @@ G_DEFINE_TYPE_WITH_CODE(FlApplication,
GTK_TYPE_APPLICATION,
G_ADD_PRIVATE(FlApplication))
// Called when the first frame is received.
static void first_frame_cb(FlApplication* self, FlView* view) {
GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(view));
// Show the main window.
if (window != nullptr && GTK_IS_WINDOW(window)) {
gtk_window_present(GTK_WINDOW(window));
}
}
// Default implementation of FlApplication::register_plugins
static void fl_application_register_plugins(FlApplication* self,
FlPluginRegistry* registry) {}
@ -80,17 +90,20 @@ static void fl_application_activate(GApplication* application) {
project, priv->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
self);
gtk_widget_show(GTK_WIDGET(view));
GtkWindow* window;
g_signal_emit(self, fl_application_signals[kSignalCreateWindow], 0, view,
&window);
gtk_widget_show(GTK_WIDGET(window));
// Make the resources for the view so rendering can start.
// We'll show the view when we have the first frame.
gtk_widget_realize(GTK_WIDGET(view));
g_signal_emit(self, fl_application_signals[kSignalRegisterPlugins], 0,
FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.

View File

@ -52,6 +52,9 @@ struct _FlView {
// Background color.
GdkRGBA* background_color;
// TRUE if have got the first frame to render.
gboolean have_first_frame;
// Pointer button state recorded for sending status updates.
int64_t button_state;
@ -82,7 +85,9 @@ struct _FlView {
FlViewAccessible* view_accessible;
};
enum { kPropFlutterProject = 1, kPropLast };
enum { kSignalFirstFrame, kSignalLastSignal };
static guint fl_view_signals[kSignalLastSignal];
static void fl_view_plugin_registry_iface_init(
FlPluginRegistryInterface* iface);
@ -109,6 +114,15 @@ G_DEFINE_TYPE_WITH_CODE(
G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(),
fl_view_text_input_delegate_iface_init))
// Emit the first frame signal in the main thread.
static gboolean first_frame_idle_cb(gpointer user_data) {
FlView* self = FL_VIEW(user_data);
g_signal_emit(self, fl_view_signals[kSignalFirstFrame], 0);
return FALSE;
}
// Signal handler for GtkWidget::delete-event
static gboolean window_delete_event_cb(FlView* self) {
fl_platform_handler_request_app_exit(self->platform_handler);
@ -678,6 +692,16 @@ static void fl_view_dispose(GObject* object) {
G_OBJECT_CLASS(fl_view_parent_class)->dispose(object);
}
// Implements GtkWidget::realize.
static void fl_view_realize(GtkWidget* widget) {
FlView* self = FL_VIEW(widget);
GTK_WIDGET_CLASS(fl_view_parent_class)->realize(widget);
// Realize the child widgets.
gtk_widget_realize(GTK_WIDGET(self->gl_area));
}
// Implements GtkWidget::key_press_event.
static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) {
FlView* self = FL_VIEW(widget);
@ -702,9 +726,14 @@ static void fl_view_class_init(FlViewClass* klass) {
object_class->dispose = fl_view_dispose;
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
widget_class->realize = fl_view_realize;
widget_class->key_press_event = fl_view_key_press_event;
widget_class->key_release_event = fl_view_key_release_event;
fl_view_signals[kSignalFirstFrame] =
g_signal_new("first-frame", fl_view_get_type(), G_SIGNAL_RUN_LAST, 0,
NULL, NULL, NULL, G_TYPE_NONE, 0);
gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass),
fl_socket_accessible_get_type());
}
@ -810,7 +839,15 @@ G_MODULE_EXPORT void fl_view_set_background_color(FlView* self,
void fl_view_redraw(FlView* self) {
g_return_if_fail(FL_IS_VIEW(self));
gtk_widget_queue_draw(GTK_WIDGET(self->gl_area));
if (!self->have_first_frame) {
self->have_first_frame = TRUE;
// This is not the main thread, so the signal needs to be done via an idle
// callback.
g_idle_add(first_frame_idle_cb, self);
}
}
GHashTable* fl_view_get_keyboard_state(FlView* self) {

View File

@ -3,31 +3,57 @@
// found in the LICENSE file.
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
#include "flutter/shell/platform/linux/fl_view_private.h"
#include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
#include "gtest/gtest.h"
static void first_frame_cb(FlView* view, gboolean* first_frame_emitted) {
*first_frame_emitted = TRUE;
}
TEST(FlViewTest, GetEngine) {
flutter::testing::fl_ensure_gtk_init();
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlView) view = fl_view_new(project);
FlView* view = fl_view_new(project);
// Check the engine is immediately available (i.e. before the widget is
// realized).
FlEngine* engine = fl_view_get_engine(view);
EXPECT_NE(engine, nullptr);
g_object_ref_sink(view);
}
TEST(FlViewTest, StateUpdateDoesNotHappenInInit) {
flutter::testing::fl_ensure_gtk_init();
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlView) view = fl_view_new(project);
FlView* view = fl_view_new(project);
// Check that creating a view doesn't try to query the window state in
// initialization, causing a critical log to be issued.
EXPECT_EQ(
flutter::testing::fl_get_received_gtk_log_levels() & G_LOG_LEVEL_CRITICAL,
(GLogLevelFlags)0x0);
g_object_ref_sink(view);
(void)view;
}
TEST(FlViewTest, FirstFrameSignal) {
flutter::testing::fl_ensure_gtk_init();
g_autoptr(FlDartProject) project = fl_dart_project_new();
FlView* view = fl_view_new(project);
gboolean first_frame_emitted = FALSE;
g_signal_connect(view, "first-frame", G_CALLBACK(first_frame_cb),
&first_frame_emitted);
EXPECT_FALSE(first_frame_emitted);
fl_view_redraw(view);
// Signal is emitted in idle, clear the main loop.
while (g_main_context_iteration(g_main_context_default(), FALSE)) {
// Repeat until nothing to iterate on.
}
// Check view has detected frame.
EXPECT_TRUE(first_frame_emitted);
}