npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fx-inject-shim

v0.1.0

Published

Drop-in Firefox WebExtension shim for tabs/scripting injection APIs deprecated in Firefox 149 and removed in Firefox 152.

Readme

fx-inject-shim

npm version npm downloads license Firefox 152 Ko-fi

A drop-in Firefox WebExtension compatibility shim for tabs.executeScript, tabs.insertCSS, tabs.removeCSS, scripting.executeScript, scripting.insertCSS, and scripting.removeCSS.


The problem

Firefox 149 deprecated — and Firefox 152 removed — the ability to call script/CSS injection APIs directly from extension pages (moz-extension:// documents such as popups, options pages, and sidebars). The calls work fine from a background script, but any call made directly from a popup or options page now throws an error.

The six affected APIs are:

browser.tabs.executeScript()   browser.scripting.executeScript()
browser.tabs.insertCSS()       browser.scripting.insertCSS()
browser.tabs.removeCSS()       browser.scripting.removeCSS()

Mozilla's recommended fix is to replace each call with a runtime.sendMessage round-trip to the background script. That is 30–80 lines of boilerplate per extension, and it forces you to rewrite every call site. fx-inject-shim does this for you automatically so you change nothing at your call sites.


Installation

npm install fx-inject-shim

Or grab the standalone files for manual inclusion (no bundler required):

  • dist/shim.min.js — page-side shim (include via <script> in your popup/options HTML)
  • dist/background.min.js — background listener (include via background.scripts in your manifest)

Migration — two lines, zero call-site changes

Before (Firefox < 149, still works)

// popup.js
const result = await browser.tabs.executeScript(tabId, {
  code: `document.body.style.background = 'red'`
});
// background.js — no changes needed

After (Firefox 152+ compatible)

// popup.js — add ONE line at the very top
import 'fx-inject-shim';

// Everything below is UNCHANGED
const result = await browser.tabs.executeScript(tabId, {
  code: `document.body.style.background = 'red'`
});
// background.js — add ONE line
import 'fx-inject-shim/background';

That is the entire migration. No call sites change. No promise chains to rewrite.


Manual <script> usage (no bundler)

If your extension uses plain script tags rather than ES modules, copy the two files from dist/ and load them:

<!-- popup.html — shim must be loaded BEFORE your popup script -->
<script src="vendor/shim.min.js"></script>
<script src="popup.js"></script>
// manifest.json — background listener loaded before any other background scripts
{
  "background": {
    "scripts": ["vendor/background.min.js", "background.js"]
  }
}

Before / after code comparison

// ── BEFORE ────────────────────────────────────────────────────────────────────
// popup.js (Firefox < 149)

async function highlightPage(tabId) {
  const [result] = await browser.tabs.executeScript(tabId, {
    code: 'document.querySelectorAll("p").length'
  });
  console.log('Found paragraphs:', result);

  await browser.tabs.insertCSS(tabId, {
    code: 'p { outline: 2px solid red }'
  });
}
// ── AFTER ─────────────────────────────────────────────────────────────────────
// popup.js (Firefox 152+)

import 'fx-inject-shim'; // ← only change

async function highlightPage(tabId) {
  // Identical to before ↓
  const [result] = await browser.tabs.executeScript(tabId, {
    code: 'document.querySelectorAll("p").length'
  });
  console.log('Found paragraphs:', result);

  await browser.tabs.insertCSS(tabId, {
    code: 'p { outline: 2px solid red }'
  });
}

How it works

The shim intercepts calls made from extension pages and routes them through the background script, which is the only context where injection APIs are still permitted.

Extension page (popup.js)           Background script
──────────────────────────          ─────────────────────────────────────

browser.tabs.executeScript(args)
  │  (shimmed — calls sendMessage)
  │
  └─► runtime.sendMessage({         onMessage listener (installed by
        __fxInjectShim: true,    ── fx-inject-shim/background)
        method: 'tabs.executeScript'   │
        args: [tabId, details]         │
      })                               ▼
                                   browser.tabs.executeScript(tabId, details)
                                   (original, pre-shim reference)
                                       │
  ◄──────────────────────────────────  │  Promise resolves/rejects
  Promise resolves with result         ▼
                                   sendMessage resolves → { ok: true, result }
                                   or { ok: false, error: { message, stack } }

Error propagation: If the real API rejects, the background serialises the Error (message + stack) into a plain object that can cross the structured-clone boundary, then the page-side shim reconstructs a proper Error object before rejecting the original promise.

Timeout: If the background script is not loaded and no response arrives within 5 seconds, the shim rejects with FxInjectShimTimeoutError.

Safety: The shim checks location.protocol === 'moz-extension:' before patching anything. If imported in a background script or a non-extension context it is a strict no-op, so it is safe to import unconditionally everywhere.


API

import 'fx-inject-shim'

Auto-detects context and installs the appropriate side. Recommended for most users.

import 'fx-inject-shim/background'

Install only the background listener. Use this in your background script.

Named exports

import { installShim, installBackgroundListener, FxInjectShimTimeoutError } from 'fx-inject-shim';

| Export | Description | |--------|-------------| | installShim() | Manually install the page-side shim. Idempotent. | | installBackgroundListener() | Manually install the background listener. Idempotent. | | FxInjectShimTimeoutError | Error class thrown when the background does not respond within 5 s. |


Manifest V3 note

Under MV3 the browser.tabs.executeScript / insertCSS / removeCSS APIs are fully removed (not just deprecated) in favour of browser.scripting.*. This shim covers browser.scripting.executeScript, browser.scripting.insertCSS, and browser.scripting.removeCSS as well.

However, in MV3 the background is a service worker, not a persistent background page. Service workers cannot use importScripts with moz-extension:// URLs in the same way. If you are migrating to MV3, the recommended path is to use browser.scripting.* APIs directly in your background service worker rather than shimming them — the scripting.* APIs work fine from background service workers. The shim's scripting.* coverage is therefore most useful for MV2 extensions that use scripting.* calls from popup/options pages.


Example extension

example-extension/ is a complete, installable MV2 Firefox extension. Load it via about:debugging → Load Temporary Add-on and select example-extension/manifest.json. Click the toolbar button to toggle paragraph highlighting on any page.


Running tests

npm test

Tests run in Node with no browser required. The test/mock-browser.js mock simulates the full browser.runtime.onMessage / sendMessage round-trip in-process.


License

MIT