Content Scripts

Add-ons using the techniques described in this document are considered a legacy technology in Firefox. Don't use these techniques to develop new add-ons. Use WebExtensions instead. If you maintain an add-on which uses the techniques described here, consider migrating it to use WebExtensions.

Starting from Firefox 53, no new legacy add-ons will be accepted on addons.mozilla.org (AMO) for desktop Firefox and Firefox for Android.

Starting from Firefox 57, only extensions developed using WebExtensions APIs will be supported on Desktop Firefox and Firefox for Android.

Even before Firefox 57, changes coming up in the Firefox platform will break many legacy extensions. These changes include multiprocess Firefox (e10s), sandboxing, and multiple content processes. Legacy extensions that are affected by these changes should migrate to use WebExtensions APIs if they can. See the "Compatibility Milestones" document for more information.

Many add-ons need to access and modify the content of web pages. But the main add-on code doesn't get direct access to web content. Instead, SDK add-ons need to factor the code that gets access to web content into separate scripts that are called content scripts. This page describes how to develop and implement content scripts.

Content scripts can be one of the more confusing aspects of working with the SDK, but you're very likely to have to use them. There are five basic principles:

the add-on's main code, including "main.js" and other modules in "lib", can use the SDK high-level and low-level APIs, but can't access web content directly

SDK APIs that use content scripts, like page-mod and tabs, provide functions that enable the add-on's main code to load content scripts into web pages

content scripts can be loaded in as strings, but are more often stored as separate files under the add-on's "data" directory. jpm doesn't make a "data" directory by default, so you must add it and put your content scripts in there.

a message-passing API allows the main code and content scripts to communicate with each other

This complete add-on illustrates all of these principles. Its "main.js" attaches a content script to the current tab using the tabs module. In this case the content script is passed in as a string. The content script simply replaces the content of the page:

The following high-level SDK modules can use content scripts to modify web pages:

page-mod: enables you to attach content scripts to web pages that match a specific URL pattern.

tabs: exports a Tab object for working with a browser tab. The Tab object includes an attach() function to attach a content script to the tab.

page-worker: lets you retrieve a web page without displaying it. You can attach content scripts to the page, to access and manipulate the page's DOM.

context-menu: use a content script to interact with the page in which the menu is invoked.

Additionally, some SDK user interface components - panel, sidebar, frame - are specified using HTML, and use separate scripts to interact with this content. In many ways these are like content scripts, but they're not the focus of this article. To learn about how to interact with the content for a given user interface module, please see the module-specific documentation: panel, sidebar, frame.

The contentScriptFile option treats the string as a resource:// URL pointing to a script file stored in your add-on's data directory. jpm doesn't make a "data" directory by default, so you must add it and put your content scripts in there.

This add-on supplies a URL pointing to the file "content-script.js", located in the data subdirectory under the add-on's root directory:

If you do this, the scripts can interact directly with each other, just like scripts loaded by the same web page.

You can also use contentScript and contentScriptFile together. If you do this, scripts specified using contentScriptFile are loaded before those specified using contentScript. This enables you to load a JavaScript library like jQuery by URL, then pass in a simple script inline that can use jQuery:

Unless your content script is extremely simple and consists only of a static string, don't use contentScript: if you do, you may have problems getting your add-on approved on AMO.

Instead, keep the script in a separate file and load it using contentScriptFile. This makes your code easier to maintain, secure, debug and review.

Controlling when to attach the script

The contentScriptWhen option specifies when the content script(s) should be loaded. It takes one of:

"start": load the scripts immediately after the document element for the page is inserted into the DOM. At this point the DOM content hasn't been loaded yet, so the script won't be able to interact with it.

"ready": load the scripts after the DOM for the page has been loaded: that is, at the point the DOMContentLoaded event fires. At this point, content scripts are able to interact with the DOM content, but externally-referenced stylesheets and images may not have finished loading.

"end": load the scripts after all content (DOM, JS, CSS, images) for the page has been loaded, at the time the window.onload event fires.

The default value is "end".

Note that tab.attach() doesn't accept contentScriptWhen, because it's generally called after the page has loaded.

