Throw an error when an update to word processor plugin fails to install (#4579)

- This commit will retrigger the installation for all word processor
  plugins. For the majority of users this will succeed silently.
  - It will fail with an error for Windows users that have Word open.
  - It will succeed with a warning for macOS users that have Word open.
- The updated error prompt links to the new documentation page for
  failed plugin installs
This commit is contained in:
Adomas Venčkauskas 2024-08-16 16:51:07 +03:00 committed by Dan Stillman
parent 2a0d245018
commit aef0e51186
2 changed files with 113 additions and 66 deletions

View file

@ -13,6 +13,7 @@ general-open-settings = Open Settings
general-help = Help general-help = Help
general-tag = Tag general-tag = Tag
general-done = Done general-done = Done
general-view-troubleshooting-instructions = View Troubleshooting Instructions
menu-file-show-in-finder = menu-file-show-in-finder =
.label = Show in Finder .label = Show in Finder

View file

@ -30,67 +30,100 @@
var EXPORTED_SYMBOLS = ["ZoteroPluginInstaller"]; var EXPORTED_SYMBOLS = ["ZoteroPluginInstaller"];
var { Zotero } = ChromeUtils.importESModule("chrome://zotero/content/zotero.mjs"); var { Zotero } = ChromeUtils.importESModule("chrome://zotero/content/zotero.mjs");
var { setTimeout } = ChromeUtils.importESModule("resource://gre/modules/Timer.sys.mjs");
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
var installationInProgress = false; var installationInProgress = false;
// Prefs:
// version - last successfully installed version
// lastAttemptedVersion - last version that was attempted to automatically install
// skipInstallation - if user cancels an attempt to automatically install the plugin, we do not
// automatically install it again until a successful manual installation
/**
* Generic plugin installation orchestrator for word processor installers
* @param addon {Object}
* @param failSilently {Boolean} whether the installation should not throw errors (typically when installing automatically)
* @param force {Boolean} force install even if the plugin version is up-to-date
* @constructor
*/
var ZoteroPluginInstaller = function (addon, failSilently, force) { var ZoteroPluginInstaller = function (addon, failSilently, force) {
this._addon = addon; this._addon = addon;
this.failSilently = failSilently; this.failSilently = failSilently;
this.force = force; this.force = force;
this.prefBranch = Services.prefs.getBranch(this._addon.EXTENSION_PREF_BRANCH); this.prefBranch = Services.prefs.getBranch(this._addon.EXTENSION_PREF_BRANCH);
// Prefs:
// version - last successfully installed version
// skipInstallation - if user cancels an attempt to automatically install the plugin, we do not
// attempt to automatically install it again until a successful installation
this.prefPaneDoc = null; this.prefPaneDoc = null;
this._errorDisplayed = false;
this.init(); this.init();
}; };
ZoteroPluginInstaller.prototype = { ZoteroPluginInstaller.prototype = {
init: async function () { init: async function () {
if (this._initialized) return; this.debug('Fetching addon info');
Zotero.debug("PluginInstaller: fetching addon info");
Zotero.debug("PluginInstaller: addon info fetched");
this._currentPluginVersion = (await Zotero.File.getContentsFromURLAsync(this._addon.VERSION_FILE)).trim();
this.debug('Addon info fetched');
let lastInstalledVersion = this.prefBranch.getCharPref("version");
let lastAttemptedVersion = this.prefBranch.getCharPref("lastAttemptedVersion", "");
let lastPluginFileVersion = this._addon.LAST_INSTALLED_FILE_UPDATE;
const newVersionSinceLastInstall = Services.vc.compare(lastInstalledVersion, lastPluginFileVersion) < 0;
const newVersionSinceLastAttempt = Services.vc.compare(lastAttemptedVersion, lastPluginFileVersion) < 0;
const shouldSkipInstallation = this.prefBranch.getBoolPref("skipInstallation");
if (this.force) {
this.debug('Force-installing');
// Should never fail silently
this.failSilently = false;
return this.install();
}
else if (shouldSkipInstallation) {
this.debug('Skipping automatic installation because skipInstallation is true');
return;
}
if (newVersionSinceLastAttempt) {
this.debug('New version since last attempt to install. Will display prompt upon failure.');
this.failSilently = false;
this.prefBranch.setCharPref("lastAttemptedVersion", this._currentPluginVersion);
return this.install();
}
else if (newVersionSinceLastInstall) {
this.debug('New version since last successful install. Attempting to install silently.');
return this.install();
}
this.debug('No new updates');
},
install: async function () {
if (installationInProgress) {
this.debug('Extension installation is already in progress');
return;
}
installationInProgress = true;
try { try {
this._version = (await Zotero.File.getContentsFromURLAsync(this._addon.VERSION_FILE)).trim(); if (!this._addon.DISABLE_PROGRESS_WINDOW) {
var version = this.prefBranch.getCharPref("version"); this._progressWindow = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
if (this.force || (Services.vc.compare(version, this._addon.LAST_INSTALLED_FILE_UPDATE) < 0 .getService(Components.interfaces.nsIWindowWatcher)
&& !this.prefBranch.getBoolPref("skipInstallation"))) { .openWindow(null, "chrome://zotero/content/progressWindow.xhtml", '',
if (installationInProgress) { "chrome,resizable=no,close=no,centerscreen", null);
Zotero.debug(`${this._addon.APP} extension installation is already in progress`); this._progressWindow.addEventListener("load", () => this._firstRunListener(), false);
return; }
} else {
let result = this._addon.install(this);
installationInProgress = true; if (result.then) await result;
if (!this._addon.DISABLE_PROGRESS_WINDOW) {
this._progressWindow = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null, "chrome://zotero/content/progressWindow.xhtml", '',
"chrome,resizable=no,close=no,centerscreen", null);
this._progressWindow.addEventListener("load", () => this._firstRunListener(), false);
}
else {
let result = this._addon.install(this);
if (result.then) await result;
}
} }
} }
catch (e) { catch (e) {
Zotero.logError(e); Zotero.logError(e);
this.error(e);
} }
finally { finally {
installationInProgress = false; installationInProgress = false;
} }
this._initialized = true;
}, },
_errorDisplayed: false,
isInstalled: function () { isInstalled: function () {
return !!this.prefBranch.getCharPref("version"); return !!this.prefBranch.getCharPref("version");
@ -105,51 +138,62 @@ ZoteroPluginInstaller.prototype = {
}, },
success: function () { success: function () {
this.debug(`Installation was successful. Version ${this._currentPluginVersion}`);
installationInProgress = false; installationInProgress = false;
this.closeProgressWindow(); this.closeProgressWindow();
this.prefBranch.setCharPref("version", this._version); this.prefBranch.setCharPref("version", this._currentPluginVersion);
this.updateInstallStatus(); this.updateInstallStatus();
this.prefBranch.setBoolPref("skipInstallation", false); this.prefBranch.setBoolPref("skipInstallation", false);
if (this.force && !this._addon.DISABLE_PROGRESS_WINDOW) { if (this.force && !this._addon.DISABLE_PROGRESS_WINDOW) {
var addon = this._addon; setTimeout(() => {
setTimeout(function () {
Services.prompt.alert( Services.prompt.alert(
null, null,
addon.EXTENSION_STRING, this._addon.EXTENSION_STRING,
Zotero.getString("zotero.preferences.wordProcessors.installationSuccess") Zotero.getString("zotero.preferences.wordProcessors.installationSuccess")
); );
}); });
} }
}, },
error: function (error, notFailure) { error: async function (error, notFailure) {
this.debug(`Installation failed with error ${error}`);
installationInProgress = false; installationInProgress = false;
this.closeProgressWindow(); this.closeProgressWindow();
if (!notFailure) { if (notFailure) {
this.prefBranch.setCharPref("version", this._version); this.prefBranch.setCharPref("version", this._currentPluginVersion);
this.updateInstallStatus(); this.updateInstallStatus();
} }
if (this.failSilently) return; if (this.failSilently) {
this.debug('Not displaying error because failSilently is true');
return;
}
if (this._errorDisplayed) return; if (this._errorDisplayed) return;
this._errorDisplayed = true; this._errorDisplayed = true;
var addon = this._addon; let errorMessage = await Zotero.getString("zotero.preferences.wordProcessors.installationError", [
setTimeout(function () { this._addon.APP,
Zotero.appName
]);
if (error) {
errorMessage += "\n\n" + error;
}
setTimeout(() => {
var ps = Services.prompt; var ps = Services.prompt;
var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK var buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) + ps.BUTTON_POS_0_DEFAULT
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING); + (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL);
var result = ps.confirmEx(null, var result = ps.confirmEx(null,
addon.EXTENSION_STRING, this._addon.EXTENSION_STRING,
(error ? error : Zotero.getString("zotero.preferences.wordProcessors.installationError", [addon.APP, Zotero.appName])), errorMessage,
buttonFlags, null, buttonFlags,
Zotero.getString('zotero.preferences.wordProcessors.manualInstallation.button'), Zotero.getString('general-view-troubleshooting-instructions'),
null, null, {}); null, null, null, {});
if (result == 1) { if (result == 0) {
Zotero.launchURL("https://www.zotero.org/support/word_processor_plugin_manual_installation"); Zotero.launchURL("https://www.zotero.org/support/kb/word_processor_plugin_installation_error");
} }
}); });
}, },
cancelled: function (dontSkipInstallation) { cancelled: function (dontSkipInstallation) {
this.debug('Installation cancelled');
installationInProgress = false; installationInProgress = false;
this.closeProgressWindow(); this.closeProgressWindow();
if (!this.force && !dontSkipInstallation) this.prefBranch.setBoolPref("skipInstallation", true); if (!this.force && !dontSkipInstallation) this.prefBranch.setBoolPref("skipInstallation", true);
@ -181,8 +225,8 @@ ZoteroPluginInstaller.prototype = {
? Zotero.getString('zotero.preferences.wordProcessors.reinstall', this._addon.APP) ? Zotero.getString('zotero.preferences.wordProcessors.reinstall', this._addon.APP)
: Zotero.getString('zotero.preferences.wordProcessors.install', this._addon.APP)); : Zotero.getString('zotero.preferences.wordProcessors.install', this._addon.APP));
button.addEventListener("command", function () { button.addEventListener("command", () => {
Zotero.debug(`Install button pressed for ${addon.APP} plugin`); this.debug('Install button pressed');
try { try {
var zpi = new ZoteroPluginInstaller(addon, false, true); var zpi = new ZoteroPluginInstaller(addon, false, true);
zpi.showPreferences(document); zpi.showPreferences(document);
@ -218,22 +262,24 @@ ZoteroPluginInstaller.prototype = {
: Zotero.getString('zotero.preferences.wordProcessors.install', this._addon.APP)); : Zotero.getString('zotero.preferences.wordProcessors.install', this._addon.APP));
}, },
_firstRunListener: function () { _firstRunListener: async function () {
this._progressWindowLabel = this._progressWindow.document.getElementById("progress-label"); this._progressWindowLabel = this._progressWindow.document.getElementById("progress-label");
this._progressWindowLabel.value = Zotero.getString('zotero.preferences.wordProcessors.installing', this._addon.EXTENSION_STRING); this._progressWindowLabel.value = Zotero.getString('zotero.preferences.wordProcessors.installing', this._addon.EXTENSION_STRING);
this._progressWindow.sizeToContent(); this._progressWindow.sizeToContent();
setTimeout(() => { await Zotero.Promise.delay(100);
this._progressWindow.focus(); this._progressWindow.focus();
setTimeout(async () => { await Zotero.Promise.delay(500);
this._progressWindow.focus(); this._progressWindow.focus();
try { try {
await this._addon.install(this); await this._addon.install(this);
} }
catch (e) { catch (e) {
this.error(); Zotero.logError(e);
throw e; this.error(e);
} }
}, 500);
}, 100);
}, },
debug: function (message) {
Zotero.debug(`PluginInstaller ${this._addon.APP}: ${message}`);
}
}; };