From 4220cd66249a93ee45bb05ba4def3db5cb6591a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 7 May 2020 13:54:24 +0200 Subject: [PATCH] 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 --- js/ui/extensionSystem.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js index d8c16d91f..7362a00e3 100644 --- a/js/ui/extensionSystem.js +++ b/js/ui/extensionSystem.js @@ -26,6 +26,7 @@ var ExtensionManager = class { this._updateNotified = false; this._extensions = new Map(); + this._unloadedExtensions = new Map(); this._enabledExtensions = []; this._extensionOrder = []; @@ -319,6 +320,14 @@ var ExtensionManager = class { 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) { // Default to error, we set success as the last step extension.state = ExtensionState.ERROR; @@ -327,6 +336,9 @@ var ExtensionManager = class { if (checkVersion && ExtensionUtils.isOutOfDate(extension)) { 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 { let enabled = this._enabledExtensions.includes(extension.uuid); if (enabled) { @@ -337,6 +349,8 @@ var ExtensionManager = class { } else { extension.state = ExtensionState.INITIALIZED; } + + this._unloadedExtensions.delete(extension.uuid); } this._updateCanChange(extension); @@ -344,15 +358,22 @@ var ExtensionManager = class { } 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(extension.uuid); + this._callExtensionDisable(uuid); extension.state = ExtensionState.UNINSTALLED; 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; }