[Linux] Request a frame when a view starts rendering (#192911)

Frames rendered before a view is realized are dropped, as there is
nothing to present them to. The view is then left empty until something
else makes Flutter render, which happens to be the window metrics sent
when the view is first given a size.

Add fl_engine_schedule_frame and ask the engine for a frame instead of
relying on that.
diff --git a/engine/src/flutter/shell/platform/linux/BUILD.gn b/engine/src/flutter/shell/platform/linux/BUILD.gn
index b7112d0..a5a09ee 100644
--- a/engine/src/flutter/shell/platform/linux/BUILD.gn
+++ b/engine/src/flutter/shell/platform/linux/BUILD.gn
@@ -284,6 +284,7 @@
     "fl_touch_manager_test.cc",
     "fl_value_test.cc",
     "fl_view_accessible_test.cc",
+    "fl_view_renderer_software_test.cc",
     "fl_view_renderer_subsurface_test.cc",
     "fl_view_test.cc",
     "fl_wayland_display_test.cc",
diff --git a/engine/src/flutter/shell/platform/linux/fl_engine_private.h b/engine/src/flutter/shell/platform/linux/fl_engine_private.h
index 6584d6e..f4f0111 100644
--- a/engine/src/flutter/shell/platform/linux/fl_engine_private.h
+++ b/engine/src/flutter/shell/platform/linux/fl_engine_private.h
@@ -254,7 +254,7 @@
  * @engine: an #FlEngine.
  *
  * Asks the engine to render another frame. Used when a view needs the current
- * frame again, e.g. because the surface it was presented to has been replaced.
+ * frame again, e.g. because it wasn't able to use the last one.
  */
 void fl_engine_schedule_frame(FlEngine* engine);
 
diff --git a/engine/src/flutter/shell/platform/linux/fl_engine_test.cc b/engine/src/flutter/shell/platform/linux/fl_engine_test.cc
index bca0875..cfc6d4b 100644
--- a/engine/src/flutter/shell/platform/linux/fl_engine_test.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_engine_test.cc
@@ -72,6 +72,22 @@
   EXPECT_TRUE(called);
 }
 
+// Checks asking the engine to render a frame works.
+TEST_F(FlEngineTest, ScheduleFrame) {
+  StartEngine();
+
+  bool called = false;
+  fl_engine_get_embedder_api(engine)->ScheduleFrame =
+      MOCK_ENGINE_PROC(ScheduleFrame, ([&called](auto engine) {
+                         called = true;
+                         return kSuccess;
+                       }));
+
+  fl_engine_schedule_frame(engine);
+
+  EXPECT_TRUE(called);
+}
+
 // Checks sending window metrics events works.
 TEST_F(FlEngineTest, WindowMetrics) {
   StartEngine();
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.cc b/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.cc
index e942c93..f0c973d 100644
--- a/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.cc
@@ -138,6 +138,11 @@
       FL_TASK_RUNNER(g_object_ref(fl_engine_get_task_runner(self->engine)));
   self->compositor =
       fl_compositor_opengl_new(fl_engine_get_opengl_manager(self->engine));
+
+  // Any frames rendered before this point were dropped, as there was nothing
+  // to present them to. Ask for another one so the view isn't left empty until
+  // something else causes Flutter to render.
+  fl_engine_schedule_frame(self->engine);
 }
 
 // Implements GtkWidget::draw.
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.cc b/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.cc
index e13bbd2..ed3d6db 100644
--- a/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.cc
@@ -118,6 +118,11 @@
   self->task_runner =
       FL_TASK_RUNNER(g_object_ref(fl_engine_get_task_runner(self->engine)));
   self->compositor = fl_compositor_software_new();
