123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
- import Gio from 'gi://Gio';
- import GLib from 'gi://GLib';
- import GObject from 'gi://GObject';
- import Meta from 'gi://GdkPixbuf';
- import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';
- export class CancellablePromise extends Promise {
- constructor(executor, cancellable) {
- if (!(executor instanceof Function))
- throw TypeError('executor is not a function');
- if (cancellable && !(cancellable instanceof Gio.Cancellable))
- throw TypeError('cancellable parameter is not a Gio.Cancellable');
- let rejector;
- let resolver;
- super((resolve, reject) => {
- resolver = resolve;
- rejector = reject;
- });
- const {stack: promiseStack} = new Error();
- this._promiseStack = promiseStack;
- this._resolver = (...args) => {
- resolver(...args);
- this._resolved = true;
- this._cleanup();
- };
- this._rejector = (...args) => {
- rejector(...args);
- this._rejected = true;
- this._cleanup();
- };
- if (!cancellable) {
- executor(this._resolver, this._rejector);
- return;
- }
- this._cancellable = cancellable;
- this._cancelled = cancellable.is_cancelled();
- if (this._cancelled) {
- this._rejector(new GLib.Error(Gio.IOErrorEnum,
- Gio.IOErrorEnum.CANCELLED, 'Promise cancelled'));
- return;
- }
- this._cancellationId = cancellable.connect(() => {
- const id = this._cancellationId;
- this._cancellationId = 0;
- GLib.idle_add(GLib.PRIORITY_DEFAULT, () => cancellable.disconnect(id));
- this.cancel();
- });
- executor(this._resolver, this._rejector);
- }
- _cleanup() {
- if (this._cancellationId)
- this._cancellable.disconnect(this._cancellationId);
- }
- get cancellable() {
- return this._chainRoot._cancellable || null;
- }
- get _chainRoot() {
- return this._root ? this._root : this;
- }
- then(...args) {
- const ret = super.then(...args);
- /* Every time we call then() on this promise we'd get a new
- * CancellablePromise however that won't have the properties that the
- * root one has set, and then it won't be possible to cancel a promise
- * chain from the last one.
- * To allow this we keep track of the root promise, make sure that
- * the same method on the root object is called during cancellation
- * or any destruction method if you want this to work. */
- if (ret instanceof CancellablePromise)
- ret._root = this._chainRoot;
- return ret;
- }
- resolved() {
- return !!this._chainRoot._resolved;
- }
- rejected() {
- return !!this._chainRoot._rejected;
- }
- cancelled() {
- return !!this._chainRoot._cancelled;
- }
- pending() {
- return !this.resolved() && !this.rejected();
- }
- cancel() {
- if (this._root) {
- this._root.cancel();
- return this;
- }
- if (!this.pending())
- return this;
- this._cancelled = true;
- const error = new GLib.Error(Gio.IOErrorEnum,
- Gio.IOErrorEnum.CANCELLED, 'Promise cancelled');
- error.stack += `## Promise created at:\n${this._promiseStack}`;
- this._rejector(error);
- return this;
- }
- }
- export class SignalConnectionPromise extends CancellablePromise {
- constructor(object, signal, cancellable) {
- if (arguments.length === 1 && object instanceof Function) {
- super(object);
- return;
- }
- if (!(object.connect instanceof Function))
- throw new TypeError('Not a valid object');
- if (object instanceof GObject.Object &&
- !GObject.signal_lookup(signal.split(':')[0], object.constructor.$gtype))
- throw new TypeError(`Signal ${signal} not found on object ${object}`);
- let id;
- let destroyId;
- super(resolve => {
- let connectSignal;
- if (object instanceof GObject.Object)
- connectSignal = (sig, cb) => GObject.signal_connect(object, sig, cb);
- else
- connectSignal = (sig, cb) => object.connect(sig, cb);
- id = connectSignal(signal, (_obj, ...args) => {
- if (!args.length)
- resolve();
- else
- resolve(args.length === 1 ? args[0] : args);
- });
- if (signal !== 'destroy' &&
- (!(object instanceof GObject.Object) ||
- GObject.signal_lookup('destroy', object.constructor.$gtype)))
- destroyId = connectSignal('destroy', () => this.cancel());
- }, cancellable);
- this._object = object;
- this._id = id;
- this._destroyId = destroyId;
- }
- _cleanup() {
- if (this._id) {
- let disconnectSignal;
- if (this._object instanceof GObject.Object)
- disconnectSignal = id => GObject.signal_handler_disconnect(this._object, id);
- else
- disconnectSignal = id => this._object.disconnect(id);
- disconnectSignal(this._id);
- if (this._destroyId) {
- disconnectSignal(this._destroyId);
- this._destroyId = 0;
- }
- this._object = null;
- this._id = 0;
- }
- super._cleanup();
- }
- get object() {
- return this._chainRoot._object;
- }
- }
- export class GSourcePromise extends CancellablePromise {
- constructor(gsource, priority, cancellable) {
- if (arguments.length === 1 && gsource instanceof Function) {
- super(gsource);
- return;
- }
- if (gsource.constructor.$gtype !== GLib.Source.$gtype)
- throw new TypeError(`gsource ${gsource} is not of type GLib.Source`);
- if (priority === undefined)
- priority = GLib.PRIORITY_DEFAULT;
- else if (!Number.isInteger(priority))
- throw TypeError('Invalid priority');
- super(resolve => {
- gsource.set_priority(priority);
- gsource.set_callback(() => {
- resolve();
- return GLib.SOURCE_REMOVE;
- });
- gsource.attach(null);
- }, cancellable);
- this._gsource = gsource;
- this._gsource.set_name(`[gnome-shell] ${this.constructor.name} ${
- new Error().stack.split('\n').filter(line =>
- !line.match(/misc\/promiseUtils\.js/))[0]}`);
- if (this.rejected())
- this._gsource.destroy();
- }
- get gsource() {
- return this._chainRoot._gsource;
- }
- _cleanup() {
- if (this._gsource) {
- this._gsource.destroy();
- this._gsource = null;
- }
- super._cleanup();
- }
- }
- export class IdlePromise extends GSourcePromise {
- constructor(priority, cancellable) {
- if (arguments.length === 1 && priority instanceof Function) {
- super(priority);
- return;
- }
- if (priority === undefined)
- priority = GLib.PRIORITY_DEFAULT_IDLE;
- super(GLib.idle_source_new(), priority, cancellable);
- }
- }
- export class TimeoutPromise extends GSourcePromise {
- constructor(interval, priority, cancellable) {
- if (arguments.length === 1 && interval instanceof Function) {
- super(interval);
- return;
- }
- if (!Number.isInteger(interval) || interval < 0)
- throw TypeError('Invalid interval');
- super(GLib.timeout_source_new(interval), priority, cancellable);
- }
- }
- export class TimeoutSecondsPromise extends GSourcePromise {
- constructor(interval, priority, cancellable) {
- if (arguments.length === 1 && interval instanceof Function) {
- super(interval);
- return;
- }
- if (!Number.isInteger(interval) || interval < 0)
- throw TypeError('Invalid interval');
- super(GLib.timeout_source_new_seconds(interval), priority, cancellable);
- }
- }
- export class MetaLaterPromise extends CancellablePromise {
- constructor(laterType, cancellable) {
- if (arguments.length === 1 && laterType instanceof Function) {
- super(laterType);
- return;
- }
- if (laterType && laterType.constructor.$gtype !== Meta.LaterType.$gtype)
- throw new TypeError(`laterType ${laterType} is not of type Meta.LaterType`);
- else if (!laterType)
- laterType = Meta.LaterType.BEFORE_REDRAW;
- let id;
- super(resolve => {
- id = Meta.later_add(laterType, () => {
- this.remove();
- resolve();
- return GLib.SOURCE_REMOVE;
- });
- }, cancellable);
- this._id = id;
- }
- _cleanup() {
- if (this._id) {
- Meta.later_remove(this._id);
- this._id = 0;
- }
- super._cleanup();
- }
- }
- export function _promisifySignals(proto) {
- if (proto.connect_once)
- return;
- proto.connect_once = function (signal, cancellable) {
- return new SignalConnectionPromise(this, signal, cancellable);
- };
- }
- _promisifySignals(GObject.Object.prototype);
- _promisifySignals(Signals.EventEmitter.prototype);
|