From 6206aaf88532d153f0e01e22772b7f6e1c979afd Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Mon, 19 Aug 2024 14:36:34 +0530 Subject: Enabled user js for firefox + global keybindings manager --- .../firefox.home/chrome/utils/BootstrapLoader.js | 419 +++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 modules/firefox.home/chrome/utils/BootstrapLoader.js (limited to 'modules/firefox.home/chrome/utils/BootstrapLoader.js') 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 -- cgit v1.3.1