Fix plugin loading on Firefox 153.3 (untrusted URI) (#6058)

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, and temporarily enable
security.allow_unsafe_subscript_loads, which covers loads from plugin
code outside the bootstrap scope.

Also add a test that installs a fixture plugin that loads a script from its XPI
and sets a default pref.

---------

Co-authored-by: Dan Stillman <dstillman@zotero.org>
This commit is contained in:
Thenewmanator15 2026-10-05 15:51:07 +01:00 • committed by GitHub
parent 15f6a81181
commit ba2d6ecee4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 78 additions and 4 deletions

View file

@ -54,6 +54,11 @@ pref("dom.disable_open_during_load", true);
// scraping the page, since we don't provide any information to the site.
pref("security.warn_viewing_mixed", false);
// Temporarily allow plugins to load their own scripts from jar:file: URIs with loadSubScript()
// without passing allowUnsafeURL (Mozilla bug 1974213). This will be disabled once plugins have
// had time to update.
pref("security.allow_unsafe_subscript_loads", true);
// We do need synchronous XHR
pref("network.xhr.block_sync_system_requests", false);

View file

@ -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) {

View file

@ -206,7 +206,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 +531,8 @@ Zotero.Plugins = new function () {
addon.getResourceURI("prefs.js").spec,
{
target: obj,
ignoreCache: true
ignoreCache: true,
allowUnsafeURL: true
}
);
}
@ -554,7 +558,8 @@ Zotero.Plugins = new function () {
addon.getResourceURI("prefs.js").spec,
{
target: obj,
ignoreCache: true
ignoreCache: true,
allowUnsafeURL: true
}
);
}

View file

@ -0,0 +1,14 @@
/* global Zotero, Services */
function startup({ rootURI }) {
// Load a script from this XPI into the plugin scope
Services.scriptloader.loadSubScript(rootURI + 'main.js');
Zotero.PluginLoadingTest = pluginLoadingTestMain();
}
function shutdown() {
delete Zotero.PluginLoadingTest;
}
function install() {}
function uninstall() {}

View file

@ -0,0 +1,3 @@
function pluginLoadingTestMain() {
return 'loaded';
}

View file

@ -0,0 +1,13 @@
{
"manifest_version": 2,
"name": "Plugin Loading Test",
"version": "1.0",
"applications": {
"zotero": {
"id": "plugin-loading-test@zotero.org",
"update_url": "https://www.zotero.org/",
"strict_min_version": "7.0",
"strict_max_version": "*"
}
}
}

View file

@ -0,0 +1 @@
pref("extensions.zotero.pluginLoadingTest.pref", "default");

29
test/tests/pluginsTest.js Normal file
View file

@ -0,0 +1,29 @@
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(rootURI + ...), as plugins commonly do
it("should load scripts and default prefs from a plugin's XPI", async function () {
let dir = getTestDataDirectory();
dir.append('plugin-loading-test');
let xpiPath = PathUtils.join(await getTempDirectory(), 'plugin-loading-test.xpi');
await Zotero.File.zipDirectory(dir.path, xpiPath);
addon = await AddonManager.installTemporaryAddon(Zotero.File.pathToFile(xpiPath));
await waitForCallback(() => Zotero.PluginLoadingTest, 100, 10);
assert.equal(Zotero.PluginLoadingTest, 'loaded');
assert.equal(Zotero.Prefs.get('pluginLoadingTest.pref'), 'default');
});
});
});