fx-autoconfig

repository·master·Indexed 19 days ago

https://github.com/mrotherguy/fx-autoconfig

A toolkit for managing userChrome.js scripts and styles in Firefox. It leverages Firefox's autoconfig functionality to inject custom JavaScript and CSS into the browser context, supporting classic scripts (.uc.js), ES6 modules (.uc.mjs), and module scripts (.sys.mjs). The toolkit includes the UC_API for script development and provides mechanisms for controlling script execution order, injection scopes, and style modes.

Tokens
12.8K
Snippets
39
Records
44
Agent score
16%

What's inside fx-autoconfig

  1. Supported script file types and loading behavior

    master

    The loader (boot.sys.mjs) automatically detects and loads three types of scripts from the <profile>/chrome/JS/ directory (or the directory specified in chrome.manifest):

    • *.uc.js: Classic scripts. Synchronously injected into target documents.
    • *.uc.mjs: ES6 modules (introduced in v0.8). Loaded into target documents asynchronously.
    • *.sys.mjs: Module scripts. Loaded into the global context synchronously once on startup.

    Script Injection Scopes

    • Default Behavior: Scripts are executed at the DOMContentLoaded event of the top-level window. They do not automatically trigger for sub-documents (like iframes or sidebars).
    • Persistent DOM Injection: To inject scripts/styles into sub-documents, create a boolean preference userChromeJS.persistent_domcontent_callback and set it to true. This is a global toggle.
    • Process Limitations: Scripts are only injected into parent-process documents. They will not run in separate processes like about:newtab.
    /* Example file names */
    my_script.uc.js
    my_module.uc.mjs
    global_logic.sys.mjs
  2. Target specific windows with @include and @exclude

    master

    By default, scripts execute only in the main browser window. You can use @include and @exclude headers to control the execution scope.

    • Use @include <url> to target specific documents (e.g., chrome://browser/content/places/places.xhtml for the Library window).
    • Use main as an alias for the primary window (chrome://browser/content/browser.xhtml in Firefox or chrome://messenger/content/messenger.xhtml in Thunderbird).
    • Use a wildcard * to target all windows.
    • Use @exclude to prevent execution in specific windows.
    • Use @backgroundmodule to execute a script "outside" of any document when the loader reads the file.
    // Execute only in the Library window
    // ==UserScript==
    // @include           chrome://browser/content/places/places.xhtml
    // ==/UserScript==
    
    // Execute in all documents except the main window
    // ==UserScript==
    // @include           *
    // @exclude           main
    // ==/UserScript==
  3. Share data between scripts using SharedStorage

    master

    The UC_API.SharedStorage object provides a synchronous, in-memory, non-persistent storage area for sharing data between different scripts. It behaves similarly to the WebExtensions storage API but is synchronous.

    Usage

    Data is accessed via property assignment or the .get() method.

    Change Listeners

    You can listen for changes to the storage using UC_API.SharedStorage.onChanged.addListener(callback).

    Important Limitations:

    • Top-level only: Only assignments or removals of top-level keys trigger the onChanged event. Modifying properties of an existing object inside storage will not fire an event.
    • Synchronous: Both storage access and the onChanged event callback are currently synchronous.
    • Non-persistent: Data is lost when the browser is closed.
    // Setting data
    let myThing = { thing: 123 };
    UC_API.SharedStorage.MyStuff = myThing;
    
    // Retrieving data
    let retrieved = UC_API.SharedStorage.get("MyStuff");
    
    // Listening for changes
    UC_API.SharedStorage.onChanged.addListener((change) => {
      console.log("Storage changed:", change);
    });
    
    // Triggering a change
    UC_API.SharedStorage.MyStuff1 = "Test-1";
    
    // Clearing storage
    UC_API.SharedStorage.clear();
  4. Use WindowActors for cross-process communication

    master

    The WindowActors feature (Experimental) allows communication between the main window process and child processes (like about:newtab or about:home).

    Setup Requirements:

    1. Enable Experimental Features: Set the preference userChromeJS.experimental.enabled to true and restart Firefox.
    2. Declare the Actor: Use the @WindowActor <name> and @WindowActorMatches [<urls>] headers in your script.
    3. File Structure: You must create a folder named after your actor containing two files: <ActorName>Parent.sys.mjs and <ActorName>Child.sys.mjs.

    Implementation Pattern:

    • Parent Actor: Extends JSWindowActorParent. Use sendQuery to handle requests from the child and receiveMessage to handle asynchronous messages from the child.
    • Child Actor: Extends JSWindowActorChild. Use this.document for the content document and this.contentWindow for the window object. Implement handleEvent(event) to respond to browser events like DOMContentLoaded.
    • Communication: Use UC_API.Experimental.WindowActors.get("Name")?.sendQuery("method", {args}) from the main window to trigger logic in the child process.
    // Example: Declaring an actor in a .uc.js file
    // ==UserScript==
    // @name test_actors
    // @WindowActor TestActor
    // @WindowActorMatches ["about:newtab","about:home"]
    // ==/UserScript==
    
    window.getThing = (...args) => {
      UC_API.Experimental.WindowActors.get("TestActor")?.sendQuery("doThing", {args: args})
      .then(console.log)
    }
  5. Understand filesystem URI schemes

    master

    The project uses specific URI schemes to organize files. Scripts should generally use the resources folder for their assets.

    • chrome://userChrome/content/<filename>: Accesses files within the resources folder.
    • chrome://userScripts/content/: The registered path for the Scripts folder.
    • chrome://userchromejs/content/: The registered path for the loader module folder.
  6. Configure TypeScript paths for UC_API

    master

    To get type support for UC_API when using chrome:// imports in a TypeScript project, add the following to your tsconfig.json:

    {
      "compilerOptions": {
        "paths": {
          "chrome://userchromejs/content/uc_api.sys.mjs": [
            "./node_modules/@types/fx-autoconfig/index.d.ts"
          ]
        }
      }
    }
  7. Run project tests

    master

    Tests are located in the test_profile directory. To run them, launch Firefox using a command-line argument that points to the test_profile folder as a non-relative profile. Test results are output to the browser console.

    firefox -profile "C:/things/fx-autoconfig/test_profile"
  8. Set up the Firefox profile for script loading

    master

    To load custom scripts and styles, copy the contents of the profile folder into your Firefox profile directory.

    If a chrome folder already exists, the contents should be merged. After copying, your profile should have a chrome folder containing JS, resources, and utils subdirectories.

    Key files in <profile>/chrome/utils/:

    • chrome.manifest: Registers file paths to the chrome:// protocol.
    • boot.sys.mjs: Implements the user-script loading logic.
    • fs.jsm: Implements filesystem operations (used internally by boot.sys.mjs).
    • utils.sys.mjs: Collection of helper functions.
    • uc_api.sys.mjs (v0.10.0+): The recommended interface for scripts to import.

    Warning: Malicious programs can inject logic into Firefox by modifying boot.sys.mjs or adding script files. Use with caution.

  9. Use ES6 modules (.sys.mjs) as background modules

    master

    The @backgroundmodule header is deprecated (since 0.10.0). Instead, use the .sys.mjs file extension. The manager automatically treats .sys.mjs files as background modules and loads them as ES6 modules, allowing the use of import and export declarations.

    Important Notes:

    • Background modules run before any window exists, so they do not have access to window objects, _ucUtils, or UC_API automatically.
    • To access UC_API in a .sys.mjs or .uc.mjs script, use the following import:
    import * from "chrome://userchromejs/content/uc_api.sys.mjs";

    Warning for .uc.mjs scripts: Because .uc.mjs scripts run in their own module scope, importing UC_API via import creates a different instance than the one automatically attached to the window object. This instance may lack internal properties initialized by boot.sys.mjs (like .getScriptData()). To get the fully initialized object, use ChromeUtils.importESModule or access UC_API directly from the window object.

    // Standard ES6 module import for background modules
    import * from "chrome://userchromejs/content/uc_api.sys.mjs";
    
    // Alternative for .uc.mjs to get the initialized window-scoped object
    const UC_API = ChromeUtils.importESModule("chrome://userchromejs/content/uc_api.sys.mjs");
  10. Install fx-autoconfig in the Firefox program directory

    master

    To enable the autoconfig functionality, you must copy the contents of the program folder into your Firefox installation directory. This affects all profiles using that specific Firefox executable.

    Platform-specific locations:

    • Windows: Typically C:\Program Files\Mozilla Firefox\. Copy defaults/ and config.js so that config.js is in the same directory as firefox.exe.
    • Linux: Typically /usr/lib/firefox/ or /usr/lib64/firefox/. Copy defaults/ and config.js so that config.js is in the same directory as the firefox binary.
    • macOS: Typically /Applications/Firefox.app/Contents/Resources/. Copy defaults/ and config.js to this directory.

    NixOS / Home Manager

    For Nix users, use the following configurations:

    NixOS:

    programs.firefox = {
      enable = true;
      autoConfig = builtins.readFile(builtins.fetchurl {
        url = "https://raw.githubusercontent.com/MrOtherGuy/fx-autoconfig/master/program/config.js";
        sha256 = "1mx679fbc4d9x4bnqajqx5a95y1lfasvf90pbqkh9sm3ch945p40";
      });
    };

    Home Manager:

    home.packages = with pkgs; [
      (firefox.override {
        extraPrefsFiles = [(builtins.fetchurl {
          url = "https://raw.githubusercontent.com/MrOtherGuy/fx-autoconfig/master/program/config.js";
          sha256 = "1mx679fbc4d9x4bnqajqx5a95y1lfasvf90pbqkh9sm3ch945p40";
        })];
      })
    ];

    Note for non-regular installs: If your Firefox installation (like Librefox) already uses autoconfiguration, you may need to merge the contents of config.js with your existing autoconfiguration file and potentially set preferences from <program>/defaults/pref/config-prefs.js (except for general.config.filename).

    Copy `defaults/` and `config.js` from the `program` folder to the Firefox installation directory.
  11. Inject custom styles with .uc.css files

    master

    The loader supports injecting styles from the chrome/CSS/ directory (re-mappable via chrome.manifest).

    • File Naming: Files must end with .uc.css.
    • Injection Mode: By default, styles are injected in author mode and only into browser.xhtml.
    • Targeting: Use the @include directive in the header to target other documents.
    • Agent Styles: Use the @stylemode agent_sheet directive in the header to register the style as an agent style.

    Note: CSS does not support // line comments; use standard CSS comment syntax.

    /* Example .uc.css header */
    /* @include document-url */
    /* @stylemode agent_sheet */
    
    body {
      background-color: red !important;
    }
  12. Resolve 'fx-autoconfig: Startup is broken' errors

    master

    If you see the error fx-autoconfig: Startup is broken, it is likely because a custom script is attempting to access the gBrowser object before it is available. Accessing gURLBar is a common trigger for this.

    Solutions:

    1. Wait for Startup: Use UC_API.Runtime.startupFinished() to ensure windows are restored before accessing gBrowser.
    2. Use Alternatives: Instead of gURLBar, use document.getElementById("urlbar").
    3. Check Availability: Check if gBrowser exists; if not, try using _gBrowser.
    4. Apply the gBrowser Hack: You can enable a workaround by setting the preference userChromeJS.gBrowser_hack.enabled to true. This makes gBrowser available via a hack in boot.sys.mjs.

    To manually disable the hack, set both userChromeJS.gBrowser_hack.enabled and userChromeJS.gBrowser_hack.required to false.