diff options
| author | Akshay Nair <phenax5@gmail.com> | 2024-08-19 14:36:34 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2024-08-19 14:36:34 +0530 |
| commit | 6206aaf88532d153f0e01e22772b7f6e1c979afd (patch) | |
| tree | 15573ffa93088ba2c10e86af2675045086975555 /modules/firefox.home | |
| parent | 02612ccd49d561808ff931611b2ea865038b0a51 (diff) | |
| download | nixos-config-6206aaf88532d153f0e01e22772b7f6e1c979afd.tar.gz nixos-config-6206aaf88532d153f0e01e22772b7f6e1c979afd.zip | |
Enabled user js for firefox + global keybindings manager
Diffstat (limited to 'modules/firefox.home')
| -rw-r--r-- | modules/firefox.home/autoconfig.js | 24 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/globalKeybindings.uc.js | 102 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/setupNewTab.uc.js | 20 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/userChrome.css (renamed from modules/firefox.home/userChrome.css) | 0 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/utils/BootstrapLoader.js | 419 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/utils/RDFDataSource.sys.mjs | 434 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/utils/RDFManifestConverter.sys.mjs | 99 | ||||
| -rw-r--r-- | modules/firefox.home/chrome/utils/chrome.manifest | 2 | ||||
| -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/default.nix | 73 | ||||
| -rw-r--r-- | modules/firefox.home/tridactyl/tridactylrc | 56 |
12 files changed, 1489 insertions, 56 deletions
diff --git a/modules/firefox.home/autoconfig.js b/modules/firefox.home/autoconfig.js new file mode 100644 index 0000000..cc74675 --- /dev/null +++ b/modules/firefox.home/autoconfig.js @@ -0,0 +1,24 @@ +// Autoconfig + +pref('general.config.sandbox_enabled', false); +pref('general.config.obscure_value', 0); +lockPref('xpinstall.signatures.required', false); +lockPref('extensions.install_origins.enabled', false); +lockPref("extensions.experiments.enabled", true); + +// Setup user chrome support +try { + const cmanifest = Services.dirsvc.get('UChrm', Ci.nsIFile); + cmanifest.append('utils'); + cmanifest.append('chrome.manifest'); + Components.manager.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(cmanifest); +} catch (ex) { }; + +try { + Services.scriptloader.loadSubScript('chrome://userchromejs/content/BootstrapLoader.js'); +} catch (ex) { }; + +try { + ChromeUtils.import('chrome://userchromejs/content/userChrome.jsm'); +} catch (ex) { }; + diff --git a/modules/firefox.home/chrome/globalKeybindings.uc.js b/modules/firefox.home/chrome/globalKeybindings.uc.js new file mode 100644 index 0000000..95d8a10 --- /dev/null +++ b/modules/firefox.home/chrome/globalKeybindings.uc.js @@ -0,0 +1,102 @@ +// ==UserScript== +// @name Global Keybindings +// @version 1.0 +// @description Setup global keybindings on all windows +// @startup UC.globalKeybindings.init(win); +// @shutdown UC.globalKeybindings.destroy(); +// ==/UserScript== + + +(() => { + // Configure global keybindings + const KEYBINDINGS = () => ({ + RELEASE: [ + [ctrl(key('j')), nextTab()], + [ctrl(key('k')), prevTab()], + [alt(key('j')), moveSelectedTabBy(+1)], + [alt(key('k')), moveSelectedTabBy(-1)], + [ctrl(shift(key('J'))), moveSelectedTabBy(+1)], + [ctrl(shift(key('K'))), moveSelectedTabBy(-1)], + + ...(Array.from({ length: 10 }, (_, idx) => + [ctrl(key(idx === 9 ? 0 : idx + 1)), tabIndex(idx)], + )), + ], + PRESS: [ + // Prevent the default browser's action + [ctrl(shift(key('J'))), preventDefault()], + [ctrl(shift(key('K'))), preventDefault()], + ], + }); + + const nextTab = () => () => updateTabIndex((n, len) => (n + 1) % len); + const prevTab = () => () => updateTabIndex((n, len) => n === 0 ? len - 1 : n - 1); + const tabIndex = idx => () => updateTabIndex((_n, _len) => idx) + const preventDefault = f => e => { e.preventDefault(); f?.(e); }; + const moveSelectedTabBy = incr => () => updateSelectedTabIndex(incr); + + const ctrl = b => e => b(e) && e.ctrlKey; + const alt = b => e => b(e) && e.altKey; + const shift = b => e => b(e) && e.shiftKey; + const key = key => e => `${e.key}` === `${key}`; + + const updateTabIndex = (f) => { + const newIndex = f(gBrowser.tabContainer.selectedIndex, gBrowser.tabs.length) + if (typeof newIndex !== 'number') return; + if (newIndex >= gBrowser.tabContainer.allTabs.length) return; + if (newIndex < 0) return; + gBrowser.selectTabAtIndex(newIndex); + }; + + const updateSelectedTabIndex = (incr) => { + if (typeof incr !== 'number') return; + if (incr === +1) return gBrowser.moveTabForward(); + if (incr === -1) return gBrowser.moveTabBackward(); + const newIndex = gBrowser.tabContainer.selectedIndex + incr; + if (newIndex >= gBrowser.tabContainer.allTabs.length) return; + if (newIndex < 0) return; + gBrowser.moveTabTo(gBrowser.selectedTab, newIndex) + }; + + const module = { + enabled: true, + + evaluateKeybindings: (bindings, e) => { + if (!module.enabled) return; + + for (const [isKey, action] of bindings) { + if (isKey(e)) { + const shouldContinue = action(e); + if (!shouldContinue) break; + } + } + }, + + handleKeyUpEvent: e => module.evaluateKeybindings(KEYBINDINGS().RELEASE, e), + handleKeyDownEvent: e => module.evaluateKeybindings(KEYBINDINGS().PRESS, e), + + init(win) { + let observe = () => { + Services.obs.removeObserver(observe, 'browser-window-before-show'); + win.addEventListener('keyup', module.handleKeyUpEvent, true); + win.addEventListener('keydown', module.handleKeyDownEvent, true); + } + + if (win.__SSi) { + win.addEventListener('keyup', module.handleKeyUpEvent, true); + win.addEventListener('keydown', module.handleKeyDownEvent, true); + } else { + Services.obs.addObserver(observe, 'browser-window-before-show'); + } + }, + destroy() { + _uc.windows((_doc, win) => { + win.removeEventListener('keyup', module.handleKeyUpEvent, true); + win.removeEventListener('keydown', module.handleKeyDownEvent, true); + }); + delete UC.globalKeybindings; + }, + }; + + UC.globalKeybindings = module; +})(); diff --git a/modules/firefox.home/chrome/setupNewTab.uc.js b/modules/firefox.home/chrome/setupNewTab.uc.js new file mode 100644 index 0000000..1eb971c --- /dev/null +++ b/modules/firefox.home/chrome/setupNewTab.uc.js @@ -0,0 +1,20 @@ +// ==UserScript== +// @name Custom New Tab +// @version 1.0 +// @description Load a custom link or local file, instead of the default new tab page (about:newtab). +// ==/UserScript== + +// For Firefox 72 onward, see the autoconfig alternative to this: +// https://support.mozilla.org/questions/1251199#answer-1199709 + +globalThis.newtabLinkStarted = true; + +if (!AboutNewTab) { + globalThis.AboutNewTab = ChromeUtils.import('resource:///modules/AboutNewTab.jsm').AboutNewTab; +} + +const newTabUrl = xPref.get('browser.newtab.url') || 'about:blank'; + +AboutNewTab.newTabURL = newTabUrl; + +console.log('Updated new tab url to', newTabUrl); diff --git a/modules/firefox.home/userChrome.css b/modules/firefox.home/chrome/userChrome.css index 67809b0..67809b0 100644 --- a/modules/firefox.home/userChrome.css +++ b/modules/firefox.home/chrome/userChrome.css diff --git a/modules/firefox.home/chrome/utils/BootstrapLoader.js b/modules/firefox.home/chrome/utils/BootstrapLoader.js new file mode 100644 index 0000000..eddc1de --- /dev/null +++ b/modules/firefox.home/chrome/utils/BootstrapLoader.js @@ -0,0 +1,419 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +ChromeUtils.defineESModuleGetters(this, { + Blocklist: 'resource://gre/modules/Blocklist.sys.mjs', + ConsoleAPI: 'resource://gre/modules/Console.sys.mjs', + InstallRDF: 'chrome://userchromejs/content/RDFManifestConverter.sys.mjs', +}); + +Services.obs.addObserver(doc => { + if (doc.location.protocol + doc.location.pathname === 'about:addons' || + doc.location.protocol + doc.location.pathname === 'chrome:/content/extensions/aboutaddons.html') { + const win = doc.defaultView; + let handleEvent_orig = win.customElements.get('addon-card').prototype.handleEvent; + win.customElements.get('addon-card').prototype.handleEvent = function (e) { + if (e.type === 'click' && + e.target.getAttribute('action') === 'preferences' && + this.addon.__AddonInternal__.optionsType == 1/*AddonManager.OPTIONS_TYPE_DIALOG*/) { + var windows = Services.wm.getEnumerator(null); + while (windows.hasMoreElements()) { + var win2 = windows.getNext(); + if (win2.closed) { + continue; + } + if (win2.document.documentURI == this.addon.optionsURL) { + win2.focus(); + return; + } + } + var features = 'chrome,titlebar,toolbar,centerscreen'; + win.docShell.rootTreeItem.domWindow.openDialog(this.addon.optionsURL, this.addon.id, features); + } else { + handleEvent_orig.apply(this, arguments); + } + } + let update_orig = win.customElements.get('addon-options').prototype.update; + win.customElements.get('addon-options').prototype.update = function (card, addon) { + update_orig.apply(this, arguments); + if (addon.__AddonInternal__.optionsType == 1/*AddonManager.OPTIONS_TYPE_DIALOG*/) + this.querySelector('panel-item[data-l10n-id="preferences-addon-button"]').hidden = false; + } + } +}, 'chrome-document-loaded'); + +const { AddonManager } = ChromeUtils.importESModule('resource://gre/modules/AddonManager.sys.mjs'); +const { XPIDatabase, AddonInternal } = ChromeUtils.importESModule('resource://gre/modules/addons/XPIDatabase.sys.mjs'); +const { XPIExports } = ChromeUtils.importESModule('resource://gre/modules/addons/XPIExports.sys.mjs'); + +var orig_verifyBundleSignedState = XPIExports.verifyBundleSignedState; +XPIExports.verifyBundleSignedState = async (aBundle, aAddon) => { + if(!aAddon.isWebExtension && aAddon.type === "extension") return { signedState: undefined, signedTypes: [] }; + return orig_verifyBundleSignedState(aBundle, aAddon); +} + +XPIDatabase.isDisabledLegacy = () => false; + +ChromeUtils.defineLazyGetter(this, 'BOOTSTRAP_REASONS', () => { + const { XPIProvider } = ChromeUtils.importESModule('resource://gre/modules/addons/XPIProvider.sys.mjs'); + return XPIProvider.BOOTSTRAP_REASONS; +}); + +const { Log } = ChromeUtils.importESModule('resource://gre/modules/Log.sys.mjs'); +var logger = Log.repository.getLogger('addons.bootstrap'); + +/** + * Valid IDs fit this pattern. + */ +var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i; + +// Properties that exist in the install manifest +const PROP_METADATA = ['id', 'version', 'type', 'internalName', 'updateURL', + 'optionsURL', 'optionsType', 'aboutURL', 'iconURL']; +const PROP_LOCALE_SINGLE = ['name', 'description', 'creator', 'homepageURL']; +const PROP_LOCALE_MULTI = ['developers', 'translators', 'contributors']; + +// Map new string type identifiers to old style nsIUpdateItem types. +// Retired values: +// 32 = multipackage xpi file +// 8 = locale +// 256 = apiextension +// 128 = experiment +// theme = 4 +const TYPES = { + extension: 2, + dictionary: 64, +}; + +const COMPATIBLE_BY_DEFAULT_TYPES = { + extension: true, + dictionary: true, +}; + +const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty); + +function isXPI(filename) { + let ext = filename.slice(-4).toLowerCase(); + return ext === '.xpi' || ext === '.zip'; +} + +/** + * Gets an nsIURI for a file within another file, either a directory or an XPI + * file. If aFile is a directory then this will return a file: URI, if it is an + * XPI file then it will return a jar: URI. + * + * @param {nsIFile} aFile + * The file containing the resources, must be either a directory or an + * XPI file + * @param {string} aPath + * The path to find the resource at, '/' separated. If aPath is empty + * then the uri to the root of the contained files will be returned + * @returns {nsIURI} + * An nsIURI pointing at the resource + */ +function getURIForResourceInFile(aFile, aPath) { + if (!isXPI(aFile.leafName)) { + let resource = aFile.clone(); + if (aPath) + aPath.split('/').forEach(part => resource.append(part)); + + return Services.io.newFileURI(resource); + } + + return buildJarURI(aFile, aPath); +} + +/** + * Creates a jar: URI for a file inside a ZIP file. + * + * @param {nsIFile} aJarfile + * The ZIP file as an nsIFile + * @param {string} aPath + * The path inside the ZIP file + * @returns {nsIURI} + * An nsIURI for the file + */ +function buildJarURI(aJarfile, aPath) { + let uri = Services.io.newFileURI(aJarfile); + uri = 'jar:' + uri.spec + '!/' + aPath; + return Services.io.newURI(uri); +} + +var BootstrapLoader = { + name: 'bootstrap', + manifestFile: 'install.rdf', + async loadManifest(pkg) { + /** + * Reads locale properties from either the main install manifest root or + * an em:localized section in the install manifest. + * + * @param {Object} aSource + * The resource to read the properties from. + * @param {boolean} isDefault + * True if the locale is to be read from the main install manifest + * root + * @param {string[]} aSeenLocales + * An array of locale names already seen for this install manifest. + * Any locale names seen as a part of this function will be added to + * this array + * @returns {Object} + * an object containing the locale properties + */ + function readLocale(aSource, isDefault, aSeenLocales) { + let locale = {}; + if (!isDefault) { + locale.locales = []; + for (let localeName of aSource.locales || []) { + if (!localeName) { + logger.warn('Ignoring empty locale in localized properties'); + continue; + } + if (aSeenLocales.includes(localeName)) { + logger.warn('Ignoring duplicate locale in localized properties'); + continue; + } + aSeenLocales.push(localeName); + locale.locales.push(localeName); + } + + if (locale.locales.length == 0) { + logger.warn('Ignoring localized properties with no listed locales'); + return null; + } + } + + for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) { + if (hasOwnProperty(aSource, prop)) { + locale[prop] = aSource[prop]; + } + } + + return locale; + } + + let manifestData = await pkg.readString('install.rdf'); + let manifest = InstallRDF.loadFromString(manifestData).decode(); + + let addon = new AddonInternal(); + for (let prop of PROP_METADATA) { + if (hasOwnProperty(manifest, prop)) { + addon[prop] = manifest[prop]; + } + } + + if (!addon.type) { + addon.type = 'extension'; + } else { + let type = addon.type; + addon.type = null; + for (let name in TYPES) { + if (TYPES[name] == type) { + addon.type = name; + break; + } + } + } + + if (!(addon.type in TYPES)) + throw new Error('Install manifest specifies unknown type: ' + addon.type); + + if (!addon.id) + throw new Error('No ID in install manifest'); + if (!gIDTest.test(addon.id)) + throw new Error('Illegal add-on ID ' + addon.id); + if (!addon.version) + throw new Error('No version in install manifest'); + + addon.strictCompatibility = (!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) || + manifest.strictCompatibility == 'true'); + + // Only read these properties for extensions. + if (addon.type == 'extension') { + if (manifest.bootstrap != 'true') { + throw new Error('Non-restartless extensions no longer supported'); + } + + if (addon.optionsType && + addon.optionsType != 1/*AddonManager.OPTIONS_TYPE_DIALOG*/ && + addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_BROWSER && + addon.optionsType != AddonManager.OPTIONS_TYPE_TAB) { + throw new Error('Install manifest specifies unknown optionsType: ' + addon.optionsType); + } + + if (addon.optionsType) + addon.optionsType = parseInt(addon.optionsType); + } + + addon.defaultLocale = readLocale(manifest, true); + + let seenLocales = []; + addon.locales = []; + for (let localeData of manifest.localized || []) { + let locale = readLocale(localeData, false, seenLocales); + if (locale) + addon.locales.push(locale); + } + + let dependencies = new Set(manifest.dependencies); + addon.dependencies = Object.freeze(Array.from(dependencies)); + + let seenApplications = []; + addon.targetApplications = []; + for (let targetApp of manifest.targetApplications || []) { + if (!targetApp.id || !targetApp.minVersion || + !targetApp.maxVersion) { + logger.warn('Ignoring invalid targetApplication entry in install manifest'); + continue; + } + if (seenApplications.includes(targetApp.id)) { + logger.warn('Ignoring duplicate targetApplication entry for ' + targetApp.id + + ' in install manifest'); + continue; + } + seenApplications.push(targetApp.id); + addon.targetApplications.push(targetApp); + } + + // Note that we don't need to check for duplicate targetPlatform entries since + // the RDF service coalesces them for us. + addon.targetPlatforms = []; + for (let targetPlatform of manifest.targetPlatforms || []) { + let platform = { + os: null, + abi: null, + }; + + let pos = targetPlatform.indexOf('_'); + if (pos != -1) { + platform.os = targetPlatform.substring(0, pos); + platform.abi = targetPlatform.substring(pos + 1); + } else { + platform.os = targetPlatform; + } + + addon.targetPlatforms.push(platform); + } + + addon.userDisabled = false; + addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED; + addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + + addon.userPermissions = null; + + addon.icons = {}; + if (await pkg.hasResource('icon.png')) { + addon.icons[32] = 'icon.png'; + addon.icons[48] = 'icon.png'; + } + + if (await pkg.hasResource('icon64.png')) { + addon.icons[64] = 'icon64.png'; + } + + Object.defineProperty(addon, 'appDisabled', { + set: _ => {}, + get: _ => false + }); + + Object.defineProperty(addon, 'signedState', { + set: _ => {}, + get: _ => AddonManager.SIGNEDSTATE_NOT_REQUIRED + }); + + return addon; + }, + + loadScope(addon) { + let file = addon.file || addon._sourceBundle; + let uri = getURIForResourceInFile(file, 'bootstrap.js').spec; + let principal = Services.scriptSecurityManager.getSystemPrincipal(); + + let sandbox = new Cu.Sandbox(principal, { + sandboxName: uri, + addonId: addon.id, + wantGlobalProperties: ['ChromeUtils'], + metadata: { addonID: addon.id, URI: uri }, + }); + + try { + Object.assign(sandbox, BOOTSTRAP_REASONS); + + ChromeUtils.defineLazyGetter(sandbox, 'console', () => + new ConsoleAPI({ consoleID: `addon/${addon.id}` })); + + Services.scriptloader.loadSubScript(uri, sandbox); + } catch (e) { + logger.warn(`Error loading bootstrap.js for ${addon.id}`, e); + } + + function findMethod(name) { + if (sandbox[name]) { + return sandbox[name]; + } + + try { + let method = Cu.evalInSandbox(name, sandbox); + return method; + } catch (err) { } + + return () => { + logger.warn(`Add-on ${addon.id} is missing bootstrap method ${name}`); + }; + } + + let install = findMethod('install'); + let uninstall = findMethod('uninstall'); + let startup = findMethod('startup'); + let shutdown = findMethod('shutdown'); + + return { + install(...args) { + install(...args); + // Forget any cached files we might've had from this extension. + Services.obs.notifyObservers(null, 'startupcache-invalidate'); + }, + + uninstall(...args) { + uninstall(...args); + // Forget any cached files we might've had from this extension. + Services.obs.notifyObservers(null, 'startupcache-invalidate'); + }, + + startup(...args) { + if (addon.type == 'extension') { + logger.debug(`Registering manifest for ${file.path}\n`); + Components.manager.addBootstrappedManifestLocation(file); + } + return startup(...args); + }, + + shutdown(data, reason) { + try { + return shutdown(data, reason); + } catch (err) { + throw err; + } finally { + if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) { + logger.debug(`Removing manifest for ${file.path}\n`); + Components.manager.removeBootstrappedManifestLocation(file); + } + } + }, + }; + }, +}; + +AddonManager.addExternalExtensionLoader(BootstrapLoader); + +if (AddonManager.isReady) { + AddonManager.getAllAddons().then(addons => { + addons.forEach(addon => { + if (addon.type == 'extension' && !addon.isWebExtension && !addon.userDisabled) { + addon.reload(); + }; + }); + }); +}
\ No newline at end of file diff --git a/modules/firefox.home/chrome/utils/RDFDataSource.sys.mjs b/modules/firefox.home/chrome/utils/RDFDataSource.sys.mjs new file mode 100644 index 0000000..c6380c1 --- /dev/null +++ b/modules/firefox.home/chrome/utils/RDFDataSource.sys.mjs @@ -0,0 +1,434 @@ + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This module creates a new API for accessing and modifying RDF graphs. The + * goal is to be able to serialise the graph in a human readable form. Also + * if the graph was originally loaded from an RDF/XML the serialisation should + * closely match the original with any new data closely following the existing + * layout. The output should always be compatible with Mozilla's RDF parser. + * + * This is all achieved by using a DOM Document to hold the current state of the + * graph in XML form. This can be initially loaded and parsed from disk or + * a blank document used for an empty graph. As assertions are added to the + * graph, appropriate DOM nodes are added to the document to represent them + * along with any necessary whitespace to properly layout the XML. + * + * In general the order of adding assertions to the graph will impact the form + * the serialisation takes. If a resource is first added as the object of an + * assertion then it will eventually be serialised inside the assertion's + * property element. If a resource is first added as the subject of an assertion + * then it will be serialised at the top level of the XML. + */ + +const NS_XML = "http://www.w3.org/XML/1998/namespace"; +const NS_XMLNS = "http://www.w3.org/2000/xmlns/"; +const NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; +const NS_NC = "http://home.netscape.com/NC-rdf#"; + +/* eslint prefer-template: 1 */ + +function isElement(obj) { + return Element.isInstance(obj); +} +function isText(obj) { + return obj && typeof obj == "object" && ChromeUtils.getClassName(obj) == "Text"; +} + +/** + * Returns either an rdf namespaced attribute or an un-namespaced attribute + * value. Returns null if neither exists, + */ +function getRDFAttribute(element, name) { + if (element.hasAttributeNS(NS_RDF, name)) + return element.getAttributeNS(NS_RDF, name); + if (element.hasAttribute(name)) + return element.getAttribute(name); + return undefined; +} + +/** + * Represents an assertion in the datasource + */ +class RDFAssertion { + constructor(subject, predicate, object) { + // The subject on this assertion, an RDFSubject + this._subject = subject; + // The predicate, a string + this._predicate = predicate; + // The object, an RDFNode + this._object = object; + // The datasource this assertion exists in + this._ds = this._subject._ds; + // Marks that _DOMnode is the subject's element + this._isSubjectElement = false; + // The DOM node that represents this assertion. Could be a property element, + // a property attribute or the subject's element for rdf:type + this._DOMNode = null; + } + + getPredicate() { + return this._predicate; + } + + getObject() { + return this._object; + } +} + +class RDFNode { + equals(rdfnode) { + return (rdfnode.constructor === this.constructor && + rdfnode._value == this._value); + } +} + +/** + * A simple literal value + */ +export class RDFLiteral extends RDFNode { + constructor(value) { + super(); + this._value = value; + } + + getValue() { + return this._value; + } +} + +/** + * This is an RDF node that can be a subject so a resource or a blank node + */ +class RDFSubject extends RDFNode { + constructor(ds) { + super(); + // A lookup of the assertions with this as the subject. Keyed on predicate + this._assertions = {}; + // A lookup of the assertions with this as the object. Keyed on predicate + this._backwards = {}; + // The datasource this subject belongs to + this._ds = ds; + // The DOM elements in the document that represent this subject. Array of Element + this._elements = []; + } + + /** + * Parses the given Element from the DOM document + */ + /* eslint-disable complexity */ + _parseElement(element) { + this._elements.push(element); + + // There might be an inferred rdf:type assertion in the element name + if (element.namespaceURI != NS_RDF || + element.localName != "Description") { + var assertion = new RDFAssertion(this, RDF_R("type"), + this._ds.getResource(element.namespaceURI + element.localName)); + assertion._DOMnode = element; + assertion._isSubjectElement = true; + this._addAssertion(assertion); + } + + // Certain attributes can be literal properties + for (let attr of element.attributes) { + if (attr.namespaceURI == NS_XML || attr.namespaceURI == NS_XMLNS || + attr.nodeName == "xmlns") + continue; + if ((attr.namespaceURI == NS_RDF || !attr.namespaceURI) && + (["nodeID", "about", "resource", "ID", "parseType"].includes(attr.localName))) + continue; + var object = null; + if (attr.namespaceURI == NS_RDF) { + if (attr.localName == "type") + object = this._ds.getResource(attr.nodeValue); + } + if (!object) + object = new RDFLiteral(attr.nodeValue); + assertion = new RDFAssertion(this, attr.namespaceURI + attr.localName, object); + assertion._DOMnode = attr; + this._addAssertion(assertion); + } + + var child = element.firstChild; + element.listCounter = 1; + while (child) { + if (isElement(child)) { + object = null; + var predicate = child.namespaceURI + child.localName; + if (child.namespaceURI == NS_RDF) { + if (child.localName == "li") { + predicate = RDF_R(`_${element.listCounter}`); + element.listCounter++; + } + } + + // Check for and bail out on unknown attributes on the property element + for (let attr of child.attributes) { + // Ignore XML namespaced attributes + if (attr.namespaceURI == NS_XML) + continue; + // These are reserved by XML for future use + if (attr.localName.substring(0, 3).toLowerCase() == "xml") + continue; + // We can handle these RDF attributes + if ((!attr.namespaceURI || attr.namespaceURI == NS_RDF) && + ["resource", "nodeID"].includes(attr.localName)) + continue; + // This is a special attribute we handle for compatibility with Mozilla RDF + if (attr.namespaceURI == NS_NC && + attr.localName == "parseType") + continue; + } + + var parseType = child.getAttributeNS(NS_NC, "parseType"); + + var resource = getRDFAttribute(child, "resource"); + var nodeID = getRDFAttribute(child, "nodeID"); + + if (resource !== undefined) { + var base = Services.io.newURI(element.baseURI); + object = this._ds.getResource(base.resolve(resource)); + } else if (nodeID !== undefined) { + object = this._ds.getBlankNode(nodeID); + } else { + var hasText = false; + var childElement = null; + var subchild = child.firstChild; + while (subchild) { + if (isText(subchild) && /\S/.test(subchild.nodeValue)) { + hasText = true; + } else if (isElement(subchild)) { + childElement = subchild; + } + subchild = subchild.nextSibling; + } + + if (childElement) { + object = this._ds._getSubjectForElement(childElement); + object._parseElement(childElement); + } else + object = new RDFLiteral(child.textContent); + } + + assertion = new RDFAssertion(this, predicate, object); + this._addAssertion(assertion); + assertion._DOMnode = child; + } + child = child.nextSibling; + } + } + /* eslint-enable complexity */ + + /** + * Adds a new assertion to the internal hashes. Should be called for every + * new assertion parsed or created programmatically. + */ + _addAssertion(assertion) { + var predicate = assertion.getPredicate(); + if (predicate in this._assertions) + this._assertions[predicate].push(assertion); + else + this._assertions[predicate] = [ assertion ]; + + var object = assertion.getObject(); + if (object instanceof RDFSubject) { + // Create reverse assertion + if (predicate in object._backwards) + object._backwards[predicate].push(assertion); + else + object._backwards[predicate] = [ assertion ]; + } + } + + /** + * Returns all objects in assertions with this subject and the given predicate. + */ + getObjects(predicate) { + if (predicate in this._assertions) + return Array.from(this._assertions[predicate], + i => i.getObject()); + + return []; + } + + /** + * Retrieves the first property value for the given predicate. + */ + getProperty(predicate) { + if (predicate in this._assertions) + return this._assertions[predicate][0].getObject(); + return null; + } +} + +/** + * Creates a new RDFResource for the datasource. Private. + */ +export class RDFResource extends RDFSubject { + constructor(ds, uri) { + super(ds); + // This is the uri that the resource represents. + this._uri = uri; + } +} + +/** + * Creates a new blank node. Private. + */ +export class RDFBlankNode extends RDFSubject { + constructor(ds, nodeID) { + super(ds); + // The nodeID of this node. May be null if there is no ID. + this._nodeID = nodeID; + } + + /** + * Sets attributes on the DOM element to mark it as representing this node + */ + _applyToElement(element) { + if (!this._nodeID) + return; + if (USE_RDFNS_ATTR) { + var prefix = this._ds._resolvePrefix(element, RDF_R("nodeID")); + element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID); + } else { + element.setAttribute("nodeID", this._nodeID); + } + } + + /** + * Creates a new Element in the document for holding assertions about this + * subject. The URI controls what tagname to use. + */ + _createNewElement(uri) { + // If there are already nodes representing this in the document then we need + // a nodeID to match them + if (!this._nodeID && this._elements.length > 0) { + this._ds._createNodeID(this); + for (let element of this._elements) + this._applyToElement(element); + } + + return super._createNewElement.call(uri); + } + + /** + * Adds a reference to this node to the given property Element. + */ + _addReferenceToElement(element) { + if (this._elements.length > 0 && !this._nodeID) { + // In document elsewhere already + // Create a node ID and update the other nodes referencing + this._ds._createNodeID(this); + for (let element of this._elements) + this._applyToElement(element); + } + + if (this._nodeID) { + if (USE_RDFNS_ATTR) { + let prefix = this._ds._resolvePrefix(element, RDF_R("nodeID")); + element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID); + } else { + element.setAttribute("nodeID", this._nodeID); + } + } else { + // Add the empty blank node, this is generally right since further + // assertions will be added to fill this out + var newelement = this._ds._addElement(element, RDF_R("Description")); + newelement.listCounter = 1; + this._elements.push(newelement); + } + } + + /** + * Removes any reference to this node from the given property Element. + */ + _removeReferenceFromElement(element) { + if (element.hasAttributeNS(NS_RDF, "nodeID")) + element.removeAttributeNS(NS_RDF, "nodeID"); + if (element.hasAttribute("nodeID")) + element.removeAttribute("nodeID"); + } + + getNodeID() { + return this._nodeID; + } +} + +/** + * Creates a new RDFDataSource from the given document. The document will be + * changed as assertions are added and removed to the RDF. Pass a null document + * to start with an empty graph. + */ +export class RDFDataSource { + constructor(document) { + // All known resources, indexed on URI + this._resources = {}; + // All blank nodes + this._allBlankNodes = []; + + // The underlying DOM document for this datasource + this._document = document; + this._parseDocument(); + } + + static loadFromString(text) { + let parser = new DOMParser(); + let document = parser.parseFromString(text, "application/xml"); + + return new this(document); + } + + /** + * Returns an rdf subject for the given DOM Element. If the subject has not + * been seen before a new one is created. + */ + _getSubjectForElement(element) { + var about = getRDFAttribute(element, "about"); + + if (about !== undefined) { + let base = Services.io.newURI(element.baseURI); + return this.getResource(base.resolve(about)); + } + return this.getBlankNode(null); + } + + /** + * Parses the document for subjects at the top level. + */ + _parseDocument() { + var domnode = this._document.documentElement.firstChild; + while (domnode) { + if (isElement(domnode)) { + var subject = this._getSubjectForElement(domnode); + subject._parseElement(domnode); + } + domnode = domnode.nextSibling; + } + } + + /** + * Gets a blank node. nodeID may be null and if so a new blank node is created. + * If a nodeID is given then the blank node with that ID is returned or created. + */ + getBlankNode(nodeID) { + var rdfnode = new RDFBlankNode(this, nodeID); + this._allBlankNodes.push(rdfnode); + return rdfnode; + } + + /** + * Gets the resource for the URI. The resource is created if it has not been + * used already. + */ + getResource(uri) { + if (uri in this._resources) + return this._resources[uri]; + + var resource = new RDFResource(this, uri); + this._resources[uri] = resource; + return resource; + } +}
\ No newline at end of file diff --git a/modules/firefox.home/chrome/utils/RDFManifestConverter.sys.mjs b/modules/firefox.home/chrome/utils/RDFManifestConverter.sys.mjs new file mode 100644 index 0000000..25cae7e --- /dev/null +++ b/modules/firefox.home/chrome/utils/RDFManifestConverter.sys.mjs @@ -0,0 +1,99 @@ + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +import { RDFDataSource } from "chrome://userchromejs/content/RDFDataSource.sys.mjs"; + +const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest"; + +function EM_R(aProperty) { + return `http://www.mozilla.org/2004/em-rdf#${aProperty}`; +} + +function getValue(literal) { + return literal && literal.getValue(); +} + +function getProperty(resource, property) { + return getValue(resource.getProperty(EM_R(property))); +} + +class Manifest { + constructor(ds) { + this.ds = ds; + } + + static loadFromString(text) { + return new this(RDFDataSource.loadFromString(text)); + } +} + +export class InstallRDF extends Manifest { + _readProps(source, obj, props) { + for (let prop of props) { + let val = getProperty(source, prop); + if (val != null) { + obj[prop] = val; + } + } + } + + _readArrayProp(source, obj, prop, target, decode = getValue) { + let result = Array.from(source.getObjects(EM_R(prop)), + target => decode(target)); + if (result.length) { + obj[target] = result; + } + } + + _readArrayProps(source, obj, props, decode = getValue) { + for (let [prop, target] of Object.entries(props)) { + this._readArrayProp(source, obj, prop, target, decode); + } + } + + _readLocaleStrings(source, obj) { + this._readProps(source, obj, ["name", "description", "creator", "homepageURL"]); + this._readArrayProps(source, obj, { + locale: "locales", + developer: "developers", + translator: "translators", + contributor: "contributors", + }); + } + + decode() { + let root = this.ds.getResource(RDFURI_INSTALL_MANIFEST_ROOT); + let result = {}; + + let props = ["id", "version", "type", "updateURL", "optionsURL", + "optionsType", "aboutURL", "iconURL", + "bootstrap", "unpack", "strictCompatibility"]; + this._readProps(root, result, props); + + let decodeTargetApplication = source => { + let app = {}; + this._readProps(source, app, ["id", "minVersion", "maxVersion"]); + return app; + }; + + let decodeLocale = source => { + let localized = {}; + this._readLocaleStrings(source, localized); + return localized; + }; + + this._readLocaleStrings(root, result); + + this._readArrayProps(root, result, {"targetPlatform": "targetPlatforms"}); + this._readArrayProps(root, result, {"targetApplication": "targetApplications"}, + decodeTargetApplication); + this._readArrayProps(root, result, {"localized": "localized"}, + decodeLocale); + this._readArrayProps(root, result, {"dependency": "dependencies"}, + source => getProperty(source, "id")); + + return result; + } +}
\ No newline at end of file diff --git a/modules/firefox.home/chrome/utils/chrome.manifest b/modules/firefox.home/chrome/utils/chrome.manifest new file mode 100644 index 0000000..d9076b2 --- /dev/null +++ b/modules/firefox.home/chrome/utils/chrome.manifest @@ -0,0 +1,2 @@ +content userchromejs ./ +resource userchromejs ../ diff --git a/modules/firefox.home/chrome/utils/userChrome.jsm b/modules/firefox.home/chrome/utils/userChrome.jsm new file mode 100644 index 0000000..17a9224 --- /dev/null +++ b/modules/firefox.home/chrome/utils/userChrome.jsm @@ -0,0 +1,231 @@ +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 new file mode 100644 index 0000000..dcb44b3 --- /dev/null +++ b/modules/firefox.home/chrome/utils/xPref.jsm @@ -0,0 +1,85 @@ +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/default.nix b/modules/firefox.home/default.nix index b315a36..e6f5aad 100644 --- a/modules/firefox.home/default.nix +++ b/modules/firefox.home/default.nix @@ -12,15 +12,17 @@ let "https://addons.mozilla.org/firefox/downloads/file/4218479/font_inspect-0.5.8.xpi" "https://addons.mozilla.org/firefox/downloads/file/4314064/react_devtools-5.3.1.xpi" - # content + # others "https://addons.mozilla.org/firefox/downloads/file/4332776/languagetool-8.11.6.xpi" "https://addons.mozilla.org/firefox/downloads/file/4317971/darkreader-4.9.88.xpi" + "https://addons.mozilla.org/firefox/downloads/file/3970625/screen_recorder-0.1.8.xpi" ]; # about:config preferences = { "browser.newtabpage.enabled" = false; "browser.startup.homepage" = homepage-url; + "browser.newtab.url" = homepage-url; "browser.startup.blankWindow" = false; "browser.tabs.drawInTitlebar" = true; @@ -29,17 +31,21 @@ let "browser.theme.toolbar-theme" = 0; "browser.aboutConfig.showWarning" = false; "toolkit.legacyUserProfileCustomizations.stylesheets" = true; - "devtools.theme" = "dark"; - "devtools.toolbox.alwaysOnTop" = true; - "devtools.toolbox.host" = "window"; + # "devtools.theme" = "dark"; + "ui.systemUsesDarkTheme" = 2; + # "devtools.toolbox.alwaysOnTop" = true; + # "devtools.toolbox.host" = "window"; "layers.acceleration.force-enabled" = true; "gfx.webrender.all" = true; - "svg.context-properties.content.enabled" = true; + # "svg.context-properties.content.enabled" = true; + "widget.gtk.rounded-bottom-corners.enabled" = true; + "browser.urlbar.suggest.calculator" = true; + "browser.urlbar.unitConversion.enabled" = true; "extensions.pocket.enabled" = false; "extensions.pocket.showHome" = false; "browser.urlbar.suggest.pocket" = false; - "privacy.donottrackheader.enabled" = true; + # "privacy.donottrackheader.enabled" = true; "privacy.globalprivacycontrol.enabled" = true; "browser.search.suggest.enabled" = false; "browser.urlbar.suggest.searches" = false; @@ -48,11 +54,23 @@ let "browser.dataFeatureRecommendations.enabled" = false; "extensions.htmlaboutaddons.recommendations.enabled" = false; + "layout.css.has-selector.enabled" = true; "browser.toolbars.bookmarks.visibility" = "never"; - "identity.fxaccounts.toolbar.enabled" = false; + # "identity.fxaccounts.toolbar.enabled" = false; "browser.tabs.insertAfterCurrent" = true; "browser.urlbar.shortcuts.bookmarks" = false; "browser.urlbar.suggest.bookmark" = false; + "browser.newtab.privateAllowed" = true; + + "browser.download.forbid_open_with" = true; + "browser.download.autohideButton" = true; + "browser.download.useDownloadDir" = false; + + # HAcky stuff + # "general.config.sandbox_enabled" = false; + # "general.config.obscure_value" = false; + "xpinstall.signatures.required" = false; + "extensions.install_origins.enabled" = false; }; # https://mozilla.github.io/policy-templates @@ -60,30 +78,40 @@ let 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"; NoDefaultBookmarks = true; - DisableTelemetry = true; + DisableAppUpdate = true; + DisableSystemAddonUpdate = true; + DisableProfileImport = true; DisableFirefoxStudies = true; + DisableTelemetry = true; + DisableFeedbackCommands = true; + DisablePocket = true; DisableAccounts = true; DontCheckDefaultBrowser = true; OfferToSaveLogins = false; PasswordManagerEnabled = false; - DefaultDownloadDirectory = "\${home}/Downloads/firefox"; + DefaultDownloadDirectory = "${config.home.homeDirectory}/Downloads/firefox"; NewTabPage = false; Homepage = { URL = homepage-url; StartPage = "previous-session"; }; + OverrideFirstRunPage = ""; # PopupBlocking = []; SearchEngines = { Default = "DuckDuckGo"; SearchSuggestEnabled = false; + Remove = [ "Google" "Bing" "Amazon.com" "eBay" "Wikipedia" ]; + PreventInstalls = false; Add = [ { Name = "DuckDuckGo"; URLTemplate = "https://duckduckgo.com/?q={searchTerms}"; - Alias = "google"; + Alias = "ddg"; } { Name = "Google"; @@ -107,12 +135,30 @@ let # }; # }; - Extensions = { Install = extensions; }; + Extensions = { + Install = extensions; + Uninstall = [ + "google@search.mozilla.org" + "bing@search.mozilla.org" + "amazondotcom@search.mozilla.org" + "ebay@search.mozilla.org" + "wikipedia@search.mozilla.org" + ]; + }; + ExtensionSettings = { + "google@search.mozilla.org".installation_mode = "blocked"; + "bing@search.mozilla.org".installation_mode = "blocked"; + "amazondotcom@search.mozilla.org".installation_mode = "blocked"; + "ebay@search.mozilla.org".installation_mode = "blocked"; + "wikipedia@search.mozilla.org".installation_mode = "blocked"; + }; Preferences = lib.mapAttrs (_key: val: { Status = "locked"; Value = val; }) preferences; }; - firefox = pkgs.wrapFirefox unwrapped-firefox-package {}; + firefox = pkgs.wrapFirefox unwrapped-firefox-package { + extraPrefs = builtins.readFile ./autoconfig.js; + }; profilePath = "default"; in { @@ -121,14 +167,13 @@ in { package = firefox; nativeMessagingHosts = [ pkgs.tridactyl-native ]; - policies = policies; }; home.file = { ".config/tridactyl".source = ./tridactyl; - ".mozilla/firefox/${profilePath}/chrome/userChrome.css".text = builtins.readFile ./userChrome.css; + ".mozilla/firefox/${profilePath}/chrome".source = ./chrome; ".mozilla/firefox/profiles.ini".text = lib.generators.toINI {} { Profile1 = { diff --git a/modules/firefox.home/tridactyl/tridactylrc b/modules/firefox.home/tridactyl/tridactylrc index cb1a043..2fe7075 100644 --- a/modules/firefox.home/tridactyl/tridactylrc +++ b/modules/firefox.home/tridactyl/tridactylrc @@ -1,56 +1,28 @@ colorscheme phenax -bind o fillcmdline tabopen -bind O fillcmdline open -bind p clipboard tabopen -bind P clipboard open - set hintnames numeric +set hintfiltermode vimperator-reflow set editorcmd sensible-terminal -e nvim set newtab about:blank set viewsource default -set browsermaps { "<c-//>": "escapehatch", "<c-,>": "escapehatch" } +set browsermaps { "<C-,>": "escapehatch", "<C-6>": "noh" } -" Tab control +" Unbind default keys unbind d unbind D -unbind <c-s-j> -unbind <c-s-k> -bind <c-d> tabclose -bind <c-j> tabnext -bind <c-k> tabprev -bind <cs-j> tabmove +1 -bind <cs-k> tabmove -1 - +unbind <C-j> +unbind <C-k> unbind t +unbind tt unbind T -" unbind tt -" bind tt tabopen file:///home/imsohexy/.config/qutebrowser/homepage/index.html +unbind J +unbind K -unbind <s-j> -unbind <s-k> -bind <s-j> scrollpx 0 200 -bind <s-k> scrollpx 0 -200 +bind o fillcmdline tabopen +bind O fillcmdline open +bind p clipboard tabopen +bind P clipboard open -" Open tab by index -bind <space>1 tab 1 -bind <c-1> tab 1 -bind <space>2 tab 2 -bind <c-2> tab 2 -bind <space>3 tab 3 -bind <c-3> tab 3 -bind <space>4 tab 4 -bind <c-4> tab 4 -bind <space>5 tab 5 -bind <c-5> tab 5 -bind <space>6 tab 6 -bind <c-6> tab 6 -bind <space>7 tab 7 -bind <c-7> tab 7 -bind <space>8 tab 8 -bind <c-8> tab 8 -bind <space>9 tab 9 -bind <c-9> tab 9 -bind <space>0 tab 10 -bind <c-0> tab 10 +bind --mode=normal J scrollpx 0 200 +bind --mode=normal K scrollpx 0 -200 |
