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; 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 { class SignalManager {
/** /**
* @returns {SignalManager} - the SignalManager singleton * @returns {SignalManager} - the SignalManager singleton
@ -31,23 +59,13 @@ class SignalTracker {
* @param {Object=} owner - object that owns the tracker * @param {Object=} owner - object that owns the tracker
*/ */
constructor(owner) { constructor(owner) {
if (this._hasDestroySignal(owner)) if (_hasDestroySignal(owner))
this._ownerDestroyId = owner.connect_after('destroy', () => this.clear()); this._ownerDestroyId = owner.connect_after('destroy', () => this.clear());
this._owner = owner; this._owner = owner;
this._map = new Map(); 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 * @typedef SignalData
* @property {number[]} ownerSignals - a list of handler IDs * @property {number[]} ownerSignals - a list of handler IDs
@ -89,7 +107,7 @@ class SignalTracker {
* @returns {void} * @returns {void}
*/ */
track(obj, ...handlerIds) { track(obj, ...handlerIds) {
if (this._hasDestroySignal(obj)) if (_hasDestroySignal(obj))
this._trackDestroy(obj); this._trackDestroy(obj);
this._getSignalData(obj).ownerSignals.push(...handlerIds); this._getSignalData(obj).ownerSignals.push(...handlerIds);

View File

@ -8,6 +8,8 @@ const JsUnit = imports.jsUnit;
const Signals = imports.signals; const Signals = imports.signals;
const Environment = imports.ui.environment; const Environment = imports.ui.environment;
const { TransientSignalHolder } = imports.misc.signalTracker;
Environment.init(); Environment.init();
const Destroyable = GObject.registerClass({ const Destroyable = GObject.registerClass({
@ -77,3 +79,39 @@ emitter1.emit('signal');
emitter2.emit('signal'); emitter2.emit('signal');
JsUnit.assertEquals(count, 10); 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);