Skip to content

Advanced GObject

TIP

See GObject Basics for an introduction to GObject in GJS.

This guide covers advanced GObject features that are less commonly used.

GObject Construction

Failable Initialization

NOTE

It is not currently possible to implement Gio.AsyncInitable in GJS due to thread-safety issues.

In JavaScript object construction is usually non-blocking and non-failable. GObject classes can support failable and asynchronous construction by implementing the Gio.Initable or Gio.AsyncInitable interfaces.

js
import Gio from 'gi://Gio';

Gio._promisify(Gio.DBusObjectManagerClient, 'new_for_bus');

try {
    const proc = Gio.Subprocess.new(['unknown-command'],
        Gio.SubprocessFlags.NONE);
} catch (e) {
    // GLib.SpawnError: Failed to execute child process “unknown-command” (No such file or directory)
    console.error(e);
}

try {
    const proc = new Gio.Subprocess({
        argv: ['ls'],
        flags: Gio.SubprocessFlags.NONE,
    });

    const cancellable = Gio.Cancellable.new();
    cancellable.cancel();
    proc.init(cancellable);
} catch (e) {
    // Gio.IOErrorEnum: Operation was cancelled
    console.error(e);
}

try {
    const manager = await Gio.DBusObjectManagerClient.new_for_bus(
        Gio.BusType.SYSTEM,
        Gio.DBusObjectManagerClientFlags,
        'org.freedesktop.login1',
        '/org/freedesktop/login1',
        null,
        null);
} catch (e) {
    if (e instanceof Gio.DBusError)
        Gio.DBusError.strip_remote_error(e);

    // Gio.DBusError: Sender is not authorized to send message
    console.error(e);
}

Most classes will provide constructor methods that call Gio.Initable.init() or Gio.AsyncInitable.init_async(), but the interface methods may be called directly if necessary. Below is an example of calling Gio.Initable.init() to pass in a Gio.Cancellable.

js
import Gio from 'gi://Gio';

Gio._promisify(Gio.DBusObjectManagerClient, 'new_for_bus');

try {
    const proc = Gio.Subprocess.new(['unknown-command'],
        Gio.SubprocessFlags.NONE);
} catch (e) {
    // GLib.SpawnError: Failed to execute child process “unknown-command” (No such file or directory)
    console.error(e);
}

try {
    const proc = new Gio.Subprocess({
        argv: ['ls'],
        flags: Gio.SubprocessFlags.NONE,
    });

    const cancellable = Gio.Cancellable.new();
    cancellable.cancel();
    proc.init(cancellable);
} catch (e) {
    // Gio.IOErrorEnum: Operation was cancelled
    console.error(e);
}

try {
    const manager = await Gio.DBusObjectManagerClient.new_for_bus(
        Gio.BusType.SYSTEM,
        Gio.DBusObjectManagerClientFlags,
        'org.freedesktop.login1',
        '/org/freedesktop/login1',
        null,
        null);
} catch (e) {
    if (e instanceof Gio.DBusError)
        Gio.DBusError.strip_remote_error(e);

    // Gio.DBusError: Sender is not authorized to send message
    console.error(e);
}

Gio.DBusObjectManagerClient is an example of a class that implements Gio.AsyncInitable and can be constructed using asynchronous JavaScript. If construction fails, the Promise will reject with the error.

js
import Gio from 'gi://Gio';

Gio._promisify(Gio.DBusObjectManagerClient, 'new_for_bus');

try {
    const proc = Gio.Subprocess.new(['unknown-command'],
        Gio.SubprocessFlags.NONE);
} catch (e) {
    // GLib.SpawnError: Failed to execute child process “unknown-command” (No such file or directory)
    console.error(e);
}

try {
    const proc = new Gio.Subprocess({
        argv: ['ls'],
        flags: Gio.SubprocessFlags.NONE,
    });

    const cancellable = Gio.Cancellable.new();
    cancellable.cancel();
    proc.init(cancellable);
} catch (e) {
    // Gio.IOErrorEnum: Operation was cancelled
    console.error(e);
}

