Add Linux platform support for fetching refresh rate on startup. (#52934)

This patch addresses the missing implementation of
`platformDispatcher.views` on Linux. It checks the refresh rate of the
renderer's window and returns the value. Without this implementation,
`WidgetsBinding.instance.platformDispatcher.views.first.display.size`
would throw an exception on Linux, preventing safe usage.

Related: https://github.com/flutter/flutter/issues/144230
diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc
index f2c8040..98d4389 100644
--- a/shell/platform/linux/fl_engine.cc
+++ b/shell/platform/linux/fl_engine.cc
@@ -573,6 +573,26 @@
     g_warning("Failed to enable accessibility features on Flutter engine");
   }
 
+  gdouble refresh_rate = fl_renderer_get_refresh_rate(self->renderer);
+  // FlutterEngineDisplay::refresh_rate expects 0 if the refresh rate is
+  // unknown.
+  if (refresh_rate <= 0.0) {
+    refresh_rate = 0.0;
+  }
+  FlutterEngineDisplay display = {};
+  display.struct_size = sizeof(FlutterEngineDisplay);
+  display.display_id = 0;
+  display.single_display = true;
+  display.refresh_rate = refresh_rate;
+
+  std::vector displays = {display};
+  result = self->embedder_api.NotifyDisplayUpdate(
+      self->engine, kFlutterEngineDisplaysUpdateTypeStartup, displays.data(),
+      displays.size());
+  if (result != kSuccess) {
+    g_warning("Failed to notify display update to Flutter engine: %d", result);
+  }
+
   return TRUE;
 }
 
diff --git a/shell/platform/linux/fl_renderer.cc b/shell/platform/linux/fl_renderer.cc
index 595c315..a2163aa 100644
--- a/shell/platform/linux/fl_renderer.cc
+++ b/shell/platform/linux/fl_renderer.cc
@@ -153,6 +153,11 @@
   FL_RENDERER_GET_CLASS(self)->clear_current(self);
 }
 
+gdouble fl_renderer_get_refresh_rate(FlRenderer* self) {
+  g_return_val_if_fail(FL_IS_RENDERER(self), -1.0);
+  return FL_RENDERER_GET_CLASS(self)->get_refresh_rate(self);
+}
+
 guint32 fl_renderer_get_fbo(FlRenderer* self) {
   g_return_val_if_fail(FL_IS_RENDERER(self), 0);
 
diff --git a/shell/platform/linux/fl_renderer.h b/shell/platform/linux/fl_renderer.h
index e2145fd..39222ef 100644
--- a/shell/platform/linux/fl_renderer.h
+++ b/shell/platform/linux/fl_renderer.h
@@ -80,6 +80,16 @@
    */
   gboolean (*collect_backing_store)(FlRenderer* renderer,
                                     const FlutterBackingStore* backing_store);
+
+  /**
+   * Virtual method called when Flutter wants to get the refresh rate of the
+   * renderer.
+   * @renderer: an #FlRenderer.
+   *
+   * Returns: The refresh rate of the display in Hz. If the refresh rate is
+   * not available, returns -1.0.
+   */
+  gdouble (*get_refresh_rate)(FlRenderer* renderer);
 };
 
 /**
@@ -223,6 +233,15 @@
  */
 void fl_renderer_cleanup(FlRenderer* renderer);
 
+/**
+ * fl_renderer_get_refresh_rate:
+ * @renderer: an #FlRenderer.
+ *
+ * Returns: The refresh rate of the display in Hz. If the refresh rate is
+ * not available, returns -1.0.
+ */
+gdouble fl_renderer_get_refresh_rate(FlRenderer* renderer);
+
 G_END_DECLS
 
 #endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERER_H_
diff --git a/shell/platform/linux/fl_renderer_gdk.cc b/shell/platform/linux/fl_renderer_gdk.cc
index f5d4990..dec1b74 100644
--- a/shell/platform/linux/fl_renderer_gdk.cc
+++ b/shell/platform/linux/fl_renderer_gdk.cc
@@ -36,6 +36,23 @@
   gdk_gl_context_clear_current();
 }
 
