From 653b19aa1e732f56453db383504fe8a5438bda5a Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sun, 8 Feb 2026 13:15:45 +0530 Subject: Enable firefox --- home.nix | 1 + modules/firefox.home/autoconfig.js | 15 +- modules/firefox.home/chrome/customNewTab.uc.js | 13 +- .../firefox.home/chrome/extensionsManager.uc.js | 73 ---- .../firefox.home/chrome/globalKeybindings.uc.js | 4 + modules/firefox.home/chrome/loadingIndicator.uc.js | 109 ------ modules/firefox.home/chrome/styles/variables.css | 10 +- modules/firefox.home/chrome/userChrome.css | 72 ++-- .../firefox.home/chrome/utils/BootstrapLoader.js | 133 ++++++- .../chrome/utils/ChromeManifest.sys.mjs | 415 +++++++++++++++++++++ modules/firefox.home/chrome/utils/userChrome.js | 272 ++++++++++++++ modules/firefox.home/chrome/utils/userChrome.jsm | 231 ------------ modules/firefox.home/chrome/utils/xPref.jsm | 85 ----- modules/firefox.home/chrome/utils/xPref.sys.mjs | 92 +++++ modules/firefox.home/default.nix | 22 +- modules/firefox.home/policies.nix | 3 - modules/firefox.home/preferences.nix | 2 +- modules/firefox.home/tridactyl/themes/phenax.css | 173 --------- modules/firefox.home/tridactyl/tridactylrc | 36 -- 19 files changed, 959 insertions(+), 802 deletions(-) delete mode 100644 modules/firefox.home/chrome/extensionsManager.uc.js delete mode 100644 modules/firefox.home/chrome/loadingIndicator.uc.js create mode 100644 modules/firefox.home/chrome/utils/ChromeManifest.sys.mjs create mode 100644 modules/firefox.home/chrome/utils/userChrome.js delete mode 100644 modules/firefox.home/chrome/utils/userChrome.jsm delete mode 100644 modules/firefox.home/chrome/utils/xPref.jsm create mode 100644 modules/firefox.home/chrome/utils/xPref.sys.mjs delete mode 100644 modules/firefox.home/tridactyl/themes/phenax.css delete mode 100644 modules/firefox.home/tridactyl/tridactylrc diff --git a/home.nix b/home.nix index e3a0d9e..385b0a1 100644 --- a/home.nix +++ b/home.nix @@ -12,6 +12,7 @@ in # ./modules/newsboat.home ./modules/email.home ./modules/calendar.home + ./modules/firefox.home ]; home.packages = with pkgs; [ diff --git a/modules/firefox.home/autoconfig.js b/modules/firefox.home/autoconfig.js index a5744d2..bd9d747 100644 --- a/modules/firefox.home/autoconfig.js +++ b/modules/firefox.home/autoconfig.js @@ -1,11 +1,13 @@ // Autoconfig +try { pref('general.config.sandbox_enabled', false); pref('general.config.obscure_value', 0); pref('svg.context-properties.content.enabled', true); lockPref('xpinstall.signatures.required', false); lockPref('extensions.install_origins.enabled', false); lockPref('extensions.experiments.enabled', true); +} catch (ex) { console.error(ex); }; // Setup user chrome support try { @@ -13,20 +15,23 @@ try { cmanifest.append('utils'); cmanifest.append('chrome.manifest'); Components.manager.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(cmanifest); -} catch (ex) { }; +} catch (ex) { console.error(ex); }; try { Services.scriptloader.loadSubScript('chrome://userchromejs/content/BootstrapLoader.js'); -} catch (ex) { }; +} catch (ex) { console.error(ex); }; try { - ChromeUtils.import('chrome://userchromejs/content/userChrome.jsm'); -} catch (ex) { }; + Services.scriptloader.loadSubScript('chrome://userchromejs/content/userChrome.js'); +} catch (ex) {}; // Prefs +try { pref('devtools.theme', 'dark'); pref('devtools.toolbox.alwaysOnTop', true); pref('devtools.toolbox.host', 'window'); +pref('devtools.chrome.enabled', true); +pref('devtools.debugger.remote-enabled', true); pref('privacy.donottrackheader.enabled', true); pref('identity.fxaccounts.toolbar.enabled', false); - +} catch (ex) { console.error(ex); }; diff --git a/modules/firefox.home/chrome/customNewTab.uc.js b/modules/firefox.home/chrome/customNewTab.uc.js index 4c8faca..7ea8ee2 100644 --- a/modules/firefox.home/chrome/customNewTab.uc.js +++ b/modules/firefox.home/chrome/customNewTab.uc.js @@ -2,20 +2,19 @@ // @name Custom New Tab // @version 1.0 // @description Load a custom link or local file, instead of the default new tab page +// @include main // @startup UC.customNewTab.updateNewTabURL() // @shutdown UC.customNewTab.destroy(); +// @onlyonce // ==/UserScript== +console.log('------------------------- newtab') (() => { // Managed in about:config const NEWTAB_URL_PREF = 'browser.newtab.url' - console.log('-------------------------------------------'); - console.log('-------------------------------------------'); - console.log('-------------------------------------------'); - if (!AboutNewTab) { - globalThis.AboutNewTab = ChromeUtils.import('resource:///modules/AboutNewTab.jsm').AboutNewTab; + globalThis.AboutNewTab = ChromeUtils.importESModule('resource:///modules/AboutNewTab.sys.mjs').AboutNewTab; } const module = { @@ -28,10 +27,6 @@ }, init() { - console.log('-------------------------------------------'); - console.log('init new tab'); - console.log('-------------------------------------------'); - module.updateNewTabURL(); Services.obs.addObserver(module.updateNewTabURL, 'newtab-url-changed'); }, diff --git a/modules/firefox.home/chrome/extensionsManager.uc.js b/modules/firefox.home/chrome/extensionsManager.uc.js deleted file mode 100644 index f5f3ed2..0000000 --- a/modules/firefox.home/chrome/extensionsManager.uc.js +++ /dev/null @@ -1,73 +0,0 @@ -// ==UserScript== -// @name Custom New Tab -// @version 1.0 -// @description Load a custom link or local file, instead of the default new tab page -// @startup UC.extensionsManager.init(win) -// @shutdown UC.extensionsManager.destroy(); -// ==/UserScript== - -(() => { - const ENABLE = false; - - // Managed in about:config - const EXTENSION_SETTINGS_PREF = 'extensions.uc.userExtensionSettings' - - const getSettings = () => { - try { - return JSON.parse(xPref.get(EXTENSION_SETTINGS_PREF) || 'null') ?? {}; - } catch (e) { - return {}; - } - } - - const saveSettings = (settings) => - xPref.set(EXTENSION_SETTINGS_PREF, JSON.stringify(settings)); - - const updateExtensionSetting = (id, update) => { - const settings = getSettings(); - settings.extensions[id] ??= {}; - settings.extensions[id] = update(settings.extensions[id] ?? {}) ?? {}; - saveSettings(settings); - } - - const getCurrentTabHost = () => gBrowser.selectedTab.linkedBrowser.currentURI.host.trim() || null; - - const module = { - disableForHost(id, host = getCurrentTabHost()) { - updateExtensionSetting(id, s => { - const urls = new Set(s.disabledHosts || []); - urls.add(host); - s.disabledHosts = [...urls]; - return s; - }); - }, - - async onTabChange() { - const currentHost = getCurrentTabHost(); - if (!currentHost) return; - - const settings = getSettings(); - const addons = await AddonManager.getAllAddons(); - addons.forEach(addon => { - const disabledHosts = settings?.extensions[addon.id].disabledHosts ?? []; - const shouldDisable = disabledHosts.includes(currentHost) - - if (addon.isActive && shouldDisable) - return addon.disable(); - if (!addon.isActive && !shouldDisable) - return addon.enable(); - }) - }, - - init(_win) { - if (!ENABLE) return; - - Services.obs.addObserver(module.onTabChange, 'TabSelect'); - }, - destroy() { - Services.obs.removeObserver(module.onTabChange, 'TabSelect'); - }, - }; - - UC.extensionsManager = module; -})(); diff --git a/modules/firefox.home/chrome/globalKeybindings.uc.js b/modules/firefox.home/chrome/globalKeybindings.uc.js index d8c0f51..fe1d1f7 100644 --- a/modules/firefox.home/chrome/globalKeybindings.uc.js +++ b/modules/firefox.home/chrome/globalKeybindings.uc.js @@ -2,10 +2,14 @@ // @name Global Keybindings // @version 1.0 // @description Setup global keybindings on all windows +// @include main // @startup UC.globalKeybindings.init(win); // @shutdown UC.globalKeybindings.destroy(); +// @onlyonce // ==/UserScript== +console.log('------------------------- keybinds') + (() => { // Configure global keybindings const KEYBINDINGS = () => ({ diff --git a/modules/firefox.home/chrome/loadingIndicator.uc.js b/modules/firefox.home/chrome/loadingIndicator.uc.js deleted file mode 100644 index 286376a..0000000 --- a/modules/firefox.home/chrome/loadingIndicator.uc.js +++ /dev/null @@ -1,109 +0,0 @@ -// ==UserScript== -// @name Loading indicator -// @version 1.0 -// @description Loading indicator for the url bar -// @startup UC.loadingIndicator.init(win) -// @shutdown UC.loadingIndicator.destroy() -// ==/UserScript== - -(() => { - const ENABLED = true; - - class TabProgressListener { - static instances = []; - - static createListener(tab, onStateChange) { - const oldListener = TabProgressListener.instances.find(l => l.tab === tab) - if (oldListener) return oldListener; - const listener = new TabProgressListener(tab, onStateChange); - TabProgressListener.instances.push(listener); - listener.attach(); - return listener; - } - - static cleanup() { - TabProgressListener.instances.forEach(l => l.cleanup()) - } - - loadedPercentage = 0; - constructor(tab, onStateChange) { - this.tab = tab; - this._stateChange = onStateChange; - } - - attach() { - const browser = this.tab.linkedBrowser; - browser.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL) - } - - cleanup() { - try { - const index = TabProgressListener.instances.indexOf(this); - if (index !== -1) - TabProgressListener.instances.splice(index, 1); - } finally { - this.tab.linkedBrowser.webProgress.removeProgressListener(this); - } - } - - // Start query listener methods - - QueryInterface = ChromeUtils.generateQI(['nsIWebProgressListener', 'nsISupportsWeakReference']) - - onStateChange(_webProgress, _request, stateFlags, _status) { - if (stateFlags & Ci.nsIWebProgressListener.STATE_START) { - this._stateChange?.('loading'); - } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) { - this._stateChange?.('loaded'); - } - } - } - - const module = { - onProgressStateUpdate: (win, state) => { - /** @type {HTMLElement} */ - const urlBar = win.gURLBar.textbox; - // console.log(state, urlBar); - if (!urlBar) return; - - // urlBar.style.setProperty('--ff-urlbar-progress', perc); - urlBar.dataset.pageProgress = state; - }, - - setupTab: (tab, win) => { - const listener = TabProgressListener.createListener(tab, (state) => module.onProgressStateUpdate(win, state)); - - tab.addEventListener('TabClose', () => listener.cleanup()); - }, - - setupWindow: win => { - win.gBrowser.tabs.forEach(tab => module.setupTab(tab, win)); - win.gBrowser.tabContainer.addEventListener('TabOpen', event => { - module.setupTab(event.target, win); - }); - win.gBrowser.tabContainer.addEventListener('TabSelect', _event => { - const isLoading = gBrowser.selectedTab.linkedBrowser.webProgress.isLoadingDocument - module.onProgressStateUpdate(win, isLoading ? 'loading' : 'loaded'); - }); - }, - - init(win) { - if (!ENABLED) return; - - const handle = () => { - Services.obs.removeObserver(handle, 'browser-window-before-show'); - module.setupWindow(win); - } - - if (win.__SSi) handle(); - else Services.obs.addObserver(handle, 'browser-window-before-show'); - }, - - destroy() { - TabProgressListener.cleanup(); - }, - }; - - UC.loadingIndicator = module; -})() - diff --git a/modules/firefox.home/chrome/styles/variables.css b/modules/firefox.home/chrome/styles/variables.css index 82e1723..fa6a944 100644 --- a/modules/firefox.home/chrome/styles/variables.css +++ b/modules/firefox.home/chrome/styles/variables.css @@ -1,9 +1,9 @@ :root { - --ff-accent-color: #4e3aA3; - --ff-accent-color-1: #8161ff; - --ff-bg-color: #0f0c19; - --ff-bg-color-1: #16121f; - --ff-bg-color-2: #26222f; + --ff-accent-color: #007070; + --ff-accent-color-1: #009090; + --ff-bg-color: #000000; + --ff-bg-color-1: #101414; + --ff-bg-color-2: #202222; --ff-bg-private: hsl(247, 36%, 15%); --ff-urlbar-height: 24px; } diff --git a/modules/firefox.home/chrome/userChrome.css b/modules/firefox.home/chrome/userChrome.css index e845194..2746e21 100644 --- a/modules/firefox.home/chrome/userChrome.css +++ b/modules/firefox.home/chrome/userChrome.css @@ -26,16 +26,15 @@ toolbarbutton { min-height: 0; } -/* Rounded browser content window */ +/* browser content window */ #browser { background-color: var(--ff-bg-color); } #appcontent { - margin: 4px; + margin: 0 !important; margin-top: 0; - border-radius: 6px; - border: 1px solid rgba(255,255,255,0.06); - overflow: hidden; + border-radius: 0 !important; + border: none !important; } @@ -103,7 +102,7 @@ toolbarbutton#alltabs-button { #nav-bar { border-top: none !important; background-color: var(--toolbar-bgcolor) !important; - padding: 3px 2px 0 !important; + padding: 0 2px 0 !important; } #TabsToolbar { visibility: collapse !important; @@ -137,20 +136,34 @@ toolbarbutton#alltabs-button { color: var(--ff-accent-color-1) !important; } .urlbarView { - background-color: var(--ff-bg-color-2) !important; + background-color: var(--ff-bg-color-1) !important; + margin-inline: 0 !important; + width: 100% !important; +} +.urlbarView-row { + border-radius: 0 !important; } .urlbar-input-container { - background-color: var(--ff-bg-color-2) !important; + background-color: var(--ff-bg-color) !important; + border-radius: 0 !important; border-color: transparent !important; min-height: 0 !important; padding-inline: 0 !important; padding-block: 0 !important; height: var(--ff-urlbar-height) !important; } - +#urlbar { + top: 0 !important; +} +#urlbar[breakout-extend] { + top: 0 !important; +} +#urlbar:not([breakout-extend]), .urlbar-input-container { + height: 26px !important; +} #urlbar[breakout-extend] .urlbar-input-container { - background-color: var(--ff-bg-color-2) !important; + background-color: var(--ff-bg-color-1) !important; padding-inline: 0 !important; } #urlbar[breakout-extend] .urlbar-input-container #identity-box { @@ -164,55 +177,32 @@ toolbarbutton#alltabs-button { /* Sidebar + tab */ +#browser { background-color: red !important; } #sidebar-main { background: var(--ff-bg-color) !important; } #sidebar-box { - background: var(--ff-bg-color) !important; + background: var(--ff-bg-color-1) !important; margin: 3px 0 !important; } #sidebar { - background: var(--ff-bg-color-2) !important; + background: var(--ff-bg-color) !important; + border-radius: 0 !important; + border: none !important; + outline: none !important; } #tabbrowser-tabbox { - border-radius: 6px !important; - overflow: hidden !important; - margin: 4px !important; + border-radius: 0 !important; + margin: 0 !important; } -/* URL bar progress indicator */ -/* #urlbar[data-page-progress="loaded"]::before { - display: none !important; -} */ - @keyframes ucLoadingSnimation { 0% { background-position: 130% 50%; } 100% { background-position: -70% 50%; } } -/* #urlbar[data-page-progress]::before { - display: block; - content: " "; - pointer-events: none; - width: 100%; - height: 100%; - position: absolute; - bottom: 0; - left: 0; - z-index: 1; - background: linear-gradient( - 140deg, - rgba(255, 255, 255, 0) 35%, - rgba(255, 255, 255, .25) 50%, - rgba(255, 255, 255, 0) 65% - ) rgba(255, 255, 255, 0.05); - background-size: 200% 200%; - animation: ucLoadingSnimation 2.2s ease-in-out infinite; - border-bottom: 2px solid var(--ff-accent-color); -} */ - /* Key mode indicator */ #navigator-toolbox[data-key-mode=""]::after { 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} 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); + } +} diff --git a/modules/firefox.home/default.nix b/modules/firefox.home/default.nix index 208d2e7..0d53fe9 100644 --- a/modules/firefox.home/default.nix +++ b/modules/firefox.home/default.nix @@ -1,10 +1,11 @@ { config, pkgs, lib, ... }: let - unwrappedFirefoxPackage = pkgs.firefox-devedition-unwrapped; - - firefoxBinName = "firefox-devedition"; - + unwrappedFirefoxPackage = pkgs.firefox-unwrapped; + firefoxBinName = "firefox"; configDir = ".mozilla/firefox"; + # unwrappedFirefoxPackage = pkgs.librewolf-unwrapped; + # firefoxBinName = "librewolf"; + # configDir = ".config/librewolf/librewolf"; extensions = [ "https://addons.mozilla.org/firefox/downloads/file/3792127/firefox_dracula-1.0.xpi" @@ -36,10 +37,8 @@ let profilePath = "default"; in { programs.firefox = { - enable = false; + enable = true; package = firefox; - - # nativeMessagingHosts = [ pkgs.tridactyl-native ]; policies = policies; }; @@ -48,18 +47,11 @@ in { ] else []; home.file = if config.programs.firefox.enable then { - # ".config/tridactyl".source = ./tridactyl; - "${configDir}/${profilePath}/chrome".source = ./chrome; "${configDir}/profiles.ini".text = lib.generators.toINI {} { - Profile1 = { - Name = "default"; - IsRelative = 1; - Path = profilePath; - }; Profile0 = { - Name = "dev-edition-default"; + Name = "default"; Default = 1; IsRelative = 1; Path = profilePath; diff --git a/modules/firefox.home/policies.nix b/modules/firefox.home/policies.nix index f18f3c8..cacc70d 100644 --- a/modules/firefox.home/policies.nix +++ b/modules/firefox.home/policies.nix @@ -4,8 +4,6 @@ ManagedBookmarks = [ { toplevel_name = "Managed bookmarks"; } { name = "Daily Dev"; url = "https://app.daily.dev"; } - { name = "Email: Microsoft Outlook"; url = "https://outlook.office365.com/mail/"; } - { name = "Email: GMail"; url = "https://mail.google.com/mail/u/0/"; } # { name = "Shtuff"; children = [] } ]; DisplayBookmarksToolbar = "never"; @@ -33,7 +31,6 @@ }; ShowHomeButton = false; - # PopupBlocking = []; SearchEngines = { Default = "DuckDuckGo lite"; SearchSuggestEnabled = false; diff --git a/modules/firefox.home/preferences.nix b/modules/firefox.home/preferences.nix index accaa05..3d3dc45 100644 --- a/modules/firefox.home/preferences.nix +++ b/modules/firefox.home/preferences.nix @@ -37,7 +37,7 @@ in { "browser.dataFeatureRecommendations.enabled" = false; "extensions.htmlaboutaddons.recommendations.enabled" = false; - "layout.css.has-selector.enabled" = true; + "layout.css.has-selector.enabled" = true; "browser.toolbars.bookmarks.visibility" = "never"; "browser.tabs.insertAfterCurrent" = true; "browser.urlbar.shortcuts.bookmarks" = false; diff --git a/modules/firefox.home/tridactyl/themes/phenax.css b/modules/firefox.home/tridactyl/themes/phenax.css deleted file mode 100644 index 43f2ad8..0000000 --- a/modules/firefox.home/tridactyl/themes/phenax.css +++ /dev/null @@ -1,173 +0,0 @@ -:root { - /* Dracula Colors */ - --bg: #0f0c19; - --currentline: #44475a; - --fg: #f8f8f2; - --comment: #6272a4; - --cyan: #8be9fd; - --green: #50fa7b; - --orange: #ffb86c; - --pink: #ff79c6; - --violet: #4e3aA3; - --red: #ff5555; - --yellow: #f1fa8c; - --font: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif; - - --tridactyl-fg: var(--fg); - --tridactyl-bg: var(--bg); - --tridactyl-url-fg: var(--violet); - --tridactyl-url-bg: var(--bg); - --tridactyl-highlight-box-bg: var(--currentline); - --tridactyl-highlight-box-fg: var(--fg); - --tridactyl-of-fg: var(--fg); - --tridactyl-of-bg: var(--currentline); - --tridactyl-cmdl-fg: var(--bg); - --tridactyl-cmdl-font-family: var(--font); - --tridactyl-cmplt-font-family: var(--font); - --tridactyl-hintspan-font-family: var(--font); - - /* Hint character tags */ - --tridactyl-hintspan-fg: var(--bg) !important; - --tridactyl-hintspan-bg: var(--green) !important; - - /* Element Highlights */ - --tridactyl-hint-active-fg: none; - --tridactyl-hint-active-bg: none; - --tridactyl-hint-active-outline: none; - --tridactyl-hint-bg: none; - --tridactyl-hint-outline: none; -} - -#command-line-holder { - order: 1; - border: 2px solid var(--violet); - background: var(--tridactyl-bg); -} - -#tridactyl-colon { - color: var(--violet) !important; - font-size: 1rem; -} - -#tridactyl-input { - color: var(--tridactyl-fg); - width: 90%; - font-size: 1.1rem !important; - line-height: 1em; - background: var(--tridactyl-bg); - padding: 0.5rem 0.2rem !important; -} - -#completions table { - font-size: 0.8rem; - font-weight: 200; - border-spacing: 0; - table-layout: fixed; - padding: 0.5rem 1rem !important; -} - -#completions > div { - max-height: calc(20 * var(--option-height)); - min-height: calc(10 * var(--option-height)); -} - -/* COMPLETIONS */ - -#completions { - --option-height: 1.4em; - color: var(--tridactyl-fg); - background: var(--tridactyl-bg); - display: inline-block; - font-size: unset; - font-weight: 200; - overflow: hidden; - width: 100%; - border-top: unset; - order: 2; -} - -/* Olie doesn't know how CSS inheritance works */ -#completions .HistoryCompletionSource { - max-height: unset; - min-height: unset; -} - -#completions .HistoryCompletionSource table { - width: 100%; - font-size: 9pt; - border-spacing: 0; - table-layout: fixed; -} - -/* redundancy 2: redundancy 2: more redundancy */ -#completions .BmarkCompletionSource { - max-height: unset; - min-height: unset; -} - -#completions table tr td.prefix, -#completions table tr td.privatewindow, -#completions table tr td.container, -#completions table tr td.icon { - display: none; -} - -#completions .BufferCompletionSource table { - width: unset; - font-size: unset; - border-spacing: unset; - table-layout: unset; -} - -#completions table tr .title { - width: 50%; -} - -#completions table tr { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -#completions .sectionHeader { - background: unset; - font-weight: bold; - border-bottom: unset; - font-size: 0.8rem !important; - color: var(--violet) !important; - text-transform: uppercase; - padding: 0.4rem 1rem !important; -} - -#cmdline_iframe:not(.hidden) { - position: fixed !important; - bottom: unset; - top: 25% !important; - left: 10% !important; - z-index: 2147483647 !important; - transition: none !important; - width: 80% !important; - box-shadow: rgba(0, 0, 0, 0.5) 0px 0px 20px !important; - border: 1px solid #1b1825 !important; -} - -.TridactylStatusIndicator { - position: fixed !important; - bottom: 0 !important; - background: var(--tridactyl-bg) !important; - border: unset !important; - border: 1px var(--violet) solid !important; - font-size: 10pt !important; - /*font-weight: 200 !important;*/ - padding: 0.3ex 0.8ex !important; -} - -#completions .focused { - background: var(--violet); - color: var(--fg); -} - -#completions .focused .url { - background: var(--violet); - color: var(--fg); -} diff --git a/modules/firefox.home/tridactyl/tridactylrc b/modules/firefox.home/tridactyl/tridactylrc deleted file mode 100644 index f3373a8..0000000 --- a/modules/firefox.home/tridactyl/tridactylrc +++ /dev/null @@ -1,36 +0,0 @@ -colorscheme phenax - -set hintnames numeric -set hintfiltermode vimperator-reflow -set editorcmd sensible-terminal -e nvim -set newtab about:blank -set viewsource default -set browsermaps { "": "escapehatch", "": "noh" } - -" Unbind default keys -unbind d -unbind D -unbind -unbind -unbind t -unbind tt -unbind T -unbind J -unbind K -unbind f -unbind n -unbind N -unbind a -unbind A -unbind - -bind o fillcmdline tabopen -bind O fillcmdline open -bind p clipboard tabopen -bind P clipboard open -bind n findnext -bind N findprev - -bind --mode=normal J scrollpx 0 200 -bind --mode=normal K scrollpx 0 -200 - -- cgit v1.3.1