2022-08-05 04:43:42 +00:00
|
|
|
/*
|
|
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
|
|
|
|
Copyright © 2022 Corporation for Digital Scholarship
|
|
|
|
Vienna, Virginia, USA
|
|
|
|
https://www.zotero.org
|
|
|
|
|
|
|
|
This file is part of Zotero.
|
|
|
|
|
|
|
|
Zotero is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
Zotero is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU Affero General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
|
|
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
Zotero.Plugins = new function () {
|
|
|
|
var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
|
2023-06-06 19:54:44 +00:00
|
|
|
var { XPIInstall } = ChromeUtils.import("resource://gre/modules/addons/XPIInstall.jsm");
|
2022-08-05 04:43:42 +00:00
|
|
|
var scopes = new Map();
|
2022-08-10 22:26:23 +00:00
|
|
|
var observers = new Set();
|
2022-08-05 04:43:42 +00:00
|
|
|
|
2022-08-05 08:49:45 +00:00
|
|
|
const REASONS = {
|
|
|
|
APP_STARTUP: 1,
|
|
|
|
APP_SHUTDOWN: 2,
|
|
|
|
ADDON_ENABLE: 3,
|
|
|
|
ADDON_DISABLE: 4,
|
|
|
|
ADDON_INSTALL: 5,
|
|
|
|
ADDON_UNINSTALL: 6,
|
|
|
|
ADDON_UPGRADE: 7, // TODO
|
|
|
|
ADDON_DOWNGRADE: 8 // TODO
|
|
|
|
};
|
2022-08-05 04:43:42 +00:00
|
|
|
|
|
|
|
this.init = async function () {
|
|
|
|
this._addonObserver.init();
|
|
|
|
|
|
|
|
var { addons } = await AddonManager.getActiveAddons(["extension"]);
|
|
|
|
for (let addon of addons) {
|
2023-01-05 06:36:33 +00:00
|
|
|
setDefaultPrefs(addon);
|
2022-08-05 08:49:45 +00:00
|
|
|
await _callMethod(addon, 'startup', REASONS.APP_STARTUP);
|
2022-08-05 04:43:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Zotero.addShutdownListener(async () => {
|
|
|
|
var { addons } = await AddonManager.getActiveAddons(["extension"]);
|
|
|
|
for (let addon of addons) {
|
2022-08-05 08:49:45 +00:00
|
|
|
await _callMethod(addon, 'shutdown', REASONS.APP_SHUTDOWN);
|
2022-08-05 04:43:42 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adapted from loadBootstrapScope() in Firefox 60 ESR
|
|
|
|
*
|
|
|
|
* https://searchfox.org/mozilla-esr60/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm#4233
|
|
|
|
*/
|
|
|
|
function _loadScope(addon) {
|
|
|
|
var scope = new Cu.Sandbox(
|
|
|
|
Services.scriptSecurityManager.getSystemPrincipal(),
|
|
|
|
{
|
|
|
|
sandboxName: addon.id,
|
|
|
|
wantGlobalProperties: [
|
|
|
|
"atob",
|
|
|
|
"btoa",
|
|
|
|
"Blob",
|
|
|
|
"crypto",
|
|
|
|
"CSS",
|
|
|
|
"ChromeUtils",
|
2023-02-09 10:36:03 +00:00
|
|
|
"DOMParser",
|
2022-08-05 04:43:42 +00:00
|
|
|
"fetch",
|
|
|
|
"File",
|
|
|
|
"FileReader",
|
|
|
|
"TextDecoder",
|
|
|
|
"TextEncoder",
|
|
|
|
"URL",
|
|
|
|
"URLSearchParams",
|
|
|
|
"XMLHttpRequest"
|
|
|
|
]
|
|
|
|
}
|
|
|
|
);
|
2022-08-05 08:49:45 +00:00
|
|
|
for (let name in REASONS) {
|
|
|
|
scope[name] = REASONS[name];
|
|
|
|
}
|
|
|
|
Object.assign(scope, { Services, Worker, ChromeWorker, Zotero });
|
2022-12-10 09:10:48 +00:00
|
|
|
// Add additional global functions
|
2023-05-28 08:48:31 +00:00
|
|
|
scope.setTimeout = setTimeout;
|
|
|
|
scope.clearTimeout = clearTimeout;
|
|
|
|
scope.setInterval = setInterval;
|
|
|
|
scope.clearInterval = clearInterval;
|
|
|
|
scope.requestIdleCallback = requestIdleCallback;
|
|
|
|
scope.cancelIdleCallback = cancelIdleCallback;
|
2022-12-10 09:10:48 +00:00
|
|
|
|
2022-08-05 04:43:42 +00:00
|
|
|
scopes.set(addon.id, scope);
|
|
|
|
|
|
|
|
var uri = addon.getResourceURI().spec + 'bootstrap.js';
|
|
|
|
Services.scriptloader.loadSubScript(uri, scope);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adapted from callBootstrapMethod() in Firefox 60 ESR
|
|
|
|
*
|
|
|
|
* https://searchfox.org/mozilla-esr60/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm#4343
|
|
|
|
*/
|
2022-08-05 08:49:45 +00:00
|
|
|
async function _callMethod(addon, method, reason) {
|
2022-08-05 04:43:42 +00:00
|
|
|
try {
|
|
|
|
let id = addon.id;
|
|
|
|
Zotero.debug(`Calling bootstrap method '${method}' for plugin ${id} version ${addon.version}`);
|
|
|
|
|
|
|
|
let scope = scopes.get(id);
|
|
|
|
if (!scope) {
|
|
|
|
_loadScope(addon);
|
|
|
|
scope = scopes.get(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
let func;
|
|
|
|
try {
|
|
|
|
func = scope[method] || Cu.evalInSandbox(`${method};`, scope);
|
|
|
|
}
|
|
|
|
catch (e) {}
|
|
|
|
|
|
|
|
if (!func) {
|
|
|
|
Zotero.warn(`Plugin ${id} is missing bootstrap method '${method}'`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let params = {
|
|
|
|
id: addon.id,
|
|
|
|
version: addon.version,
|
2022-08-05 08:49:45 +00:00
|
|
|
rootURI: addon.getResourceURI().spec
|
2022-08-05 04:43:42 +00:00
|
|
|
};
|
|
|
|
let result;
|
|
|
|
try {
|
|
|
|
result = func.call(scope, params, reason);
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
Zotero.logError(`Error running bootstrap method '${method}' on ${id}`);
|
|
|
|
Zotero.logError(e);
|
|
|
|
}
|
2022-08-10 22:26:23 +00:00
|
|
|
|
|
|
|
for (let observer of observers) {
|
|
|
|
if (observer[method]) {
|
|
|
|
try {
|
|
|
|
let maybePromise = observer[method](params, reason);
|
|
|
|
if (maybePromise && maybePromise.then) {
|
|
|
|
await maybePromise;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
Zotero.logError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 04:43:42 +00:00
|
|
|
// TODO: Needed?
|
|
|
|
/*if (method == "startup") {
|
|
|
|
activeAddon.startupPromise = Promise.resolve(result);
|
|
|
|
activeAddon.startupPromise.catch(Cu.reportError);
|
|
|
|
}*/
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
Zotero.logError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _unloadScope(id) {
|
|
|
|
scopes.delete(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-09-20 04:38:06 +00:00
|
|
|
this.getRootURI = async function (id) {
|
2022-08-05 04:43:42 +00:00
|
|
|
var addon = await AddonManager.getAddonByID(id);
|
|
|
|
return addon.getResourceURI().spec;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2022-08-10 22:26:23 +00:00
|
|
|
this.getName = async function (id) {
|
|
|
|
var addon = await AddonManager.getAddonByID(id);
|
|
|
|
return addon.name;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {String} id
|
|
|
|
* @param {Number} idealSize In logical pixels (scaled automatically on hiDPI displays)
|
|
|
|
* @returns {Promise<String | null>}
|
|
|
|
*/
|
|
|
|
this.getIconURI = async function (id, idealSize) {
|
|
|
|
var addon = await AddonManager.getAddonByID(id);
|
|
|
|
return AddonManager.getPreferredIconURL(addon, idealSize, Services.appShell.hiddenDOMWindow);
|
|
|
|
};
|
2023-01-05 06:36:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
function setDefaultPrefs(addon) {
|
|
|
|
var branch = Services.prefs.getDefaultBranch("");
|
|
|
|
var obj = {
|
|
|
|
pref(pref, value) {
|
|
|
|
switch (typeof value) {
|
|
|
|
case 'boolean':
|
|
|
|
branch.setBoolPref(pref, value);
|
|
|
|
break;
|
|
|
|
case 'string':
|
|
|
|
branch.setStringPref(pref, value);
|
|
|
|
break;
|
|
|
|
case 'number':
|
|
|
|
branch.setIntPref(pref, value);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Zotero.logError(`Invalid type '${typeof(value)}' for pref '${pref}'`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
try {
|
|
|
|
Services.scriptloader.loadSubScript(
|
|
|
|
addon.getResourceURI("prefs.js").spec,
|
|
|
|
obj
|
|
|
|
);
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
if (!e.toString().startsWith('Error opening input stream')) {
|
|
|
|
Zotero.logError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function clearDefaultPrefs(addon) {
|
|
|
|
var branch = Services.prefs.getDefaultBranch("");
|
|
|
|
var obj = {
|
|
|
|
pref(pref, value) {
|
|
|
|
if (!branch.prefHasUserValue(pref)) {
|
|
|
|
branch.deleteBranch(pref);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
try {
|
|
|
|
Services.scriptloader.loadSubScript(
|
|
|
|
addon.getResourceURI("prefs.js").spec,
|
|
|
|
obj
|
|
|
|
);
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
if (!e.toString().startsWith('Error opening input stream')) {
|
|
|
|
Zotero.logError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-08-10 22:26:23 +00:00
|
|
|
/**
|
|
|
|
* Add an observer to be notified of lifecycle events on all plugins.
|
|
|
|
*
|
|
|
|
* @param observer
|
|
|
|
* @param {Function} [observer.install]
|
|
|
|
* @param {Function} [observer.startup]
|
|
|
|
* @param {Function} [observer.shutdown]
|
|
|
|
* @param {Function} [observer.uninstall]
|
|
|
|
*/
|
|
|
|
this.addObserver = function (observer) {
|
|
|
|
observers.add(observer);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.removeObserver = function (observer) {
|
|
|
|
observers.delete(observer);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2022-08-05 04:43:42 +00:00
|
|
|
this._addonObserver = {
|
|
|
|
initialized: false,
|
|
|
|
|
2023-06-06 19:54:44 +00:00
|
|
|
uninstalling: new Set(),
|
|
|
|
|
2022-08-05 04:43:42 +00:00
|
|
|
init() {
|
|
|
|
if (!this.initialized) {
|
|
|
|
AddonManager.addAddonListener(this);
|
|
|
|
this.initialized = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
async onInstalling(addon) {
|
|
|
|
Zotero.debug("Installing plugin " + addon.id);
|
|
|
|
},
|
|
|
|
|
|
|
|
async onInstalled(addon) {
|
|
|
|
if (addon.type !== "extension") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Zotero.debug("Installed plugin " + addon.id);
|
2023-01-05 06:36:33 +00:00
|
|
|
setDefaultPrefs(addon);
|
2022-08-05 08:49:45 +00:00
|
|
|
await _callMethod(addon, 'install');
|
|
|
|
await _callMethod(addon, 'startup', REASONS.ADDON_INSTALL);
|
2022-08-05 04:43:42 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
async onEnabling(addon) {
|
|
|
|
if (addon.type !== "extension") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Zotero.debug("Enabling plugin " + addon.id);
|
2023-01-05 06:36:33 +00:00
|
|
|
setDefaultPrefs(addon);
|
2022-08-05 08:49:45 +00:00
|
|
|
await _callMethod(addon, 'startup', REASONS.ADDON_ENABLE);
|
2022-08-05 04:43:42 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
async onDisabled(addon) {
|
|
|
|
if (addon.type !== "extension") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Zotero.debug("Disabling plugin " + addon.id);
|
2022-08-05 08:49:45 +00:00
|
|
|
await _callMethod(addon, 'shutdown', REASONS.ADDON_DISABLE);
|
2023-01-05 06:36:33 +00:00
|
|
|
clearDefaultPrefs(addon);
|
2022-08-05 04:43:42 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
async onUninstalling(addon) {
|
|
|
|
Zotero.debug("Uninstalling plugin " + addon.id);
|
2023-06-06 19:54:44 +00:00
|
|
|
this.uninstalling.add(addon.id);
|
|
|
|
await _callMethod(addon, 'shutdown', REASONS.ADDON_UNINSTALL);
|
|
|
|
await _callMethod(addon, 'uninstall');
|
|
|
|
clearDefaultPrefs(addon);
|
2022-08-05 04:43:42 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
async onUninstalled(addon) {
|
|
|
|
Zotero.debug("Uninstalled plugin " + addon.id);
|
|
|
|
},
|
2023-06-06 19:54:44 +00:00
|
|
|
|
|
|
|
async onOperationCancelled(addon) {
|
|
|
|
if (!this.uninstalling.has(addon.id) || addon.type !== "extension") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Zotero.debug("Cancelled uninstallation of plugin " + addon.id);
|
|
|
|
this.uninstalling.delete(addon.id);
|
|
|
|
setDefaultPrefs(addon);
|
|
|
|
await _callMethod(addon, 'install');
|
|
|
|
if (addon.isActive) {
|
|
|
|
await _callMethod(addon, 'startup', REASONS.ADDON_INSTALL);
|
|
|
|
}
|
|
|
|
}
|
2022-08-05 04:43:42 +00:00
|
|
|
};
|
|
|
|
};
|