try {
    const manager = await Gio.DBusObjectManagerClient.new_for_bus(
        Gio.BusType.SYSTEM,
        Gio.DBusObjectManagerClientFlags,
        'org.freedesktop.login1',
        '/org/freedesktop/login1',
        null,
        null);
} catch (e) {
    if (e instanceof Gio.DBusError)
        Gio.DBusError.strip_remote_error(e);

    // Gio.DBusError: Sender is not authorized to send message
    console.error(e);
}

Properties

Introspection

You can list the properties for a class with GObject.Object.list_properties() or lookup a property by name with GObject.Object.find_property().

js
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';


Gtk.init();

/*
 * Introspection
 */
// Listing properties
for (const pspec of Gtk.Widget.list_properties())
    console.log(`Found property: ${pspec.name}`);

// Looking up a property by canonical name
const wrapParamSpec = Gtk.Label.find_property('wrap');

if (wrapParamSpec instanceof GObject.ParamSpec)
    console.log(`Found property: ${wrapParamSpec.name}`);

// Looking up a property for an instance
const exampleBox = new Gtk.Box();
const spacingParamSpec = exampleBox.constructor.find_property('spacing');

if (spacingParamSpec instanceof GObject.ParamSpec)
    console.log(`Found property: ${spacingParamSpec.name}`);

/*
 * Notification Freezing
 */
const exampleLabel = new Gtk.Label({
    label: 'Initial Value',
});

exampleLabel.connect('notify::label', (object, _pspec) => {
    console.log(`New label is "${object.label}"`);
});

// Freeze notification during multiple changes
exampleLabel.freeze_notify();

exampleLabel.label = 'Example 1';
exampleLabel.label = 'Example 2';
exampleLabel.label = 'Example 3';

// Expected output: New label is "Example 3"
exampleLabel.thaw_notify();

To lookup a property for an instance, use the constructor property:

js
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';


Gtk.init();

/*
 * Introspection
 */
// Listing properties
for (const pspec of Gtk.Widget.list_properties())
    console.log(`Found property: ${pspec.name}`);

// Looking up a property by canonical name
const wrapParamSpec = Gtk.Label.find_property('wrap');

if (wrapParamSpec instanceof GObject.ParamSpec)
    console.log(`Found property: ${wrapParamSpec.name}`);

// Looking up a property for an instance
const exampleBox = new Gtk.Box();
const spacingParamSpec = exampleBox.constructor.find_property('spacing');

if (spacingParamSpec instanceof GObject.ParamSpec)
    console.log(`Found property: ${spacingParamSpec.name}`);

/*
 * Notification Freezing
 */
const exampleLabel = new Gtk.Label({
    label: 'Initial Value',
});

exampleLabel.connect('notify::label', (object, _pspec) => {
    console.log(`New label is "${object.label}"`);
});

// Freeze notification during multiple changes
exampleLabel.freeze_notify();

exampleLabel.label = 'Example 1';
exampleLabel.label = 'Example 2';
exampleLabel.label = 'Example 3';

// Expected output: New label is "Example 3"
exampleLabel.thaw_notify();

API Documentation

Notification Freezing

When a property is expected to be set multiple times, it may result in unwanted emissions of GObject.Object::notify.

GObject.Object.freeze_notify() allows you to freeze change notifications until GObject.Object.thaw_notify() is called. Duplicate notifications are squashed so that at most one signal is emitted for each property.

js
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';


Gtk.init();

/*
 * Introspection
 */
// Listing properties
for (const pspec of Gtk.Widget.list_properties())
    console.log(`Found property: ${pspec.name}`);

// Looking up a property by canonical name
const wrapParamSpec = Gtk.Label.find_property('wrap');

if (wrapParamSpec instanceof GObject.ParamSpec)
    console.log(`Found property: ${wrapParamSpec.name}`);

// Looking up a property for an instance
const exampleBox = new Gtk.Box();
const spacingParamSpec = exampleBox.constructor.find_property('spacing');

