Keep FileChooserNative alive while a portal is running
authorEmmanuele Bassi <ebassi@gnome.org>
Fri, 29 Apr 2022 14:18:02 +0000 (15:18 +0100)
committerEmmanuele Bassi <ebassi@gnome.org>
Fri, 29 Apr 2022 14:27:10 +0000 (15:27 +0100)
Even if the FileChooserNative instance drops out on us while we're still
waiting for the portal to answer, we should keep the data and pointers
alive until the sequence of asynchronous operations is running. The code
already tries to do that, by acquiring a strong reference to the
GtkFileChooserNative instance, but it's also freeing data as soon as the
dialog is hidden, while asynchronous callbacks that will look at the
fields on that data are still in flight.

To avoid that, we defer freeing the data until the asynchronous
callbacks are invoked, and we keep a reference on the dialog while we're
emitting signals on it.

Fixes: #4883
gtk/gtkfilechoosernativeportal.c

index d31337931e99889e4f887e597f0772275630d81f..00b28e55970107827c7f207fbf073eafc988d91e 100644 (file)
@@ -60,30 +60,42 @@ typedef struct {
 
 
 static void
-filechooser_portal_data_free (FilechooserPortalData *data)
+filechooser_portal_data_clear (FilechooserPortalData *data)
 {
   if (data->portal_response_signal_id != 0)
-    g_dbus_connection_signal_unsubscribe (data->connection,
-                                          data->portal_response_signal_id);
+    {
+      g_dbus_connection_signal_unsubscribe (data->connection,
+                                            data->portal_response_signal_id);
+      data->portal_response_signal_id = 0;
+    }
 
-  g_object_unref (data->connection);
+  g_clear_object (&data->connection);
 
   if (data->grab_widget)
     {
       gtk_grab_remove (data->grab_widget);
-      g_object_unref (data->grab_widget);
+      g_clear_object (&data->grab_widget);
     }
 
   g_clear_object (&data->self);
 
   if (data->exported_window)
-    gtk_window_unexport_handle (data->exported_window);
-
-  g_clear_object (&data->exported_window);
+    {
+      gtk_window_unexport_handle (data->exported_window);
+      g_clear_object (&data->exported_window);
+    }
 
-  g_free (data->portal_handle);
+  g_clear_pointer (&data->portal_handle, g_free);
+}
 
-  g_free (data);
+static void
+filechooser_portal_data_free (FilechooserPortalData *data)
+{
+  if (data != NULL)
+    {
+      filechooser_portal_data_clear (data);
+      g_free (data);
+    }
 }
 
 static void
@@ -175,10 +187,18 @@ response_cb (GDBusConnection  *connection,
       break;
     }
 
+  /* Keep a reference on the native dialog until we can emit the response
+   * signal; filechooser_portal_data_free() will drop a reference on the
+   * dialog as well
+   */
+  g_object_ref (self);
+
   filechooser_portal_data_free (data);
   self->mode_data = NULL;
 
   _gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (self), gtk_response);
+
+  g_object_unref (self);
 }
 
 static void
@@ -239,7 +259,6 @@ open_file_msg_cb (GObject *source_object,
   if (data->hidden)
     {
       /* The dialog was hidden before we got the handle, close it now */
-      send_close (data);
       filechooser_portal_data_free (data);
       self->mode_data = NULL;
     }
@@ -344,15 +363,15 @@ show_portal_file_chooser (GtkFileChooserNative *self,
 
   data->portal_handle = gtk_get_portal_request_path (data->connection, &token);
   data->portal_response_signal_id =
-        g_dbus_connection_signal_subscribe (data->connection,
-                                            PORTAL_BUS_NAME,
-                                            PORTAL_REQUEST_INTERFACE,
-                                            "Response",
-                                            data->portal_handle,
-                                            NULL,
-                                            G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
-                                            response_cb,
-                                            self, NULL);
+    g_dbus_connection_signal_subscribe (data->connection,
+                                        PORTAL_BUS_NAME,
+                                        PORTAL_REQUEST_INTERFACE,
+                                        "Response",
+                                        data->portal_handle,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+                                        response_cb,
+                                        self, NULL);
 
   multiple = gtk_file_chooser_get_select_multiple (GTK_FILE_CHOOSER (self));
   directory = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (self)) == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
@@ -527,7 +546,9 @@ gtk_file_chooser_native_portal_hide (GtkFileChooserNative *self)
   if (data->portal_handle)
     send_close (data);
 
-  filechooser_portal_data_free (data);
-
+  /* We clear the data because we might have in-flight async
+   * operations that can still access it
+   */
+  filechooser_portal_data_clear (data);
   self->mode_data = NULL;
 }