From 2cc8bf8053afffbd2171490598e61f960258c511 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 15 Sep 2026 22:11:52 -0400 Subject: [PATCH] Fix blank reader from invalid custom themes The AI4Paper plugin wrote a non-array value to the readerCustomThemes synced setting, which broke attachment opening in the reader. Validate the value where it's used. https://forums.zotero.org/discussion/133694/ https://forums.zotero.org/discussion/134248/ --- chrome/content/zotero/xpcom/reader.js | 16 ++++++++++++++-- test/tests/readerTest.js | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/chrome/content/zotero/xpcom/reader.js b/chrome/content/zotero/xpcom/reader.js index 94b423e9e9..13bea81fae 100644 --- a/chrome/content/zotero/xpcom/reader.js +++ b/chrome/content/zotero/xpcom/reader.js @@ -46,6 +46,18 @@ const READ_ALOUD_VOICE_DEFAULTS_PATH = PathUtils.join(Zotero.Profile.dir, 'readA // Whether the Read Aloud audio cache has been pruned of stale versions this session let readAloudCachePruned = false; +/** + * Return the custom reader themes from the synced setting + * + * Plugins can write to the setting, and the reader fails to load if the value isn't an array. + * + * @return {Object[]} + */ +function getCustomThemes() { + let customThemes = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'readerCustomThemes'); + return Array.isArray(customThemes) ? customThemes : []; +} + class ReaderInstance { constructor(options) { this.stateFileName = '.zotero-reader-state'; @@ -251,7 +263,7 @@ class ReaderInstance { ftl, showAnnotations: true, textSelectionAnnotationMode: Zotero.Prefs.get('reader.textSelectionAnnotationMode'), - customThemes: Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'readerCustomThemes') ?? [], + customThemes: getCustomThemes(), lightTheme: Zotero.Prefs.get('reader.lightTheme'), darkTheme: Zotero.Prefs.get('reader.darkTheme'), fontFamily: Zotero.Prefs.get('reader.ebookFontFamily'), @@ -2868,7 +2880,7 @@ class Reader { else if (type === 'setting') { let id = ids[0]; if (id === `${Zotero.Libraries.userLibraryID}/readerCustomThemes`) { - let newCustomThemes = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'readerCustomThemes') ?? []; + let newCustomThemes = getCustomThemes(); this._readers.forEach((reader) => { reader._internalReader.setCustomThemes( Components.utils.cloneInto(newCustomThemes, reader._iframeWindow) diff --git a/test/tests/readerTest.js b/test/tests/readerTest.js index 36c68d5fbb..4979ca0b9f 100644 --- a/test/tests/readerTest.js +++ b/test/tests/readerTest.js @@ -352,6 +352,25 @@ describe("Reader", function () { } }); + it('should open with an invalid custom themes setting', async function () { + var attachment = await importFileAttachment('test.pdf'); + // Plugins can write anything to a synced setting + await Zotero.SyncedSettings.set( + Zotero.Libraries.userLibraryID, + 'readerCustomThemes', + { 0: { id: 'custom1', label: 'Custom', background: '#FFFFFF', foreground: '#000000' } } + ); + + try { + var reader = await Zotero.Reader.open(attachment.itemID); + await reader._initPromise; + assert.ok(reader._internalReader); + } + finally { + await Zotero.SyncedSettings.clear(Zotero.Libraries.userLibraryID, 'readerCustomThemes'); + } + }); + describe("#importFromEPUB()", function () { let bookEpubPath; // The EPUB itself let bookSdrPath; // The KOReader "sidecar" folder