+static gdouble fl_renderer_gdk_get_refresh_rate(FlRenderer* renderer) {
+  FlRendererGdk* self = FL_RENDERER_GDK(renderer);
+  GdkDisplay* display = gdk_window_get_display(self->window);
+  GdkMonitor* monitor =
+      gdk_display_get_monitor_at_window(display, self->window);
+  if (monitor == nullptr) {
+    return -1.0;
+  }
+
+  int refresh_rate = gdk_monitor_get_refresh_rate(monitor);
+  if (refresh_rate <= 0) {
+    return -1.0;
+  }
+  // the return value is in milli-hertz, convert to hertz
+  return static_cast<gdouble>(refresh_rate) / 1000.0;
+}
+
 static void fl_renderer_gdk_dispose(GObject* object) {
   FlRendererGdk* self = FL_RENDERER_GDK(object);
 
@@ -52,6 +69,7 @@
   FL_RENDERER_CLASS(klass)->make_resource_current =
       fl_renderer_gdk_make_resource_current;
   FL_RENDERER_CLASS(klass)->clear_current = fl_renderer_gdk_clear_current;
+  FL_RENDERER_CLASS(klass)->get_refresh_rate = fl_renderer_gdk_get_refresh_rate;
 }
 
 static void fl_renderer_gdk_init(FlRendererGdk* self) {}
diff --git a/shell/platform/linux/fl_renderer_headless.cc b/shell/platform/linux/fl_renderer_headless.cc
index 1736e14..c94cd1f 100644
--- a/shell/platform/linux/fl_renderer_headless.cc
+++ b/shell/platform/linux/fl_renderer_headless.cc
@@ -19,11 +19,18 @@
 // Implements FlRenderer::clear_current.
 static void fl_renderer_headless_clear_current(FlRenderer* renderer) {}
 
+// Implements FlRenderer::get_refresh_rate.
+static gdouble fl_renderer_headless_get_refresh_rate(FlRenderer* renderer) {
+  return -1.0;
+}
+
 static void fl_renderer_headless_class_init(FlRendererHeadlessClass* klass) {
   FL_RENDERER_CLASS(klass)->make_current = fl_renderer_headless_make_current;
   FL_RENDERER_CLASS(klass)->make_resource_current =
       fl_renderer_headless_make_resource_current;
   FL_RENDERER_CLASS(klass)->clear_current = fl_renderer_headless_clear_current;
+  FL_RENDERER_CLASS(klass)->get_refresh_rate =
+      fl_renderer_headless_get_refresh_rate;
 }
 
 static void fl_renderer_headless_init(FlRendererHeadless* self) {}
diff --git a/shell/platform/linux/fl_renderer_test.cc b/shell/platform/linux/fl_renderer_test.cc
index 7c01204..a44e546 100644
--- a/shell/platform/linux/fl_renderer_test.cc
+++ b/shell/platform/linux/fl_renderer_test.cc
@@ -51,3 +51,19 @@
 
   g_object_ref_sink(view);
 }
+
+static constexpr double kExpectedRefreshRate = 120.0;
+static gdouble renderer_get_refresh_rate(FlRenderer* renderer) {
+  return kExpectedRefreshRate;
+}
+
+TEST(FlRendererTest, RefreshRate) {
+  flutter::testing::fl_ensure_gtk_init();
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+  g_autoptr(FlMockRenderer) renderer =
+      fl_mock_renderer_new(&renderer_get_refresh_rate);
+
+  gdouble result_refresh_rate =
+      fl_renderer_get_refresh_rate(FL_RENDERER(renderer));
+  EXPECT_DOUBLE_EQ(result_refresh_rate, kExpectedRefreshRate);
+}
diff --git a/shell/platform/linux/testing/mock_engine.cc b/shell/platform/linux/testing/mock_engine.cc
index eac0cb3..040e0eb 100644
--- a/shell/platform/linux/testing/mock_engine.cc
+++ b/shell/platform/linux/testing/mock_engine.cc
@@ -513,6 +513,14 @@
   return kSuccess;
 }
 
