From efd24c11fa64d2437010a55f554e3d440c71f13e Mon Sep 17 00:00:00 2001 From: TheNEwmanator15 <3686761+Thenewmanator15@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:18:17 +0100 Subject: [PATCH] Fix plugin loading on Firefox 153.3 (untrusted URI) As of Firefox 153.3.0esr, Services.scriptloader refuses jar:file: and file: URIs unless allowUnsafeURL is passed (Mozilla bug 1974213), so no plugin loads: bootstrap.js fails with "Trying to load untrusted URI", then "Plugin ... is missing bootstrap method 'startup'". - Pass allowUnsafeURL when loading a plugin's bootstrap.js and prefs.js, and preference pane scripts - Give plugins a Services.scriptloader that allows it for URIs within the plugin itself, since plugins commonly load their own scripts from their XPI with loadSubScript(). A load without a target still goes into the plugin scope, as a direct call would. Add a test that installs a fixture plugin that loads a script from its XPI and sets a default pref. Co-Authored-By: Claude Opus 5.5 --- .../content/zotero/preferences/preferences.js | 6 +- chrome/content/zotero/xpcom/plugins.js | 60 ++++++++++++++++-- test/tests/data/plugin-loading-test.xpi | Bin 0 -> 912 bytes test/tests/pluginsTest.js | 27 ++++++++ 4 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 test/tests/data/plugin-loading-test.xpi create mode 100644 test/tests/pluginsTest.js diff --git a/chrome/content/zotero/preferences/preferences.js b/chrome/content/zotero/preferences/preferences.js index 985510fd5a..1b35f91c33 100644 --- a/chrome/content/zotero/preferences/preferences.js +++ b/chrome/content/zotero/preferences/preferences.js @@ -317,7 +317,11 @@ var Zotero_Preferences = { sameZoneAs: window, }); for (let script of pane.scripts) { - Services.scriptloader.loadSubScript(script, pane.scope); + // Plugin panes load scripts from jar:file: URIs + Services.scriptloader.loadSubScriptWithOptions(script, { + target: pane.scope, + allowUnsafeURL: true + }); } } if (pane.stylesheets) { diff --git a/chrome/content/zotero/xpcom/plugins.js b/chrome/content/zotero/xpcom/plugins.js index a2ff9d1228..3eeaf11367 100644 --- a/chrome/content/zotero/xpcom/plugins.js +++ b/chrome/content/zotero/xpcom/plugins.js @@ -197,7 +197,54 @@ Zotero.Plugins = new function () { return Reflect.get(target, property, receiver); } }); - + + // fx153.3: The script loader now refuses jar:file: URIs unless allowUnsafeURL is + // passed (Mozilla bug 1974213), and plugins load their own scripts from their + // XPI that way. Allow it for URIs within the plugin, and keep loading into the + // plugin scope when no target is given, as a direct call would. + let rootURI = addon.getResourceURI().spec; + let withinPlugin = url => typeof url == 'string' && url.startsWith(rootURI); + // The loader's methods are non-configurable, so proxy an empty object instead + let scriptloader = new Proxy({}, { + has(_, property) { + return property in Services.scriptloader; + }, + get(_, property) { + let target = Services.scriptloader; + if (property === 'loadSubScript') { + return function (url, targetObj, charset) { + if (!withinPlugin(url)) { + return target.loadSubScript(url, targetObj || scope, charset); + } + let options = { target: targetObj || scope, allowUnsafeURL: true }; + if (charset) { + options.charset = charset; + } + return target.loadSubScriptWithOptions(url, options); + }; + } + if (property === 'loadSubScriptWithOptions') { + return function (url, options = {}) { + options = { ...options, target: options.target || scope }; + if (withinPlugin(url) && !('allowUnsafeURL' in options)) { + options.allowUnsafeURL = true; + } + return target.loadSubScriptWithOptions(url, options); + }; + } + let value = Reflect.get(target, property, target); + return typeof value == 'function' ? value.bind(target) : value; + } + }); + scope.Services = new Proxy(Services, { + get(target, property) { + if (property === 'scriptloader') { + return scriptloader; + } + return Reflect.get(target, property, target); + } + }); + scopes.set(addon.id, scope); try { @@ -206,7 +253,10 @@ Zotero.Plugins = new function () { uri, { target: scope, - ignoreCache: true + ignoreCache: true, + // Plugins are loaded from jar:file: URIs, which the script loader + // otherwise refuses (Mozilla bug 1974213) + allowUnsafeURL: true } ); } @@ -528,7 +578,8 @@ Zotero.Plugins = new function () { addon.getResourceURI("prefs.js").spec, { target: obj, - ignoreCache: true + ignoreCache: true, + allowUnsafeURL: true } ); } @@ -554,7 +605,8 @@ Zotero.Plugins = new function () { addon.getResourceURI("prefs.js").spec, { target: obj, - ignoreCache: true + ignoreCache: true, + allowUnsafeURL: true } ); } diff --git a/test/tests/data/plugin-loading-test.xpi b/test/tests/data/plugin-loading-test.xpi new file mode 100644 index 0000000000000000000000000000000000000000..4e6d7ccbf172deefeffc26786fbf3f00aa92dcc4 GIT binary patch literal 912 zcmWIWW@Zs#U|`^2s1CG@jeTxcyavcqU}Rw61=6{Rd6{Xc#U*-K#rb)m{TulX8SuD# zuW5ZM=*X?(G4lrl8{0RnEa#0=)B+y`xm>Mx_Y~A^X8(O}?zzPb2mB&;y*_a@xZUI2 zqL#;IoMEL>st>My(T&_;adkrO!)40JY+}rEU4LtW?@6x`oxE94wTFp;;eKCf!G2kW zm7Zqv7ni5?-P_n#Gwg=G?ogDE@|{Pd(eH= z_1?m=kH^jZHmp5k8Mt^J=OVK#sn6U>o9p(-7U&ijMut9Y&E@9KTqH00`~>rs?@i|) zGtQf`aPMJ>py!;IKD;Q~(A4NEu9a~!<@=3KX(Bha=}o)5;_}LQk#*qg+2YXME8G?U3gY(!K-QIf6QyAR0(ZW z^f_hf_hrKmW>o)&H>L_&0{v|c#Oy$vo0ypg^t!k9S#Q0QXU+#}oY3?=f9CT^f1i_Q zy!kzK^*;KZIdl0!$ckr=bi8ymbx#EcoCivJ`UY-)w(2Pp1FGp|%FSK&KvQjjm;;Cl zic-^xfyQfVc==uq@LH?kc~<+}ne*D$R-e-M)ID`w8{M{xAeF(F0t37}eZ6(gY6f1~ z@(g4NBa;XN?z9QC2@JM1f+&=fimnws{vjF}7`8QD2QraU1G-N1kVDc5OiR#kMAw8K dAP7ySz!VM(o&axFHjoSp5Iz9Xvw(683;@nlLr(wz literal 0 HcmV?d00001 diff --git a/test/tests/pluginsTest.js b/test/tests/pluginsTest.js new file mode 100644 index 0000000000..19096ee28d --- /dev/null +++ b/test/tests/pluginsTest.js @@ -0,0 +1,27 @@ +describe("Zotero.Plugins", function () { + var { AddonManager } = ChromeUtils.importESModule("resource://gre/modules/AddonManager.sys.mjs"); + + describe("Loading", function () { + var addon; + + afterEach(async function () { + if (addon) { + await addon.uninstall(); + addon = null; + } + Zotero.Prefs.clear('pluginLoadingTest.pref'); + }); + + // The fixture's bootstrap.js loads main.js from its own XPI with + // Services.scriptloader.loadSubScript() and no target, as most plugins do + it("should load scripts and default prefs from a plugin's XPI", async function () { + let file = getTestDataDirectory(); + file.append('plugin-loading-test.xpi'); + addon = await AddonManager.installTemporaryAddon(file); + + await waitForCallback(() => Zotero.PluginLoadingTest, 100, 10); + assert.equal(Zotero.PluginLoadingTest, 'loaded'); + assert.equal(Zotero.Prefs.get('pluginLoadingTest.pref'), 'default'); + }); + }); +});