firefox-scripts icon indicating copy to clipboard operation
firefox-scripts copied to clipboard

Exception in BootstrapLoader.js with Nightly 142.0a1 from 2025-06-25

Open onemen opened this issue 6 months ago • 80 comments

Starting with Firefox Nightly 142.0a1 from 2025-06-25 there is new error BootstrapLoader.js

 1751116296574	addons.xpi	WARN	Exception running bootstrap method startup on {dc572301-7619-498c-a57d-39143191b318}: TypeError: Components.manager.addBootstrappedManifestLocation is not a function(chrome://userchromejs/content/BootstrapLoader.js:389:30) JS Stack trace: [email protected]:389:30
[email protected]:2067:33
[email protected]:2183:34
[email protected]:2813:41
[email protected]:228:31
[email protected]:537:17
[email protected]:750:14
[email protected]:3702:26
[email protected]:73:29

Components.manager.addBootstrappedManifestLocation is not a function Components.manager.removeBootstrappedManifestLocation is not a function

Bug 1953136 Get rid of nsIComponentManager.addBootstrappedManifestLocation

@117649 can you look for replacement?

onemen avatar Jun 28 '25 13:06 onemen

Starting with Firefox Nightly 142.0a1 from 2025-06-25 there is new error BootstrapLoader.js

 1751116296574	addons.xpi	WARN	Exception running bootstrap method startup on {dc572301-7619-498c-a57d-39143191b318}: TypeError: Components.manager.addBootstrappedManifestLocation is not a function(chrome://userchromejs/content/BootstrapLoader.js:389:30) JS Stack trace: [email protected]:389:30
[email protected]:2067:33
[email protected]:2183:34
[email protected]:2813:41
[email protected]:228:31
[email protected]:537:17
[email protected]:750:14
[email protected]:3702:26
[email protected]:73:29

Components.manager.addBootstrappedManifestLocation is not a function Components.manager.removeBootstrappedManifestLocation is not a function

Bug 1953136 Get rid of nsIComponentManager.addBootstrappedManifestLocation

@117649 can you look for replacement?

Just saw this. And I feel...... nothing. Guess is because Mozilla is never disappointing on disappoint.

Anyway I was thought about such contingency but way pale in comparison to this. Looks like Mozilla is not getting rid of all built-in addons so there is still a way to register a path in jar://. We could check XPIDatabase.sys.mjs XPIInstall.sys.mjs XPIProvider.sys.mjs to see if its possible to be used. If its required we may be able to replace these modules in XPIExports.sys.mjs with our own to load our addon using remaining ways.

117649 avatar Jun 28 '25 16:06 117649

@117649, look at Extension.sys.mjs I think we can use aomStartup.registerChrome but we will need to convert addon/chrome.manifest to chromeEntries array

const chromeEntries = [
            ['content', 'tabmixplus', 'chrome/content/'],
            ['content', 'tabmix-prefs', 'defaults/preferences/'],
            ['content', 'tabmix-resource', 'modules/'],
            ['content', 'tabmix-skin', 'chrome/skin/'],
            ['locale', 'tabmixplus', 'en-US', 'chrome/locale/en-US/'],
          ]

onemen avatar Jun 28 '25 17:06 onemen

@evilpie, any advice? without nsIComponentManager.addBootstrappedManifestLocation all legacy extension will stop working on Firefox 142

onemen avatar Jun 28 '25 17:06 onemen

@117649, look at Extension.sys.mjs I think we can use aomStartup.registerChrome but we will need to convert addon/chrome.manifest to chromeEntries array

const chromeEntries = [ ['content', 'tabmixplus', 'chrome/content/'], ['content', 'tabmix-prefs', 'defaults/preferences/'], ['content', 'tabmix-resource', 'modules/'], ['content', 'tabmix-skin', 'chrome/skin/'], ['locale', 'tabmixplus', 'en-US', 'chrome/locale/en-US/'], ]

https://searchfox.org/mozilla-central/source/toolkit/mozapps/extensions/test/xpcshell/test_registerchrome.js This show it take a "chrome.manifest" but I don't sure what contents is in it.

117649 avatar Jun 28 '25 18:06 117649

@onemen https://searchfox.org/mozilla-central/source/toolkit/mozapps/extensions/amIAddonManagerStartup.idl#39

https://searchfox.org/mozilla-central/source/build/docs/chrome-registration.rst

If I'm not misunderstand we may use override to restore some path structure but that override is per file level meaning if override from a folder to another folder you'll be take to that folder but access file in it with that override uri will fail.

117649 avatar Jun 28 '25 18:06 117649

@onemen I think I may see 2 relatively simple ways so far. 1st: Use renumerateJARSubtree registerChrome to restore our old chrome path structure at best. No idea if that is fully possible. 2nd: Use autoRegister for load and temp file for unload. https://searchfox.org/mozilla-central/source/toolkit/content/tests/chrome/RegisterUnregisterChrome.js#106

117649 avatar Jun 28 '25 19:06 117649

@117649

test this utils.zip

I replaced Components.manager.addBootstrappedManifestLocation(file) with:

          const addonManagerStartup = Cc["@mozilla.org/addons/addon-manager-startup;1"]
            .getService(Ci.amIAddonManagerStartup);
          const manifestURI = getURIForResourceInFile(file, 'chrome.manifest');
          const chromeEntries = await parseManifestEntries(manifestURI)
          this.chromeRegistryHandle = addonManagerStartup.registerChrome(manifestURI, chromeEntries);

parseManifestEntries is in new file ParseManifestEntries.sys.mjs

notice that amIAddonManagerStartup.registerChrome does not support skin entry from chrome.manifest

/**
   * Registers a set of dynamic chrome registry entries, and returns an object
   * with a `destruct()` method which must be called in order to unregister
   * the entries.
   *
   * @param manifestURI The base manifest URI for the entries. URL values are
   *        resolved relative to this URI.
   * @param entries An array of arrays, each containing a registry entry as it
   *        would appar in a chrome.manifest file. Only the following entry
   *        types are currently accepted:
   *
   *         - "locale" A locale package entry. Must be a 4-element array.
   *         - "override" A URL override entry. Must be a 3-element array.
   */

In Tab Mix Plus I had to replace all skin entries with one content entries

+ content   tabmix-skin                chrome/skin/
- skin      tabmixplus   classic/1.0   chrome/skin/

- skin      tabmix-version tabmixplus  chrome/skin/app_version/91/         platformversion>=91
- skin      tabmix-version tabmixplus  chrome/skin/app_version/119/        platformversion>=119
- skin      tabmix-os    classic/1.0   chrome://tabmix-version/skin/win/   os=WINNT
- skin      tabmix-os    classic/1.0   chrome://tabmix-version/skin/mac/   os=Darwin
- skin      tabmix-os    classic/1.0   chrome://tabmix-version/skin/linux/ os=Linux

onemen avatar Jun 29 '25 10:06 onemen

@117649

In Tab Mix Plus I had to replace all skin entries with one content entries

  • content tabmix-skin chrome/skin/
  • skin tabmixplus classic/1.0 chrome/skin/

  • skin tabmix-version tabmixplus chrome/skin/app_version/91/ platformversion>=91

  • skin tabmix-version tabmixplus chrome/skin/app_version/119/ platformversion>=119

  • skin tabmix-os classic/1.0 chrome://tabmix-version/skin/win/ os=WINNT

  • skin tabmix-os classic/1.0 chrome://tabmix-version/skin/mac/ os=Darwin

  • skin tabmix-os classic/1.0 chrome://tabmix-version/skin/linux/ os=Linux

@onemen I'll try autoRegister first when I'm free, TMP aside it's better not to require any change on all of the legacy addons.

And you should also try override:

override
~~~~~~~~

In some cases an extension or embedder may wish to override a chrome
file provided by the application or XULRunner. In order to allow for
this, the chrome registration manifest allows for "override"
instructions:

.. code:: text

   override chrome://package/type/original-uri.whatever new-resolved-URI [flags]

Note: overrides are not recursive (so overriding
chrome://foo/content/bar/ with file:///home/john/blah/ will not usually
do what you want or expect it to do). Also, the path inside overridden
files is relative to the overridden path, not the original one (this can
be annoying and/or useful in CSS files, for example).

.. _chrome_manifest_resource:

Last time I tried I believe you can do something like:

content    addonid                                                 /
content    addonscope                                          chrome/content
override    chrome://addonscope/skin/boo.css    chrome://addonid/content/chrome/skin/boo.css

Then went on map every file in chrome/skin .

117649 avatar Jun 29 '25 10:06 117649

@onemen I have partially success with the autoRegister.

Tested on 141 by use a chrome.manifest FILE with absolute path I can register an addon's chrome path when addon is disabled.

content    easydragtogo              jar:file:///C:/Users/l/AppData/Roaming/Mozilla/Firefox/Profiles/5qsr10oq.dev-edition-default-1608570548307/extensions/[email protected]!/chrome/content/
content	   _easydragtogo             jar:file:///C:/Users/l/AppData/Roaming/Mozilla/Firefox/Profiles/5qsr10oq.dev-edition-default-1608570548307/extensions/[email protected]!/

skin       easydragtogo  classic/1.0  jar:file:///C:/Users/l/AppData/Roaming/Mozilla/Firefox/Profiles/5qsr10oq.dev-edition-default-1608570548307/extensions/[email protected]!/chrome/skin/classic/

locale     easydragtogo  en-US        jar:file:///C:/Users/l/AppData/Roaming/Mozilla/Firefox/Profiles/5qsr10oq.dev-edition-default-1608570548307/extensions/[email protected]!/chrome/locale/en-US/
locale     easydragtogo  zh-CN        jar:file:///C:/Users/l/AppData/Roaming/Mozilla/Firefox/Profiles/5qsr10oq.dev-edition-default-1608570548307/extensions/[email protected]!/chrome/locale/zh-CN/
locale     easydragtogo  zh-TW        jar:file:///C:/Users/l/AppData/Roaming/Mozilla/Firefox/Profiles/5qsr10oq.dev-edition-default-1608570548307/extensions/[email protected]!/chrome/locale/zh-TW/
locale     easydragtogo  de        jar:file:///C:/Users/l/AppData/Roaming/Mozilla/Firefox/Profiles/5qsr10oq.dev-edition-default-1608570548307/extensions/[email protected]!/chrome/locale/de/

All it take is a temp file to hold the manifest contents. After add or remove content to it we could use

Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
  Ci.nsIXULChromeRegistry
).checkForNewChrome()

to let the edits take effect.

117649 avatar Jun 29 '25 13:06 117649

@117649

test this utils.zip

I replaced Components.manager.addBootstrappedManifestLocation(file) with:

      const addonManagerStartup = Cc["@mozilla.org/addons/addon-manager-startup;1"]
        .getService(Ci.amIAddonManagerStartup);
      const manifestURI = getURIForResourceInFile(file, 'chrome.manifest');
      const chromeEntries = await parseManifestEntries(manifestURI)
      this.chromeRegistryHandle = addonManagerStartup.registerChrome(manifestURI, chromeEntries);

parseManifestEntries is in new file ParseManifestEntries.sys.mjs

notice that amIAddonManagerStartup.registerChrome does not support skin entry from chrome.manifest

/**
   * Registers a set of dynamic chrome registry entries, and returns an object
   * with a `destruct()` method which must be called in order to unregister
   * the entries.
   *
   * @param manifestURI The base manifest URI for the entries. URL values are
   *        resolved relative to this URI.
   * @param entries An array of arrays, each containing a registry entry as it
   *        would appar in a chrome.manifest file. Only the following entry
   *        types are currently accepted:
   *
   *         - "locale" A locale package entry. Must be a 4-element array.
   *         - "override" A URL override entry. Must be a 3-element array.
   */

In Tab Mix Plus I had to replace all skin entries with one content entries

  • content tabmix-skin chrome/skin/
  • skin tabmixplus classic/1.0 chrome/skin/

  • skin tabmix-version tabmixplus chrome/skin/app_version/91/ platformversion>=91

  • skin tabmix-version tabmixplus chrome/skin/app_version/119/ platformversion>=119

  • skin tabmix-os classic/1.0 chrome://tabmix-version/skin/win/ os=WINNT

  • skin tabmix-os classic/1.0 chrome://tabmix-version/skin/mac/ os=Darwin

  • skin tabmix-os classic/1.0 chrome://tabmix-version/skin/linux/ os=Linux

@onemen Don't do this. Use autoRegister() parse relative path to absolute path and write to temp file for load and unload.

Don't change any bit on addon's side. Don't change any bit on addon's side.

117649 avatar Jun 29 '25 14:06 117649

@onemen Use this

BootstrapLoader.zip

Put into the utils and replace bootstraploader. Mind the file name. No change to the addons. You'll need a path under utils called addonManifest to host the manifest files. Temporary files will be deleted on app exit.

117649 avatar Jun 29 '25 20:06 117649

@117649

On My Windows 11 pc, when Firefox start cold, and {dc572301-7619-498c-a57d-39143191b318}.manifest does not exist i get this error: Could not read chrome manifest 'file:///C:/Users/_______/AppData/Roaming/Mozilla/Firefox/Profiles/40xnlurj.Default%20Nightly%202025/chrome/utils/addonManifest/%7Bdc572301-7619-498c-a57d-39143191b318%7D.manifest'.

the file is created.

Edit:

look like adding timestamp to the file name solved this issue I also moved the temp manifest to browser-extension-data directory in the profile

      let tempDir = Services.dirsvc.get("ProfD", Ci.nsIFile)
      tempDir.append("browser-extension-data");
      tempDir.append(addon.id);
      tempDir.append("temp.manifest." + Date.now());

can we use Components.manager .QueryInterface(Ci.nsIComponentRegistrar) .autoUnRegister(tempDir); on exit instead of checkForNewChrome ?

onemen avatar Jul 01 '25 08:07 onemen

@117649

On My Windows 11 pc, when Firefox start cold, and {dc572301-7619-498c-a57d-39143191b318}.manifest does not exist i get this error: Could not read chrome manifest 'file:///C:/Users/_______/AppData/Roaming/Mozilla/Firefox/Profiles/40xnlurj.Default%20Nightly%202025/chrome/utils/addonManifest/%7Bdc572301-7619-498c-a57d-39143191b318%7D.manifest'.

the file is created.

Edit:

look like adding timestamp to the file name solved this issue I also moved the temp manifest to browser-extension-data directory in the profile

      let tempDir = Services.dirsvc.get("ProfD", Ci.nsIFile)
      tempDir.append("browser-extension-data");
      tempDir.append(addon.id);
      tempDir.append("temp.manifest." + Date.now());

can we use Components.manager .QueryInterface(Ci.nsIComponentRegistrar) .autoUnRegister(tempDir); on exit instead of checkForNewChrome ?

Didn't saw that method before need test. And I also not sure will autoregister check extension name or not if tested and success then it's fine. I'm also not understanding the need to have all the version of temp file by timestamp. If separating by sub path is works for you, you can just have a 'chrome.manifest' inside every and reuse it every time.

117649 avatar Jul 01 '25 13:07 117649

@117649

when i use

    function createManifestTemporarily(manifestText) {
      let tempDir = Services.dirsvc.get("ProfD", Ci.nsIFile)
      tempDir.append("browser-extension-data");
      tempDir.append(addon.id);
      tempDir.append("temp.manifest");

      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();
      };
    }

