signalTracker: Add TransientSignalHolder class

There are cases where we want to connect to a number of signals
for the lifetime of an object, but also other signals for a
limited period (say: between show and hide).

It is currently not possible to use disconnectObject() for the
latter, because it will disconnect all signals.

To address this use case, add a small class that can be used as
a transient signal holder, while still benefiting from autocleanup
by proxying the real owner.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2221>
This commit is contained in:
Florian Müllner 2022-03-04 23:32:24 +01:00 committed by Marge Bot
parent 7b0a94b246
commit cf29ec2f22
2 changed files with 69 additions and 13 deletions

View File

@ -1,6 +1,34 @@
/* exported addObjectSignalMethods */
/* exported TransientSignalHolder, addObjectSignalMethods */
const { GObject } = imports.gi;
/**
* @private
* @param {Object} obj - an object
* @returns {bool} - true if obj has a 'destroy' GObject signal
*/
function _hasDestroySignal(obj) {
return obj instanceof GObject.Object &&
GObject.signal_lookup('destroy', obj);
}
var TransientSignalHolder = GObject.registerClass(
class TransientSignalHolder extends GObject.Object {
static [GObject.signals] = {
'destroy': {},
};
constructor(owner) {
super();
if (_hasDestroySignal(owner))
owner.connectObject('destroy', () => this.destroy(), this);
}
destroy() {
this.emit('destroy');
}
});
class SignalManager {
/**
* @returns {SignalManager} - the SignalManager singleton
@ -31,23 +59,13 @@ class SignalTracker {
* @param {Object=} owner - object that owns the tracker
*/
constructor(owner) {
if (this._hasDestroySignal(owner))
if (_hasDestroySignal(owner))
this._ownerDestroyId = owner.connect_after('destroy', () => this.clear());
this._owner = owner;
this._map = new Map();
}
/**
* @private
* @param {Object} obj - an object
* @returns {bool} - true if obj has a 'destroy' GObject signal
*/
_hasDestroySignal(obj) {
return obj instanceof GObject.Object &&
GObject.signal_lookup('destroy', obj);
}
/**
* @typedef SignalData
* @property {number[]} ownerSignals - a list of handler IDs
@ -89,7 +107,7 @@ class SignalTracker {
* @returns {void}
*/
track(obj, ...handlerIds) {
if (this._hasDestroySignal(obj))
if (_hasDestroySignal(obj))
this._trackDestroy(obj);
this._getSignalData(obj).ownerSignals.push(...handlerIds);

View File

@ -8,6 +8,8 @@ const JsUnit = imports.jsUnit;
const Signals = imports.signals;
const Environment = imports.ui.environment;
const { TransientSignalHolder } = imports.misc.signalTracker;
Environment.init();
const Destroyable = GObject.registerClass({
@ -77,3 +79,39 @@ emitter1.emit('signal');
emitter2.emit('signal');
JsUnit.assertEquals(count, 10);
emitter1.connectObject('signal', handler, tracked1);
emitter2.connectObject('signal', handler, tracked1);
transientHolder = new TransientSignalHolder(tracked1);
emitter1.connectObject('signal', handler, transientHolder);
emitter2.connectObject('signal', handler, transientHolder);
emitter1.emit('signal');
emitter2.emit('signal');
JsUnit.assertEquals(count, 14);
transientHolder.destroy();
emitter1.emit('signal');
emitter2.emit('signal');
JsUnit.assertEquals(count, 16);
transientHolder = new TransientSignalHolder(tracked1);
emitter1.connectObject('signal', handler, transientHolder);
emitter2.connectObject('signal', handler, transientHolder);
emitter1.emit('signal');
emitter2.emit('signal');
JsUnit.assertEquals(count, 20);
tracked1.emit('destroy');
emitter1.emit('signal');
emitter2.emit('signal');
JsUnit.assertEquals(count, 20);