Blame SOURCES/fix-invalid-access-warnings.patch

c7fac9
From 1c838205dcd99a0a2a901f7449197da3df7b3954 Mon Sep 17 00:00:00 2001
c7fac9
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
c7fac9
Date: Tue, 5 Dec 2017 22:41:17 +0100
c7fac9
Subject: [PATCH 1/6] dnd: Nullify _dragActor after we've destroyed it, and
c7fac9
 avoid invalid access
c7fac9
c7fac9
We need to avoid that we use the _dragActor instance after that it has
c7fac9
been destroyed or we'll get errors. We now set it to null when this
c7fac9
happens, protecting any access to that.
c7fac9
c7fac9
Add a DragState enum-like object to keep track of the state
c7fac9
instead of using booleans.
c7fac9
c7fac9
Remove duplicated handler on 'destroy' and just use a generic one.
c7fac9
c7fac9
https://bugzilla.gnome.org/show_bug.cgi?id=791233
c7fac9
---
c7fac9
 js/ui/dnd.js | 65 +++++++++++++++++++++++++++++++---------------------
c7fac9
 1 file changed, 39 insertions(+), 26 deletions(-)
c7fac9
c7fac9
diff --git a/js/ui/dnd.js b/js/ui/dnd.js
c7fac9
index a38607c24..431c60d6c 100644
c7fac9
--- a/js/ui/dnd.js
c7fac9
+++ b/js/ui/dnd.js
c7fac9
@@ -27,6 +27,12 @@ var DragMotionResult = {
c7fac9
     CONTINUE:  3
c7fac9
 };
c7fac9
 