Passing configuration options

The contentScriptOptions is a JSON object that is exposed to content scripts as a read-only value under the self.options property:

There are good reasons for this insulation. First, it means that content scripts don't leak objects to web pages, potentially opening up security holes. Second, it means that content scripts can create objects without worrying about whether they might clash with objects added by page scripts.

This insulation means that, for example, if a web page loads the jQuery library, then the content script won't be able to see the jQuery object added by the library - but the content script can add its own jQuery object, and it won't clash with the page script's version.

Interacting with page scripts

Usually the insulation between content scripts and page scripts is what you want. But sometimes you might want to interact with page scripts: you might want to share objects between content scripts and page scripts or to send messages between them. If you need to do this, read about interacting with page scripts.

Event listeners

You can listen for DOM events in a content script just as you can in a normal page script, but there are two important differences:

First, if you define an event listener by passing it as a string into setAttribute(), then the listener is evaluated in the page's context, so it will not have access to any variables defined in the content script.

For example, this content script will fail with the error "theMessage is not defined":

Second, if you define an event listener by direct assignment to a global event handler like onclick, then the assignment might be overridden by the page. For example, here's an add-on that tries to add a click handler by assignment to window.onclick:

Note that the global self object is completely different from the self module, which provides an API for an add-on to access its data files and ID.

Accessing port in the add-on script

In the add-on code, the channel of communication between the add-on and a particular content script context is encapsulated by the worker object. So the port object for communicating with a content script is a property of the corresponding worker object.

However, the worker is not exposed to add-on code in quite the same way in all modules.

From page-worker

The page-worker object integrates the worker API directly. So to receive messages from a content script associated with a page-worker you use pageWorker.port.on():

From page-mod

A single page-mod object might attach its scripts to multiple pages, each with its own context in which the content scripts are executing, so it needs a separate channel (worker) for each page.

So page-mod does not integrate the worker API directly. Instead, each time a content script is attached to a page, the page-mod emits an attach event, whose listener is passed the worker for that context. By supplying a listener to attach you can access the port object for content scripts attached to that page by this page-mod:

click is sent from the page-mod to the add-on, when the user clicks an element in the page

warning sends a silly string back to the page-mod

From Tab.attach()

The Tab.attach() method returns the worker you can use to communicate with the content script(s) you attached.

This add-on adds a button to Firefox: when the user clicks the button, the add-on attaches a content script to the active tab, sends the content script a message called "my-addon-message", and listens for a response called "my-script-response":

The port API

The postMessage API

Before the port object was added, add-on code and content scripts communicated using a different API:

the content script called self.postMessage() to send and self.on() to receive

the add-on script called worker.postMessage() to send and worker.on()to receive

The API is still available and documented, but there's no reason to use it instead of the port API described here. The exception is the context-menu module, which still uses postMessage.

Content script to content script

Content scripts can only communicate with each other directly if they have been loaded into the same context. For example, if a single call to Tab.attach() attaches two content scripts, then they can see each other directly, just as page scripts loaded by the same page can. But if you call Tab.attach() twice, attaching a content script each time, then these content scripts are not loaded into the same context and must communicate with each other using the normal methods of communicating from one context to another. One option is that you can relay messages through the main add-on code using the port API with the sending context script sending a message to the main add-on code and the the main add-on code sends the message to the other content script. This will work regardless of the context in which the content script was loaded.

In the special case where the two content scripts are loaded into the same exact page, it is possible for the content scripts to communicate directly to each other using the DOM postMessage() API or a CustomEvent. The following add-on shows a content script added by page-mod receiving a CustomEvent sent from a context-menu item when the context menu item is clicked. The page-mod script will then issue an alert with the URL of the link on which the context menu was displayed. The URL is passed to the page-mod script in the CustomEvent.

Cross-domain content scripts

By default, content scripts don't have any cross-domain privileges. In particular, they can't access content hosted in an iframe, if that content is served from a different domain, or make cross-domain XMLHttpRequests.

However, you can enable these features for specific domains by adding them to your add-on's package.json under the "cross-domain-content" key, which itself lives under the "permissions" key. See the article on cross-domain content scripts.