if (spacingParamSpec instanceof GObject.ParamSpec)
    console.log(`Found property: ${spacingParamSpec.name}`);

/*
 * Notification Freezing
 */
const exampleLabel = new Gtk.Label({
    label: 'Initial Value',
});

exampleLabel.connect('notify::label', (object, _pspec) => {
    console.log(`New label is "${object.label}"`);
});

// Freeze notification during multiple changes
exampleLabel.freeze_notify();

exampleLabel.label = 'Example 1';
exampleLabel.label = 'Example 2';
exampleLabel.label = 'Example 3';

// Expected output: New label is "Example 3"
exampleLabel.thaw_notify();

API Documentation

Signals

Introspection

You can list the signal IDs for a class with GObject.signal_list_ids() or lookup a signal ID by name with GObject.signal_lookup().

js
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';


Gtk.init();

/*
 * Introspection
 */
// Listing signals
for (const signalId of GObject.signal_list_ids(Gtk.Widget))
    console.log(`Found signal: ${signalId}`);

// Looking up a signal by canonical name
const notifyId = GObject.signal_lookup('notify', GObject.Object);

if (notifyId !== 0)
    console.log(`Found signal: ${notifyId}`);

// Looking up a signal for an instance
const exampleBox = new Gtk.Box();
const destroyId = GObject.signal_lookup('destroy', exampleBox.constructor);

if (destroyId !== 0)
    console.log(`Found signal: ${destroyId}`);

// Query signal metadata
const destroyQuery = GObject.signal_query(destroyId);

if (destroyQuery !== null)
    console.log(`Found signal: ${destroyQuery.itype.name}::${destroyQuery.signal_name}`);

/*
 * Signal Handlers
 */
function notifyCallback(obj, pspec) {
    console.log(pspec.name);
}

const objectInstance = new GObject.Object();
const handlerId = objectInstance.connect('notify::property-name',
    notifyCallback);

const result = GObject.signal_handler_find(objectInstance, {
    detail: 'property-name',
    func: notifyCallback,
});

console.assert(result === handlerId);

/*
 * Blocking Signals
 */
function notifyLabelCallback(object, _pspec) {
    console.log('notify::label emitted');

    const blocked = GObject.signal_handlers_block_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Blocked ${blocked} handlers`);

    // The handler will not be run recursively, since it is currently blocked
    object.label = object.label.toUpperCase();

    GObject.signal_handlers_unblock_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Unblocked ${blocked} handlers`);
}

const upperCaseLabel = new Gtk.Label();
const notifyLabelId = upperCaseLabel.connect('notify::label',
    notifyLabelCallback);
upperCaseLabel.label = 'example';

/*
 * Stopping Signals
 */
const linkLabel = new Gtk.Label({label: 'Example'});
linkLabel.connect('activate-current-link', (label, _uri) => {
    console.log('First handler that will be run');

    // No other handlers will run after the emission is stopped
    GObject.signal_stop_emission_by_name(label, 'activate-current-link');
});
linkLabel.connect('activate-current-link', (_label, _uri) => {
    console.log('Second handler that will not be run');
});
linkLabel.emit('activate-current-link');

To lookup a signal for an instance, use the constructor property:

js
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';


Gtk.init();

/*
 * Introspection
 */
// Listing signals
for (const signalId of GObject.signal_list_ids(Gtk.Widget))
    console.log(`Found signal: ${signalId}`);

// Looking up a signal by canonical name
const notifyId = GObject.signal_lookup('notify', GObject.Object);

if (notifyId !== 0)
    console.log(`Found signal: ${notifyId}`);

// Looking up a signal for an instance
const exampleBox = new Gtk.Box();
const destroyId = GObject.signal_lookup('destroy', exampleBox.constructor);

if (destroyId !== 0)
    console.log(`Found signal: ${destroyId}`);

// Query signal metadata
const destroyQuery = GObject.signal_query(destroyId);