I get this error when Firefox start

Could not read chrome manifest 'file:///C:/Users/___/AppData/Roaming/Mozilla/Firefox/Profiles/40xnlurj.Default%20Nightly%202025/browser-extension-data/%7Bdc572301-7619-498c-a57d-39143191b318%7D/temp.manifest'.

changing

- tempDir.append("temp.manifest");
+ tempDir.append("temp.manifest." + Date.now());

solve this problem, I guess it is cache issue

regarding autoUnregister read this

onemen avatar Jul 01 '25 14:07 onemen

regarding autoUnregister read this

Image And no result on https://searchfox.org/mozilla-central/search?q=autoUnregister&path=&case=false&regexp=false

This method seems to be long gone unlucky for us.

117649 avatar Jul 01 '25 15:07 117649

BootstrapLoader.zip

This is working fine to restore the two legacy addons installed but there is one bug: if you close the browser and re-open it the addons are no longer functional. The remedy is to delete the startupCache folder each time before re-opening.

marty60 avatar Jul 01 '25 16:07 marty60

BootstrapLoader.zip

This is working fine to restore the two legacy addons installed but there is one bug: if you close the browser and re-open it the addons are no longer functional. The remedy is to delete the startupCache folder each time before re-opening.

@onemen @marty60 See if that still happens if un-comment those 'nglayout.debug.disable_xul_cache' uses.