+
+  // Any frames rendered before this point were dropped, as there was nothing
+  // to present them to. Ask for another one so the view isn't left empty until
+  // something else causes Flutter to render.
+  fl_engine_schedule_frame(self->engine);
 }
 
 // Implements GtkWidget::draw.
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer_software_test.cc b/engine/src/flutter/shell/platform/linux/fl_view_renderer_software_test.cc
new file mode 100644
index 0000000..21290dd
--- /dev/null
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer_software_test.cc
@@ -0,0 +1,58 @@
+// 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.
+
+// Included first as it collides with the X11 headers.
+#include "flutter/shell/platform/linux/testing/linux_test.h"
+#include "gtest/gtest.h"
+
+#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
+#include "flutter/shell/platform/linux/fl_engine_private.h"
+#include "flutter/shell/platform/linux/fl_view_renderer.h"
+#include "flutter/shell/platform/linux/fl_view_renderer_software.h"
+#include "flutter/shell/platform/linux/testing/mock_gtk.h"
+
+// MOCK_ENGINE_PROC is leaky by design
+// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
+
+class FlViewRendererSoftwareTest : public flutter::testing::LinuxTest {
+ protected:
+  // GTK queries the window state when widgets are realized.
+  ::testing::NiceMock<flutter::testing::MockGtk> gtk;
+};
+
+TEST_F(FlViewRendererSoftwareTest, New) {
+  g_autoptr(FlViewRendererSoftware) renderer =
+      fl_view_renderer_software_new(engine, FALSE);
+  ASSERT_NE(renderer, nullptr);
+  g_object_ref_sink(renderer);
+  EXPECT_TRUE(FL_IS_VIEW_RENDERER(renderer));
+  EXPECT_TRUE(GTK_IS_WIDGET(renderer));
+}
+
+// Frames rendered before the widget is realized are dropped, so a frame is
+// requested once there is somewhere to present one.
+TEST_F(FlViewRendererSoftwareTest, RealizeSchedulesFrame) {
+  StartEngine();
+
+  bool called = false;
+  fl_engine_get_embedder_api(engine)->ScheduleFrame =
+      MOCK_ENGINE_PROC(ScheduleFrame, ([&called](auto engine) {
+                         called = true;
+                         return kSuccess;
+                       }));
+
+  FlViewRendererSoftware* renderer =
+      fl_view_renderer_software_new(engine, FALSE);
+  ASSERT_NE(renderer, nullptr);
+  GtkWidget* window = gtk_offscreen_window_new();
+  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(renderer));
+
+  EXPECT_FALSE(called);
+  gtk_widget_show_all(window);
+  EXPECT_TRUE(called);
+
+  gtk_widget_destroy(window);
+}
+
+// NOLINTEND(clang-analyzer-core.StackAddressEscape)
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer_subsurface.cc b/engine/src/flutter/shell/platform/linux/fl_view_renderer_subsurface.cc
index 636a7fc..2fb99cd 100644
--- a/engine/src/flutter/shell/platform/linux/fl_view_renderer_subsurface.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer_subsurface.cc
@@ -240,7 +240,8 @@
   self->compositor =
       fl_compositor_opengl_new(fl_engine_get_opengl_manager(self->engine));
 
-  // Create a subsurface on the toplevel's surface.
+  // Create a subsurface on the toplevel's surface. This asks for a frame, as
+  // any rendered before this point were dropped.
   update_parent_surface(self);
 }
 
diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_engine.cc b/engine/src/flutter/shell/platform/linux/testing/mock_engine.cc
index c414ff1..573bef6 100644
--- a/engine/src/flutter/shell/platform/linux/testing/mock_engine.cc
+++ b/engine/src/flutter/shell/platform/linux/testing/mock_engine.cc
@@ -71,6 +71,11 @@
   return kSuccess;
 }
 
+FlutterEngineResult FlutterEngineScheduleFrame(FLUTTER_API_SYMBOL(FlutterEngine)
+                                                   engine) {
+  return kSuccess;
+}
+
 FlutterEngineResult FlutterEngineSendWindowMetricsEvent(
     FLUTTER_API_SYMBOL(FlutterEngine) engine,
     const FlutterWindowMetricsEvent* event) {
@@ -212,6 +217,7 @@
   table->Initialize = &FlutterEngineInitialize;
   table->Deinitialize = &FlutterEngineDeinitialize;
   table->RunInitialized = &FlutterEngineRunInitialized;
+  table->ScheduleFrame = &FlutterEngineScheduleFrame;
   table->SendWindowMetricsEvent = &FlutterEngineSendWindowMetricsEvent;
   table->SendPointerEvent = &FlutterEngineSendPointerEvent;
   table->SendKeyEvent = &FlutterEngineSendKeyEvent;