[Linux] Synchronize multi-window renderer lifetime across GTK and raster threads

When destroying an active secondary window on Linux:

1. FlView did not retain a strong reference to self->renderer, allowing gtk_container_dispose() on self->event_box to unreference and free self->renderer during dispose() while the Flutter raster thread may still call fl_view_present_layers(self->renderer). Retain a strong reference to self->renderer until fl_view_finalize().

2. FlViewRendererOpenGL and FlViewRendererSubsurface destroyed frame_mutex and task_runner in dispose() while leaving compositor and frame for finalize(). Move task_runner and frame_mutex cleanup to finalize() so present_layers() can safely access them after dispose().

3. Synchronize compositor, frame, and render_context teardown under frame_mutex in cleanup_opengl_resources() during unrealize() and dispose() on the GTK main thread. Only clear the GTK thread's current GdkGLContext if this window's render_context is the one currently bound.
diff --git a/engine/src/flutter/shell/platform/linux/fl_view.cc b/engine/src/flutter/shell/platform/linux/fl_view.cc
index 6a51c26..e565a24 100644
--- a/engine/src/flutter/shell/platform/linux/fl_view.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_view.cc
@@ -156,11 +156,17 @@
 
 // Called when the mouse cursor changes.
 static void cursor_changed_cb(FlView* self) {
+  if (self->engine == nullptr) {
+    return;
+  }
   FlMouseCursorHandler* handler =
       fl_engine_get_mouse_cursor_handler(self->engine);
   const gchar* cursor_name = fl_mouse_cursor_handler_get_cursor_name(handler);
   GdkWindow* window =
       gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self)));
+  if (window == nullptr) {
+    return;
+  }
   g_autoptr(GdkCursor) cursor =
       gdk_cursor_new_from_name(gdk_window_get_display(window), cursor_name);
   gdk_window_set_cursor(window, cursor);
@@ -168,6 +174,9 @@
 
 // Set the mouse cursor.
 static void setup_cursor(FlView* self) {
+  if (self->engine == nullptr) {
+    return;
+  }
   FlMouseCursorHandler* handler =
       fl_engine_get_mouse_cursor_handler(self->engine);
 
@@ -180,7 +189,7 @@
 // Updates the engine with the current window metrics.
 static void handle_geometry_changed(FlView* self) {
   // No updates required when size controlled by Flutter.
-  if (self->sized_to_content) {
+  if (self->sized_to_content || self->engine == nullptr) {
     return;
   }
 
@@ -556,7 +565,9 @@
 static void fl_view_dispose(GObject* object) {
   FlView* self = FL_VIEW(object);
 
-  g_cancellable_cancel(self->cancellable);
+  if (self->cancellable != nullptr) {
+    g_cancellable_cancel(self->cancellable);
+  }
 
   g_clear_object(&self->zoom_gesture);
   g_clear_object(&self->rotate_gesture);
@@ -576,7 +587,6 @@
                           nullptr);
   }
 
-  g_clear_object(&self->renderer);
   g_clear_object(&self->engine);
   g_clear_object(&self->window_state_monitor);
   g_clear_object(&self->scrolling_manager);
@@ -657,10 +667,22 @@
   return handle_key_event(self, key_event);
 }
 
+static void fl_view_finalize(GObject* object) {
+  FlView* self = FL_VIEW(object);
+
+  // Released in finalize rather than dispose so self->renderer remains valid if
+  // the Flutter raster thread holds a strong reference to this FlView (via
+  // get_renderable()) while gtk_container_dispose() destroys child widgets.
+  g_clear_object(&self->renderer);
+
+  G_OBJECT_CLASS(fl_view_parent_class)->finalize(object);
+}
+
 static void fl_view_class_init(FlViewClass* klass) {
   GObjectClass* object_class = G_OBJECT_CLASS(klass);
   object_class->notify = fl_view_notify;
   object_class->dispose = fl_view_dispose;
+  object_class->finalize = fl_view_finalize;
 
   GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
   widget_class->realize = fl_view_realize;
@@ -711,6 +733,10 @@
       }
       break;
   }
