From 6131da92c4e24e5703eabe5e6597491d0c4ef7d0 Mon Sep 17 00:00:00 2001 From: Abe Jellinek Date: Wed, 8 Oct 2025 10:25:43 -0400 Subject: [PATCH] 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. --- chrome/content/zotero/xpcom/quickCopy.js | 59 +++++++++---------- .../zotero/xpcom/translation/translators.js | 2 +- chrome/content/zotero/xpcom/zotero.js | 2 +- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/chrome/content/zotero/xpcom/quickCopy.js b/chrome/content/zotero/xpcom/quickCopy.js index 56ef34c04b..373de65615 100644 --- a/chrome/content/zotero/xpcom/quickCopy.js +++ b/chrome/content/zotero/xpcom/quickCopy.js @@ -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); }; diff --git a/chrome/content/zotero/xpcom/translation/translators.js b/chrome/content/zotero/xpcom/translation/translators.js index 1d67c91ee5..4fbf137561 100644 --- a/chrome/content/zotero/xpcom/translation/translators.js +++ b/chrome/content/zotero/xpcom/translation/translators.js @@ -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(); }; diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 6372113674..61eb03174e 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -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();