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 🙏

© 2024 – Pkg Stats / Ryan Hefner

content-script-activation

v1.0.3

Published

Simpler injection of content scripts in browser extensions. Inject once, activate on click.

Downloads

38

Readme

API reference Bundle size

Simpler injection of content scripts in browser extensions. Inject once, activate on click.

npm i content-script-activation

What this does

When building a browser extension, it is a common pattern to inject a content script when the extension icon is clicked. This is usually done like this:

browser.action.onClicked.addListener((tab) => {
  browser.scripting.executeScript({
    target: { tabId: tab.id },
    files: ["content-script.js"],
  });
});

However, the problem is that on every click, the content script is injected again. This can cause trouble depending on how the content script is written. For example, if the content script adds an event listener to the window object, it will be added again on every click, leading to unexpected behavior.

This package does things differently:

  • The content script is injected only once on the first click.
  • The "activation" event is triggered on every click, including the first one.

To illustrate this, consider the following sequence of events:

Extension icon clicked
  Content script injected
  Activation event triggered
Extension icon clicked
  Activation event triggered
Extension icon clicked
  Activation event triggered
(...)

This model is simpler and lets you think about "activation" as a single event that happens on every click. Script injection is handled for you.

Usage

On the service worker:

import { setupContentScriptActivation } from "content-script-activation";

setupContentScriptActivation("content-script.js");

On the content script:

import { setupActivation } from "content-script-activation";

setupActivation(() => {
  // do something on activation
});

Documentation

Filtering tabs

If you want to inject the content script only on certain tabs, you can pass a filter function to setupContentScriptActivation:

setupContentScriptActivation({
  filterTab: (tab) => tab.url?.startsWith("http"),
  inject: "content-script.js",
});

The tab object (tabs.Tab type) is the one passed to the browser.action.onClicked.addListener callback, and contains information about the tab where the extension icon was clicked (such as the ID, URL, title, etc).

Executing code before or after injection

If you need to run some code before injection (e.g. preparing a database connection) or after injection (e.g. sending a message to the content script), you can use the beforeInject and afterInject options:

setupContentScriptActivation({
  inject: {
    async beforeInjection(context) {
      // ...
    },
    async afterInjection(context) {
      // ...
    },
    scripts: "content-script.js",
  },
});

Both functions can be synchronous or asynchronous. They receive a context object with information about the tab where the content script is injected.

Injecting styles

You can inject stylesheets in a similar way to scripts:

setupContentScriptActivation({
  inject: {
    // ...
    styles: "content-style.css",
  },
});

Customizing script and stylesheet injection

If you need more control over how scripts or stylesheets are injected, you can pass option objects instead of strings:

setupContentScriptActivation({
  inject: {
    scripts: {
      files: ["content-script.js"],
      injectImmediately: false,
    },
    styles: {
      files: ["content-style.css"],
      origin: "USER",
    },
  },
});

The options that can be passed correspond to the options that can be passed to browser.scripting.executeScript and browser.scripting.insertCSS, except for the target option (which is always set to the tab where the extension icon was clicked).

Injecting multiple scripts and stylesheets

You can inject multiple scripts and stylesheets by passing an array of strings or option objects:

setupContentScriptActivation({
  inject: {
    scripts: ["content-script.js", "content-script-2.js"],
    styles: ["content-style.css", "content-style-2.css"],
  },
});

Note that you need to call setupActivation from every content script you want to inject.

Scripts shorthands

For brevity, setupContentScriptActivation has two shorthand APIs:

  • If you don't need to pass any other options, you can pass the script or scripts to inject directly in string form:

    setupContentScriptActivation("content-script.js");
  • If you need to pass other options, but don't need any of the inject options, you can pass the script or scripts to inject directly to inject:

    setupContentScriptActivation({
      filterTab: (tab) => tab.url?.startsWith("http"),
      inject: "content-script.js",
    });

Omitting the activation callback

If you don't need to run any code in your content script on activation (for example, if you only want to make sure that the script and styles are only injected once), you can omit the callback when calling setupActivation:

import { setupActivation } from "content-script-activation";

setupActivation();

Note that you still need to call setupActivation from every content script you want to inject.

Multiple instances

If you want to use setupContentScriptActivation more than once, you must pass a unique ID to each instance:

// service-worker.js
setupContentScriptActivation({
  // ...
  inject: "content-script-1.js",
  scriptId: "content-script-1",
});
setupContentScriptActivation({
  // ...
  inject: "content-script-2.js",
  scriptId: "content-script-2",
});

// content-script-1.js
setupActivation(() => {
  // ...
}, "content-script-1");

// content-script-2.js
setupActivation(() => {
  // ...
}, "content-script-2");

An example use case for this is when you want to inject different scripts on different tabs. In this case, you can use the filterTab option to filter the tabs where each script is injected.

Manual activation

By default, the content script is activated when the extension icon is clicked. For advanced use cases, you can pass false to the injectOnClick option. This will disable the default behavior, and setupContentScriptActivation will return an asynchronous function that you can call to activate the content script manually. The function takes a target as an argument, which corresponds to the target option of browser.scripting.executeScript (scripting.InjectionTarget).

const activate = setupContentScriptActivation({
  inject: "content-script.js",
  injectOnClick: false,
});

// when you want to activate the content script:
await activate({ tabId: myTabId });

Browser support

All browsers that support the underlying APIs should be supported. This is the case for Chrome and Firefox, and probably all desktop browsers that support extensions in the first place. Cross-browser API namespace compatibility is achieved through the browser-namespace package.

Features under consideration

  • Support browser.scripting.removeCSS.