Blob Blame History Raw
From 82757fd7bd099d849c11847be75f9f4cf43544a7 Mon Sep 17 00:00:00 2001
From: "Owen W. Taylor" <otaylor@fishsoup.net>
Date: Wed, 7 Oct 2015 22:56:11 -0400
Subject: [PATCH] Don't crash on accesses to stale window-backed apps

The JS code could still be holding on to a reference to a window-backed app
after all windows have vanished. (For example, the dash queues an idle to
refetch apps and display them.) Avoid dying with an error message if we
attempt to activate or otherwise manipulate such a window.

https://bugzilla.gnome.org/show_bug.cgi?id=674799
---
 src/shell-app.c | 32 +++++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 9 deletions(-)

diff --git a/src/shell-app.c b/src/shell-app.c
index 051f84f..36aa47a 100644
--- a/src/shell-app.c
+++ b/src/shell-app.c
@@ -146,93 +146,99 @@ static void
 shell_app_set_property (GObject      *gobject,
                         guint         prop_id,
                         const GValue *value,
                         GParamSpec   *pspec)
 {
   ShellApp *app = SHELL_APP (gobject);
 
   switch (prop_id)
     {
     case PROP_APP_INFO:
       _shell_app_set_app_info (app, g_value_get_object (value));
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
       break;
     }
 }
 
 const char *
 shell_app_get_id (ShellApp *app)
 {
   if (app->info)
     return g_app_info_get_id (G_APP_INFO (app->info));
   return app->window_id_string;
 }
 
 static MetaWindow *
 window_backed_app_get_window (ShellApp     *app)
 {
   g_assert (app->info == NULL);
-  g_assert (app->running_state);
-  g_assert (app->running_state->windows);
-  return app->running_state->windows->data;
+  if (app->running_state)
+    {
+      g_assert (app->running_state->windows);
+      return app->running_state->windows->data;
+    }
+  else
+    return NULL;
 }
 
 static ClutterActor *
 window_backed_app_get_icon (ShellApp *app,
                             int       size)
 {
-  MetaWindow *window;
+  MetaWindow *window = NULL;
   ClutterActor *actor;
   gint scale;
   ShellGlobal *global;
   StThemeContext *context;
 
   global = shell_global_get ();
   context = st_theme_context_get_for_stage (shell_global_get_stage (global));
   g_object_get (context, "scale-factor", &scale, NULL);
 
   size *= scale;
 
   /* During a state transition from running to not-running for
    * window-backend apps, it's possible we get a request for the icon.
    * Avoid asserting here and just return an empty image.
    */
-  if (app->running_state == NULL)
+  if (app->running_state != NULL)
+    window = window_backed_app_get_window (app);
+
+  if (window == NULL)
     {
       actor = clutter_texture_new ();
       g_object_set (actor, "opacity", 0, "width", (float) size, "height", (float) size, NULL);
       return actor;
     }
 
-  window = window_backed_app_get_window (app);
   actor = st_texture_cache_bind_pixbuf_property (st_texture_cache_get_default (),
                                                                G_OBJECT (window),
                                                                "icon");
   g_object_set (actor, "width", (float) size, "height", (float) size, NULL);
   return actor;
 }
 
 /**
  * shell_app_create_icon_texture:
  *
  * Look up the icon for this application, and create a #ClutterTexture
  * for it at the given size.
  *
  * Return value: (transfer none): A floating #ClutterActor
  */
 ClutterActor *
 shell_app_create_icon_texture (ShellApp   *app,
                                int         size)
 {
   GIcon *icon;
   gint scale;
   ClutterActor *ret;
   ShellGlobal *global;
   StThemeContext *context;
 
   global = shell_global_get ();
   context = st_theme_context_get_for_stage (shell_global_get_stage (global));
   g_object_get (context, "scale-factor", &scale, NULL);
   ret = NULL;
 
@@ -415,63 +421,65 @@ shell_app_get_faded_icon (ShellApp *app, int size, ClutterTextDirection directio
   texture = st_texture_cache_load (st_texture_cache_get_default (),
                                    cache_key,
                                    ST_TEXTURE_CACHE_POLICY_FOREVER,
                                    shell_app_create_faded_icon_cpu,
                                    &data,
                                    NULL);
   g_free (cache_key);
 
   if (texture != COGL_INVALID_HANDLE)
     {
       result = clutter_texture_new ();
       clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (result), texture);
     }
   else
     {
       result = clutter_texture_new ();
       g_object_set (result, "opacity", 0, "width", (float) size * scale, "height", (float) size * scale, NULL);
 
     }
   return result;
 }
 
 const char *
 shell_app_get_name (ShellApp *app)
 {
   if (app->info)
     return g_app_info_get_name (G_APP_INFO (app->info));
   else
     {
       MetaWindow *window = window_backed_app_get_window (app);
-      const char *name;
+      const char *name = NULL;
+
+      if (window)
+        name = meta_window_get_title (window);
 
-      name = meta_window_get_title (window);
       if (!name)
         name = C_("program", "Unknown");
       return name;
     }
 }
 
 const char *
 shell_app_get_description (ShellApp *app)
 {
   if (app->info)
     return g_app_info_get_description (G_APP_INFO (app->info));
   else
     return NULL;
 }
 
 /**
  * shell_app_is_window_backed:
  *
  * A window backed application is one which represents just an open
  * window, i.e. there's no .desktop file assocation, so we don't know
  * how to launch it again.
  */
 gboolean
 shell_app_is_window_backed (ShellApp *app)
 {
   return app->info == NULL;
 }
 
 typedef struct {
   MetaWorkspace *workspace;
@@ -1325,61 +1333,67 @@ app_child_setup (gpointer user_data)
       do
         res = dup2 (journalfd, 1);
       while (G_UNLIKELY (res == -1 && errno == EINTR));
       do
         res = dup2 (journalfd, 2);
       while (G_UNLIKELY (res == -1 && errno == EINTR));
       (void) close (journalfd);
     }
 }
 #endif
 
 /**
  * shell_app_launch:
  * @timestamp: Event timestamp, or 0 for current event timestamp
  * @workspace: Start on this workspace, or -1 for default
  * @error: A #GError
  */
 gboolean
 shell_app_launch (ShellApp     *app,
                   guint         timestamp,
                   int           workspace,
                   GError      **error)
 {
   ShellGlobal *global;
   GAppLaunchContext *context;
   gboolean ret;
 
   if (app->info == NULL)
     {
       MetaWindow *window = window_backed_app_get_window (app);
-      meta_window_activate (window, timestamp);
+      /* We don't use an error return if there no longer any windows, because the
+       * user attempting to activate a stale window backed app isn't something
+       * we would expect the caller to meaningfully handle or display an error
+       * message to the user.
+       */
+      if (window)
+        meta_window_activate (window, timestamp);
       return TRUE;
     }
 
   global = shell_global_get ();
   context = shell_global_create_app_launch_context (global, timestamp, workspace);
 
   ret = g_desktop_app_info_launch_uris_as_manager (app->info, NULL,
                                                    context,
                                                    G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
 #ifdef HAVE_SYSTEMD
                                                    app_child_setup, (gpointer)shell_app_get_id (app),
 #else
                                                    NULL, NULL,
 #endif
                                                    _gather_pid_callback, app,
                                                    error);
   g_object_unref (context);
 
   return ret;
 }
 
 /**
  * shell_app_launch_action:
  * @app: the #ShellApp
  * @action_name: the name of the action to launch (as obtained by
  *               g_desktop_app_info_list_actions())
  * @timestamp: Event timestamp, or 0 for current event timestamp
  * @workspace: Start on this workspace, or -1 for default
  */
 void
-- 
2.7.4