What is Autoconfig Startup Scripting (AKA userChrome.js)?

Updated December 16, 2023; Originally posted by jscher2000 on November 25, 2017.

While custom style rules in a userChrome.css file can modify the appearance of Firefox's user interface, they cannot change the fundamental behavior of user interface features or restore ones that have been removed. In some cases, extensions can resolve the issue of missing functionality, but in other cases, you might need to turn to a higher level of hackery: Autoconfig startup scripts.

How it Works

At startup, Firefox checks for JavaScript files in the program folder under /defaults/pref. An Autoconfig file — which may be named config-prefs.js, local-settings.js, or whatever.js — directs Firefox to load a JavaScript file (placed at the top level of the program folder) that contains the actual script code. That second file can contain script code, and also can load and run other scripts, which load and run other scripts, and so on. This Autoconfig mechanism is most often used by companies to preconfigure or lock down Firefox, but it has wide ranging applications. Mozilla Support has some documentation here: Customizing Firefox Using AutoConfig.

Note: Like userChrome.css, Firefox only reads your Autoconfig files at startup. To make sure that edits to scripts are reflected after startup, it may be best to restart Firefox using the button on the right side of the Troubleshooting Information page under "Try clearing the startup cache."

userChrome.js

Autoconfig allows users to run powerful code that extensions are no longer allowed to run in Firefox 57+. The scripts the community is sharing have inherited code and terminology from the userChrome.js legacy extension. Just like userChrome.js would load and run individual scripts, the Autoconfig-based loaders can run a variety of individual scripts directed toward specific changes you would like to make to Firefox. After installing a main "loader" script, you can add a wide variety of task-specific script files (somefunction.uc.js) to your chrome folder where the loader will find and run them.

SAFETY FIRST! userChrome.js scripting requires disabling a safety mechanism that limits what startup scripts can do. Unlike style rules that mess up the interface a bit, unrestricted startup scripts have broad ranging powers. They can modify Firefox in almost any way, and beyond the browser itself, they can reach parts of the system and take actions that are off limits to extensions. Please exercise great caution in adding scripts to your Firefox as they may not have gone through any kind of review process to screen for malicious code. Also, consider routinely checking your chrome folder for any user scripts that might have been dropped in by someone else.

Getting Started

Installing a userChrome.js Loader — or Not

If you are looking for one simple, targeted, quick fix, you may not need to install a full userChrome.js loader. The advantage of a quick fix script is that it is self-contained and raises the fewest security concerns. (You can find an example here.) However, a quick fix script may be difficult to expand with additional functions. If you plan to experiment more broadly, you probably will need a userChrome.js loader eventually. That doesn't mean you need to start there, but like the killer app for iOS or Android, or the program that only runs on MacOS or Windows, the script you need will tend to guide you toward using the platform needed to run it. That means you might not have a completely free choice of loaders, but I think it's still helpful to have an overview.

First, set up your chrome folder in your profile, as described on the HOW page. I suggest testing that with a userChrome.css file to make sure Firefox is finding it. Then you will need to drop the loader files into three locations:

Note: When setting up Autoconfig files, you may be surprised to find startup script files you did not create yourself. It is normal to find Firefox's channel-prefs.js file in the defaults\pref folder, but any other file is worth investigating. In a business environment, files may have been placed by your IT department. Otherwise, the source may have been security software or malware modifying how Firefox runs.

Here are three options for loader scripts. Does it matter which one you use? There are important technical differences between these loaders, but I have not studied them closely myself. Compatbility with the script you need may be the dominant factor for most users.

Finding Scripts

There are numerous repositories for user scripts, but since it can be difficult to know what a file does from its name, you may want to seek guidance from the Reddit community on r/FirefoxCSS or r/Firefox. Here are some recently updated repositories you could check out:

As with custom style rules, an extra burden of modifying Firefox this way is that scripts are not updated automatically to keep up with changes in Firefox. It's a good practice to bookmark the specific page where you found a script in case it stops working correctly and you need to find the author's latest update.

Sample Script: Menu Access Key Edits

In recent versions of Firefox, menu wording has changed to be less technical and more consistent with other browsers. For example, rather than "Copy Link Location", the menu now refers to "Copy Link". One result of this was that the access key — the key you press to select the menu item (also known as an accelerator key, hotkey, or shortcut) — had to be changed. Users who relied on a right-click then tap key pattern, now had to learn a new key or adopt a right-click then click pattern. This can't be fixed with style rules, but can be fixed with a .uc.js file.