+  // Retain a strong reference owned by FlView (released in fl_view_finalize).
+  // Without this, gtk_container_dispose() on self->event_box unrefs and frees
+  // self->renderer during dispose() while the raster thread may still call
+  // fl_view_present_layers(self->renderer).
   g_object_ref_sink(self->renderer);
   gtk_widget_show(GTK_WIDGET(self->renderer));
   gtk_container_add(GTK_CONTAINER(self->event_box), GTK_WIDGET(self->renderer));
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 f0c973d..2a2b1d5 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
@@ -59,7 +59,7 @@
 static gboolean redraw_cb(gpointer user_data) {
   g_autoptr(FlViewRendererOpenGL) self = FL_VIEW_RENDERER_OPENGL(user_data);
 
-  if (self->compositor == nullptr) {
+  if (self->compositor == nullptr || self->engine == nullptr) {
     return G_SOURCE_REMOVE;
   }
 
@@ -91,6 +91,9 @@
                            gint scale_factor) {
   gint64 expiry_time = g_get_monotonic_time() + kRenderTimeoutMicroseconds;
   while (true) {
+    if (self->compositor == nullptr) {
+      break;
+    }
     size_t width = gdk_window_get_width(window) * scale_factor;
     size_t height = gdk_window_get_height(window) * scale_factor;
     size_t frame_width, frame_height;
@@ -194,18 +197,22 @@
 }
 
 // Implements FlViewRenderer::present_layers.
+// Called on the Flutter raster thread.
 static void fl_view_renderer_opengl_present_layers(FlViewRenderer* renderer,
                                                    const FlutterLayer** layers,
                                                    size_t layers_count) {
   FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(renderer);
 
-  // Frames may be presented before the widget is realized and the compositor
-  // is set up; ignore them.
-  if (self->compositor == nullptr) {
+  g_mutex_lock(&self->frame_mutex);
+
+  // Check compositor and frame under frame_mutex so the GTK main thread cannot
+  // clear and free them in cleanup_opengl_resources() between the null check
+  // and fl_opengl_frame_composite().
+  if (self->compositor == nullptr || self->frame == nullptr) {
+    g_mutex_unlock(&self->frame_mutex);
     return;
   }
 
-  g_mutex_lock(&self->frame_mutex);
   fl_opengl_frame_composite(self->frame, self->compositor, layers,
                             layers_count);
   g_mutex_unlock(&self->frame_mutex);
@@ -217,13 +224,51 @@
   g_idle_add(redraw_cb, g_object_ref(self));
 }
 
+// Teardown of FlViewRendererOpenGL spans three GObject/GTK stages across two
+// threads (the GTK main thread and the Flutter raster thread):
+//
+// 1. unrealize() [GTK main thread]: Destroys the native X11 GdkWindow.
+// 2. dispose()   [GTK main thread]: Triggered by gtk_widget_destroy(). Detaches
+//    references immediately, even if the Flutter raster thread still holds a
+//    strong reference to FlView (and thus this renderer) inside
+//    present_layers().
+// 3. finalize()  [GTK main thread OR Flutter raster thread]: Runs when the
+//    final g_object_unref() drops ref_count to 0.
+//
+// Because FlCompositorOpenGLShader disposal calls
+// gdk_gl_context_clear_current() and binds platform_context,
+// self->render_context, self->compositor, and self->frame must be released on
+// the GTK main thread in unrealize()/dispose() under frame_mutex, whereas
+// self->task_runner and self->frame_mutex must remain valid until finalize().
+static void cleanup_opengl_resources(FlViewRendererOpenGL* self) {
+  g_mutex_lock(&self->frame_mutex);
+  // Only clear the GTK thread's current GdkGLContext if this window's
+  // render_context is the one currently bound, preserving any other window's
+  // active fallback GdkGLContext (see #192137).
+  if (self->render_context != nullptr &&
+      gdk_gl_context_get_current() == self->render_context) {
+    gdk_gl_context_clear_current();
+  }
+  g_clear_object(&self->render_context);
+  g_clear_object(&self->compositor);
+  g_clear_object(&self->frame);
+  g_mutex_unlock(&self->frame_mutex);
+}
+
+// Implements GtkWidget::unrealize.
+static void fl_view_renderer_opengl_unrealize(GtkWidget* widget) {
+  FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(widget);
+
+  cleanup_opengl_resources(self);
+
+  GTK_WIDGET_CLASS(fl_view_renderer_opengl_parent_class)->unrealize(widget);
+}
+
 static void fl_view_renderer_opengl_dispose(GObject* object) {
   FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(object);
 
+  cleanup_opengl_resources(self);
   g_clear_object(&self->engine);
-  g_clear_object(&self->render_context);
-  g_clear_object(&self->task_runner);
-  g_mutex_clear(&self->frame_mutex);
 
   G_OBJECT_CLASS(fl_view_renderer_opengl_parent_class)->dispose(object);
 }
@@ -231,13 +276,12 @@
 static void fl_view_renderer_opengl_finalize(GObject* object) {
   FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(object);
 
-  // The compositor is released here rather than in dispose() so it outlives a
-  // forced dispose (e.g. gtk_widget_destroy()) and is only freed once the last
-  // reference is dropped. This keeps it alive for the raster thread, which
-  // holds a strong reference on the view (and thus this renderer) while
-  // presenting.
-  g_clear_object(&self->compositor);
-  g_clear_object(&self->frame);
+  // Released in finalize() after the last reference is dropped so an in-flight
+  // present_layers() call on the Flutter raster thread can safely lock
+  // frame_mutex, observe self->compositor == nullptr, and unlock after
+  // gtk_widget_destroy().
+  g_clear_object(&self->task_runner);
+  g_mutex_clear(&self->frame_mutex);
 
   G_OBJECT_CLASS(fl_view_renderer_opengl_parent_class)->finalize(object);
 }
@@ -250,6 +294,7 @@
 
   GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
   widget_class->realize = fl_view_renderer_opengl_realize;
+  widget_class->unrealize = fl_view_renderer_opengl_unrealize;
   widget_class->draw = fl_view_renderer_opengl_draw;
 
   FlViewRendererClass* renderer_class = FL_VIEW_RENDERER_CLASS(klass);
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 2fb99cd..c05f63e 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
@@ -434,8 +434,6 @@
   FlViewRendererSubsurface* self = FL_VIEW_RENDERER_SUBSURFACE(object);
 
   g_clear_object(&self->engine);
-  g_clear_object(&self->task_runner);
-  g_mutex_clear(&self->frame_mutex);
 
   G_OBJECT_CLASS(fl_view_renderer_subsurface_parent_class)->dispose(object);
 }
@@ -450,6 +448,8 @@
   // presenting.
   g_clear_object(&self->compositor);
   g_clear_object(&self->framebuffer);
+  g_clear_object(&self->task_runner);
+  g_mutex_clear(&self->frame_mutex);
 
   G_OBJECT_CLASS(fl_view_renderer_subsurface_parent_class)->finalize(object);
 }
diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_gtk.cc b/engine/src/flutter/shell/platform/linux/testing/mock_gtk.cc
index 0c16f89..2ca0faf 100644
--- a/engine/src/flutter/shell/platform/linux/testing/mock_gtk.cc
+++ b/engine/src/flutter/shell/platform/linux/testing/mock_gtk.cc
@@ -159,8 +159,16 @@
   check_thread();
 }
 
+static GdkGLContext* current_gl_context = nullptr;
+
+GdkGLContext* gdk_gl_context_get_current() {
+  check_thread();
+  return current_gl_context;
+}
+
 void gdk_gl_context_clear_current() {
   check_thread();
+  current_gl_context = nullptr;
   if (mock != nullptr) {
     mock->gdk_gl_context_clear_current();
   }
@@ -168,6 +176,7 @@
 
 void gdk_gl_context_make_current(GdkGLContext* context) {
   check_thread();
+  current_gl_context = context;
 }
 
 void gdk_cairo_draw_from_gl(cairo_t* cr,