if (destroyQuery !== null)
    console.log(`Found signal: ${destroyQuery.itype.name}::${destroyQuery.signal_name}`);

/*
 * Signal Handlers
 */
function notifyCallback(obj, pspec) {
    console.log(pspec.name);
}

const objectInstance = new GObject.Object();
const handlerId = objectInstance.connect('notify::property-name',
    notifyCallback);

const result = GObject.signal_handler_find(objectInstance, {
    detail: 'property-name',
    func: notifyCallback,
});

console.assert(result === handlerId);

/*
 * Blocking Signals
 */
function notifyLabelCallback(object, _pspec) {
    console.log('notify::label emitted');

    const blocked = GObject.signal_handlers_block_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Blocked ${blocked} handlers`);

    // The handler will not be run recursively, since it is currently blocked
    object.label = object.label.toUpperCase();

    GObject.signal_handlers_unblock_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Unblocked ${blocked} handlers`);
}

const upperCaseLabel = new Gtk.Label();
const notifyLabelId = upperCaseLabel.connect('notify::label',
    notifyLabelCallback);
upperCaseLabel.label = 'example';

/*
 * Stopping Signals
 */
const linkLabel = new Gtk.Label({label: 'Example'});
linkLabel.connect('activate-current-link', (label, _uri) => {
    console.log('First handler that will be run');

    // No other handlers will run after the emission is stopped
    GObject.signal_stop_emission_by_name(label, 'activate-current-link');
});
linkLabel.connect('activate-current-link', (_label, _uri) => {
    console.log('Second handler that will not be run');
});
linkLabel.emit('activate-current-link');

Passing the signal ID to GObject.signal_query() will return the signal information as a GObject.SignalQuery object.

js
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';


Gtk.init();

/*
 * Introspection
 */
// Listing signals
for (const signalId of GObject.signal_list_ids(Gtk.Widget))
    console.log(`Found signal: ${signalId}`);

// Looking up a signal by canonical name
const notifyId = GObject.signal_lookup('notify', GObject.Object);

if (notifyId !== 0)
    console.log(`Found signal: ${notifyId}`);

// Looking up a signal for an instance
const exampleBox = new Gtk.Box();
const destroyId = GObject.signal_lookup('destroy', exampleBox.constructor);

if (destroyId !== 0)
    console.log(`Found signal: ${destroyId}`);

// Query signal metadata
const destroyQuery = GObject.signal_query(destroyId);

if (destroyQuery !== null)
    console.log(`Found signal: ${destroyQuery.itype.name}::${destroyQuery.signal_name}`);

/*
 * Signal Handlers
 */
function notifyCallback(obj, pspec) {
    console.log(pspec.name);
}

const objectInstance = new GObject.Object();
const handlerId = objectInstance.connect('notify::property-name',
    notifyCallback);

const result = GObject.signal_handler_find(objectInstance, {
    detail: 'property-name',
    func: notifyCallback,
});

console.assert(result === handlerId);

/*
 * Blocking Signals
 */
function notifyLabelCallback(object, _pspec) {
    console.log('notify::label emitted');

    const blocked = GObject.signal_handlers_block_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Blocked ${blocked} handlers`);

    // The handler will not be run recursively, since it is currently blocked
    object.label = object.label.toUpperCase();

    GObject.signal_handlers_unblock_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Unblocked ${blocked} handlers`);
}

const upperCaseLabel = new Gtk.Label();
const notifyLabelId = upperCaseLabel.connect('notify::label',
    notifyLabelCallback);
upperCaseLabel.label = 'example';

/*
 * Stopping Signals
 */
const linkLabel = new Gtk.Label({label: 'Example'});
linkLabel.connect('activate-current-link', (label, _uri) => {
    console.log('First handler that will be run');

    // No other handlers will run after the emission is stopped
    GObject.signal_stop_emission_by_name(label, 'activate-current-link');
});
linkLabel.connect('activate-current-link', (_label, _uri) => {
    console.log('Second handler that will not be run');
});
linkLabel.emit('activate-current-link');

