content-script-activation
v1.0.3
Published
Simpler injection of content scripts in browser extensions. Inject once, activate on click.
Downloads
33
Readme
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 toinject
: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
.