c7fac9
+var DragState = {
c7fac9
+    INIT:      0,
c7fac9
+    DRAGGING:  1,
c7fac9
+    CANCELLED: 2,
c7fac9
+};
c7fac9
+
c7fac9
 var DRAG_CURSOR_MAP = {
c7fac9
     0: Meta.Cursor.DND_UNSUPPORTED_TARGET,
c7fac9
     1: Meta.Cursor.DND_COPY,
c7fac9
@@ -78,6 +84,8 @@ var _Draggable = new Lang.Class({
c7fac9
                                         dragActorOpacity: undefined });
c7fac9
 
c7fac9
         this.actor = actor;
c7fac9
+        this._dragState = DragState.INIT;
c7fac9
+
c7fac9
         if (!params.manualMode) {
c7fac9
             this.actor.connect('button-press-event',
c7fac9
                                this._onButtonPress.bind(this));
c7fac9
@@ -88,7 +96,7 @@ var _Draggable = new Lang.Class({
c7fac9
         this.actor.connect('destroy', () => {
c7fac9
             this._actorDestroyed = true;
c7fac9
 
c7fac9
-            if (this._dragInProgress && this._dragCancellable)
c7fac9
+            if (this._dragState == DragState.DRAGGING && this._dragCancellable)
c7fac9
                 this._cancelDrag(global.get_current_time());
c7fac9
             this.disconnectAll();
c7fac9
         });
c7fac9
@@ -100,7 +108,6 @@ var _Draggable = new Lang.Class({
c7fac9
         this._dragActorOpacity = params.dragActorOpacity;
c7fac9
 
c7fac9
         this._buttonDown = false; // The mouse button has been pressed and has not yet been released.
c7fac9
-        this._dragInProgress = false; // The drag has been started, and has not been dropped or cancelled yet.
c7fac9
         this._animationInProgress = false; // The drag is over and the item is in the process of animating to its original position (snapping back or reverting).
c7fac9
         this._dragCancellable = true;
c7fac9
 
c7fac9
@@ -206,9 +213,10 @@ var _Draggable = new Lang.Class({
c7fac9
             (event.type() == Clutter.EventType.TOUCH_END &&
c7fac9
              global.display.is_pointer_emulating_sequence(event.get_event_sequence()))) {
c7fac9
             this._buttonDown = false;
c7fac9
-            if (this._dragInProgress) {
c7fac9
+            if (this._dragState == DragState.DRAGGING) {
c7fac9
                 return this._dragActorDropped(event);
c7fac9
-            } else if (this._dragActor != null && !this._animationInProgress) {
c7fac9
+            } else if ((this._dragActor != null || this._dragState == DragState.CANCELLED) &&
c7fac9
+                       !this._animationInProgress) {
c7fac9
                 // Drag must have been cancelled with Esc.
c7fac9
                 this._dragComplete();
c7fac9
                 return Clutter.EVENT_STOP;
c7fac9
@@ -222,14 +230,14 @@ var _Draggable = new Lang.Class({
c7fac9
         } else if (event.type() == Clutter.EventType.MOTION ||
c7fac9
                    (event.type() == Clutter.EventType.TOUCH_UPDATE &&
c7fac9
                     global.display.is_pointer_emulating_sequence(event.get_event_sequence()))) {
c7fac9
-            if (this._dragInProgress) {
c7fac9
+            if (this._dragActor && this._dragState == DragState.DRAGGING) {
c7fac9
                 return this._updateDragPosition(event);
c7fac9
-            } else if (this._dragActor == null) {
c7fac9
+            } else if (this._dragActor == null && this._dragState != DragState.CANCELLED) {
c7fac9
                 return this._maybeStartDrag(event);
c7fac9
             }
c7fac9
         // We intercept KEY_PRESS event so that we can process Esc key press to cancel
c7fac9
         // dragging and ignore all other key presses.
c7fac9
-        } else if (event.type() == Clutter.EventType.KEY_PRESS && this._dragInProgress) {
c7fac9
+        } else if (event.type() == Clutter.EventType.KEY_PRESS && this._dragState == DragState.DRAGGING) {
c7fac9
             let symbol = event.get_key_symbol();
c7fac9
             if (symbol == Clutter.Escape) {
c7fac9
                 this._cancelDrag(event.get_time());
c7fac9
@@ -265,7 +273,7 @@ var _Draggable = new Lang.Class({
c7fac9
      */
c7fac9
     startDrag(stageX, stageY, time, sequence) {
c7fac9
         currentDraggable = this;
c7fac9
-        this._dragInProgress = true;
c7fac9
+        this._dragState = DragState.DRAGGING;
c7fac9
 
c7fac9
         // Special-case St.Button: the pointer grab messes with the internal
c7fac9
         // state, so force a reset to a reasonable state here
c7fac9
@@ -342,6 +350,13 @@ var _Draggable = new Lang.Class({
c7fac9
             Shell.util_set_hidden_from_pick(this._dragActor, true);
c7fac9
         }
c7fac9
 
c7fac9
+        this._dragActorDestroyId = this._dragActor.connect('destroy', () => {
c7fac9
+            // Cancel ongoing animation (if any)
c7fac9
+            this._finishAnimation();
c7fac9
+
c7fac9
+            this._dragActor = null;
c7fac9
+            this._dragState = DragState.CANCELLED;
c7fac9
+        });
c7fac9
         this._dragOrigOpacity = this._dragActor.opacity;
c7fac9
         if (this._dragActorOpacity != undefined)
c7fac9
             this._dragActor.opacity = this._dragActorOpacity;
c7fac9
@@ -500,7 +515,7 @@ var _Draggable = new Lang.Class({
c7fac9
                                                 event.get_time())) {
c7fac9
                     // If it accepted the drop without taking the actor,
c7fac9
                     // handle it ourselves.
c7fac9
-                    if (this._dragActor.get_parent() == Main.uiGroup) {
c7fac9
+                    if (this._dragActor && this._dragActor.get_parent() == Main.uiGroup) {
c7fac9
                         if (this._restoreOnSuccess) {
c7fac9
                             this._restoreDragActor(event.get_time());
c7fac9
                             return true;
c7fac9
@@ -508,7 +523,7 @@ var _Draggable = new Lang.Class({
c7fac9
                             this._dragActor.destroy();
c7fac9
                     }
c7fac9
 
c7fac9
-                    this._dragInProgress = false;
c7fac9
+                    this._dragState = DragState.INIT;
c7fac9
                     global.screen.set_cursor(Meta.Cursor.DEFAULT);
c7fac9
                     this.emit('drag-end', event.get_time(), true);
c7fac9
                     this._dragComplete();
c7fac9
@@ -557,20 +572,22 @@ var _Draggable = new Lang.Class({
c7fac9
 
c7fac9
     _cancelDrag(eventTime) {
c7fac9
         this.emit('drag-cancelled', eventTime);
c7fac9
-        this._dragInProgress = false;
c7fac9
-        let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();
c7fac9
+        let wasCancelled = (this._dragState == DragState.CANCELLED);
c7fac9
+        this._dragState = DragState.CANCELLED;
c7fac9
 
c7fac9
-        if (this._actorDestroyed) {
c7fac9
+        if (this._actorDestroyed || wasCancelled) {
c7fac9
             global.screen.set_cursor(Meta.Cursor.DEFAULT);
c7fac9
             if (!this._buttonDown)
c7fac9
                 this._dragComplete();
c7fac9
             this.emit('drag-end', eventTime, false);
c7fac9
-            if (!this._dragOrigParent)
c7fac9
+            if (!this._dragOrigParent && this._dragActor)
c7fac9
                 this._dragActor.destroy();
c7fac9
 
c7fac9
             return;
c7fac9
         }
c7fac9
 
c7fac9
+        let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();
c7fac9
+
c7fac9
         this._animateDragEnd(eventTime,
c7fac9
                              { x: snapBackX,
c7fac9
                                y: snapBackY,
c7fac9
@@ -581,7 +598,7 @@ var _Draggable = new Lang.Class({
c7fac9
     },
c7fac9
 
c7fac9
     _restoreDragActor(eventTime) {
c7fac9
-        this._dragInProgress = false;
c7fac9
+        this._dragState = DragState.INIT;
c7fac9
         let [restoreX, restoreY, restoreScale] = this._getRestoreLocation();
c7fac9
 
c7fac9
         // fade the actor back in at its original location
c7fac9
@@ -596,12 +613,6 @@ var _Draggable = new Lang.Class({
c7fac9
     _animateDragEnd(eventTime, params) {
c7fac9
         this._animationInProgress = true;
c7fac9
 
c7fac9
-        // finish animation if the actor gets destroyed
c7fac9
-        // during it
c7fac9
-        this._dragActorDestroyId =
c7fac9
-            this._dragActor.connect('destroy',
c7fac9
-                                    this._finishAnimation.bind(this));
c7fac9
-
c7fac9
         params['opacity']          = this._dragOrigOpacity;
c7fac9
         params['transition']       = 'easeOutQuad';
c7fac9
         params['onComplete']       = this._onAnimationComplete;
c7fac9
@@ -624,9 +635,6 @@ var _Draggable = new Lang.Class({
c7fac9
     },
c7fac9
 
c7fac9
     _onAnimationComplete(dragActor, eventTime) {
c7fac9
-        dragActor.disconnect(this._dragActorDestroyId);
c7fac9
-        this._dragActorDestroyId = 0;
c7fac9
-
c7fac9
         if (this._dragOrigParent) {
c7fac9
             Main.uiGroup.remove_child(this._dragActor);
c7fac9
             this._dragOrigParent.add_actor(this._dragActor);
c7fac9
@@ -641,7 +649,7 @@ var _Draggable = new Lang.Class({
c7fac9
     },
c7fac9
 
c7fac9
     _dragComplete() {
c7fac9
-        if (!this._actorDestroyed)
c7fac9
+        if (!this._actorDestroyed && this._dragActor)
c7fac9
             Shell.util_set_hidden_from_pick(this._dragActor, false);
c7fac9
 
c7fac9
         this._ungrabEvents();
c7fac9
@@ -652,7 +660,12 @@ var _Draggable = new Lang.Class({
c7fac9
             this._updateHoverId = 0;
c7fac9
         }
c7fac9
 
c7fac9
-        this._dragActor = undefined;
c7fac9
+        if (this._dragActor) {
c7fac9
+            this._dragActor.disconnect(this._dragActorDestroyId);
c7fac9
+            this._dragActor = null;
c7fac9
+        }
c7fac9
+
c7fac9
+        this._dragState = DragState.INIT;
c7fac9
         currentDraggable = null;
c7fac9
     }
c7fac9
 });
c7fac9
-- 
c7fac9
2.20.1
c7fac9
c7fac9
c7fac9
From 98fd633f3b124f72f71aa5da38df9c69121fd4ed Mon Sep 17 00:00:00 2001
c7fac9
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
c7fac9
Date: Thu, 3 Jan 2019 11:53:13 +0100
c7fac9
Subject: [PATCH 2/6] dnd: Repick target actor if destroyed mid iteration
c7fac9
c7fac9
The picked target actor may be destroyed (e.g. hover style change
c7fac9
resulting in the ClutterTexture to be destroyed). If we don't handle
c7fac9
this, GJS will abort when it sees the exception caused by Javascript
c7fac9
code trying to access the destroyed target actor.
c7fac9
c7fac9
To handle it, listen on the 'destroy' signal on the target actor, and
c7fac9
repick, so a valid actor is passed to the next motion callback.
c7fac9
c7fac9
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/632
c7fac9
---
c7fac9
 js/ui/dnd.js | 22 ++++++++++++++++++++--
c7fac9
 1 file changed, 20 insertions(+), 2 deletions(-)
c7fac9
c7fac9
diff --git a/js/ui/dnd.js b/js/ui/dnd.js
c7fac9
index 431c60d6c..9e961a186 100644
c7fac9
--- a/js/ui/dnd.js
c7fac9
+++ b/js/ui/dnd.js
c7fac9
@@ -411,10 +411,15 @@ var _Draggable = new Lang.Class({
c7fac9
         return true;
c7fac9
     },
c7fac9
 
c7fac9
+    _pickTargetActor() {
c7fac9
+        return this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
c7fac9
+                                                            this._dragX, this._dragY);
c7fac9
+    },
c7fac9
+
c7fac9
     _updateDragHover() {
c7fac9
         this._updateHoverId = 0;
c7fac9
-        let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
c7fac9
-                                                                  this._dragX, this._dragY);
c7fac9
+        let target = this._pickTargetActor();
c7fac9
+
c7fac9
         let dragEvent = {
c7fac9
             x: this._dragX,
c7fac9
             y: this._dragY,
c7fac9
@@ -422,6 +427,18 @@ var _Draggable = new Lang.Class({
c7fac9
             source: this.actor._delegate,
c7fac9
             targetActor: target
c7fac9
         };
c7fac9
+
c7fac9
+        let targetActorDestroyHandlerId;
c7fac9
+        let handleTargetActorDestroyClosure;
c7fac9
+        handleTargetActorDestroyClosure = () => {
c7fac9
+            target = this._pickTargetActor();
c7fac9
+            dragEvent.targetActor = target;
c7fac9
+            targetActorDestroyHandlerId =
c7fac9
+                target.connect('destroy', handleTargetActorDestroyClosure);
c7fac9
+        };
c7fac9
+        targetActorDestroyHandlerId =
c7fac9
+            target.connect('destroy', handleTargetActorDestroyClosure);
c7fac9
+
c7fac9
         for (let i = 0; i < dragMonitors.length; i++) {
c7fac9
             let motionFunc = dragMonitors[i].dragMotion;
c7fac9
             if (motionFunc) {
c7fac9
@@ -432,6 +449,7 @@ var _Draggable = new Lang.Class({
c7fac9
                 }
c7fac9
             }
c7fac9
         }
c7fac9
+        dragEvent.targetActor.disconnect(targetActorDestroyHandlerId);
c7fac9
 
c7fac9
         while (target) {
c7fac9
             if (target._delegate && target._delegate.handleDragOver) {
c7fac9
-- 
c7fac9
2.20.1
c7fac9
c7fac9
c7fac9
From 2dfae7213f0d3849dfcc38bb2fbaeaf80d4e8b19 Mon Sep 17 00:00:00 2001
c7fac9
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
c7fac9
Date: Wed, 4 Jul 2018 15:56:25 +0200
c7fac9
Subject: [PATCH 3/6] messageList: stop syncing if closeButton has been
c7fac9
 destroyed
c7fac9
c7fac9
The _sync function for Message only updates the close button visibility,
c7fac9
so we can safely stop doing that if the close button get get destroyed earlier
c7fac9
(as it happens when clicking on it).
c7fac9
c7fac9
https://bugzilla.gnome.org/show_bug.cgi?id=791233
c7fac9
---
c7fac9
 js/ui/messageList.js | 3 ++-
c7fac9
 1 file changed, 2 insertions(+), 1 deletion(-)
c7fac9
c7fac9
diff --git a/js/ui/messageList.js b/js/ui/messageList.js
c7fac9
index aff201ed6..2d397c1d7 100644
c7fac9
--- a/js/ui/messageList.js
c7fac9
+++ b/js/ui/messageList.js
c7fac9
@@ -362,7 +362,8 @@ var Message = new Lang.Class({
c7fac9
         this.setBody(body);
c7fac9
 
c7fac9
         this._closeButton.connect('clicked', this.close.bind(this));
c7fac9
-        this.actor.connect('notify::hover', this._sync.bind(this));
c7fac9
+        let actorHoverId = this.actor.connect('notify::hover', this._sync.bind(this));
c7fac9
+        this._closeButton.connect('destroy', this.actor.disconnect.bind(this.actor, actorHoverId));
c7fac9
         this.actor.connect('clicked', this._onClicked.bind(this));
c7fac9
         this.actor.connect('destroy', this._onDestroy.bind(this));
c7fac9
         this._sync();
c7fac9
-- 
c7fac9
2.20.1
c7fac9
c7fac9
c7fac9
From 41bde330384ba7021c96ea3d4575bfa12295dff1 Mon Sep 17 00:00:00 2001
c7fac9
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
c7fac9
Date: Wed, 4 Jul 2018 16:55:28 +0200
c7fac9
Subject: [PATCH 4/6] automountManager: remove allowAutorun expire timeout on
c7fac9
 volume removal
c7fac9
c7fac9
If the volume is removed before AUTORUN_EXPIRE_TIMEOUT_SECS seconds, we can stop
c7fac9
the timeout earlier as there's nothing to unset, while the volume instance
c7fac9
won't be valid anymore.
c7fac9
c7fac9
https://bugzilla.gnome.org/show_bug.cgi?id=791233
c7fac9
---
c7fac9
 js/ui/components/automountManager.js | 6 ++++++
c7fac9
 1 file changed, 6 insertions(+)
c7fac9
c7fac9
diff --git a/js/ui/components/automountManager.js b/js/ui/components/automountManager.js
c7fac9
index 2d8f3f8fb..a6cd85792 100644
c7fac9
--- a/js/ui/components/automountManager.js
c7fac9
+++ b/js/ui/components/automountManager.js
c7fac9
@@ -210,6 +210,10 @@ var AutomountManager = new Lang.Class({
c7fac9
     },
c7fac9
 
c7fac9
     _onVolumeRemoved(monitor, volume) {
c7fac9
+        if (volume._allowAutorunExpireId && volume._allowAutorunExpireId > 0) {
c7fac9
+            Mainloop.source_remove(volume._allowAutorunExpireId);
c7fac9
+            delete volume._allowAutorunExpireId;
c7fac9
+        }
c7fac9
         this._volumeQueue = 
c7fac9
             this._volumeQueue.filter(element => (element != volume));
c7fac9
     },
c7fac9
@@ -234,8 +238,10 @@ var AutomountManager = new Lang.Class({
c7fac9
     _allowAutorunExpire(volume) {
c7fac9
         let id = Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, () => {
c7fac9
             volume.allowAutorun = false;
c7fac9
+            delete volume._allowAutorunExpireId;
c7fac9
             return GLib.SOURCE_REMOVE;
c7fac9
         });
c7fac9
+        volume._allowAutorunExpireId = id;
c7fac9
         GLib.Source.set_name_by_id(id, '[gnome-shell] volume.allowAutorun');
c7fac9
     }
c7fac9
 });
c7fac9
-- 
c7fac9
2.20.1
c7fac9
c7fac9
c7fac9
From f0d608cea903538683b2eaafadbdefe7ff41475e Mon Sep 17 00:00:00 2001
c7fac9
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
c7fac9
Date: Mon, 9 Jul 2018 13:31:26 +0200
c7fac9
Subject: [PATCH 5/6] calendar: chain up to parent on _onDestroy
c7fac9
c7fac9
---
c7fac9
 js/ui/calendar.js | 2 ++
c7fac9
 1 file changed, 2 insertions(+)
c7fac9
c7fac9
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
c7fac9
index a46017ad0..65133d9f7 100644
c7fac9
--- a/js/ui/calendar.js
c7fac9
+++ b/js/ui/calendar.js
c7fac9
@@ -802,6 +802,8 @@ var NotificationMessage = new Lang.Class({
c7fac9
     },
c7fac9
 
c7fac9
     _onDestroy() {
c7fac9
+        this.parent();
c7fac9
+
c7fac9
         if (this._updatedId)
c7fac9
             this.notification.disconnect(this._updatedId);
c7fac9
         this._updatedId = 0;
c7fac9
-- 
c7fac9
2.20.1
c7fac9
c7fac9
c7fac9
From dc75a4e785e5f19f61bfd8f58e079c0bdfd0fca8 Mon Sep 17 00:00:00 2001
c7fac9
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
c7fac9
Date: Tue, 5 Dec 2017 02:41:50 +0100
c7fac9
Subject: [PATCH 6/6] tweener: Save handlers on target and remove them on
c7fac9
 destroy
c7fac9
c7fac9
Saving handlers we had using the wrapper as a property of the object and delete
c7fac9
them when resetting the object state.
c7fac9
Without doing this an handler could be called on a destroyed target when this
c7fac9
happens on the onComplete callback.
c7fac9
c7fac9
https://bugzilla.gnome.org/show_bug.cgi?id=791233
c7fac9
---
c7fac9
 js/ui/tweener.js | 63 ++++++++++++++++++++++++++++++++++++++----------
c7fac9
 1 file changed, 50 insertions(+), 13 deletions(-)
c7fac9
c7fac9
diff --git a/js/ui/tweener.js b/js/ui/tweener.js
c7fac9
index 1a85e2fb1..22818ba4b 100644
c7fac9
--- a/js/ui/tweener.js
c7fac9
+++ b/js/ui/tweener.js
c7fac9
@@ -69,30 +69,67 @@ function _getTweenState(target) {
c7fac9
     return target.__ShellTweenerState;
c7fac9
 }
c7fac9
 
c7fac9
+function _ensureHandlers(target) {
c7fac9
+    if (!target.__ShellTweenerHandlers)
c7fac9
+        target.__ShellTweenerHandlers = {};
c7fac9
+    return target.__ShellTweenerHandlers;
c7fac9
+}
c7fac9
+
c7fac9
 function _resetTweenState(target) {
c7fac9
     let state = target.__ShellTweenerState;
c7fac9
 
c7fac9
     if (state) {
c7fac9
-        if (state.destroyedId)
c7fac9
+        if (state.destroyedId) {
c7fac9
             state.actor.disconnect(state.destroyedId);
c7fac9
+            delete state.destroyedId;
c7fac9
+        }
c7fac9
     }
c7fac9
 
c7fac9
+    _removeHandler(target, 'onComplete', _tweenCompleted);
c7fac9
     target.__ShellTweenerState = {};
c7fac9
 }
c7fac9
 
c7fac9
 function _addHandler(target, params, name, handler) {
c7fac9
-    if (params[name]) {
c7fac9
-        let oldHandler = params[name];
c7fac9
-        let oldScope = params[name + 'Scope'];
c7fac9
-        let oldParams = params[name + 'Params'];
c7fac9
-        let eventScope = oldScope ? oldScope : target;
c7fac9
-
c7fac9
-        params[name] = () => {
c7fac9
-            oldHandler.apply(eventScope, oldParams);
c7fac9
-            handler(target);
c7fac9
-        };
c7fac9
-    } else
c7fac9
-        params[name] = () => { handler(target); };
c7fac9
+    let wrapperNeeded = false;
c7fac9
+    let tweenerHandlers = _ensureHandlers(target);
c7fac9
+
c7fac9
+    if (!(name in tweenerHandlers)) {
c7fac9
+        tweenerHandlers[name] = [];
c7fac9
+        wrapperNeeded = true;
c7fac9
+    }
c7fac9
+
c7fac9
+    let handlers = tweenerHandlers[name];
c7fac9
+    handlers.push(handler);
c7fac9
+
c7fac9
+    if (wrapperNeeded) {
c7fac9
+        if (params[name]) {
c7fac9
+            let oldHandler = params[name];
c7fac9
+            let oldScope = params[name + 'Scope'];
c7fac9
+            let oldParams = params[name + 'Params'];
c7fac9
+            let eventScope = oldScope ? oldScope : target;
c7fac9
+
c7fac9
+            params[name] = () => {
c7fac9
+                oldHandler.apply(eventScope, oldParams);
c7fac9
+                handlers.forEach((h) => h(target));
c7fac9
+            };
c7fac9
+        } else {
c7fac9
+            params[name] = () => { handlers.forEach((h) => h(target)); };
c7fac9
+        }
c7fac9
+    }
c7fac9
+}
c7fac9
+
c7fac9
+function _removeHandler(target, name, handler) {
c7fac9
+    let tweenerHandlers = _ensureHandlers(target);
c7fac9
+
c7fac9
+    if (name in tweenerHandlers) {
c7fac9
+        let handlers = tweenerHandlers[name];
c7fac9
+        let handlerIndex = handlers.indexOf(handler);
c7fac9
+
c7fac9
+        while (handlerIndex > -1) {
c7fac9
+            handlers.splice(handlerIndex, 1);
c7fac9
+            handlerIndex = handlers.indexOf(handler);
c7fac9
+        }
c7fac9
+    }
c7fac9
 }
c7fac9
 
c7fac9
 function _actorDestroyed(target) {
c7fac9
-- 
c7fac9
2.20.1
c7fac9