API Documentation

Signal Matches

WARNING

Function.prototype.bind() creates a new Function instance and a handler will only match to the connected instance.

GJS has overrides for a number of signal utilities that take an object of parameter for matching signals. There are three properties, but func must be specified for a successful match.

  • signalId (String) — A signal name. Note that this is not the signal ID as it is in the original GObject functions.
  • detail (String) — A signal detail, such as prop in notify::prop.
  • func (Function) — A signal callback function.
js
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';


Gtk.init();

/*
 * Introspection
 */
// Listing signals
for (const signalId of GObject.signal_list_ids(Gtk.Widget))
    console.log(`Found signal: ${signalId}`);

// Looking up a signal by canonical name
const notifyId = GObject.signal_lookup('notify', GObject.Object);

if (notifyId !== 0)
    console.log(`Found signal: ${notifyId}`);

// Looking up a signal for an instance
const exampleBox = new Gtk.Box();
const destroyId = GObject.signal_lookup('destroy', exampleBox.constructor);

if (destroyId !== 0)
    console.log(`Found signal: ${destroyId}`);

// Query signal metadata
const destroyQuery = GObject.signal_query(destroyId);

if (destroyQuery !== null)
    console.log(`Found signal: ${destroyQuery.itype.name}::${destroyQuery.signal_name}`);

/*
 * Signal Handlers
 */
function notifyCallback(obj, pspec) {
    console.log(pspec.name);
}

const objectInstance = new GObject.Object();
const handlerId = objectInstance.connect('notify::property-name',
    notifyCallback);

const result = GObject.signal_handler_find(objectInstance, {
    detail: 'property-name',
    func: notifyCallback,
});

console.assert(result === handlerId);

/*
 * Blocking Signals
 */
function notifyLabelCallback(object, _pspec) {
    console.log('notify::label emitted');

    const blocked = GObject.signal_handlers_block_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Blocked ${blocked} handlers`);

    // The handler will not be run recursively, since it is currently blocked
    object.label = object.label.toUpperCase();

    GObject.signal_handlers_unblock_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Unblocked ${blocked} handlers`);
}

const upperCaseLabel = new Gtk.Label();
const notifyLabelId = upperCaseLabel.connect('notify::label',
    notifyLabelCallback);
upperCaseLabel.label = 'example';

/*
 * Stopping Signals
 */
const linkLabel = new Gtk.Label({label: 'Example'});
linkLabel.connect('activate-current-link', (label, _uri) => {
    console.log('First handler that will be run');

    // No other handlers will run after the emission is stopped
    GObject.signal_stop_emission_by_name(label, 'activate-current-link');
});
linkLabel.connect('activate-current-link', (_label, _uri) => {
    console.log('Second handler that will not be run');
});
linkLabel.emit('activate-current-link');

Blocking Signals

A signal can blocked from being emitted for a period of time by calling GObject.signal_handlers_block_matched() followed by GObject.signal_handlers_unblock_matched() to resume normal behavior. This can prevent reentrancy issues when a signal handler may cause the same signal to be re-emitted.

js
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';


Gtk.init();

/*
 * Introspection
 */
// Listing signals
for (const signalId of GObject.signal_list_ids(Gtk.Widget))
    console.log(`Found signal: ${signalId}`);

// Looking up a signal by canonical name
const notifyId = GObject.signal_lookup('notify', GObject.Object);

if (notifyId !== 0)
    console.log(`Found signal: ${notifyId}`);

// Looking up a signal for an instance
const exampleBox = new Gtk.Box();
const destroyId = GObject.signal_lookup('destroy', exampleBox.constructor);

if (destroyId !== 0)
    console.log(`Found signal: ${destroyId}`);

// Query signal metadata
const destroyQuery = GObject.signal_query(destroyId);

if (destroyQuery !== null)
    console.log(`Found signal: ${destroyQuery.itype.name}::${destroyQuery.signal_name}`);

/*
 * Signal Handlers
 */
