Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit a027991

Browse files
Delay the window until the first frame is received from the Flutter engine (#54703)
Fixes flutter/flutter#151098
1 parent 04bc90b commit a027991

File tree

3 files changed

+85
-9
lines changed

3 files changed

+85
-9
lines changed

shell/platform/linux/fl_application.cc

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ G_DEFINE_TYPE_WITH_CODE(FlApplication,
3131
GTK_TYPE_APPLICATION,
3232
G_ADD_PRIVATE(FlApplication))
3333

34+
// Called when the first frame is received.
35+
static void first_frame_cb(FlApplication* self, FlView* view) {
36+
GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(view));
37+
38+
// Show the main window.
39+
if (window != nullptr && GTK_IS_WINDOW(window)) {
40+
gtk_window_present(GTK_WINDOW(window));
41+
}
42+
}
43+
3444
// Default implementation of FlApplication::register_plugins
3545
static void fl_application_register_plugins(FlApplication* self,
3646
FlPluginRegistry* registry) {}
@@ -80,17 +90,20 @@ static void fl_application_activate(GApplication* application) {
8090
project, priv->dart_entrypoint_arguments);
8191

8292
FlView* view = fl_view_new(project);
93+
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
94+
self);
8395
gtk_widget_show(GTK_WIDGET(view));
8496

8597
GtkWindow* window;
8698
g_signal_emit(self, fl_application_signals[kSignalCreateWindow], 0, view,
8799
&window);
88-
gtk_widget_show(GTK_WIDGET(window));
100+
101+
// Make the resources for the view so rendering can start.
102+
// We'll show the view when we have the first frame.
103+
gtk_widget_realize(GTK_WIDGET(view));
89104

90105
g_signal_emit(self, fl_application_signals[kSignalRegisterPlugins], 0,
91106
FL_PLUGIN_REGISTRY(view));
92-
93-
gtk_widget_grab_focus(GTK_WIDGET(view));
94107
}
95108

96109
// Implements GApplication::local_command_line.

shell/platform/linux/fl_view.cc

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ struct _FlView {
5252
// Background color.
5353
GdkRGBA* background_color;
5454

55+
// TRUE if have got the first frame to render.
56+
gboolean have_first_frame;
57+
5558
// Pointer button state recorded for sending status updates.
5659
int64_t button_state;
5760

@@ -82,7 +85,9 @@ struct _FlView {
8285
FlViewAccessible* view_accessible;
8386
};
8487

85-
enum { kPropFlutterProject = 1, kPropLast };
88+
enum { kSignalFirstFrame, kSignalLastSignal };
89+
90+
static guint fl_view_signals[kSignalLastSignal];
8691

8792
static void fl_view_plugin_registry_iface_init(
8893
FlPluginRegistryInterface* iface);
@@ -109,6 +114,15 @@ G_DEFINE_TYPE_WITH_CODE(
109114
G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(),
110115
fl_view_text_input_delegate_iface_init))
111116