Here is an example of a user script that modifies the menu wording and accelerator keys. You can download this as CopyLinkLocation.uc.js. For the alice0775/userChrome.js and xiaoxiaoflood/firefox-scripts loaders, the script goes into the chrome folder in your profile. For the MrOtherGuy/fx-autoconfig loader, the script goes into the JS subfolder in the chrome folder in your profile.

  // ==UserScript==
  // @name           Copy Link Location
  // @version        1.0
  // @description    Restore old label and accelerator key
  // ==/UserScript==
  
  /* 
    Compatibility Testing Results May 22-23, 2021 
      alice0775/userChrome.js - working
      MrOtherGuy/fx-autoconfig - working
      xiaoxiaoflood/firefox-scripts - working
  */
  
  (function(){
    let myKeyChanges = [
      {
        id: 'context-copylink', 
        newkey: 'a',
        newlabel: 'Copy Link Location'
      },
      {
        id: 'context-copyemail', 
        newkey: 'A',
        newlabel: 'Copy Email Address'
      }
    ];
  
    for (var i=0; i<myKeyChanges.length; i++){
      var menuitem = window.document.getElementById(myKeyChanges[i].id);
      if (menuitem){
        if (myKeyChanges[i].newkey.length == 1){
          menuitem.setAttribute('accesskey', myKeyChanges[i].newkey);
        }
        if (myKeyChanges[i].newlabel.length > 0){
          menuitem.setAttribute('label', myKeyChanges[i].newlabel);
        }
      }
    }
  })();
  

Combining a Script with a Compact Loader

What if you wanted to apply the access key changes using a simpler two-file solution rather than a full userChrome.js loader? We can follow the lead of Reddit user AveYo in Restore Ctrl+Shift+B = Library by setting config.js and include all the necessary code in a config1.js file so that no files need to be placed in the chrome folder. Here is an example (the files are available for download in autoconfig-context-menu-items.zip):

  // config-prefs.js file for [Firefox program folder]\defaults\pref
  pref("general.config.obscure_value", 0);
  // the file named in the following line must be in [Firefox program folder]
  pref("general.config.filename", "config1.js");
  // disable the sandbox to run unsafe code in Autoconfig
  pref("general.config.sandbox_enabled", false);
  
  // config1.js file for [Firefox program folder]
  // file name must match the name in [Firefox program folder]\defaults\pref
  function applyCustomScriptToNewWindow(win){
    /*** Context menu changes ***/ 
    let myKeyChanges = [
      {
        id: 'context-copylink', 
        newkey: 'a',
        newlabel: 'Copy Link Location'
      },
      {
        id: 'context-copyemail', 
        newkey: 'A',
        newlabel: 'Copy Email Address'
      }
    ];
    for (var i=0; i<myKeyChanges.length; i++){
      var menuitem = win.document.getElementById(myKeyChanges[i].id);
      if (menuitem){
        if (myKeyChanges[i].newkey.length == 1){
          menuitem.setAttribute('accesskey', myKeyChanges[i].newkey);
        }
        if (myKeyChanges[i].newlabel.length > 0){
          menuitem.setAttribute('label', myKeyChanges[i].newlabel);
        }
      }
    }
    /*** Other changes ***/ 
  }
  /* Single function userChrome.js loader to run the above init function (no external scripts)
    derived from https://www.reddit.com/r/firefox/comments/kilmm2/ */
  try {
    let { classes: Cc, interfaces: Ci, manager: Cm  } = Components;
    // pre-Fx117: const {Services} = Components.utils.import('resource://gre/modules/Services.jsm');
    const Services = globalThis.Services;
    function ConfigJS() { Services.obs.addObserver(this, 'chrome-document-global-created', false); }
    ConfigJS.prototype = {
      observe: function (aSubject) { aSubject.addEventListener('DOMContentLoaded', this, {once: true}); },
      handleEvent: function (aEvent) {
        let document = aEvent.originalTarget; let window = document.defaultView; let location = window.location;
        if (/^(chrome:(?!\/\/(global\/content\/commonDialog|browser\/content\/webext-panels)\.x?html)|about:(?!blank))/i.test(location.href)) {
          if (window._gBrowser) applyCustomScriptToNewWindow(window);
        }
      }
    };
    if (!Services.appinfo.inSafeMode) { new ConfigJS(); }
  } catch(ex) {};
  

If you do adopt this approach and you want to add more tweaks, you may need to do some coding to adapt the additional script to this approach. If you find it simpler to install a full userChrome.js loader at that point, you can remove the config1.js file to avoid conflicts.

Share Your Experience!

If you try any of these solutions, please post comments using the FEEDBACK button in the bar at the top of the page (you can be anonymous if you like). Let me know how it went, and let others know if you recommend particular scripts.