function notifyCallback(obj, pspec) {
    console.log(pspec.name);
}

const objectInstance = new GObject.Object();
const handlerId = objectInstance.connect('notify::property-name',
    notifyCallback);

const result = GObject.signal_handler_find(objectInstance, {
    detail: 'property-name',
    func: notifyCallback,
});

console.assert(result === handlerId);

/*
 * Blocking Signals
 */
function notifyLabelCallback(object, _pspec) {
    console.log('notify::label emitted');

    const blocked = GObject.signal_handlers_block_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Blocked ${blocked} handlers`);

    // The handler will not be run recursively, since it is currently blocked
    object.label = object.label.toUpperCase();

    GObject.signal_handlers_unblock_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Unblocked ${blocked} handlers`);
}

const upperCaseLabel = new Gtk.Label();
const notifyLabelId = upperCaseLabel.connect('notify::label',
    notifyLabelCallback);
upperCaseLabel.label = 'example';

/*
 * Stopping Signals
 */
const linkLabel = new Gtk.Label({label: 'Example'});
linkLabel.connect('activate-current-link', (label, _uri) => {
    console.log('First handler that will be run');

    // No other handlers will run after the emission is stopped
    GObject.signal_stop_emission_by_name(label, 'activate-current-link');
});
linkLabel.connect('activate-current-link', (_label, _uri) => {
    console.log('Second handler that will not be run');
});
linkLabel.emit('activate-current-link');

API Documentation

Stopping Signals

A signal emission can be stopped from inside a signal handler by calling GObject.signal_stop_emission_by_name(). This prevents any remaining signal handlers from being invoked for the current emission.

js
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';


Gtk.init();

/*
 * Introspection
 */
// Listing signals
for (const signalId of GObject.signal_list_ids(Gtk.Widget))
    console.log(`Found signal: ${signalId}`);

// Looking up a signal by canonical name
const notifyId = GObject.signal_lookup('notify', GObject.Object);

if (notifyId !== 0)
    console.log(`Found signal: ${notifyId}`);

// Looking up a signal for an instance
const exampleBox = new Gtk.Box();
const destroyId = GObject.signal_lookup('destroy', exampleBox.constructor);

if (destroyId !== 0)
    console.log(`Found signal: ${destroyId}`);

// Query signal metadata
const destroyQuery = GObject.signal_query(destroyId);

if (destroyQuery !== null)
    console.log(`Found signal: ${destroyQuery.itype.name}::${destroyQuery.signal_name}`);

/*
 * Signal Handlers
 */
function notifyCallback(obj, pspec) {
    console.log(pspec.name);
}

const objectInstance = new GObject.Object();
const handlerId = objectInstance.connect('notify::property-name',
    notifyCallback);

const result = GObject.signal_handler_find(objectInstance, {
    detail: 'property-name',
    func: notifyCallback,
});

console.assert(result === handlerId);

/*
 * Blocking Signals
 */
function notifyLabelCallback(object, _pspec) {
    console.log('notify::label emitted');

    const blocked = GObject.signal_handlers_block_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Blocked ${blocked} handlers`);

    // The handler will not be run recursively, since it is currently blocked
    object.label = object.label.toUpperCase();

    GObject.signal_handlers_unblock_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Unblocked ${blocked} handlers`);
}

const upperCaseLabel = new Gtk.Label();
const notifyLabelId = upperCaseLabel.connect('notify::label',
    notifyLabelCallback);
upperCaseLabel.label = 'example';

/*
 * Stopping Signals
 */
const linkLabel = new Gtk.Label({label: 'Example'});
linkLabel.connect('activate-current-link', (label, _uri) => {
    console.log('First handler that will be run');

    // No other handlers will run after the emission is stopped
    GObject.signal_stop_emission_by_name(label, 'activate-current-link');
});
linkLabel.connect('activate-current-link', (_label, _uri) => {
    console.log('Second handler that will not be run');
});
linkLabel.emit('activate-current-link');

API Documentation

MIT Licensed | GJS, A GNOME Project