From 3033506f2c266115a00ff43daaad14e59e3215c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
Date: Tue, 5 Dec 2017 22:41:17 +0100
Subject: [PATCH 1/4] dnd: Nullify _dragActor after we've destroyed it, and
 avoid invalid access

We need to avoid that we use the _dragActor instance after that it has
been destroyed or we'll get errors. We now set it to null when this
happens, protecting any access to that.

Add a DragState enum-like object to keep track of the state
instead of using booleans.

Remove duplicated handler on 'destroy' and just use a generic one.

https://bugzilla.gnome.org/show_bug.cgi?id=791233
---
 js/ui/dnd.js | 65 ++++++++++++++++++++++++++++++++++++------------------------
 1 file changed, 39 insertions(+), 26 deletions(-)

diff --git a/js/ui/dnd.js b/js/ui/dnd.js
index 634a7d6d7..8483e89fc 100644
--- a/js/ui/dnd.js
+++ b/js/ui/dnd.js
@@ -27,6 +27,12 @@ var DragMotionResult = {
     CONTINUE:  3
 };
 
+var DragState = {
+    INIT:      0,
+    DRAGGING:  1,
+    CANCELLED: 2,
+};
+
 var DRAG_CURSOR_MAP = {
     0: Meta.Cursor.DND_UNSUPPORTED_TARGET,
     1: Meta.Cursor.DND_COPY,
@@ -78,6 +84,8 @@ var _Draggable = new Lang.Class({
                                         dragActorOpacity: undefined });
 
         this.actor = actor;
+        this._dragState = DragState.INIT;
+
         if (!params.manualMode) {
             this.actor.connect('button-press-event',
                                Lang.bind(this, this._onButtonPress));
@@ -88,7 +96,7 @@ var _Draggable = new Lang.Class({
         this.actor.connect('destroy', Lang.bind(this, function() {
             this._actorDestroyed = true;
 
-            if (this._dragInProgress && this._dragCancellable)
+            if (this._dragState == DragState.DRAGGING && this._dragCancellable)
                 this._cancelDrag(global.get_current_time());
             this.disconnectAll();
         }));
@@ -100,7 +108,6 @@ var _Draggable = new Lang.Class({
         this._dragActorOpacity = params.dragActorOpacity;
 
         this._buttonDown = false; // The mouse button has been pressed and has not yet been released.
-        this._dragInProgress = false; // The drag has been started, and has not been dropped or cancelled yet.
         this._animationInProgress = false; // The drag is over and the item is in the process of animating to its original position (snapping back or reverting).
         this._dragCancellable = true;
 
@@ -206,9 +213,10 @@ var _Draggable = new Lang.Class({
             (event.type() == Clutter.EventType.TOUCH_END &&
              global.display.is_pointer_emulating_sequence(event.get_event_sequence()))) {
             this._buttonDown = false;
-            if (this._dragInProgress) {
+            if (this._dragState == DragState.DRAGGING) {
                 return this._dragActorDropped(event);
-            } else if (this._dragActor != null && !this._animationInProgress) {
+            } else if ((this._dragActor != null || this._dragState == DragState.CANCELLED) &&
+                       !this._animationInProgress) {
                 // Drag must have been cancelled with Esc.
                 this._dragComplete();
                 return Clutter.EVENT_STOP;
@@ -222,14 +230,14 @@ var _Draggable = new Lang.Class({
         } else if (event.type() == Clutter.EventType.MOTION ||
                    (event.type() == Clutter.EventType.TOUCH_UPDATE &&
                     global.display.is_pointer_emulating_sequence(event.get_event_sequence()))) {
-            if (this._dragInProgress) {
+            if (this._dragActor && this._dragState == DragState.DRAGGING) {
                 return this._updateDragPosition(event);
-            } else if (this._dragActor == null) {
+            } else if (this._dragActor == null && this._dragState != DragState.CANCELLED) {
                 return this._maybeStartDrag(event);
             }
         // We intercept KEY_PRESS event so that we can process Esc key press to cancel
         // dragging and ignore all other key presses.
-        } else if (event.type() == Clutter.EventType.KEY_PRESS && this._dragInProgress) {
+        } else if (event.type() == Clutter.EventType.KEY_PRESS && this._dragState == DragState.DRAGGING) {
             let symbol = event.get_key_symbol();
             if (symbol == Clutter.Escape) {
                 this._cancelDrag(event.get_time());
@@ -265,7 +273,7 @@ var _Draggable = new Lang.Class({
      */
     startDrag: function (stageX, stageY, time, sequence) {
         currentDraggable = this;
-        this._dragInProgress = true;
+        this._dragState = DragState.DRAGGING;
 
         // Special-case St.Button: the pointer grab messes with the internal
         // state, so force a reset to a reasonable state here
@@ -342,6 +350,13 @@ var _Draggable = new Lang.Class({
             Shell.util_set_hidden_from_pick(this._dragActor, true);
         }
 
+        this._dragActorDestroyId = this._dragActor.connect('destroy', () => {
+            // Cancel ongoing animation (if any)
+            this._finishAnimation();
+
+            this._dragActor = null;
+            this._dragState = DragState.CANCELLED;
+        });
         this._dragOrigOpacity = this._dragActor.opacity;
         if (this._dragActorOpacity != undefined)
             this._dragActor.opacity = this._dragActorOpacity;
@@ -500,7 +515,7 @@ var _Draggable = new Lang.Class({
                                                 event.get_time())) {
                     // If it accepted the drop without taking the actor,
                     // handle it ourselves.
-                    if (this._dragActor.get_parent() == Main.uiGroup) {
+                    if (this._dragActor && this._dragActor.get_parent() == Main.uiGroup) {
                         if (this._restoreOnSuccess) {
                             this._restoreDragActor(event.get_time());
                             return true;
@@ -508,7 +523,7 @@ var _Draggable = new Lang.Class({
                             this._dragActor.destroy();
                     }
 
-                    this._dragInProgress = false;
+                    this._dragState = DragState.INIT;
                     global.screen.set_cursor(Meta.Cursor.DEFAULT);
                     this.emit('drag-end', event.get_time(), true);
                     this._dragComplete();
@@ -557,20 +572,22 @@ var _Draggable = new Lang.Class({
 
     _cancelDrag: function(eventTime) {
         this.emit('drag-cancelled', eventTime);
-        this._dragInProgress = false;
-        let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();
+        let wasCancelled = (this._dragState == DragState.CANCELLED);
+        this._dragState = DragState.CANCELLED;
 
-        if (this._actorDestroyed) {
+        if (this._actorDestroyed || wasCancelled) {
             global.screen.set_cursor(Meta.Cursor.DEFAULT);
             if (!this._buttonDown)
                 this._dragComplete();
             this.emit('drag-end', eventTime, false);
-            if (!this._dragOrigParent)
+            if (!this._dragOrigParent && this._dragActor)
                 this._dragActor.destroy();
 
             return;
         }
 
+        let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();
+
         this._animateDragEnd(eventTime,
                              { x: snapBackX,
                                y: snapBackY,
@@ -581,7 +598,7 @@ var _Draggable = new Lang.Class({
     },
 
     _restoreDragActor: function(eventTime) {
-        this._dragInProgress = false;
+        this._dragState = DragState.INIT;
         let [restoreX, restoreY, restoreScale] = this._getRestoreLocation();
 
         // fade the actor back in at its original location
@@ -596,12 +613,6 @@ var _Draggable = new Lang.Class({
     _animateDragEnd: function (eventTime, params) {
         this._animationInProgress = true;
 
-        // finish animation if the actor gets destroyed
-        // during it
-        this._dragActorDestroyId =
-            this._dragActor.connect('destroy',
-                                    Lang.bind(this, this._finishAnimation));
-
         params['opacity']          = this._dragOrigOpacity;
         params['transition']       = 'easeOutQuad';
         params['onComplete']       = this._onAnimationComplete;
@@ -624,9 +635,7 @@ var _Draggable = new Lang.Class({
     },
 
     _onAnimationComplete : function (dragActor, eventTime) {
-        dragActor.disconnect(this._dragActorDestroyId);
-        this._dragActorDestroyId = 0;
 
         if (this._dragOrigParent) {
             Main.uiGroup.remove_child(this._dragActor);
             this._dragOrigParent.add_actor(this._dragActor);
@@ -641,7 +649,7 @@ var _Draggable = new Lang.Class({
     },
 
     _dragComplete: function() {
-        if (!this._actorDestroyed)
+        if (!this._actorDestroyed && this._dragActor)
             Shell.util_set_hidden_from_pick(this._dragActor, false);
 
         this._ungrabEvents();
@@ -652,7 +660,12 @@ var _Draggable = new Lang.Class({
             this._updateHoverId = 0;
         }
 
-        this._dragActor = undefined;
+        if (this._dragActor) {
+            this._dragActor.disconnect(this._dragActorDestroyId);
+            this._dragActor = null;
+        }
+
+        this._dragState = DragState.INIT;
         currentDraggable = null;
     }
 });
-- 
2.16.4


From 87da623d86323a0744b8723e1991f053586defaf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
Date: Wed, 4 Jul 2018 15:56:25 +0200
Subject: [PATCH 2/4] messageList: stop syncing if closeButton has been
 destroyed

The _sync function for Message only updates the close button visibility,
so we can safely stop doing that if the close button get get destroyed earlier
(as it happens when clicking on it).

https://bugzilla.gnome.org/show_bug.cgi?id=791233
---
 js/ui/messageList.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/js/ui/messageList.js b/js/ui/messageList.js
index 3b3c2b6df..547135a1f 100644
--- a/js/ui/messageList.js
+++ b/js/ui/messageList.js
@@ -363,7 +363,8 @@ var Message = new Lang.Class({
         this.setBody(body);
 
         this._closeButton.connect('clicked', Lang.bind(this, this.close));
-        this.actor.connect('notify::hover', Lang.bind(this, this._sync));
+        let actorHoverId = this.actor.connect('notify::hover', Lang.bind(this, this._sync));
+        this._closeButton.connect('destroy', this.actor.disconnect.bind(this.actor, actorHoverId));
         this.actor.connect('clicked', Lang.bind(this, this._onClicked));
         this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
         this._sync();
-- 
2.16.4


From 9c41736a813354fd9291177b12f6c4f85bd1c5f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
Date: Wed, 4 Jul 2018 16:55:28 +0200
Subject: [PATCH 3/4] automountManager: remove allowAutorun expire timeout on
 volume removal

If the volume is removed before AUTORUN_EXPIRE_TIMEOUT_SECS seconds, we can stop
the timeout earlier as there's nothing to unset, while the volume instance
won't be valid anymore.

https://bugzilla.gnome.org/show_bug.cgi?id=791233
---
 js/ui/components/automountManager.js | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/js/ui/components/automountManager.js b/js/ui/components/automountManager.js
index 2d8f3f8fb..a6cd85792 100644
--- a/js/ui/components/automountManager.js
+++ b/js/ui/components/automountManager.js
@@ -210,6 +210,10 @@ var AutomountManager = new Lang.Class({
     },
 
     _onVolumeRemoved: function(monitor, volume) {
+        if (volume._allowAutorunExpireId && volume._allowAutorunExpireId > 0) {
+            Mainloop.source_remove(volume._allowAutorunExpireId);
+            delete volume._allowAutorunExpireId;
+        }
         this._volumeQueue = 
             this._volumeQueue.filter(function(element) {
                 return (element != volume);
@@ -234,8 +238,10 @@ var AutomountManager = new Lang.Class({
     _allowAutorunExpire: function(volume) {
         let id = Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, function() {
             volume.allowAutorun = false;
+            delete volume._allowAutorunExpireId;
             return GLib.SOURCE_REMOVE;
         });
+        volume._allowAutorunExpireId = id;
         GLib.Source.set_name_by_id(id, '[gnome-shell] volume.allowAutorun');
     }
 });
-- 
2.16.4


From 5bca4a884e8f02441a89d7b44490339d869e5966 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Mon, 9 Jul 2018 13:31:26 +0200
Subject: [PATCH 4/4] calendar: chain up to parent on _onDestroy

---
 js/ui/calendar.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index 990cac243..815c9f9c9 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -803,6 +803,8 @@ var NotificationMessage = new Lang.Class({
     },
 
     _onDestroy: function() {
+        this.parent();
+
         if (this._updatedId)
             this.notification.disconnect(this._updatedId);
         this._updatedId = 0;
-- 
2.16.4

