aboutsummaryrefslogtreecommitdiff
path: root/modules/firefox.home/chrome/utils/BootstrapLoader.js
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2024-08-19 14:36:34 +0530
committerAkshay Nair <phenax5@gmail.com>2024-08-19 14:36:34 +0530
commit6206aaf88532d153f0e01e22772b7f6e1c979afd (patch)
tree15573ffa93088ba2c10e86af2675045086975555 /modules/firefox.home/chrome/utils/BootstrapLoader.js
parent02612ccd49d561808ff931611b2ea865038b0a51 (diff)
downloadnixos-config-6206aaf88532d153f0e01e22772b7f6e1c979afd.tar.gz
nixos-config-6206aaf88532d153f0e01e22772b7f6e1c979afd.zip
Enabled user js for firefox + global keybindings manager
Diffstat (limited to '')
-rw-r--r--modules/firefox.home/chrome/utils/BootstrapLoader.js419
1 files changed, 419 insertions, 0 deletions
diff --git a/modules/firefox.home/chrome/utils/BootstrapLoader.js b/modules/firefox.home/chrome/utils/BootstrapLoader.js
new file mode 100644
index 0000000..eddc1de
--- /dev/null
+++ b/modules/firefox.home/chrome/utils/BootstrapLoader.js
@@ -0,0 +1,419 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+ChromeUtils.defineESModuleGetters(this, {
+ Blocklist: 'resource://gre/modules/Blocklist.sys.mjs',
+ ConsoleAPI: 'resource://gre/modules/Console.sys.mjs',
+ InstallRDF: 'chrome://userchromejs/content/RDFManifestConverter.sys.mjs',
+});
+
+Services.obs.addObserver(doc => {
+ if (doc.location.protocol + doc.location.pathname === 'about:addons' ||
+ doc.location.protocol + doc.location.pathname === 'chrome:/content/extensions/aboutaddons.html') {
+ const win = doc.defaultView;
+ let handleEvent_orig = win.customElements.get('addon-card').prototype.handleEvent;
+ win.customElements.get('addon-card').prototype.handleEvent = function (e) {
+ if (e.type === 'click' &&
+ e.target.getAttribute('action') === 'preferences' &&
+ this.addon.__AddonInternal__.optionsType == 1/*AddonManager.OPTIONS_TYPE_DIALOG*/) {
+ var windows = Services.wm.getEnumerator(null);
+ while (windows.hasMoreElements()) {
+ var win2 = windows.getNext();
+ if (win2.closed) {
+ continue;
+ }
+ if (win2.document.documentURI == this.addon.optionsURL) {
+ win2.focus();
+ return;
+ }
+ }
+ var features = 'chrome,titlebar,toolbar,centerscreen';
+ win.docShell.rootTreeItem.domWindow.openDialog(this.addon.optionsURL, this.addon.id, features);
+ } else {
+ handleEvent_orig.apply(this, arguments);
+ }
+ }
+ let update_orig = win.customElements.get('addon-options').prototype.update;
+ win.customElements.get('addon-options').prototype.update = function (card, addon) {
+ update_orig.apply(this, arguments);
+ if (addon.__AddonInternal__.optionsType == 1/*AddonManager.OPTIONS_TYPE_DIALOG*/)
+ this.querySelector('panel-item[data-l10n-id="preferences-addon-button"]').hidden = false;
+ }
+ }
+}, 'chrome-document-loaded');
+
+const { AddonManager } = ChromeUtils.importESModule('resource://gre/modules/AddonManager.sys.mjs');
+const { XPIDatabase, AddonInternal } = ChromeUtils.importESModule('resource://gre/modules/addons/XPIDatabase.sys.mjs');
+const { XPIExports } = ChromeUtils.importESModule('resource://gre/modules/addons/XPIExports.sys.mjs');
+
+var orig_verifyBundleSignedState = XPIExports.verifyBundleSignedState;
+XPIExports.verifyBundleSignedState = async (aBundle, aAddon) => {
+ if(!aAddon.isWebExtension && aAddon.type === "extension") return { signedState: undefined, signedTypes: [] };
+ return orig_verifyBundleSignedState(aBundle, aAddon);
+}
+
+XPIDatabase.isDisabledLegacy = () => false;
+
+ChromeUtils.defineLazyGetter(this, 'BOOTSTRAP_REASONS', () => {
+ const { XPIProvider } = ChromeUtils.importESModule('resource://gre/modules/addons/XPIProvider.sys.mjs');
+ return XPIProvider.BOOTSTRAP_REASONS;
+});
+
+const { Log } = ChromeUtils.importESModule('resource://gre/modules/Log.sys.mjs');
+var logger = Log.repository.getLogger('addons.bootstrap');
+
+/**
+ * Valid IDs fit this pattern.
+ */
+var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
+
+// Properties that exist in the install manifest
+const PROP_METADATA = ['id', 'version', 'type', 'internalName', 'updateURL',
+ 'optionsURL', 'optionsType', 'aboutURL', 'iconURL'];
+const PROP_LOCALE_SINGLE = ['name', 'description', 'creator', 'homepageURL'];
+const PROP_LOCALE_MULTI = ['developers', 'translators', 'contributors'];
+
+// Map new string type identifiers to old style nsIUpdateItem types.
+// Retired values:
+// 32 = multipackage xpi file
+// 8 = locale
+// 256 = apiextension
+// 128 = experiment
+// theme = 4
+const TYPES = {
+ extension: 2,
+ dictionary: 64,
+};
+
+const COMPATIBLE_BY_DEFAULT_TYPES = {
+ extension: true,
+ dictionary: true,
+};
+
+const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
+
+function isXPI(filename) {
+ let ext = filename.slice(-4).toLowerCase();
+ return ext === '.xpi' || ext === '.zip';
+}
+
+/**
+ * Gets an nsIURI for a file within another file, either a directory or an XPI
+ * file. If aFile is a directory then this will return a file: URI, if it is an
+ * XPI file then it will return a jar: URI.
+ *
+ * @param {nsIFile} aFile
+ * The file containing the resources, must be either a directory or an
+ * XPI file
+ * @param {string} aPath
+ * The path to find the resource at, '/' separated. If aPath is empty
+ * then the uri to the root of the contained files will be returned
+ * @returns {nsIURI}
+ * An nsIURI pointing at the resource
+ */
+function getURIForResourceInFile(aFile, aPath) {
+ if (!isXPI(aFile.leafName)) {
+ let resource = aFile.clone();
+ if (aPath)
+ aPath.split('/').forEach(part => resource.append(part));
+
+ return Services.io.newFileURI(resource);
+ }
+
+ return buildJarURI(aFile, aPath);
+}
+
+/**
+ * Creates a jar: URI for a file inside a ZIP file.
+ *
+ * @param {nsIFile} aJarfile
+ * The ZIP file as an nsIFile
+ * @param {string} aPath
+ * The path inside the ZIP file
+ * @returns {nsIURI}
+ * An nsIURI for the file
+ */
+function buildJarURI(aJarfile, aPath) {
+ let uri = Services.io.newFileURI(aJarfile);
+ uri = 'jar:' + uri.spec + '!/' + aPath;
+ return Services.io.newURI(uri);
+}
+
+var BootstrapLoader = {
+ name: 'bootstrap',
+ manifestFile: 'install.rdf',
+ async loadManifest(pkg) {
+ /**
+ * Reads locale properties from either the main install manifest root or
+ * an em:localized section in the install manifest.
+ *
+ * @param {Object} aSource
+ * The resource to read the properties from.
+ * @param {boolean} isDefault
+ * True if the locale is to be read from the main install manifest
+ * root
+ * @param {string[]} aSeenLocales
+ * An array of locale names already seen for this install manifest.
+ * Any locale names seen as a part of this function will be added to
+ * this array
+ * @returns {Object}
+ * an object containing the locale properties
+ */
+ function readLocale(aSource, isDefault, aSeenLocales) {
+ let locale = {};
+ if (!isDefault) {
+ locale.locales = [];
+ for (let localeName of aSource.locales || []) {
+ if (!localeName) {
+ logger.warn('Ignoring empty locale in localized properties');
+ continue;
+ }
+ if (aSeenLocales.includes(localeName)) {
+ logger.warn('Ignoring duplicate locale in localized properties');
+ continue;
+ }
+ aSeenLocales.push(localeName);
+ locale.locales.push(localeName);
+ }
+
+ if (locale.locales.length == 0) {
+ logger.warn('Ignoring localized properties with no listed locales');
+ return null;
+ }
+ }
+
+ for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) {
+ if (hasOwnProperty(aSource, prop)) {
+ locale[prop] = aSource[prop];
+ }
+ }
+
+ return locale;
+ }
+
+ let manifestData = await pkg.readString('install.rdf');
+ let manifest = InstallRDF.loadFromString(manifestData).decode();
+
+ let addon = new AddonInternal();
+ for (let prop of PROP_METADATA) {
+ if (hasOwnProperty(manifest, prop)) {
+ addon[prop] = manifest[prop];
+ }
+ }
+
+ if (!addon.type) {
+ addon.type = 'extension';
+ } else {
+ let type = addon.type;
+ addon.type = null;
+ for (let name in TYPES) {
+ if (TYPES[name] == type) {
+ addon.type = name;
+ break;
+ }
+ }
+ }
+
+ if (!(addon.type in TYPES))
+ throw new Error('Install manifest specifies unknown type: ' + addon.type);
+
+ if (!addon.id)
+ throw new Error('No ID in install manifest');
+ if (!gIDTest.test(addon.id))
+ throw new Error('Illegal add-on ID ' + addon.id);
+ if (!addon.version)
+ throw new Error('No version in install manifest');
+
+ addon.strictCompatibility = (!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
+ manifest.strictCompatibility == 'true');
+
+ // Only read these properties for extensions.
+ if (addon.type == 'extension') {
+ if (manifest.bootstrap != 'true') {
+ throw new Error('Non-restartless extensions no longer supported');
+ }
+
+ if (addon.optionsType &&
+ addon.optionsType != 1/*AddonManager.OPTIONS_TYPE_DIALOG*/ &&
+ addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_BROWSER &&
+ addon.optionsType != AddonManager.OPTIONS_TYPE_TAB) {
+ throw new Error('Install manifest specifies unknown optionsType: ' + addon.optionsType);
+ }
+
+ if (addon.optionsType)
+ addon.optionsType = parseInt(addon.optionsType);
+ }
+
+ addon.defaultLocale = readLocale(manifest, true);
+
+ let seenLocales = [];
+ addon.locales = [];
+ for (let localeData of manifest.localized || []) {
+ let locale = readLocale(localeData, false, seenLocales);
+ if (locale)
+ addon.locales.push(locale);
+ }
+
+ let dependencies = new Set(manifest.dependencies);
+ addon.dependencies = Object.freeze(Array.from(dependencies));
+
+ let seenApplications = [];
+ addon.targetApplications = [];
+ for (let targetApp of manifest.targetApplications || []) {
+ if (!targetApp.id || !targetApp.minVersion ||
+ !targetApp.maxVersion) {
+ logger.warn('Ignoring invalid targetApplication entry in install manifest');
+ continue;
+ }
+ if (seenApplications.includes(targetApp.id)) {
+ logger.warn('Ignoring duplicate targetApplication entry for ' + targetApp.id +
+ ' in install manifest');
+ continue;
+ }
+ seenApplications.push(targetApp.id);
+ addon.targetApplications.push(targetApp);
+ }
+
+ // Note that we don't need to check for duplicate targetPlatform entries since
+ // the RDF service coalesces them for us.
+ addon.targetPlatforms = [];
+ for (let targetPlatform of manifest.targetPlatforms || []) {
+ let platform = {
+ os: null,
+ abi: null,
+ };
+
+ let pos = targetPlatform.indexOf('_');
+ if (pos != -1) {
+ platform.os = targetPlatform.substring(0, pos);
+ platform.abi = targetPlatform.substring(pos + 1);
+ } else {
+ platform.os = targetPlatform;
+ }
+
+ addon.targetPlatforms.push(platform);
+ }
+
+ addon.userDisabled = false;
+ addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;
+ addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+
+ addon.userPermissions = null;
+
+ addon.icons = {};
+ if (await pkg.hasResource('icon.png')) {
+ addon.icons[32] = 'icon.png';
+ addon.icons[48] = 'icon.png';
+ }
+
+ if (await pkg.hasResource('icon64.png')) {
+ addon.icons[64] = 'icon64.png';
+ }
+
+ Object.defineProperty(addon, 'appDisabled', {
+ set: _ => {},
+ get: _ => false
+ });
+
+ Object.defineProperty(addon, 'signedState', {
+ set: _ => {},
+ get: _ => AddonManager.SIGNEDSTATE_NOT_REQUIRED
+ });
+
+ return addon;
+ },
+
+ loadScope(addon) {
+ let file = addon.file || addon._sourceBundle;
+ let uri = getURIForResourceInFile(file, 'bootstrap.js').spec;
+ let principal = Services.scriptSecurityManager.getSystemPrincipal();
+
+ let sandbox = new Cu.Sandbox(principal, {
+ sandboxName: uri,
+ addonId: addon.id,
+ wantGlobalProperties: ['ChromeUtils'],
+ metadata: { addonID: addon.id, URI: uri },
+ });
+
+ try {
+ Object.assign(sandbox, BOOTSTRAP_REASONS);
+
+ ChromeUtils.defineLazyGetter(sandbox, 'console', () =>
+ new ConsoleAPI({ consoleID: `addon/${addon.id}` }));
+
+ Services.scriptloader.loadSubScript(uri, sandbox);
+ } catch (e) {
+ logger.warn(`Error loading bootstrap.js for ${addon.id}`, e);
+ }
+
+ function findMethod(name) {
+ if (sandbox[name]) {
+ return sandbox[name];
+ }
+
+ try {
+ let method = Cu.evalInSandbox(name, sandbox);
+ return method;
+ } catch (err) { }
+
+ return () => {
+ logger.warn(`Add-on ${addon.id} is missing bootstrap method ${name}`);
+ };
+ }
+
+ let install = findMethod('install');
+ let uninstall = findMethod('uninstall');
+ let startup = findMethod('startup');
+ let shutdown = findMethod('shutdown');
+
+ return {
+ install(...args) {
+ install(...args);
+ // Forget any cached files we might've had from this extension.
+ Services.obs.notifyObservers(null, 'startupcache-invalidate');
+ },
+
+ uninstall(...args) {
+ uninstall(...args);
+ // Forget any cached files we might've had from this extension.
+ Services.obs.notifyObservers(null, 'startupcache-invalidate');
+ },
+
+ startup(...args) {
+ if (addon.type == 'extension') {
+ logger.debug(`Registering manifest for ${file.path}\n`);
+ Components.manager.addBootstrappedManifestLocation(file);
+ }
+ return startup(...args);
+ },
+
+ shutdown(data, reason) {
+ try {
+ return shutdown(data, reason);
+ } catch (err) {
+ throw err;
+ } finally {
+ if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
+ logger.debug(`Removing manifest for ${file.path}\n`);
+ Components.manager.removeBootstrappedManifestLocation(file);
+ }
+ }
+ },
+ };
+ },
+};
+
+AddonManager.addExternalExtensionLoader(BootstrapLoader);
+
+if (AddonManager.isReady) {
+ AddonManager.getAllAddons().then(addons => {
+ addons.forEach(addon => {
+ if (addon.type == 'extension' && !addon.isWebExtension && !addon.userDisabled) {
+ addon.reload();
+ };
+ });
+ });
+} \ No newline at end of file