117+
// Emit the first frame signal in the main thread.
118+
static gboolean first_frame_idle_cb(gpointer user_data) {
119+
FlView* self = FL_VIEW(user_data);
120+
121+
g_signal_emit(self, fl_view_signals[kSignalFirstFrame], 0);
122+
123+
return FALSE;
124+
}
125+
112126
// Signal handler for GtkWidget::delete-event
113127
static gboolean window_delete_event_cb(FlView* self) {
114128
fl_platform_handler_request_app_exit(self->platform_handler);
@@ -678,6 +692,16 @@ static void fl_view_dispose(GObject* object) {
678692
G_OBJECT_CLASS(fl_view_parent_class)->dispose(object);
679693
}
680694

695+
// Implements GtkWidget::realize.
696+
static void fl_view_realize(GtkWidget* widget) {
697+
FlView* self = FL_VIEW(widget);
698+
699+
GTK_WIDGET_CLASS(fl_view_parent_class)->realize(widget);
700+
701+
// Realize the child widgets.
702+
gtk_widget_realize(GTK_WIDGET(self->gl_area));
703+
}
704+
681705
// Implements GtkWidget::key_press_event.
682706
static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) {
683707
FlView* self = FL_VIEW(widget);
@@ -702,9 +726,14 @@ static void fl_view_class_init(FlViewClass* klass) {
702726
object_class->dispose = fl_view_dispose;
703727

704728
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
729+
widget_class->realize = fl_view_realize;
705730
widget_class->key_press_event = fl_view_key_press_event;
706731
widget_class->key_release_event = fl_view_key_release_event;
707732

733+
fl_view_signals[kSignalFirstFrame] =
734+
g_signal_new("first-frame", fl_view_get_type(), G_SIGNAL_RUN_LAST, 0,
735+
NULL, NULL, NULL, G_TYPE_NONE, 0);
736+
708737
gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass),
709738
fl_socket_accessible_get_type());
710739
}
@@ -810,7 +839,15 @@ G_MODULE_EXPORT void fl_view_set_background_color(FlView* self,
810839

811840
void fl_view_redraw(FlView* self) {
812841
g_return_if_fail(FL_IS_VIEW(self));
842+
813843
gtk_widget_queue_draw(GTK_WIDGET(self->gl_area));
844+
845+
if (!self->have_first_frame) {
846+
self->have_first_frame = TRUE;
847+
// This is not the main thread, so the signal needs to be done via an idle
848+
// callback.
849+
g_idle_add(first_frame_idle_cb, self);
850+
}
814851
}
815852

816853
GHashTable* fl_view_get_keyboard_state(FlView* self) {

shell/platform/linux/fl_view_test.cc

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,57 @@
33
// found in the LICENSE file.
44

55
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
6+
#include "flutter/shell/platform/linux/fl_view_private.h"
67
#include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
78

89
#include "gtest/gtest.h"
910

11+
static void first_frame_cb(FlView* view, gboolean* first_frame_emitted) {
12+
*first_frame_emitted = TRUE;
13+
}
14+
1015
TEST(FlViewTest, GetEngine) {
1116
flutter::testing::fl_ensure_gtk_init();
1217
g_autoptr(FlDartProject) project = fl_dart_project_new();
13-
g_autoptr(FlView) view = fl_view_new(project);
18+
FlView* view = fl_view_new(project);
1419

1520
// Check the engine is immediately available (i.e. before the widget is
1621
// realized).
1722
FlEngine* engine = fl_view_get_engine(view);
1823
EXPECT_NE(engine, nullptr);
19-
20-
g_object_ref_sink(view);
2124
}
2225

2326
TEST(FlViewTest, StateUpdateDoesNotHappenInInit) {
2427
flutter::testing::fl_ensure_gtk_init();
2528
g_autoptr(FlDartProject) project = fl_dart_project_new();
26-
g_autoptr(FlView) view = fl_view_new(project);
29+
FlView* view = fl_view_new(project);
2730
// Check that creating a view doesn't try to query the window state in
2831
// initialization, causing a critical log to be issued.
2932
EXPECT_EQ(
3033
flutter::testing::fl_get_received_gtk_log_levels() & G_LOG_LEVEL_CRITICAL,
3134
(GLogLevelFlags)0x0);
32-
g_object_ref_sink(view);
35+
36+
(void)view;
37+
}
38+
39+
TEST(FlViewTest, FirstFrameSignal) {
40+
flutter::testing::fl_ensure_gtk_init();
41+
42+
g_autoptr(FlDartProject) project = fl_dart_project_new();
43+
FlView* view = fl_view_new(project);
44+
gboolean first_frame_emitted = FALSE;
45+
g_signal_connect(view, "first-frame", G_CALLBACK(first_frame_cb),
46+
&first_frame_emitted);
47+
48+
EXPECT_FALSE(first_frame_emitted);
49+
50+
fl_view_redraw(view);
51+
52+
// Signal is emitted in idle, clear the main loop.
53+
while (g_main_context_iteration(g_main_context_default(), FALSE)) {
54+
// Repeat until nothing to iterate on.
55+
}
56+
57+
// Check view has detected frame.
58+
EXPECT_TRUE(first_frame_emitted);
3359
}

0 commit comments

Comments
 (0)