+FlutterEngineResult FlutterEngineNotifyDisplayUpdate(
+    FLUTTER_API_SYMBOL(FlutterEngine) engine,
+    FlutterEngineDisplaysUpdateType update_type,
+    const FlutterEngineDisplay* displays,
+    size_t display_count) {
+  return kSuccess;
+}
+
 }  // namespace
 
 FlutterEngineResult FlutterEngineGetProcAddresses(
@@ -552,5 +560,6 @@
   table->UnregisterExternalTexture = &FlutterEngineUnregisterExternalTexture;
   table->UpdateAccessibilityFeatures =
       &FlutterEngineUpdateAccessibilityFeatures;
+  table->NotifyDisplayUpdate = &FlutterEngineNotifyDisplayUpdate;
   return kSuccess;
 }
diff --git a/shell/platform/linux/testing/mock_renderer.cc b/shell/platform/linux/testing/mock_renderer.cc
index 91d87b4..c70980b 100644
--- a/shell/platform/linux/testing/mock_renderer.cc
+++ b/shell/platform/linux/testing/mock_renderer.cc
@@ -6,6 +6,7 @@
 
 struct _FlMockRenderer {
   FlRenderer parent_instance;
+  FlMockRendererGetRefreshRate get_refresh_rate;
 };
 
 G_DEFINE_TYPE(FlMockRenderer, fl_mock_renderer, fl_renderer_get_type())
@@ -19,16 +20,31 @@
 // Implements FlRenderer::clear_current.
 static void fl_mock_renderer_clear_current(FlRenderer* renderer) {}
 
+// Implements FlRenderer::get_refresh_rate.
+static gdouble fl_mock_renderer_default_get_refresh_rate(FlRenderer* renderer) {
+  FlMockRenderer* self = FL_MOCK_RENDERER(renderer);
+  if (self->get_refresh_rate != nullptr) {
+    return self->get_refresh_rate(renderer);
+  }
+  return -1.0;
+}
+
 static void fl_mock_renderer_class_init(FlMockRendererClass* klass) {
   FL_RENDERER_CLASS(klass)->make_current = fl_mock_renderer_make_current;
   FL_RENDERER_CLASS(klass)->make_resource_current =
       fl_mock_renderer_make_resource_current;
   FL_RENDERER_CLASS(klass)->clear_current = fl_mock_renderer_clear_current;
+  FL_RENDERER_CLASS(klass)->get_refresh_rate =
+      fl_mock_renderer_default_get_refresh_rate;
 }
 
 static void fl_mock_renderer_init(FlMockRenderer* self) {}
 
 // Creates a stub renderer
-FlMockRenderer* fl_mock_renderer_new() {
-  return FL_MOCK_RENDERER(g_object_new(fl_mock_renderer_get_type(), nullptr));
+FlMockRenderer* fl_mock_renderer_new(
+    FlMockRendererGetRefreshRate get_refresh_rate) {
+  FlMockRenderer* fl_mock_renderer = FL_MOCK_RENDERER(
+      g_object_new_valist(fl_mock_renderer_get_type(), nullptr, nullptr));
+  fl_mock_renderer->get_refresh_rate = get_refresh_rate;
+  return fl_mock_renderer;
 }
diff --git a/shell/platform/linux/testing/mock_renderer.h b/shell/platform/linux/testing/mock_renderer.h
index 5369a37..2dbbe08 100644
--- a/shell/platform/linux/testing/mock_renderer.h
+++ b/shell/platform/linux/testing/mock_renderer.h
@@ -15,7 +15,10 @@
                      MOCK_RENDERER,
                      FlRenderer)
 
-FlMockRenderer* fl_mock_renderer_new();
+typedef gdouble (*FlMockRendererGetRefreshRate)(FlRenderer* renderer);
+
+FlMockRenderer* fl_mock_renderer_new(
+    FlMockRendererGetRefreshRate get_refresh_rate = nullptr);
 
 G_END_DECLS