117649 avatar Jul 01 '25 16:07 117649

@onemen @marty60 See if that still happens if un-comment those 'nglayout.debug.disable_xul_cache' uses.

Is this the line you're referring to in bootstrapLoader:

const XUL_CACHE_PREF = "nglayout.debug.disable_xul_cache";

It was deleted but had no effect.

marty60 avatar Jul 01 '25 17:07 marty60

@onemen @marty60 Add a

Cc["@mozilla.org/chrome/chrome-registry;1"]
          .getService(Ci.nsIXULChromeRegistry)
          .checkForNewChrome();

somewhere before autoRegister() seems has effect.

117649 avatar Jul 01 '25 19:07 117649

@onemen @marty60 Add a

Cc["@mozilla.org/chrome/chrome-registry;1"] .getService(Ci.nsIXULChromeRegistry) .checkForNewChrome();

somewhere before autoRegister() seems has effect.

Does not work for me

onemen avatar Jul 01 '25 20:07 onemen

@onemen @marty60 Add a Cc["@mozilla.org/chrome/chrome-registry;1"] .getService(Ci.nsIXULChromeRegistry) .checkForNewChrome(); somewhere before autoRegister() seems has effect.

Does not work for me

Didn't work for me either.

marty60 avatar Jul 01 '25 20:07 marty60

