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 0000000000..4e6d7ccbf1 Binary files /dev/null and b/test/tests/data/plugin-loading-test.xpi differ 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'); + }); + }); +});