diff options
| author | Akshay Nair <phenax5@gmail.com> | 2026-02-08 13:15:45 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2026-02-08 13:15:45 +0530 |
| commit | 653b19aa1e732f56453db383504fe8a5438bda5a (patch) | |
| tree | 5681f12f0b1c8086e03abcf5577ca6c191d917d1 /modules/firefox.home/chrome/utils | |
| parent | 168884c4515b77f946b76b55fce207e13ceb217c (diff) | |
| download | nixos-config-653b19aa1e732f56453db383504fe8a5438bda5a.tar.gz nixos-config-653b19aa1e732f56453db383504fe8a5438bda5a.zip | |
Enable firefox
Diffstat (limited to 'modules/firefox.home/chrome/utils')
| -rw-r--r-- | modules/firefox.home/chrome/utils/BootstrapLoader.js | 133 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/utils/ChromeManifest.sys.mjs | 415 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/utils/userChrome.js | 272 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/utils/userChrome.jsm | 231 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/utils/xPref.jsm | 85 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/utils/xPref.sys.mjs | 92 |
6 files changed, 896 insertions, 332 deletions
diff --git a/modules/firefox.home/chrome/utils/BootstrapLoader.js b/modules/firefox.home/chrome/utils/BootstrapLoader.js index eddc1de..69631ab 100644 --- a/modules/firefox.home/chrome/utils/BootstrapLoader.js +++ b/modules/firefox.home/chrome/utils/BootstrapLoader.js @@ -4,10 +4,13 @@ 'use strict'; -ChromeUtils.defineESModuleGetters(this, { +const Services = globalThis.Services; + +ChromeUtils.defineESModuleGetters(globalThis, { Blocklist: 'resource://gre/modules/Blocklist.sys.mjs', ConsoleAPI: 'resource://gre/modules/Console.sys.mjs', InstallRDF: 'chrome://userchromejs/content/RDFManifestConverter.sys.mjs', + ChromeManifest: 'chrome://userchromejs/content/ChromeManifest.sys.mjs', }); Services.obs.addObserver(doc => { @@ -18,7 +21,7 @@ Services.obs.addObserver(doc => { 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*/) { + this.addon.__AddonInternal__.optionsType == 1/*AddonManager.OPTIONS_TYPE_DIALOG*/ && !!this.addon.optionsURL) { var windows = Services.wm.getEnumerator(null); while (windows.hasMoreElements()) { var win2 = windows.getNext(); @@ -39,31 +42,40 @@ Services.obs.addObserver(doc => { 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*/) + if (addon.__AddonInternal__?.optionsType == 1/*AddonManager.OPTIONS_TYPE_DIALOG*/ && !!addon.optionsURL) 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'); +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') + +XPIDatabase.isDisabledLegacy = () => false; var orig_verifyBundleSignedState = XPIExports.verifyBundleSignedState; XPIExports.verifyBundleSignedState = async (aBundle, aAddon) => { - if(!aAddon.isWebExtension && aAddon.type === "extension") return { signedState: undefined, signedTypes: [] }; + if(!aAddon.isWebExtension && aAddon.type === 'extension' || aAddon.id.includes('_N_SIGN_')) + 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'); + 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'); +ChromeUtils.defineLazyGetter(this, "logger", () => { + let { ConsoleAPI } = ChromeUtils.importESModule( + "resource://gre/modules/Console.sys.mjs" + ); + let consoleOptions = { + maxLogLevel: "all", + prefix: "BootstrapLoader", + }; + return new ConsoleAPI(consoleOptions); +}); /** * Valid IDs fit this pattern. @@ -369,6 +381,81 @@ var BootstrapLoader = { let startup = findMethod('startup'); let shutdown = findMethod('shutdown'); + /** + * Reads content from a jar: URI + * + * @param {nsIURI} jarURI - The jar: URI to read from + * @returns {Promise<string>} The content of the file inside the JAR + */ + async function readFromJarURI(jarURI) { + return new Promise((resolve, reject) => { + try { + const channel = Services.io.newChannelFromURI( + jarURI, + null, + Services.scriptSecurityManager.getSystemPrincipal(), + null, + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER + ); + + const inputStream = channel.open(); + const scriptableStream = Cc[ + '@mozilla.org/scriptableinputstream;1' + ].createInstance(Ci.nsIScriptableInputStream); + scriptableStream.init(inputStream); + + let data = ''; + let available = 0; + while ((available = scriptableStream.available()) > 0) { + data += scriptableStream.read(available); + } + + scriptableStream.close(); + inputStream.close(); + resolve(data); + } catch (e) { + reject(e); + } + }); + } + + // Register a chrome manifest temporarily and return a function which un-does + // the registrarion when no longer needed. + function createManifestTemporarily(manifestText) { + let tempDir = Services.dirsvc.get('ProfD', Ci.nsIFile) + tempDir.append('browser-extension-data'); + tempDir.append(addon.id); + tempDir.append('manifests'); + if (tempDir.exists()) { + // Clean any leftover temp.manifest + tempDir.remove(true); + } + tempDir.append('temp.manifest.' + Date.now()); + + let foStream = Cc[ + '@mozilla.org/network/file-output-stream;1' + ].createInstance(Ci.nsIFileOutputStream); + foStream.init(tempDir, 0x02 | 0x08 | 0x20, 0o664, 0); // write, create, truncate + foStream.write(manifestText, manifestText.length); + foStream.close(); + + Components.manager + .QueryInterface(Ci.nsIComponentRegistrar) + .autoRegister(tempDir); + + Cc['@mozilla.org/uriloader/external-helper-app-service;1'] + .getService(Ci.nsPIExternalAppLauncher) + .deleteTemporaryFileOnExit(tempDir); + + return function () { + tempDir.fileSize = 0; // truncate the manifest + Cc['@mozilla.org/chrome/chrome-registry;1'] + .getService(Ci.nsIXULChromeRegistry) + .checkForNewChrome(); + }; + } + return { install(...args) { install(...args); @@ -382,10 +469,23 @@ var BootstrapLoader = { Services.obs.notifyObservers(null, 'startupcache-invalidate'); }, - startup(...args) { + async startup(...args) { if (addon.type == 'extension') { logger.debug(`Registering manifest for ${file.path}\n`); - Components.manager.addBootstrappedManifestLocation(file); + const manifestURI = getURIForResourceInFile(file, 'chrome.manifest'); + let manifestData = await readFromJarURI(manifestURI); + let chromeManifest = new ChromeManifest(() => { + return manifestData; + }, { + application: Services.appinfo.ID, + appversion: Services.appinfo.version, + platformversion: Services.appinfo.platformVersion, + os: Services.appinfo.OS, + osversion: Services.sysinfo.getProperty('version'), + abi: Services.appinfo.XPCOMABI + }); + await chromeManifest.parse() + this._clearManifest = createManifestTemporarily(chromeManifest.toString(getURIForResourceInFile(file, '').spec)); } return startup(...args); }, @@ -398,7 +498,8 @@ var BootstrapLoader = { } finally { if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) { logger.debug(`Removing manifest for ${file.path}\n`); - Components.manager.removeBootstrappedManifestLocation(file); + this._clearManifest(); + this._clearManifest = null; } } }, @@ -416,4 +517,4 @@ if (AddonManager.isReady) { }; }); }); -}
\ No newline at end of file +} diff --git a/modules/firefox.home/chrome/utils/ChromeManifest.sys.mjs b/modules/firefox.home/chrome/utils/ChromeManifest.sys.mjs new file mode 100644 index 0000000..f2ebc18 --- /dev/null +++ b/modules/firefox.home/chrome/utils/ChromeManifest.sys.mjs @@ -0,0 +1,415 @@ +/* exported ChromeManifest */ +"use strict"; + +/** + * A default map, which assumes a default value on get() if the key doesn't exist + */ +class DefaultMap extends Map { + /** + * Constructs the default map + * + * @param {Function} _default A function that returns the default value for this map + * @param {*} iterable An iterable to initialize the map with + */ + constructor(_default, iterable) { + super(iterable); + this._default = _default; + } + + /** + * Get the given key, creating if necessary + * + * @param {String} key The key of the map to get + * @param {Boolean} create True, if the key should be created in case it doesn't exist. + */ + get(key, create = true) { + if (this.has(key)) { + return super.get(key); + } else if (create) { + this.set(key, this._default()); + return super.get(key); + } + + return this._default(); + } +} + +/** + * A parser for chrome.manifest files. Implements a subset of + * https://developer.mozilla.org/en-US/docs/Mozilla/Chrome_Registration + */ +export class ChromeManifest { + /** + * Constucts the chrome.manifest parser + * + * @param {Function} loader An asynchronous function that will load further files, e.g. + * those included via the |manifest| instruction. The + * function will take the file as an argument and should + * resolve with the string contents of that file + * @param {Object} options Object describing the current system. The keys are manifest + * instructions + */ + constructor(loader, options) { + this.loader = loader; + this.options = options; + + this.overlay = new DefaultMap(() => []); + this.locales = new DefaultMap(() => new Map()); + this.style = new DefaultMap(() => new Set()); + this.category = new DefaultMap(() => new Map()); + + this.component = new Map(); + this.contract = new Map(); + + this.content = new Map(); + this.skin = new DefaultMap(() => new Map()); + this.resource = new Map(); + this.override = new Map(); + } + + /** + * Parse the given file. + * + * @param {String} filename The filename to load + * @param {String} base The relative directory this file is expected to be in. + * @return {Promise} Resolved when loading completes + */ + async parse(filename = "chrome.manifest", base = "") { + await this.parseString(await this.loader(filename), base); + } + + /** + * Parse the given string. + * + * @param {String} data The file data to load + * @param {String} base The relative directory this file is expected to be in. + * @return {Promise} Resolved when loading completes + */ + async parseString(data, base = "") { + const lines = data.split("\n"); + const extraManifests = []; + for (const line of lines) { + const parts = line.split(/\s+/); + const directive = parts.shift(); + switch (directive) { + case "manifest": + extraManifests.push(this._parseManifest(base, ...parts)); + break; + case "component": this._parseComponent(...parts); + break; + case "contract": this._parseContract(...parts); + break; + case "category": this._parseCategory(...parts); + break; + case "content": this._parseContent(...parts); + break; + case "locale": this._parseLocale(...parts); + break; + case "skin": this._parseSkin(...parts); + break; + case "resource": this._parseResource(...parts); + break; + case "overlay": this._parseOverlay(...parts); + break; + case "style": this._parseStyle(...parts); + break; + case "override": this._parseOverride(...parts); + break; + } + } + + await Promise.all(extraManifests); + } + + /** + * Ensure the flags provided for the instruction match our options + * + * @param {String[]} flags An array of raw flag values in the form key=value. + * @return {Boolean} True, if the flags match the options provided in the constructor + */ + _parseFlags(flags) { + const matchString = (a, sign, b) => { + if (sign != "=") { + console.warn(`Invalid sign ${sign} in ${a}${sign}${b}, dropping manifest instruction`); + return false; + } + return a == b; + }; + + const matchVersion = (a, sign, b) => { + switch (sign) { + case "=": return Services.vc.compare(a, b) == 0; + case ">": return Services.vc.compare(a, b) > 0; + case "<": return Services.vc.compare(a, b) < 0; + case ">=": return Services.vc.compare(a, b) >= 0; + case "<=": return Services.vc.compare(a, b) <= 0; + default: + console.warn(`Invalid sign ${sign} in ${a}${sign}${b}, dropping manifest instruction`); + return false; + } + }; + + const flagdata = new DefaultMap(() => []); + + const flagMatches = (key, typeMatch) => { + return !flagdata.has(key) || flagdata.get(key).some(val => typeMatch(this.options[key], ...val)); + }; + + for (const flag of flags) { + const match = flag.match(/(\w+)(>=|<=|<|>|=)(.*)/); + if (match) { + flagdata.get(match[1]).push([match[2], match[3]]); + } else { + console.warn(`Invalid flag ${flag}, dropping manifest instruction`); + } + } + + return flagMatches("application", matchString) && + flagMatches("appversion", matchVersion) && + flagMatches("platformversion", matchVersion) && + flagMatches("os", matchString) && + flagMatches("osversion", matchVersion) && + flagMatches("abi", matchString); + } + + /** + * Parse the manifest instruction, to load other files + * + * @param {String} base The base directory the manifest file is in + * @param {String} filename The file and path to load + * @param {...String} flags The flags for this instruction + * @return {Promise} Promise resolved when the manifest is loaded + */ + async _parseManifest(base, filename, ...flags) { + if (this._parseFlags(flags)) { + const dirparts = filename.split("/"); + dirparts.pop(); + + try { + await this.parse(filename, base + "/" + dirparts.join("/")); + } catch (e) { + console.log(`Could not read manifest '${base}/${filename}'.`); + } + } + return null; + } + + /** + * Parse the component instruction, to load xpcom components + * + * @param {String} classid The xpcom class id to load + * @param {String} loction The file location of this component + * @param {...String} flags The flags for this instruction + */ + _parseComponent(classid, location, ...flags) { + if (this._parseFlags(flags)) { + this.component.set(classid, location); + } + } + + /** + * Parse the contract instruction, to load xpcom contract ids + * + * @param {String} contractid The xpcom contract id to load + * @param {String} location The file location of this component + * @param {...String} flags The flags for this instruction + */ + _parseContract(contractid, location, ...flags) { + if (this._parseFlags(flags)) { + this.contract.set(contractid, location); + } + } + + /** + * Parse the category instruction, to set up xpcom categories + * + * @param {String} category The name of the category + * @param {String} entryName The category entry name + * @param {String} value The category entry value + * @param {...String} flags The flags for this instruction + */ + _parseCategory(category, entryName, value, ...flags) { + if (this._parseFlags(flags)) { + this.category.get(category).set(entryName, value); + } + } + + /** + * Parse the content instruction, to set chrome content locations + * + * @param {String} shortname The content short name, e.g. chrome://shortname/content/ + * @param {String} location The location for this content registration + * @param {...String} flags The flags for this instruction + */ + _parseContent(shortname, location, ...flags) { + if (this._parseFlags(flags)) { + this.content.set(shortname, location); + } + } + + /** + * Parse the locale instruction, to set chrome locale locations + * + * @param {String} shortname The locale short name, e.g. chrome://shortname/locale/ + * @param {String} location The location for this locale registration + * @param {...String} flags The flags for this instruction + */ + _parseLocale(shortname, locale, location, ...flags) { + if (this._parseFlags(flags)) { + this.locales.get(shortname).set(locale, location); + } + } + + /** + * Parse the skin instruction, to set chrome skin locations + * + * @param {String} shortname The skin short name, e.g. chrome://shortname/skin/ + * @param {String} location The location for this skin registration + * @param {...String} flags The flags for this instruction + */ + _parseSkin(packagename, skinname, location, ...flags) { + if (this._parseFlags(flags)) { + this.skin.get(packagename).set(skinname, location); + } + } + + /** + * Parse the resource instruction, to set up resource uri subtitutions + * + * @param {String} packagename The resource package name, e.g. resource://packagename/ + * @param {String} url The location for this content registration + * @param {...String} flags The flags for this instruction + */ + _parseResource(packagename, location, ...flags) { + if (this._parseFlags(flags)) { + this.resource.set(packagename, location); + } + } + + /** + * Parse the overlay instruction, to set up xul overlays + * + * @param {String} targetUrl The chrome target url + * @param {String} overlayUrl The url of the xul overlay + * @param {...String} flags The flags for this instruction + */ + _parseOverlay(targetUrl, overlayUrl, ...flags) { + if (this._parseFlags(flags)) { + this.overlay.get(targetUrl).push(overlayUrl); + } + } + + /** + * Parse the style instruction, to add stylesheets into chrome windows + * + * @param {String} uri The uri of the chrome window + * @param {String} sheet The uri of the css sheet + * @param {...String} flags The flags for this instruction + */ + _parseStyle(uri, sheet, ...flags) { + if (this._parseFlags(flags)) { + this.style.get(uri).add(sheet); + } + } + + /** + * Parse the override instruction, to set chrome uri overrides + * + * @param {String} uri The uri being overridden + * @param {String} newuri The replacement uri for the original location + * @param {...String} flags The flags for this instruction + */ + _parseOverride(uri, newuri, ...flags) { + if (this._parseFlags(flags)) { + this.override.set(uri, newuri); + } + } + + /** + * Output the manifest data as a string in chrome.manifest format + * If root is provided, convert all relative locations to absolute paths. + * + * @param {String} [root] The absolute path root to resolve relative locations + * @return {String} The manifest contents as a string + */ + toString(root = "") { + const lines = []; + const isRelative = loc => { + // Not absolute if doesn't start with /, \, or protocol (chrome://, resource://, etc) + return ( + typeof loc === "string" && + !loc.match(/^(?:[a-zA-Z]+:|\\|\/)/) + ); + }; + const absolutize = loc => { + if (!root) return loc; + if (loc === "/") return root; + if (isRelative(loc)) { + return root.replace(/[\\/]$/, "") + "/" + loc.replace(/^([\\\/])/, ""); + } + return loc; + }; + + // Output components + for (const [classid, location] of this.component) { + lines.push(`component ${classid} ${absolutize(location)}`); + } + + // Output contracts + for (const [contractid, location] of this.contract) { + lines.push(`contract ${contractid} ${absolutize(location)}`); + } + + // Output categories + for (const [category, entries] of this.category) { + for (const [entryName, value] of entries) { + lines.push(`category ${category} ${entryName} ${value}`); + } + } + + // Output content + for (const [shortname, location] of this.content) { + lines.push(`content ${shortname} ${absolutize(location)}`); + } + + // Output locales + for (const [shortname, locales] of this.locales) { + for (const [locale, location] of locales) { + lines.push(`locale ${shortname} ${locale} ${absolutize(location)}`); + } + } + + // Output skins + for (const [packagename, skins] of this.skin) { + for (const [skinname, location] of skins) { + lines.push(`skin ${packagename} ${skinname} ${absolutize(location)}`); + } + } + + // Output resources + for (const [packagename, location] of this.resource) { + lines.push(`resource ${packagename} ${absolutize(location)}`); + } + + // Output overlays + for (const [targetUrl, overlays] of this.overlay) { + for (const overlayUrl of overlays) { + lines.push(`overlay ${targetUrl} ${absolutize(overlayUrl)}`); + } + } + + // Output styles + for (const [uri, sheets] of this.style) { + for (const sheet of sheets) { + lines.push(`style ${uri} ${absolutize(sheet)}`); + } + } + + // Output overrides + for (const [uri, newuri] of this.override) { + lines.push(`override ${uri} ${newuri}`); + } + + return lines.join("\n"); + } +} diff --git a/modules/firefox.home/chrome/utils/userChrome.js b/modules/firefox.home/chrome/utils/userChrome.js new file mode 100644 index 0000000..1870bee --- /dev/null +++ b/modules/firefox.home/chrome/utils/userChrome.js @@ -0,0 +1,272 @@ +'use strict';
+
+(() => {
+ ChromeUtils.defineESModuleGetters(globalThis, {
+ xPref: 'chrome://userchromejs/content/xPref.sys.mjs',
+ Management: 'resource://gre/modules/Extension.sys.mjs',
+ AppConstants: 'resource://gre/modules/AppConstants.sys.mjs',
+ });
+
+ let UC = {
+ webExts: new Map(),
+ sidebar: new Map(),
+ sandboxes: new WeakMap()
+ };
+
+ let _uc = {
+ ALWAYSEXECUTE: 'rebuild_userChrome.uc.js',
+ BROWSERCHROME: AppConstants.MOZ_APP_NAME == 'thunderbird' ? 'chrome://messenger/content/messenger.xhtml' : 'chrome://browser/content/browser.xhtml',
+ BROWSERTYPE: AppConstants.MOZ_APP_NAME == 'thunderbird' ? 'mail:3pane' : 'navigator:browser',
+ BROWSERNAME: AppConstants.MOZ_APP_NAME.charAt(0).toUpperCase() + AppConstants.MOZ_APP_NAME.slice(1),
+ PREF_ENABLED: 'userChromeJS.enabled',
+ PREF_SCRIPTSDISABLED: 'userChromeJS.scriptsDisabled',
+
+ chromedir: Services.dirsvc.get('UChrm', Ci.nsIFile),
+ scriptsDir: '',
+
+ sss: Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService),
+
+ getScripts: function () {
+ this.scripts = {};
+ let files = this.chromedir.directoryEntries.QueryInterface(Ci.nsISimpleEnumerator);
+ while (files.hasMoreElements()) {
+ let file = files.getNext().QueryInterface(Ci.nsIFile);
+ if (/\.uc\.js$/i.test(file.leafName)) {
+ _uc.getScriptData(file);
+ }
+ }
+ },
+
+ getScriptData: function (aFile) {
+ let aContent = this.readFile(aFile);
+ let header = (aContent.match(/^\/\/ ==UserScript==\s*\n(?:.*\n)*?\/\/ ==\/UserScript==\s*\n/m) || [''])[0];
+ let match, rex = {
+ include: [],
+ exclude: []
+ };
+ let findNextRe = /^\/\/ @(include|exclude)\s+(.+)\s*$/gm;
+ while ((match = findNextRe.exec(header))) {
+ rex[match[1]].push(match[2].replace(/^main$/i, _uc.BROWSERCHROME).replace(/\*/g, '.*?'));
+ }
+ if (!rex.include.length) {
+ rex.include.push(_uc.BROWSERCHROME);
+ }
+ let exclude = rex.exclude.length ? '(?!' + rex.exclude.join('$|') + '$)' : '';
+
+ let def = ['', ''];
+ let author = (header.match(/\/\/ @author\s+(.+)\s*$/im) || def)[1];
+ let filename = aFile.leafName || '';
+
+ return this.scripts[filename] = {
+ filename: filename,
+ file: aFile,
+ url: Services.io.getProtocolHandler('file').QueryInterface(Ci.nsIFileProtocolHandler).getURLSpecFromDir(this.chromedir) + filename,
+ name: (header.match(/\/\/ @name\s+(.+)\s*$/im) || def)[1],
+ description: (header.match(/\/\/ @description\s+(.+)\s*$/im) || def)[1],
+ version: (header.match(/\/\/ @version\s+(.+)\s*$/im) || def)[1],
+ author: (header.match(/\/\/ @author\s+(.+)\s*$/im) || def)[1],
+ regex: new RegExp('^' + exclude + '(' + (rex.include.join('|') || '.*') + ')$', 'i'),
+ id: (header.match(/\/\/ @id\s+(.+)\s*$/im) || ['', filename.split('.uc.js')[0] + '@' + (author || 'userChromeJS')])[1],
+ homepageURL: (header.match(/\/\/ @homepageURL\s+(.+)\s*$/im) || def)[1],
+ downloadURL: (header.match(/\/\/ @downloadURL\s+(.+)\s*$/im) || def)[1],
+ updateURL: (header.match(/\/\/ @updateURL\s+(.+)\s*$/im) || def)[1],
+ optionsURL: (header.match(/\/\/ @optionsURL\s+(.+)\s*$/im) || def)[1],
+ startup: (header.match(/\/\/ @startup\s+(.+)\s*$/im) || def)[1],
+ shutdown: (header.match(/\/\/ @shutdown\s+(.+)\s*$/im) || def)[1],
+ onlyonce: /\/\/ @onlyonce\b/.test(header),
+ isRunning: false,
+ get isEnabled() {
+ return (xPref.get(_uc.PREF_SCRIPTSDISABLED) || '').split(',').indexOf(this.filename) == -1;
+ }
+ }
+ },
+
+ readFile: function (aFile, metaOnly = false) {
+ let stream = Cc['@mozilla.org/network/file-input-stream;1'].createInstance(Ci.nsIFileInputStream);
+ stream.init(aFile, 0x01, 0, 0);
+ let cvstream = Cc['@mozilla.org/intl/converter-input-stream;1'].createInstance(Ci.nsIConverterInputStream);
+ cvstream.init(stream, 'UTF-8', 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+ let content = '',
+ data = {};
+ while (cvstream.readString(4096, data)) {
+ content += data.value;
+ if (metaOnly && content.indexOf('// ==/UserScript==') > 0) {
+ break;
+ }
+ }
+ cvstream.close();
+ return content.replace(/\r\n?/g, '\n');
+ },
+
+ everLoaded: [],
+
+ loadScript: function (script, win) {
+ if (!script.regex.test(win.location.href) || (script.filename != this.ALWAYSEXECUTE && !script.isEnabled)) {
+ return;
+ }
+
+ if (script.onlyonce && script.isRunning) {
+ if (script.startup) {
+ Cu.evalInSandbox(`(function(script, win){${script.startup}})`, this.getSandbox(win))(script, win);
+ }
+ return;
+ }
+
+ try {
+ Services.scriptloader.loadSubScript(script.url + '?' + script.file.lastModifiedTime,
+ script.onlyonce ? { window: win } : win);
+ script.isRunning = true;
+ if (script.startup) {
+ Cu.evalInSandbox(`(function(script, win){${script.startup}})`, this.getSandbox(win))(script, win);
+ }
+ if (!script.shutdown) {
+ this.everLoaded.push(script.id);
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ },
+
+ getSandbox: function (doc) {
+ if (!UC.sandboxes) UC.sandboxes = new WeakMap();
+ let global = Cu.getGlobalForObject(doc);
+ if (UC.sandboxes.has(global))
+ return UC.sandboxes.get(global);
+ let sb = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
+ sandboxPrototype: global,
+ sameZoneAs: global,
+ wantXrays: false,
+ sandboxName: 'UCJS:Sandbox'
+ });
+ UC.sandboxes.set(global, sb);
+ global.addEventListener('unload', () => {
+ UC.sandboxes.delete(global);
+ Cu.nukeSandbox(sb);
+ });
+ return sb;
+ },
+
+ windows: function (fun, onlyBrowsers = true) {
+ let windows = Services.wm.getEnumerator(onlyBrowsers ? this.BROWSERTYPE : null);
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ if (!win._uc)
+ continue;
+ if (!onlyBrowsers) {
+ let frames = win.docShell.getAllDocShellsInSubtree(Ci.nsIDocShellTreeItem.typeAll, Ci.nsIDocShell.ENUMERATE_FORWARDS);
+ let res = frames.some(frame => {
+ let fWin = frame.domWindow;
+ let {document, location} = fWin;
+ if (fun(document, fWin, location))
+ return true;
+ });
+ if (res)
+ break;
+ } else {
+ let {document, location} = win;
+ if (fun(document, win, location))
+ break;
+ }
+ }
+ },
+
+ createElement: function (doc, tag, atts, XUL = true) {
+ let el = XUL ? doc.createXULElement(tag) : doc.createElement(tag);
+ for (let att in atts) {
+ if (att.startsWith('on'))
+ el.addEventListener(att.slice(2), typeof atts[att] == "string" ?
+ Cu.evalInSandbox(`(function(event){${atts[att]}})`, this.getSandbox(doc)) : atts[att]);
+ else
+ el.setAttribute(att, atts[att]);
+ }
+ return el
+ }
+ };
+
+ if (xPref.get(_uc.PREF_ENABLED) === undefined) {
+ xPref.set(_uc.PREF_ENABLED, true, true);
+ }
+
+ if (xPref.get(_uc.PREF_SCRIPTSDISABLED) === undefined) {
+ xPref.set(_uc.PREF_SCRIPTSDISABLED, '', true);
+ }
+
+ let UserChrome_js = {
+ observe: function (aSubject) {
+ if (
+ AppConstants.MOZ_APP_NAME == "thunderbird" &&
+ aSubject?.location?.href.startsWith("chrome://messenger/content")
+ ) {
+ aSubject.addEventListener("DOMContentLoaded", () => {
+ this.load(aSubject);
+ }, {once: true})
+ } else {
+ aSubject.addEventListener('DOMContentLoaded', this, {once: true});
+ }
+ },
+
+ handleEvent: function (aEvent) {
+ let document = aEvent.originalTarget;
+ let window = document.defaultView;
+ if (window.document.isInitialDocument) {
+ this.load(window.parent);
+ } else {
+ this.load(window);
+ }
+ },
+
+ load: function (window) {
+ let location = window.location;
+
+ if (!this.sharedWindowOpened && location.href == 'chrome://extensions/content/dummy.xhtml') {
+ this.sharedWindowOpened = true;
+
+ Management.on('extension-browser-inserted', function (topic, browser) {
+ browser.messageManager.addMessageListener('Extension:BackgroundViewLoaded', this.messageListener.bind(this));
+ }.bind(this));
+ } else if (/^(chrome:(?!\/\/global\/content\/commonDialog\.x?html)|about:(?!blank))/i.test(location.href)) {
+ window.UC = UC;
+ window._uc = _uc;
+ window.xPref = xPref;
+ if (window._gBrowser) // bug 1443849
+ window.gBrowser = window._gBrowser;
+
+ if (xPref.get(_uc.PREF_ENABLED)) {
+ Object.values(_uc.scripts).forEach(script => {
+ _uc.loadScript(script, window);
+ });
+ } else if (!UC.rebuild) {
+ _uc.loadScript(_uc.scripts[_uc.ALWAYSEXECUTE], window);
+ }
+ }
+ },
+
+ messageListener: function (msg) {
+ const browser = msg.target;
+ const { addonId } = browser._contentPrincipal;
+
+ browser.messageManager.removeMessageListener('Extension:BackgroundViewLoaded', this.messageListener);
+
+ if (browser.ownerGlobal.location.href == 'chrome://extensions/content/dummy.xhtml') {
+ UC.webExts.set(addonId, browser);
+ Services.obs.notifyObservers(null, 'UCJS:WebExtLoaded', addonId);
+ } else {
+ let win = browser.ownerGlobal.windowRoot.ownerGlobal;
+ UC.sidebar.get(addonId)?.set(win, browser) || UC.sidebar.set(addonId, new Map([[win, browser]]));
+ Services.obs.notifyObservers(win, 'UCJS:SidebarLoaded', addonId);
+ }
+ }
+ };
+
+ if (!Services.appinfo.inSafeMode) {
+ _uc.chromedir.append(_uc.scriptsDir);
+ _uc.getScripts();
+ let windows = Services.wm.getEnumerator(null);
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ if (!('UC' in win))
+ UserChrome_js.load(win)
+ }
+ Services.obs.addObserver(UserChrome_js, 'chrome-document-global-created', false);
+ }
+})();
diff --git a/modules/firefox.home/chrome/utils/userChrome.jsm b/modules/firefox.home/chrome/utils/userChrome.jsm deleted file mode 100644 index 17a9224..0000000 --- a/modules/firefox.home/chrome/utils/userChrome.jsm +++ /dev/null @@ -1,231 +0,0 @@ -let EXPORTED_SYMBOLS = [];
-
-const { xPref } = ChromeUtils.import('chrome://userchromejs/content/xPref.jsm');
-const { Management } = ChromeUtils.import('resource://gre/modules/Extension.jsm');
-const { AppConstants } = ChromeUtils.import('resource://gre/modules/AppConstants.jsm');
-
-let UC = {
- webExts: new Map(),
- sidebar: new Map()
-};
-
-let _uc = {
- ALWAYSEXECUTE: 'rebuild_userChrome.uc.js',
- BROWSERCHROME: AppConstants.MOZ_APP_NAME == 'thunderbird' ? 'chrome://messenger/content/messenger.xhtml' : 'chrome://browser/content/browser.xhtml',
- BROWSERTYPE: AppConstants.MOZ_APP_NAME == 'thunderbird' ? 'mail:3pane' : 'navigator:browser',
- BROWSERNAME: AppConstants.MOZ_APP_NAME.charAt(0).toUpperCase() + AppConstants.MOZ_APP_NAME.slice(1),
- PREF_ENABLED: 'userChromeJS.enabled',
- PREF_SCRIPTSDISABLED: 'userChromeJS.scriptsDisabled',
-
- chromedir: Services.dirsvc.get('UChrm', Ci.nsIFile),
- scriptsDir: '',
-
- sss: Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService),
-
- getScripts: function () {
- this.scripts = {};
- let files = this.chromedir.directoryEntries.QueryInterface(Ci.nsISimpleEnumerator);
- while (files.hasMoreElements()) {
- let file = files.getNext().QueryInterface(Ci.nsIFile);
- if (/\.uc\.js$/i.test(file.leafName)) {
- _uc.getScriptData(file);
- }
- }
- },
-
- getScriptData: function (aFile) {
- let aContent = this.readFile(aFile);
- let header = (aContent.match(/^\/\/ ==UserScript==\s*\n(?:.*\n)*?\/\/ ==\/UserScript==\s*\n/m) || [''])[0];
- let match, rex = {
- include: [],
- exclude: []
- };
- let findNextRe = /^\/\/ @(include|exclude)\s+(.+)\s*$/gm;
- while ((match = findNextRe.exec(header))) {
- rex[match[1]].push(match[2].replace(/^main$/i, _uc.BROWSERCHROME).replace(/\*/g, '.*?'));
- }
- if (!rex.include.length) {
- rex.include.push(_uc.BROWSERCHROME);
- }
- let exclude = rex.exclude.length ? '(?!' + rex.exclude.join('$|') + '$)' : '';
-
- let def = ['', ''];
- let author = (header.match(/\/\/ @author\s+(.+)\s*$/im) || def)[1];
- let filename = aFile.leafName || '';
-
- return this.scripts[filename] = {
- filename: filename,
- file: aFile,
- url: Services.io.getProtocolHandler('file').QueryInterface(Ci.nsIFileProtocolHandler).getURLSpecFromDir(this.chromedir) + filename,
- name: (header.match(/\/\/ @name\s+(.+)\s*$/im) || def)[1],
- description: (header.match(/\/\/ @description\s+(.+)\s*$/im) || def)[1],
- version: (header.match(/\/\/ @version\s+(.+)\s*$/im) || def)[1],
- author: (header.match(/\/\/ @author\s+(.+)\s*$/im) || def)[1],
- regex: new RegExp('^' + exclude + '(' + (rex.include.join('|') || '.*') + ')$', 'i'),
- id: (header.match(/\/\/ @id\s+(.+)\s*$/im) || ['', filename.split('.uc.js')[0] + '@' + (author || 'userChromeJS')])[1],
- homepageURL: (header.match(/\/\/ @homepageURL\s+(.+)\s*$/im) || def)[1],
- downloadURL: (header.match(/\/\/ @downloadURL\s+(.+)\s*$/im) || def)[1],
- updateURL: (header.match(/\/\/ @updateURL\s+(.+)\s*$/im) || def)[1],
- optionsURL: (header.match(/\/\/ @optionsURL\s+(.+)\s*$/im) || def)[1],
- startup: (header.match(/\/\/ @startup\s+(.+)\s*$/im) || def)[1],
- shutdown: (header.match(/\/\/ @shutdown\s+(.+)\s*$/im) || def)[1],
- onlyonce: /\/\/ @onlyonce\b/.test(header),
- isRunning: false,
- get isEnabled() {
- return (xPref.get(_uc.PREF_SCRIPTSDISABLED) || '').split(',').indexOf(this.filename) == -1;
- }
- }
- },
-
- readFile: function (aFile, metaOnly = false) {
- let stream = Cc['@mozilla.org/network/file-input-stream;1'].createInstance(Ci.nsIFileInputStream);
- stream.init(aFile, 0x01, 0, 0);
- let cvstream = Cc['@mozilla.org/intl/converter-input-stream;1'].createInstance(Ci.nsIConverterInputStream);
- cvstream.init(stream, 'UTF-8', 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
- let content = '',
- data = {};
- while (cvstream.readString(4096, data)) {
- content += data.value;
- if (metaOnly && content.indexOf('// ==/UserScript==') > 0) {
- break;
- }
- }
- cvstream.close();
- return content.replace(/\r\n?/g, '\n');
- },
-
- everLoaded: [],
-
- loadScript: function (script, win) {
- if (!script.regex.test(win.location.href) || (script.filename != this.ALWAYSEXECUTE && !script.isEnabled)) {
- return;
- }
-
- if (script.onlyonce && script.isRunning) {
- if (script.startup) {
- eval(script.startup);
- }
- return;
- }
-
- try {
- Services.scriptloader.loadSubScript(script.url + '?' + script.file.lastModifiedTime,
- script.onlyonce ? { window: win } : win);
- script.isRunning = true;
- if (script.startup) {
- eval(script.startup);
- }
- if (!script.shutdown) {
- this.everLoaded.push(script.id);
- }
- } catch (ex) {
- Cu.reportError(ex);
- }
- },
-
- windows: function (fun, onlyBrowsers = true) {
- let windows = Services.wm.getEnumerator(onlyBrowsers ? this.BROWSERTYPE : null);
- while (windows.hasMoreElements()) {
- let win = windows.getNext();
- if (!win._uc)
- continue;
- if (!onlyBrowsers) {
- let frames = win.docShell.getAllDocShellsInSubtree(Ci.nsIDocShellTreeItem.typeAll, Ci.nsIDocShell.ENUMERATE_FORWARDS);
- let res = frames.some(frame => {
- let fWin = frame.domWindow;
- let {document, location} = fWin;
- if (fun(document, fWin, location))
- return true;
- });
- if (res)
- break;
- } else {
- let {document, location} = win;
- if (fun(document, win, location))
- break;
- }
- }
- },
-
- createElement: function (doc, tag, atts, XUL = true) {
- let el = XUL ? doc.createXULElement(tag) : doc.createElement(tag);
- for (let att in atts) {
- el.setAttribute(att, atts[att]);
- }
- return el
- }
-};
-
-if (xPref.get(_uc.PREF_ENABLED) === undefined) {
- xPref.set(_uc.PREF_ENABLED, true, true);
-}
-
-if (xPref.get(_uc.PREF_SCRIPTSDISABLED) === undefined) {
- xPref.set(_uc.PREF_SCRIPTSDISABLED, '', true);
-}
-
-let UserChrome_js = {
- observe: function (aSubject) {
- aSubject.addEventListener('DOMContentLoaded', this, {once: true});
- },
-
- handleEvent: function (aEvent) {
- let document = aEvent.originalTarget;
- let window = document.defaultView;
- this.load(window);
- },
-
- load: function (window) {
- let location = window.location;
-
- if (!this.sharedWindowOpened && location.href == 'chrome://extensions/content/dummy.xhtml') {
- this.sharedWindowOpened = true;
-
- Management.on('extension-browser-inserted', function (topic, browser) {
- browser.messageManager.addMessageListener('Extension:BackgroundViewLoaded', this.messageListener.bind(this));
- }.bind(this));
- } else if (/^(chrome:(?!\/\/global\/content\/commonDialog\.x?html)|about:(?!blank))/i.test(location.href)) {
- window.UC = UC;
- window._uc = _uc;
- window.xPref = xPref;
- if (window._gBrowser) // bug 1443849
- window.gBrowser = window._gBrowser;
-
- if (xPref.get(_uc.PREF_ENABLED)) {
- Object.values(_uc.scripts).forEach(script => {
- _uc.loadScript(script, window);
- });
- } else if (!UC.rebuild) {
- _uc.loadScript(_uc.scripts[_uc.ALWAYSEXECUTE], window);
- }
- }
- },
-
- messageListener: function (msg) {
- const browser = msg.target;
- const { addonId } = browser._contentPrincipal;
-
- browser.messageManager.removeMessageListener('Extension:BackgroundViewLoaded', this.messageListener);
-
- if (browser.ownerGlobal.location.href == 'chrome://extensions/content/dummy.xhtml') {
- UC.webExts.set(addonId, browser);
- Services.obs.notifyObservers(null, 'UCJS:WebExtLoaded', addonId);
- } else {
- let win = browser.ownerGlobal.windowRoot.ownerGlobal;
- UC.sidebar.get(addonId)?.set(win, browser) || UC.sidebar.set(addonId, new Map([[win, browser]]));
- Services.obs.notifyObservers(win, 'UCJS:SidebarLoaded', addonId);
- }
- }
-};
-
-if (!Services.appinfo.inSafeMode) {
- _uc.chromedir.append(_uc.scriptsDir);
- _uc.getScripts();
- let windows = Services.wm.getEnumerator(null);
- while (windows.hasMoreElements()) {
- let win = windows.getNext();
- if (!('UC' in win))
- UserChrome_js.load(win)
- }
- Services.obs.addObserver(UserChrome_js, 'chrome-document-global-created', false);
-}
diff --git a/modules/firefox.home/chrome/utils/xPref.jsm b/modules/firefox.home/chrome/utils/xPref.jsm deleted file mode 100644 index dcb44b3..0000000 --- a/modules/firefox.home/chrome/utils/xPref.jsm +++ /dev/null @@ -1,85 +0,0 @@ -let EXPORTED_SYMBOLS = ['xPref'];
-
-var xPref = {
- get: function(prefPath, def = false, valueIfUndefined, setDefault = true) {
- let sPrefs = def ?
- Services.prefs.getDefaultBranch(null) :
- Services.prefs;
-
- try {
- switch (sPrefs.getPrefType(prefPath)) {
- case 0:
- if (valueIfUndefined != undefined)
- return this.set(prefPath, valueIfUndefined, setDefault);
- else
- return undefined;
- case 32:
- return sPrefs.getStringPref(prefPath);
- case 64:
- return sPrefs.getIntPref(prefPath);
- case 128:
- return sPrefs.getBoolPref(prefPath);
- }
- } catch (ex) {
- return undefined;
- }
- return;
- },
-
- set: function(prefPath, value, def = false) {
- let sPrefs = def ?
- Services.prefs.getDefaultBranch(null) :
- Services.prefs;
-
- try {
- switch (typeof value) {
- case 'string':
- return sPrefs.setStringPref(prefPath, value) || value;
- case 'number':
- return sPrefs.setIntPref(prefPath, value) || value;
- case 'boolean':
- return sPrefs.setBoolPref(prefPath, value) || value;
- }
- } catch (e) { console.error(e) }
- return;
- },
-
- lock: function(prefPath, value) {
- let sPrefs = Services.prefs;
- this.lockedBackupDef[prefPath] = this.get(prefPath, true);
- if (sPrefs.prefIsLocked(prefPath))
- sPrefs.unlockPref(prefPath);
-
- this.set(prefPath, value, true);
- sPrefs.lockPref(prefPath);
- },
-
- lockedBackupDef: {},
-
- unlock: function(prefPath) {
- Services.prefs.unlockPref(prefPath);
- let bkp = this.lockedBackupDef[prefPath];
- if (bkp == undefined)
- Services.prefs.deleteBranch(prefPath);
- else
- this.set(prefPath, bkp, true);
- },
-
- clear: Services.prefs.clearUserPref,
-
- addListener: function(prefPath, trat) {
- this.observer = function(aSubject, aTopic, prefPath) {
- return trat(xPref.get(prefPath), prefPath);
- }
-
- Services.prefs.addObserver(prefPath, this.observer);
- return {
- prefPath,
- observer: this.observer
- };
- },
-
- removeListener: function(obs) {
- Services.prefs.removeObserver(obs.prefPath, obs.observer);
- }
-}
diff --git a/modules/firefox.home/chrome/utils/xPref.sys.mjs b/modules/firefox.home/chrome/utils/xPref.sys.mjs new file mode 100644 index 0000000..aba56db --- /dev/null +++ b/modules/firefox.home/chrome/utils/xPref.sys.mjs @@ -0,0 +1,92 @@ +'use strict';
+
+export const xPref = {
+ // Retorna o valor da preferência, seja qual for o tipo, mas não
+ // testei com tipos complexos como nsIFile, não sei como detectar
+ // uma preferência assim, na verdade nunca vi uma
+ get: function (prefPath, def = false, valueIfUndefined, setDefault = true) {
+ let sPrefs = def ?
+ Services.prefs.getDefaultBranch(null) :
+ Services.prefs;
+
+ try {
+ switch (sPrefs.getPrefType(prefPath)) {
+ case 0:
+ if (valueIfUndefined != undefined)
+ return this.set(prefPath, valueIfUndefined, setDefault);
+ else
+ return undefined;
+ case 32:
+ return sPrefs.getStringPref(prefPath);
+ case 64:
+ return sPrefs.getIntPref(prefPath);
+ case 128:
+ return sPrefs.getBoolPref(prefPath);
+ }
+ } catch (ex) {
+ return undefined;
+ }
+ return;
+ },
+
+ set: function (prefPath, value, def = false) {
+ let sPrefs = def ?
+ Services.prefs.getDefaultBranch(null) :
+ Services.prefs;
+
+ switch (typeof value) {
+ case 'string':
+ return sPrefs.setStringPref(prefPath, value) || value;
+ case 'number':
+ return sPrefs.setIntPref(prefPath, value) || value;
+ case 'boolean':
+ return sPrefs.setBoolPref(prefPath, value) || value;
+ }
+ return;
+ },
+
+ lock: function (prefPath, value) {
+ let sPrefs = Services.prefs;
+ this.lockedBackupDef[prefPath] = this.get(prefPath, true);
+ if (sPrefs.prefIsLocked(prefPath))
+ sPrefs.unlockPref(prefPath);
+
+ this.set(prefPath, value, true);
+ sPrefs.lockPref(prefPath);
+ },
+
+ lockedBackupDef: {},
+
+ unlock: function (prefPath) {
+ Services.prefs.unlockPref(prefPath);
+ let bkp = this.lockedBackupDef[prefPath];
+ if (bkp == undefined)
+ Services.prefs.deleteBranch(prefPath);
+ else
+ this.set(prefPath, bkp, true);
+ },
+
+ clear: Services.prefs.clearUserPref,
+
+ // Detecta mudanças na preferência e retorna:
+ // return[0]: valor da preferência alterada
+ // return[1]: nome da preferência alterada
+ // Guardar chamada numa var se quiser interrompê-la depois
+ addListener: function (prefPath, trat) {
+ this.observer = function (aSubject, aTopic, prefPath) {
+ return trat(xPref.get(prefPath), prefPath);
+ }
+
+ Services.prefs.addObserver(prefPath, this.observer);
+ return {
+ prefPath,
+ observer: this.observer
+ };
+ },
+
+ // Encerra pref observer
+ // Só precisa passar a var definida quando adicionou
+ removeListener: function (obs) {
+ Services.prefs.removeObserver(obs.prefPath, obs.observer);
+ }
+}
|
