extensions: Refactor extension system for asynchronous loading
This commit refactors extensionSystem to load extensions asynchronously. This will enable loading extensions using ECMAScript modules in a future commit. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2364>
This commit is contained in:
parent
56beb6ff2b
commit
d918290ac8
@ -23,7 +23,7 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._initialized = false;
|
this._initializationPromise = null;
|
||||||
this._updateNotified = false;
|
this._updateNotified = false;
|
||||||
|
|
||||||
this._extensions = new Map();
|
this._extensions = new Map();
|
||||||
@ -32,7 +32,9 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
this._extensionOrder = [];
|
this._extensionOrder = [];
|
||||||
this._checkVersion = false;
|
this._checkVersion = false;
|
||||||
|
|
||||||
Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
|
Main.sessionMode.connect('updated', () => {
|
||||||
|
this._sessionUpdated();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -54,13 +56,15 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._installExtensionUpdates();
|
this._installExtensionUpdates();
|
||||||
this._sessionUpdated();
|
this._sessionUpdated().then(() => {
|
||||||
|
ExtensionDownloader.checkForUpdates();
|
||||||
|
});
|
||||||
|
|
||||||
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => {
|
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => {
|
||||||
ExtensionDownloader.checkForUpdates();
|
ExtensionDownloader.checkForUpdates();
|
||||||
|
|
||||||
return GLib.SOURCE_CONTINUE;
|
return GLib.SOURCE_CONTINUE;
|
||||||
});
|
});
|
||||||
ExtensionDownloader.checkForUpdates();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get updatesSupported() {
|
get updatesSupported() {
|
||||||
@ -92,12 +96,12 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_callExtensionDisable(uuid) {
|
async _callExtensionDisable(uuid) {
|
||||||
let extension = this.lookup(uuid);
|
let extension = this.lookup(uuid);
|
||||||
if (!extension)
|
if (!extension)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (extension.state != ExtensionState.ENABLED)
|
if (extension.state !== ExtensionState.ENABLED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
extension.state = ExtensionState.DISABLING;
|
extension.state = ExtensionState.DISABLING;
|
||||||
@ -139,7 +143,8 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
for (let i = 0; i < order.length; i++) {
|
for (let i = 0; i < order.length; i++) {
|
||||||
let otherUuid = order[i];
|
let otherUuid = order[i];
|
||||||
try {
|
try {
|
||||||
this.lookup(otherUuid).stateObj.enable();
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await this.lookup(otherUuid).stateObj.enable();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logExtensionError(otherUuid, e);
|
this.logExtensionError(otherUuid, e);
|
||||||
}
|
}
|
||||||
@ -147,13 +152,13 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
|
|
||||||
this._extensionOrder.splice(orderIdx, 1);
|
this._extensionOrder.splice(orderIdx, 1);
|
||||||
|
|
||||||
if (extension.state != ExtensionState.ERROR) {
|
if (extension.state !== ExtensionState.ERROR) {
|
||||||
extension.state = ExtensionState.DISABLED;
|
extension.state = ExtensionState.DISABLED;
|
||||||
this.emit('extension-state-changed', extension);
|
this.emit('extension-state-changed', extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_callExtensionEnable(uuid) {
|
async _callExtensionEnable(uuid) {
|
||||||
if (!this._extensionSupportsSessionMode(uuid))
|
if (!this._extensionSupportsSessionMode(uuid))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -161,10 +166,11 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
if (!extension)
|
if (!extension)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (extension.state == ExtensionState.INITIALIZED)
|
if (extension.state === ExtensionState.INITIALIZED)
|
||||||
this._callExtensionInit(uuid);
|
await this._callExtensionInit(uuid);
|
||||||
|
|
||||||
if (extension.state != ExtensionState.DISABLED)
|
|
||||||
|
if (extension.state !== ExtensionState.DISABLED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
extension.state = ExtensionState.ENABLING;
|
extension.state = ExtensionState.ENABLING;
|
||||||
@ -187,7 +193,7 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
extension.stateObj.enable();
|
await extension.stateObj.enable();
|
||||||
extension.state = ExtensionState.ENABLED;
|
extension.state = ExtensionState.ENABLED;
|
||||||
this._extensionOrder.push(uuid);
|
this._extensionOrder.push(uuid);
|
||||||
this.emit('extension-state-changed', extension);
|
this.emit('extension-state-changed', extension);
|
||||||
@ -372,7 +378,7 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
return extension.metadata.version === version;
|
return extension.metadata.version === version;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadExtension(extension) {
|
async loadExtension(extension) {
|
||||||
// Default to error, we set success as the last step
|
// Default to error, we set success as the last step
|
||||||
extension.state = ExtensionState.ERROR;
|
extension.state = ExtensionState.ERROR;
|
||||||
|
|
||||||
@ -385,10 +391,11 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
let enabled = this._enabledExtensions.includes(extension.uuid) &&
|
let enabled = this._enabledExtensions.includes(extension.uuid) &&
|
||||||
this._extensionSupportsSessionMode(extension.uuid);
|
this._extensionSupportsSessionMode(extension.uuid);
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (!this._callExtensionInit(extension.uuid))
|
if (!await this._callExtensionInit(extension.uuid))
|
||||||
return;
|
return;
|
||||||
if (extension.state == ExtensionState.DISABLED)
|
|
||||||
this._callExtensionEnable(extension.uuid);
|
if (extension.state === ExtensionState.DISABLED)
|
||||||
|
await this._callExtensionEnable(extension.uuid);
|
||||||
} else {
|
} else {
|
||||||
extension.state = ExtensionState.INITIALIZED;
|
extension.state = ExtensionState.INITIALIZED;
|
||||||
}
|
}
|
||||||
@ -400,27 +407,26 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
this.emit('extension-state-changed', extension);
|
this.emit('extension-state-changed', extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
unloadExtension(extension) {
|
async unloadExtension(extension) {
|
||||||
const { uuid, type } = extension;
|
const { uuid, type } = extension;
|
||||||
|
|
||||||
// Try to disable it -- if it's ERROR'd, we can't guarantee that,
|
// 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
|
// but it will be removed on next reboot, and hopefully nothing
|
||||||
// broke too much.
|
// broke too much.
|
||||||
this._callExtensionDisable(uuid);
|
await this._callExtensionDisable(uuid);
|
||||||
|
|
||||||
extension.state = ExtensionState.UNINSTALLED;
|
extension.state = ExtensionState.UNINSTALLED;
|
||||||
this.emit('extension-state-changed', extension);
|
this.emit('extension-state-changed', extension);
|
||||||
|
|
||||||
// If we did install an importer, it is now cached and it's
|
// The extension is now cached and it's impossible to load a different version
|
||||||
// impossible to load a different version
|
if (type === ExtensionType.PER_USER && extension.isImported)
|
||||||
if (type === ExtensionType.PER_USER && extension.imports)
|
|
||||||
this._unloadedExtensions.set(uuid, extension.metadata.version);
|
this._unloadedExtensions.set(uuid, extension.metadata.version);
|
||||||
|
|
||||||
this._extensions.delete(uuid);
|
this._extensions.delete(uuid);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadExtension(oldExtension) {
|
async reloadExtension(oldExtension) {
|
||||||
// Grab the things we'll need to pass to createExtensionObject
|
// Grab the things we'll need to pass to createExtensionObject
|
||||||
// to reload it.
|
// to reload it.
|
||||||
let { uuid, dir, type } = oldExtension;
|
let { uuid, dir, type } = oldExtension;
|
||||||
@ -437,10 +443,10 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadExtension(newExtension);
|
await this.loadExtension(newExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
_callExtensionInit(uuid) {
|
async _callExtensionInit(uuid) {
|
||||||
if (!this._extensionSupportsSessionMode(uuid))
|
if (!this._extensionSupportsSessionMode(uuid))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -458,7 +464,16 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
let extensionModule;
|
let extensionModule;
|
||||||
let extensionState = null;
|
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);
|
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 {
|
try {
|
||||||
extensionModule = extension.imports.extension;
|
extensionModule = extension.imports.extension;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -468,7 +483,7 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
|
|
||||||
if (extensionModule.init) {
|
if (extensionModule.init) {
|
||||||
try {
|
try {
|
||||||
extensionState = extensionModule.init(extension);
|
extensionState = await extensionModule.init(extension);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logExtensionError(uuid, e);
|
this.logExtensionError(uuid, e);
|
||||||
return false;
|
return false;
|
||||||
@ -492,8 +507,8 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
|
|
||||||
_updateCanChange(extension) {
|
_updateCanChange(extension) {
|
||||||
let hasError =
|
let hasError =
|
||||||
extension.state == ExtensionState.ERROR ||
|
extension.state === ExtensionState.ERROR ||
|
||||||
extension.state == ExtensionState.OUT_OF_DATE;
|
extension.state === ExtensionState.OUT_OF_DATE;
|
||||||
|
|
||||||
let isMode = this._getModeExtensions().includes(extension.uuid);
|
let isMode = this._getModeExtensions().includes(extension.uuid);
|
||||||
let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY);
|
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));
|
return extensions.filter(item => !disabledExtensions.includes(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUserExtensionsEnabledChanged() {
|
async _onUserExtensionsEnabledChanged() {
|
||||||
this._onEnabledExtensionsChanged();
|
await this._onEnabledExtensionsChanged();
|
||||||
this._onSettingsWritableChanged();
|
this._onSettingsWritableChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onEnabledExtensionsChanged() {
|
async _onEnabledExtensionsChanged() {
|
||||||
let newEnabledExtensions = this._getEnabledExtensions();
|
let newEnabledExtensions = this._getEnabledExtensions();
|
||||||
|
|
||||||
// Find and enable all the newly enabled extensions: UUIDs found in the
|
// Find and enable all the newly enabled extensions: UUIDs found in the
|
||||||
// new setting, but not in the old one.
|
// new setting, but not in the old one.
|
||||||
newEnabledExtensions
|
const extensionsToEnable = newEnabledExtensions
|
||||||
.filter(uuid => !this._enabledExtensions.includes(uuid) &&
|
.filter(uuid => !this._enabledExtensions.includes(uuid) &&
|
||||||
this._extensionSupportsSessionMode(uuid))
|
this._extensionSupportsSessionMode(uuid));
|
||||||
.forEach(uuid => this._callExtensionEnable(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
|
// Find and disable all the newly disabled extensions: UUIDs found in the
|
||||||
// old setting, but not in the new one.
|
// old setting, but not in the new one.
|
||||||
this._extensionOrder
|
const extensionsToDisable = this._extensionOrder
|
||||||
.filter(uuid => !newEnabledExtensions.includes(uuid) ||
|
.filter(uuid => !newEnabledExtensions.includes(uuid) ||
|
||||||
!this._extensionSupportsSessionMode(uuid))
|
!this._extensionSupportsSessionMode(uuid));
|
||||||
.reverse().forEach(uuid => this._callExtensionDisable(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;
|
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);
|
const checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY);
|
||||||
if (checkVersion === this._checkVersion)
|
if (checkVersion === this._checkVersion)
|
||||||
return;
|
return;
|
||||||
@ -561,16 +585,18 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
// Disabling extensions modifies the order array, so use a copy
|
// Disabling extensions modifies the order array, so use a copy
|
||||||
let extensionOrder = this._extensionOrder.slice();
|
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...
|
// the "rebasing" done in _callExtensionDisable...
|
||||||
extensionOrder.slice().reverse().forEach(uuid => {
|
this._disableAllExtensions();
|
||||||
this._callExtensionDisable(uuid);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ...and then reload and enable extensions in the correct order again.
|
// ...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);
|
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() {
|
_installExtensionUpdates() {
|
||||||
@ -596,21 +622,25 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadExtensions() {
|
async _loadExtensions() {
|
||||||
global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`,
|
global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`, () => {
|
||||||
this._onEnabledExtensionsChanged.bind(this));
|
this._onEnabledExtensionsChanged();
|
||||||
global.settings.connect(`changed::${DISABLED_EXTENSIONS_KEY}`,
|
});
|
||||||
this._onEnabledExtensionsChanged.bind(this));
|
global.settings.connect(`changed::${DISABLED_EXTENSIONS_KEY}`, () => {
|
||||||
global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`,
|
this._onEnabledExtensionsChanged();
|
||||||
this._onUserExtensionsEnabledChanged.bind(this));
|
});
|
||||||
global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`,
|
global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`, () => {
|
||||||
this._onVersionValidationChanged.bind(this));
|
this._onUserExtensionsEnabledChanged();
|
||||||
global.settings.connect(`writable-changed::${ENABLED_EXTENSIONS_KEY}`,
|
});
|
||||||
this._onSettingsWritableChanged.bind(this));
|
global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`, () => {
|
||||||
global.settings.connect(`writable-changed::${DISABLED_EXTENSIONS_KEY}`,
|
this._onVersionValidationChanged();
|
||||||
this._onSettingsWritableChanged.bind(this));
|
});
|
||||||
|
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();
|
this._enabledExtensions = this._getEnabledExtensions();
|
||||||
|
|
||||||
@ -642,33 +672,50 @@ var ExtensionManager = class extends Signals.EventEmitter {
|
|||||||
return extension;
|
return extension;
|
||||||
}).filter(extension => extension !== null);
|
}).filter(extension => extension !== null);
|
||||||
|
|
||||||
for (const extension of extensionObjects)
|
for (const extension of extensionObjects) {
|
||||||
this.loadExtension(extension);
|
// eslint-disable-next-line no-await-in-loop
|
||||||
}
|
await this.loadExtension(extension);
|
||||||
|
|
||||||
_enableAllExtensions() {
|
|
||||||
if (!this._initialized) {
|
|
||||||
this._loadExtensions();
|
|
||||||
this._initialized = true;
|
|
||||||
} else {
|
|
||||||
this._enabledExtensions.forEach(uuid => {
|
|
||||||
this._callExtensionEnable(uuid);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_disableAllExtensions() {
|
async _enableAllExtensions() {
|
||||||
if (this._initialized) {
|
if (!this._initializationPromise)
|
||||||
this._extensionOrder.slice().reverse().forEach(uuid => {
|
this._initializationPromise = this._loadExtensions();
|
||||||
this._callExtensionDisable(uuid);
|
|
||||||
});
|
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
|
// Take care of added or removed sessionMode extensions
|
||||||
this._onEnabledExtensionsChanged();
|
await this._onEnabledExtensionsChanged();
|
||||||
this._enableAllExtensions();
|
await this._enableAllExtensions();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user