Quick Copy: Fix init race condition

QuickCopy.init() calls Style#getCiteProc(), and a side effect of
citeproc-js locale initialization could cause a recursive call back to
QuickCopy.init(), which would again call Style#getCiteProc() (after it
had checked its cache but before it had added the new engine to it),
leading to duplicate CSL.Engine initialization. Now we correctly check
whether we've already been initialized, eliminating the race condition.

Separately, remove async keyword from init(). It never actually was
async - even before Bluebird removal, it didn't await anything or return
a promise. And we can't make it await its pre-caching work because
schemaUpdatePromise won't resolve until init() resolves/returns.
This commit is contained in:
Abe Jellinek 2025-10-08 10:25:43 -04:00 • committed by Dan Stillman
parent e9dd050229
commit 6131da92c4
3 changed files with 30 additions and 33 deletions

View file

@ -28,51 +28,48 @@
Zotero.QuickCopy = new function () {
this.lastActiveURL = null;
var _initTimeoutID
var _initPromise;
var _initialized = false;
var _initCancelled = false;
var _siteSettings;
var _formattedNames;
this.init = async function () {
Zotero.debug("Initializing Quick Copy");
if (!_initialized) {
// Make sure export translator code is loaded whenever the output format changes
this._prefObserverID = Zotero.Prefs.registerObserver(
"export.quickCopy.setting", _loadOutputFormat
);
this._prefObserverID = Zotero.Prefs.registerObserver(
"export.noteQuickCopy.setting", _loadNoteOutputFormat
);
_initialized = true;
this.init = function () {
if (_initialized) {
return;
}
// Load code for selected export translators ahead of time
// (in the background, because it requires translator initialization)
Zotero.Schema.schemaUpdatePromise
.then(function () {
if (_initCancelled) return;
// Avoid random translator initialization during tests, which can result in timeouts,
Zotero.debug("Initializing Quick Copy");
_initialized = true;
// Make sure export translator code is loaded whenever the output format changes
this._prefObserverID = Zotero.Prefs.registerObserver(
"export.quickCopy.setting", _loadOutputFormat
);
this._prefObserverID = Zotero.Prefs.registerObserver(
"export.noteQuickCopy.setting", _loadNoteOutputFormat
);
Zotero.Schema.schemaUpdatePromise.then(async () => {
// Avoid random translator initialization during tests, which can result in timeouts
// if an export format is selected
if (Zotero.test) return;
_initPromise = (async () => {
await _loadOutputFormat();
await _loadNoteOutputFormat();
await this.loadSiteSettings();
})();
}.bind(this));
// Unfortunate, but we need to keep checking this to prevent race conditions
if (_initCancelled) return;
await _loadOutputFormat();
if (_initCancelled) return;
await _loadNoteOutputFormat();
if (_initCancelled) return;
await this.loadSiteSettings();
});
};
this.uninit = function () {
_initialized = false;
_initCancelled = true;
// TODO: fx140: We used to explicitly cancel the Bluebird _initPromise here
// Is it OK to let it complete?
Zotero.Prefs.unregisterObserver(this._prefObserverID);
};

View file

@ -271,7 +271,7 @@ Zotero.Translators = new function () {
await this.init(Object.assign({}, options, { reinit: true }));
this._translatorsHash = null;
this._sortedTranslatorHash = null;
await Zotero.QuickCopy.init();
Zotero.QuickCopy.init();
};

View file

@ -748,7 +748,7 @@ const { CommandLineOptions } = ChromeUtils.importESModule("chrome://zotero/conte
Zotero.Items.startEmptyTrashTimer();
await Zotero.QuickCopy.init();
Zotero.QuickCopy.init();
Zotero.addShutdownListener(() => Zotero.QuickCopy.uninit());
Zotero.Feeds.init();