Cache CiteProc Engine instances, pre-cache for Quick Copy (#5399)

This commit is contained in:
Abe Jellinek 2025-07-23 10:47:23 -04:00 committed by Dan Stillman
parent b95f9eea89
commit 7b56a3eefe
4 changed files with 76 additions and 18 deletions

View file

@ -831,7 +831,7 @@ var Zotero_File_Interface = new function() {
clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard);
Zotero.debug(`Copied bibliography to clipboard in ${new Date() - d} ms}`);
Zotero.debug(`Copied bibliography to clipboard in ${new Date() - d} ms`);
}

View file

@ -2231,10 +2231,13 @@ Zotero.Integration.Session.prototype.restoreProcessorState = function() {
}
}
if (!Zotero.Prefs.get('cite.useCiteprocRs')) {
// Due to a bug in citeproc-js there are disambiguation issues after changing items in Zotero library
// and rebuilding the processor state, so we reinitialize the processor altogether
let style = Zotero.Styles.get(this.data.style.styleID);
this.style = style.getCiteProc(this.data.style.locale, this.outputFormat, this.data.prefs.automaticJournalAbbreviations);
// Due to a bug in citeproc-js there are disambiguation issues after
// modifying items in Zotero, even after calling rebuildProcessorState(),
// because rebuildProcessorState() doesn't reset three properties of the
// processor (registry, tmp, and disambiguate) used for disambiguation.
// Call the deprecated restoreProcessorState(), which resets everything.
// Revisit if restoreProcessorState() is removed.
this.style.restoreProcessorState();
}
this.style.rebuildProcessorState(citations, this.outputFormat, uncited);
}

View file

@ -279,8 +279,7 @@ Zotero.QuickCopy = new function() {
else if (format.mode == 'bibliography') {
items = items.filter(item => !item.isNote());
// determine locale preference
var locale = format.locale ? format.locale : Zotero.Prefs.get('export.quickCopy.locale');
var locale = _getLocale(format);
// Copy citations if shift key pressed
if (modified) {
@ -354,9 +353,21 @@ Zotero.QuickCopy = new function() {
translator.cacheCode = true;
await Zotero.Translators.getCodeForTranslator(translator);
}
else if (format.mode === 'bibliography') {
let style = Zotero.Styles.get(format.id);
let locale = _getLocale(format);
// Cache CiteProc instances for HTML and text
style.getCiteProc(locale, 'html');
style.getCiteProc(locale, 'text');
}
};
function _getLocale(format) {
return format.locale || Zotero.Prefs.get('export.quickCopy.locale');
}
var _loadFormattedNames = Zotero.Promise.coroutine(function* () {
var t = new Date;
Zotero.debug("Loading formatted names for Quick Copy");

View file

@ -39,6 +39,19 @@ Zotero.Styles = new function() {
};
this.CSL_VALIDATOR_URL = "resource://zotero/csl-validator.js";
this._memoryPressureObserver = {
observe: (subject, topic) => {
if (topic !== 'memory-pressure') {
return;
}
for (let style of Object.values(this.getAll())) {
style.clearEngineCache();
}
},
QueryInterface: ChromeUtils.generateQI(['nsISupportsWeakReference']),
};
Services.obs.addObserver(this._memoryPressureObserver, 'memory-pressure', /* ownsWeak */ true);
/**
@ -201,7 +214,7 @@ Zotero.Styles = new function() {
/**
* Gets a style with a given ID
* @param {String} id
* @param {Boolean} skipMappings Don't automatically return renamed style
* @param {Boolean} [skipMappings] Don't automatically return renamed style
*/
this.get = function (id, skipMappings) {
if (!_initialized) {
@ -681,22 +694,39 @@ Zotero.Style = function (style, path) {
if(this.source === this.styleID) {
throw new Error("Style with ID "+this.styleID+" references itself as source");
}
this._cachedEngines = new Map();
}
/**
* Get a citeproc-js CSL.Engine instance
* @param {String} locale Locale code
* @param {String} format Output format one of [rtf, html, text]
* @param {Boolean} automaticJournalAbbreviations Whether to automatically abbreviate titles
* @param {String} [format] Output format one of [rtf, html, text]
* @param {Boolean} [automaticJournalAbbreviations] Whether to automatically abbreviate titles
*/
Zotero.Style.prototype.getCiteProc = function(locale, format, automaticJournalAbbreviations) {
if(!locale) {
var locale = Zotero.locale;
if(!locale) {
var locale = 'en-US';
}
}
locale = locale || Zotero.locale || 'en-US';
format = format || 'text';
automaticJournalAbbreviations = !!automaticJournalAbbreviations;
let useCiteprocRs = Zotero.Prefs.get('cite.useCiteprocRs');
// We can cache the Engine instance if we aren't using citeproc-rs
// and this is an installed style
let cacheKey = !useCiteprocRs && this.path
? JSON.stringify({ locale, format, automaticJournalAbbreviations })
: null;
if (cacheKey && this._cachedEngines.has(cacheKey)) {
let engine = this._cachedEngines.get(cacheKey);
// Due to a bug in citeproc-js there are disambiguation issues after
// modifying items in Zotero. The lighter-weight rerebuildProcessorState()
// doesn't reset three properties of the processor (registry, tmp, and
// disambiguate) used for disambiguation, so we need to call the
// deprecated restoreProcessorState(), which resets everything.
// Revisit if restoreProcessorState() is removed.
engine.restoreProcessorState();
return engine;
}
// APA and some similar styles capitalize the first word of subtitles
var uppercaseSubtitlesRE = /^apa($|-)|^academy-of-management($|-)|^(freshwater-science)/;
@ -763,7 +793,8 @@ Zotero.Style.prototype.getCiteProc = function(locale, format, automaticJournalAb
try {
var citeproc;
if (Zotero.Prefs.get('cite.useCiteprocRs')) {
var engineDesc;
if (useCiteprocRs) {
citeproc = new Zotero.CiteprocRs.Engine(
new Zotero.Cite.System({
automaticJournalAbbreviations,
@ -775,6 +806,7 @@ Zotero.Style.prototype.getCiteProc = function(locale, format, automaticJournalAb
format == 'text' ? 'plain' : format,
overrideLocale
);
engineDesc = 'CiteprocRs';
}
else {
citeproc = new Zotero.CiteProc.CSL.Engine(
@ -791,15 +823,27 @@ Zotero.Style.prototype.getCiteProc = function(locale, format, automaticJournalAb
citeproc.opt.development_extensions.wrap_url_and_doi = true;
// Don't try to parse author names. We parse them in itemToCSLJSON
citeproc.opt.development_extensions.parse_names = false;
engineDesc = 'CSL';
}
// Cache the Engine instance if allowed
if (cacheKey) {
this._cachedEngines.set(cacheKey, citeproc);
Zotero.debug(`Caching ${engineDesc}.Engine instance with ${cacheKey} for ${this.styleID}`);
}
return citeproc;
} catch(e) {
}
catch (e) {
Zotero.logError(e);
throw e;
}
};
Zotero.Style.prototype.clearEngineCache = function () {
this._cachedEngines.clear();
};
/**
* Temporarily substitute `event-title` for `event`
*