extensionSystem: Prevent broken updates

Spidermonkey caches imports, which means that uninstalling an
old extension version and installing a new one doesn't work as
expected: If the previous version was loaded, then its code will
be imported instead.

For the last couple of releases this has been a reliable source
of extension bug reports after major GNOME updates. Thankfully
chrome-gnome-shell removed its update support in favor of our
built-in support now, but users may still use older versions
or perform those actions manually, so it still makes sense to
catch this case and set an appropriate error.

https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1248
This commit is contained in:
Florian Müllner 2020-05-07 13:54:24 +02:00
parent 47bcc09516
commit 4220cd6624

View File

@ -26,6 +26,7 @@ var ExtensionManager = class {
this._updateNotified = false; this._updateNotified = false;
this._extensions = new Map(); this._extensions = new Map();
this._unloadedExtensions = new Map();
this._enabledExtensions = []; this._enabledExtensions = [];
this._extensionOrder = []; this._extensionOrder = [];
@ -319,6 +320,14 @@ var ExtensionManager = class {
return extension; return extension;
} }
_canLoad(extension) {
if (!this._unloadedExtensions.has(extension.uuid))
return true;
const version = this._unloadedExtensions.get(extension.uuid);
return extension.metadata.version === version;
}
loadExtension(extension) { 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;
@ -327,6 +336,9 @@ var ExtensionManager = class {
if (checkVersion && ExtensionUtils.isOutOfDate(extension)) { if (checkVersion && ExtensionUtils.isOutOfDate(extension)) {
extension.state = ExtensionState.OUT_OF_DATE; extension.state = ExtensionState.OUT_OF_DATE;
} else if (!this._canLoad(extension)) {
this.logExtensionError(extension.uuid, new Error(
'A different version was loaded previously. You need to log out for changes to take effect.'));
} else { } else {
let enabled = this._enabledExtensions.includes(extension.uuid); let enabled = this._enabledExtensions.includes(extension.uuid);
if (enabled) { if (enabled) {
@ -337,6 +349,8 @@ var ExtensionManager = class {
} else { } else {
extension.state = ExtensionState.INITIALIZED; extension.state = ExtensionState.INITIALIZED;
} }
this._unloadedExtensions.delete(extension.uuid);
} }
this._updateCanChange(extension); this._updateCanChange(extension);
@ -344,15 +358,22 @@ var ExtensionManager = class {
} }
unloadExtension(extension) { unloadExtension(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(extension.uuid); this._callExtensionDisable(uuid);
extension.state = ExtensionState.UNINSTALLED; extension.state = ExtensionState.UNINSTALLED;
this.emit('extension-state-changed', extension); this.emit('extension-state-changed', extension);
this._extensions.delete(extension.uuid); // 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)
this._unloadedExtensions.set(uuid, extension.metadata.version);
this._extensions.delete(uuid);
return true; return true;
} }