diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js index 5dfb325f2..4cdaa3dad 100644 --- a/js/ui/extensionSystem.js +++ b/js/ui/extensionSystem.js @@ -23,7 +23,7 @@ var ExtensionManager = class extends Signals.EventEmitter { constructor() { super(); - this._initialized = false; + this._initializationPromise = null; this._updateNotified = false; this._extensions = new Map(); @@ -32,7 +32,9 @@ var ExtensionManager = class extends Signals.EventEmitter { this._extensionOrder = []; this._checkVersion = false; - Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); + Main.sessionMode.connect('updated', () => { + this._sessionUpdated(); + }); } init() { @@ -54,13 +56,15 @@ var ExtensionManager = class extends Signals.EventEmitter { }); this._installExtensionUpdates(); - this._sessionUpdated(); + this._sessionUpdated().then(() => { + ExtensionDownloader.checkForUpdates(); + }); GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => { ExtensionDownloader.checkForUpdates(); + return GLib.SOURCE_CONTINUE; }); - ExtensionDownloader.checkForUpdates(); } get updatesSupported() { @@ -92,12 +96,12 @@ var ExtensionManager = class extends Signals.EventEmitter { return false; } - _callExtensionDisable(uuid) { + async _callExtensionDisable(uuid) { let extension = this.lookup(uuid); if (!extension) return; - if (extension.state != ExtensionState.ENABLED) + if (extension.state !== ExtensionState.ENABLED) return; extension.state = ExtensionState.DISABLING; @@ -139,7 +143,8 @@ var ExtensionManager = class extends Signals.EventEmitter { for (let i = 0; i < order.length; i++) { let otherUuid = order[i]; try { - this.lookup(otherUuid).stateObj.enable(); + // eslint-disable-next-line no-await-in-loop + await this.lookup(otherUuid).stateObj.enable(); } catch (e) { this.logExtensionError(otherUuid, e); } @@ -147,13 +152,13 @@ var ExtensionManager = class extends Signals.EventEmitter { this._extensionOrder.splice(orderIdx, 1); - if (extension.state != ExtensionState.ERROR) { + if (extension.state !== ExtensionState.ERROR) { extension.state = ExtensionState.DISABLED; this.emit('extension-state-changed', extension); } } - _callExtensionEnable(uuid) { + async _callExtensionEnable(uuid) { if (!this._extensionSupportsSessionMode(uuid)) return; @@ -161,10 +166,11 @@ var ExtensionManager = class extends Signals.EventEmitter { if (!extension) return; - if (extension.state == ExtensionState.INITIALIZED) - this._callExtensionInit(uuid); + if (extension.state === ExtensionState.INITIALIZED) + await this._callExtensionInit(uuid); - if (extension.state != ExtensionState.DISABLED) + + if (extension.state !== ExtensionState.DISABLED) return; extension.state = ExtensionState.ENABLING; @@ -187,7 +193,7 @@ var ExtensionManager = class extends Signals.EventEmitter { } try { - extension.stateObj.enable(); + await extension.stateObj.enable(); extension.state = ExtensionState.ENABLED; this._extensionOrder.push(uuid); this.emit('extension-state-changed', extension); @@ -372,7 +378,7 @@ var ExtensionManager = class extends Signals.EventEmitter { return extension.metadata.version === version; } - loadExtension(extension) { + async loadExtension(extension) { // Default to error, we set success as the last step extension.state = ExtensionState.ERROR; @@ -385,10 +391,11 @@ var ExtensionManager = class extends Signals.EventEmitter { let enabled = this._enabledExtensions.includes(extension.uuid) && this._extensionSupportsSessionMode(extension.uuid); if (enabled) { - if (!this._callExtensionInit(extension.uuid)) + if (!await this._callExtensionInit(extension.uuid)) return; - if (extension.state == ExtensionState.DISABLED) - this._callExtensionEnable(extension.uuid); + + if (extension.state === ExtensionState.DISABLED) + await this._callExtensionEnable(extension.uuid); } else { extension.state = ExtensionState.INITIALIZED; } @@ -400,27 +407,26 @@ var ExtensionManager = class extends Signals.EventEmitter { this.emit('extension-state-changed', extension); } - unloadExtension(extension) { + async unloadExtension(extension) { const { uuid, type } = extension; // Try to disable it -- if it's ERROR'd, we can't guarantee that, // but it will be removed on next reboot, and hopefully nothing // broke too much. - this._callExtensionDisable(uuid); + await this._callExtensionDisable(uuid); extension.state = ExtensionState.UNINSTALLED; this.emit('extension-state-changed', extension); - // If we did install an importer, it is now cached and it's - // impossible to load a different version - if (type === ExtensionType.PER_USER && extension.imports) + // The extension is now cached and it's impossible to load a different version + if (type === ExtensionType.PER_USER && extension.isImported) this._unloadedExtensions.set(uuid, extension.metadata.version); this._extensions.delete(uuid); return true; } - reloadExtension(oldExtension) { + async reloadExtension(oldExtension) { // Grab the things we'll need to pass to createExtensionObject // to reload it. let { uuid, dir, type } = oldExtension; @@ -437,10 +443,10 @@ var ExtensionManager = class extends Signals.EventEmitter { return; } - this.loadExtension(newExtension); + await this.loadExtension(newExtension); } - _callExtensionInit(uuid) { + async _callExtensionInit(uuid) { if (!this._extensionSupportsSessionMode(uuid)) return false; @@ -458,7 +464,16 @@ var ExtensionManager = class extends Signals.EventEmitter { let extensionModule; let extensionState = null; + // TODO: This function does not have an async operation but when ESM is + // merged there will be a dynamic import here instead of `installImporter`. + await 0; + ExtensionUtils.installImporter(extension); + + // Extensions can only be imported once, so add a property to avoid + // attempting to re-import an extension. + extension.isImported = true; + try { extensionModule = extension.imports.extension; } catch (e) { @@ -468,7 +483,7 @@ var ExtensionManager = class extends Signals.EventEmitter { if (extensionModule.init) { try { - extensionState = extensionModule.init(extension); + extensionState = await extensionModule.init(extension); } catch (e) { this.logExtensionError(uuid, e); return false; @@ -492,8 +507,8 @@ var ExtensionManager = class extends Signals.EventEmitter { _updateCanChange(extension) { let hasError = - extension.state == ExtensionState.ERROR || - extension.state == ExtensionState.OUT_OF_DATE; + extension.state === ExtensionState.ERROR || + extension.state === ExtensionState.OUT_OF_DATE; let isMode = this._getModeExtensions().includes(extension.uuid); let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY); @@ -519,27 +534,36 @@ var ExtensionManager = class extends Signals.EventEmitter { return extensions.filter(item => !disabledExtensions.includes(item)); } - _onUserExtensionsEnabledChanged() { - this._onEnabledExtensionsChanged(); + async _onUserExtensionsEnabledChanged() { + await this._onEnabledExtensionsChanged(); this._onSettingsWritableChanged(); } - _onEnabledExtensionsChanged() { + async _onEnabledExtensionsChanged() { let newEnabledExtensions = this._getEnabledExtensions(); // Find and enable all the newly enabled extensions: UUIDs found in the // new setting, but not in the old one. - newEnabledExtensions + const extensionsToEnable = newEnabledExtensions .filter(uuid => !this._enabledExtensions.includes(uuid) && - this._extensionSupportsSessionMode(uuid)) - .forEach(uuid => this._callExtensionEnable(uuid)); + this._extensionSupportsSessionMode(uuid)); + for (const uuid of extensionsToEnable) { + // eslint-disable-next-line no-await-in-loop + await this._callExtensionEnable(uuid); + } // Find and disable all the newly disabled extensions: UUIDs found in the // old setting, but not in the new one. - this._extensionOrder + const extensionsToDisable = this._extensionOrder .filter(uuid => !newEnabledExtensions.includes(uuid) || - !this._extensionSupportsSessionMode(uuid)) - .reverse().forEach(uuid => this._callExtensionDisable(uuid)); + !this._extensionSupportsSessionMode(uuid)); + // Reverse mutates the original array, but .filter() creates a new array. + extensionsToDisable.reverse(); + + for (const uuid of extensionsToDisable) { + // eslint-disable-next-line no-await-in-loop + await this._callExtensionDisable(uuid); + } this._enabledExtensions = newEnabledExtensions; } @@ -551,7 +575,7 @@ var ExtensionManager = class extends Signals.EventEmitter { } } - _onVersionValidationChanged() { + async _onVersionValidationChanged() { const checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY); if (checkVersion === this._checkVersion) return; @@ -561,16 +585,18 @@ var ExtensionManager = class extends Signals.EventEmitter { // Disabling extensions modifies the order array, so use a copy let extensionOrder = this._extensionOrder.slice(); - // Disable enabled extensions in the reverse order first to avoid + // Disable enabled extensions first to avoid // the "rebasing" done in _callExtensionDisable... - extensionOrder.slice().reverse().forEach(uuid => { - this._callExtensionDisable(uuid); - }); + this._disableAllExtensions(); // ...and then reload and enable extensions in the correct order again. - [...this._extensions.values()].sort((a, b) => { + const extensionsToReload = [...this._extensions.values()].sort((a, b) => { return extensionOrder.indexOf(a.uuid) - extensionOrder.indexOf(b.uuid); - }).forEach(extension => this.reloadExtension(extension)); + }); + for (const extension of extensionsToReload) { + // eslint-disable-next-line no-await-in-loop + await this.reloadExtension(extension); + } } _installExtensionUpdates() { @@ -596,21 +622,25 @@ var ExtensionManager = class extends Signals.EventEmitter { } } - _loadExtensions() { - global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`, - this._onEnabledExtensionsChanged.bind(this)); - global.settings.connect(`changed::${DISABLED_EXTENSIONS_KEY}`, - this._onEnabledExtensionsChanged.bind(this)); - global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`, - this._onUserExtensionsEnabledChanged.bind(this)); - global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`, - this._onVersionValidationChanged.bind(this)); - global.settings.connect(`writable-changed::${ENABLED_EXTENSIONS_KEY}`, - this._onSettingsWritableChanged.bind(this)); - global.settings.connect(`writable-changed::${DISABLED_EXTENSIONS_KEY}`, - this._onSettingsWritableChanged.bind(this)); + async _loadExtensions() { + global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`, () => { + this._onEnabledExtensionsChanged(); + }); + global.settings.connect(`changed::${DISABLED_EXTENSIONS_KEY}`, () => { + this._onEnabledExtensionsChanged(); + }); + global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`, () => { + this._onUserExtensionsEnabledChanged(); + }); + global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`, () => { + this._onVersionValidationChanged(); + }); + global.settings.connect(`writable-changed::${ENABLED_EXTENSIONS_KEY}`, () => + this._onSettingsWritableChanged()); + global.settings.connect(`writable-changed::${DISABLED_EXTENSIONS_KEY}`, () => + this._onSettingsWritableChanged()); - this._onVersionValidationChanged(); + await this._onVersionValidationChanged(); this._enabledExtensions = this._getEnabledExtensions(); @@ -642,33 +672,50 @@ var ExtensionManager = class extends Signals.EventEmitter { return extension; }).filter(extension => extension !== null); - for (const extension of extensionObjects) - this.loadExtension(extension); - } - - _enableAllExtensions() { - if (!this._initialized) { - this._loadExtensions(); - this._initialized = true; - } else { - this._enabledExtensions.forEach(uuid => { - this._callExtensionEnable(uuid); - }); + for (const extension of extensionObjects) { + // eslint-disable-next-line no-await-in-loop + await this.loadExtension(extension); } } - _disableAllExtensions() { - if (this._initialized) { - this._extensionOrder.slice().reverse().forEach(uuid => { - this._callExtensionDisable(uuid); - }); + async _enableAllExtensions() { + if (!this._initializationPromise) + this._initializationPromise = this._loadExtensions(); + + await this._initializationPromise; + + for (const uuid of this._enabledExtensions) { + // eslint-disable-next-line no-await-in-loop + await this._callExtensionEnable(uuid); } } - _sessionUpdated() { + + /** + * Disables all currently enabled extensions. + */ + async _disableAllExtensions() { + // Wait for extensions to finish loading before starting + // to disable, otherwise some extensions may enable after + // this function. + if (this._initializationPromise) + await this._initializationPromise; + + const extensionsToDisable = this._extensionOrder.slice(); + // Extensions are disabled in the reverse order + // from when they were enabled. + extensionsToDisable.reverse(); + + for (const uuid of extensionsToDisable) { + // eslint-disable-next-line no-await-in-loop + await this._callExtensionDisable(uuid); + } + } + + async _sessionUpdated() { // Take care of added or removed sessionMode extensions - this._onEnabledExtensionsChanged(); - this._enableAllExtensions(); + await this._onEnabledExtensionsChanged(); + await this._enableAllExtensions(); } };