Don't dodeleteTemporaryFileOnExit(tempFile) for now?

let tempDir = Services.dirsvc.get("ProfD", Ci.nsIFile)
    tempDir.append("browser-extension-data");
    tempDir.append(addon.id);

    function createManifestTemporarily(manifestText) {
      let tempFile = tempDir.clone();
      tempFile.append("chrome.manifest");

      let foStream = Cc[
        "@mozilla.org/network/file-output-stream;1"
      ].createInstance(Ci.nsIFileOutputStream);
      foStream.init(tempFile, 0x02 | 0x08 | 0x20, 0o664, 0); // write, create, truncate
      foStream.write(manifestText, manifestText.length);
      foStream.close();

      Components.manager
        .QueryInterface(Ci.nsIComponentRegistrar)
        .autoRegister(tempFile);

      // Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
      //   .getService(Ci.nsPIExternalAppLauncher)
      //   .deleteTemporaryFileOnExit(tempFile);

      return function () {
        tempFile.fileSize = 0; // truncate the manifest
        Cc["@mozilla.org/chrome/chrome-registry;1"]
          .getService(Ci.nsIXULChromeRegistry)
          .checkForNewChrome();
      };
    }

If we are using "browser-extension-data" why bother to delete manifest every time.

117649 avatar Jul 01 '25 21:07 117649

