content-script-activation

1.0.3 • Public • Published

content-script-activation

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.

Readme

Keywords

none

Package Sidebar

Install

npm i content-script-activation

Weekly Downloads

48

Version

1.0.3

License

MIT

Unpacked Size

21.6 kB

Total Files

12

Last publish

Collaborators

  • daniguardiola