Don't dodeleteTemporaryFileOnExit(tempFile) for now?

If we are using "browser-extension-data" why bother to delete manifest every time.

That resolved it on my end. The manifest files are extremely small so can't see a problem not deleting them but that's just me. Doubtful anyone would even notice as long as they can use their legacy addons.

marty60 avatar Jul 01 '25 21:07 marty60

@117649

why not simply create a unique filename with timestamp?

-  tempFile.append("chrome.manifest");
+  tempFile.append("temp.manifest." + Date.now());

onemen avatar Jul 02 '25 07:07 onemen

@117649 ,

Maybe we does not need to create temp manifest at all this code work with Tab Mix Plus

Can you test it

BootstrapLoader.js

      startup(...args) {
        if (addon.type == 'extension') {
          logger.debug(`Registering manifest for ${file.path}\n`);
          let manifest = file.clone();
          manifest.append("chrome.manifest");
          Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
            .autoRegister(manifest);
        }
        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`);
            Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry)
              .checkForNewChrome();
          }
        }
      },

onemen avatar Jul 02 '25 08:07 onemen

@117649

why not simply create a unique filename with timestamp?

  • tempFile.append("chrome.manifest");
  • tempFile.append("temp.manifest." + Date.now());

I don't know, what happens if you start browser at yesterday then update the addon at the next day? What if browser crashed then reopened at next day?

117649 avatar Jul 02 '25 09:07 117649

@117649 ,

Maybe we does not need to create temp manifest at all this code work with Tab Mix Plus

Can you test it

BootstrapLoader.js

  startup(...args) {
    if (addon.type == 'extension') {
      logger.debug(`Registering manifest for ${file.path}\n`);
      let manifest = file.clone();
      manifest.append("chrome.manifest");
      Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
        .autoRegister(manifest);
    }
    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`);
        Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry)
          .checkForNewChrome();
      }
    }
  },

Image

Image

I'm stay on win10. Where you put your TMP's source? Are they in a '.xpi'?

117649 avatar Jul 02 '25 10:07 117649

Where you put your TMP's source? Are they in a '.xpi'?

You are right my code does not work for xpi.

@117649 why not simply create a unique filename with timestamp?

  • tempFile.append("chrome.manifest");

  • tempFile.append("temp.manifest." + Date.now());

I don't know, what happens if you start browser at yesterday then update the addon at the next day? What if browser crashed then reopened at next day?

we can make sure to remove all leftover "temp.manifest" before creating the new one. in this way we can bypass cache without changing user preferences.

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();
  };
}

onemen avatar Jul 02 '25 11:07 onemen

we can make sure to remove all leftover "temp.manifest" before creating the new one. in this way we can bypass cache without changing user preferences.

Of course.

117649 avatar Jul 02 '25 13:07 117649

This is updated version of utils for Firefox 142+

utils.zip

This version is compatible with all Firefox version.

All users must update replace the utils files with the new version before Firefox 142 release

onemen avatar Jul 06 '25 09:07 onemen