Communicate between scripts in the background context (background script, browser action, page action, options page, etc.)
Communicating with a page in the background context
Pages which are open in the background context include:
- background pages/scripts(MDN)
- event pages (Firefox does not support event pages. All manifest.json
background
pages remain loaded at all times.) - browser action popups(MDN)
- page action popups(MDN)
- options pages(MDN1, MDN2) (in a popup, a tab, or window)
- sidebar action pages (NDN) (not available in Chrome)
- Any HTML content contained within your extension which is opened normally in a tab, window (e.g. a panel), or frame.1
Using tabs.sendMessage()
(MDN) will not send a message to any of them. You would need to use runtime.sendMessage()
(MDN) to send a message to them. The scope for any of them, except background pages and event pages, only exists when it is being displayed. Obviously, you can not communicate with the code when it does not exist. When the scope exists, you can communicate with any of them using:
Directly
From the background context, you can directly change variables, or call functions, in another page that is also in the background context (i.e. not content scripts), after having gotten a reference to its global scope, its Window, usingextension.getViews()
(MDN),extension.getBackgroundPage()
(MDN), or other method(MDN).
For example, you can call a function created withfunction myFunction
in the page of the first returned view by using something like:winViews = chrome.extension.getViews();
winViews[0].myFunction(foo);It should be noted that in your callback from
tabs.create()
(MDN) orwindows.create()
(MDN) the view for the newly opened tab or window will probably not yet exist. You will need to use some methodology to wait for the view to exist.2 See below for recommended ways to communicate with newly opened tabs or windows.Directly manipulating values in the other page's scope allows you to communicate any type of data you desire.
Messaging
Receive messages usingchrome.runtime.onMessage
(MDN), 3 which were sent withchrome.runtime.sendMessage()
(MDN). Each time you receive a message in aruntime.onMessage
listener, there will be asendResponse
function provided as the third argument which allows you to directly respond to the message. If the original sender has not supplied a callback to receive such a response in their call tochrome.runtime.sendMessage()
, then the response is lost. If using Promises (e.g.browser.runtime.sendMessage()
in Firefox), the response is passed as an argument when the Promise is fulfilled. If you want to send the response asynchronously, you will need toreturn true;
from yourruntime.onMessage
listener.Ports
You can also connect ports, usingchrome.runtime.connect()
(MDN) andchrome.runtime.onConnect
(MDN) for longer term messaging.Use
chrome.tabs.sendMessage()
to send to content scripts
If you want to send from the background context (e.g. background script or popup) to a content script you would usechrome.tabs.sendMessage()
/chrome.runtime.onMessage
, or connect port(s) usingchrome.tabs.connect()
(MDN)/chrome.runtime.onConnect
.JSON-serializable data only
Using messaging, you can only pass data which is JSON-serializable.Messages are received by all scripts in the background, except the sender
Messages sent to the background context are received by all scripts in the background context which have registered a listener, except the script which sent it.3 There is no way to specify that it is only to be received by a specific script. Thus, if you have multiple potential recipients, you will need to create a way to be sure that the message received was intended for that script. The ways to do so usually rely on specific properties existing in the message (e.g. use adestination
orrecipient
property to indicate what script is to receive it, or define that sometype
of messages are always for one recipient or another), or to differentiate based on thesender
(MDN) supplied to the message handler (e.g. if messages from one sender are always only for a specific recipient). There is no set way to do this, you must choose/create a way to do it for use in your extension.For a more detailed discussion of this issue, please see: Messages intended for one script in the background context are received by all
Data in a StorageArea
Store data to a StorageArea(MDN) and be notified of the change in other scripts usingchrome.storage.onChanged
(MDN). Thestorage.onChanged
event can be listened to in both the background context and content scripts.You can only store data which is JSON-serializable into a StorageArea.
Which method is best to use in any particular situation will depends on what you are wanting to communicate (type of data, state change, etc.), and to which portion, or portions, of your extension you are wanting to communicate from and to. For instance, if you want to communicate information which is not JSON-serializable, you would need to do so directly (i.e. not messaging or using a StorageArea). You can use multiple methods in the same extension.
More on popups
None of the popups (e.g. browser action, or page action) are directly associated with the active tab. There is no concept of a shared or separate instance per tab. However, the user can open one popup in each Chrome window. If more than one popup is open (a maximum of one per Chrome window), then each is in a separate instance (separate scope; has its own Window), but are in the same context. When a popup is actually visible, it exists in the background context.
There is only ever one page action or browser action popup open at a time per Chrome window. The HTML file which will be open will be whichever one has been defined for the active tab of the current window and opened by the user by clicking on the page/browser action button. This can be assigned a different HTML document for different tabs by using chrome.browserAction.setPopup()
(MDN), or chrome.pageAction.setPopup()
(MDN), and specifying a tabId
. The popup can/will be destroyed for multiple reasons, but definitely when another tab becomes the active tab in the window in which the popup is open.
However, any method of communication used will only communicate to the one(s) which is/are currently open, not ones which are not open. If popups are open for more than one Chrome window at a time, then they are separate instances, with their own scope (i.e. their own Window). You can think of this something like having the same web page open in more than one tab.
If you have a background script, the background script context is persistent across the entire instance of Chrome. If you do not have a background script the context may be created when needed (e.g. a popup is shown) and destroyed when no longer needed.
chrome.tabs.sendMessage()
can not communicate to popups
As mentioned above, even if the popup did exist, it will exist in the background context. Calling chrome.tabs.sendMessage()
sends a message to content scripts injected into a tab/frame, not to the background context. Thus, it will not send a message to a non-content script like a popup.
Action button: enable/disable (browser action) vs. show/hide (page action)
Calling chrome.pageAction.show()
(MDN) just causes the page action button to be shown. It does not cause any associated popup to be shown. If the popup/options page/other page is not actually being shown (not just the button), then its scope does not exist. When it does not exist, it, obviously, can not receive any message
Instead of the page action's ability to show()
(MDN) or hide()
(MDN) the button, browser actions can enable()
(MDN) or disable()
(MDN) the button.
Programmatically opening a tab or window with HTML from your extension
You can use tabs.create()
(MDN) or windows.create()
(MDN) to open a tab or window containing an HTML page from within your extension. However, the callback for both of those API calls is executed prior to the page's DOM existing and thus prior to any JavaScript associated with the page existing. Thus, you can not immediately access the DOM created by the contents of that page, nor interact with the JavaScript for the page. Very specifically: no runtime.onMessage()
listeners will have been added, so no messages sent at that time will be received by the newly opening page.
The best ways to resolve this issue are:
- Have the data available so the newly opening page can get the data when it is ready for. Do this by, prior to beginning the process of opening the page:
- If the source is in the background context: store the data in a variable available to the global scope of the sending page. The opening page can then use
chrome.extension.getBackgroundPage()
to read the data directly. - If the source of the data is in either the background context or a content script: place the data into
storage.local
(MDN). The opening page can then read it when its JavaScript is run. For example, you could use a key calledmessageToNewExtensionPage
.
- If the source is in the background context: store the data in a variable available to the global scope of the sending page. The opening page can then use
- If you are using
runtime.sendMessage()
, then initiate the transfer of the data from your newly opening page by sending a message from the that page's code to the source of the data (usingruntime.sendMessage()
, ortabs.sendMessage()
for content script sources) requesting the data. The script with the data can then send the data back using thesendResponse
(MDN) function provided byruntime.onMessage()
. - Wait to interact with the newly opening page until after at least the DOM is available, if not until after the JavaScript for the page has run. While it's possible to do this without the newly opening page providing specific notification that it's up and running, doing so is more complex and only useful in some specific cases (e.g. you want to do something prior to the JavaScript in the new page being run).2
Additional references
Chrome
- Message Passing
- Chrome extension overview
- architecture
- Communication between pages
Firefox
- WebExtensions
- Anatomy of a WebExtension
- With some minor exceptions: e.g. using a content script to insert content into the page context.
There are multiple methods which you can use. Which way is best will depend on exactly what you are doing (e.g. when you need to access the view with respect to the code being executed in the view). A simple method would be just to poll waiting for the view to exist. The following code does that for opening a window:
chrome.windows.create({url: myUrl},function(win){
//Poll for the view of the window ID. Poll every 50ms for a
// maximum of 20 times (1 second). Then do a second set of polling to
// accommodate slower machines. Testing on a single moderately fast machine
// indicated the view was available after, at most, the second 50ms delay.
waitForWindowId(win.id,50,20,actOnViewFound,do2ndWaitForWinId);
});
function waitForWindowId(id,delay,maxTries,foundCallback,notFoundCallback) {
if(maxTries--<=0){
if(typeof notFoundCallback === 'function'){
notFoundCallback(id,foundCallback);
}
return;
}
let views = chrome.extension.getViews({windowId:id});
if(views.length > 0){
if(typeof foundCallback === 'function'){
foundCallback(views[0]);
}
} else {
setTimeout(waitForWindowId,delay,id,delay,maxTries,foundCallback
,notFoundCallback);
}
}
function do2ndWaitForWinId(winId,foundCallback){
//Poll for the view of the window ID. Poll every 500ms for max 40 times (20s).
waitForWindowId(winId,500,40,foundCallback,windowViewNotFound);
}
function windowViewNotFound(winId,foundCallback){
//Did not find the view for the window. Do what you want here.
// Currently fail quietly.
}
function actOnViewFound(view){
//What you desire to happen with the view, when it exists.
}- From MDN:
In Firefox versions prior to version 51, the runtime.onMessage listener will be called for messages sent from the same script (e.g. messages sent by the background script will also be received by the background script). In those versions of Firefox, if you unconditionally call runtime.sendMessage() from within a runtime.onMessage listener, you will set up an infinite loop which will max-out the CPU and lock-up Firefox. If you need to call runtime.sendMessage() from within a runtime.onMessage, you will need to check the sender.url property to verify you are not sending a message in response to a message which was sent from the same script. This bug was resolved as of Firefox 51.
Contexts and methods for communication between the browser action, background scripts, and content scripts of chrome extensions?
Three different contexts
As a Chrome extension developer, you can distinguish three different environments.
- Extension code, runs in the process of your Chrome extension
- Background / event page
- Browser action / page action popup
- The page within an info bar.
- Tabs whose top-level frame is a document from your extension, such as the options page.
- Content scripts, running in the tab's process.
- Non-extension code running in the tab's process (injected by content scripts).
Note that <iframe src="chrome-extension://EXTENSIONID/page.htm">
in non-extension pages used to be treated like case 2 (content scripts), because the frame was loaded in an unprivileged tab process. Since out-of-process iframes was launched for extensions in Chrome 56, these pages are handled by the extension process, and therefore they may use the same full set of extension APIs. This change in behavior (allowing extension frames to use privileged extension APIs) is intentional.
Accessing the window
object within an extension process
Because all extension code runs in the same process, they can access each other global window
object. This feature is not well-known, but allows one to directly manipulate JavaScript and DOM objects within the same extension process. It's generally better to not use this method, but use the message passing APIs instead.
// To access the `window` of a background page, use
var bgWindowObject = chrome.extension.getBackgroundPage();
// To access the `window` of an event or background page, use:
chrome.runtime.getBackgroundPage(function(bgWindowObject) {
// Do something with `bgWindow` if you want
});
// To access the `window` of the badge's popup page (only if it's open!!!), use
var popupWindowObject = chrome.extension.getViews({type:'popup'})[0];
// To access the `window` of the options page (called /options.html), use
var allWindowObjects = chrome.extension.getViews({type:'tab'});
var popupWindowObjects = allWindowObjects.filter(function(windowObject) {
return windowObject.location.pathname == '/options.html';
});
// Example: Get the `window` object of the first options page:
var popupWindowObject = popupWindowObjects[0];
To keep this section short, I have intentionally limited the code example to a demonstration of accessing other global window
objects. You could use these methods to define a global method, set a global variable, call a global function, etc.
... provided that the page is open. Someone thought that the popup's window
is always available. This is not true, when the popup is closed, the global object is disposed!
Communication by message passing
A message channel always has two ends: The sender and a receiver.
To become a receiver, bind an event listener using the chrome.runtime.onMessage.addListener
method. This can be done by extension code and content scripts.
To pass messages within the extension, use chrome.runtime.sendMessage
. If you want to send a message to another tab, call chrome.tabs.sendMessage
. The target tab is specified by including an integer (tabId
) as its first argument. Note that a background page can only send a message to one tab. To reach all tabs, the method has to be called for every tab. For instance:
chrome.tabs.query({}, function(tabs) {
for (var i=0; i<tabs.length; i++) {
chrome.tabs.sendMessage(tabs[i].id, "some message");
}
});
Content scripts can only call chrome.runtime.sendMessage
to send a message to extension code. If you want to send a message from a content script to another content script, a background / event page should is needed, which takes a message and sends it to the desired tab. See this answer for an example.
The sendMessage
methods accept an optional function, which is received as a third argument to the onMessage
event.
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if (message === 'message') sendResponse('the response');
});
chrome.runtime.sendMessage('message', function(response) {
console('sendResponse was called with: ' + response);
});
The previous example shows obvious behaviour. Things get trickier when you want to send a response asynchronously, for instance if you want to perform an AJAX request to fetch some data. When the onMessage
function returns without having called sendResponse
, Chrome will immediately invoke sendResponse
. Since sendResponse
can be called only once, you will receive the following error:
Could not send response: The chrome.runtime.onMessage listener must return true if you want to send a response after the listener returns (message was sent by extension EXTENSION ID HERE)
Do as the error suggest, add return true;
inside your onMessage event listener:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
setTimeout(function() { // Example: asynchronous invocation of sendResponse
sendResponse('async response');
}, 200);
return true;
});
I've explained the practical application of simple one-time message passing in this section. If you want to know more about long-lived message channels or cross-extension messaging, read the tutorial from the official documentation.
The message passing API has undergone several name changes. Keep this in mind if you read old examples. The history and compatibility notes can be found here.
Communication between a content script and the page
It's possible to communicate with the page. Apsillers has created an excellent answer which explains how to set up a communication channel between a (non-extension) page and a content script. Read his answer at Can a site invoke a browser extension?.
The advantage of apsiller's method over the one from the documentation is that a custom event is used. The documentation uses window.postMessage
to send a message to the page, but this could cause conflict with badly coded pages which do not expect the message events.
How can I use data from background script in popup?
You can use Message API to communicate between background, popup, content-script and ui-pages.
It is also possible through the localStorage.
How to communicate from a background to a sidebar script in Firefox WebExtensions? (or vice versa)
Extension's background, content, sidebar, etc scripts can communicate with each other using runtime.sendMessage() and runtime.onMessage.addListener()
AFAIK, there isn't any direct connection between sidebar and content DOM at the moment.
You can use tabs.query() to get the Active Tab (which is the visible tab) or any other tab
function logTabs(tabs) {
for (let tab of tabs) {
// tab.url requires the `tabs` permission
console.log(tab.url);
}
}
function onError(error) {
console.log(`Error: ${error}`);
}
var querying = browser.tabs.query({currentWindow: true, active: true});
querying.then(logTabs, onError);
Or
chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {
// tabs[0] is the active tab
});
You can then use tabs.executeScript() with the tabId you got from tabs.query()
(i.e. tabs[0].id
) to inject JavaScript code into the page (DOM) and interact with its DOM.
Chrome extension - generate report from background but show in pop up?
The idea is to keep message listener in background script, so each time popup is running it asks for a report.
That is the code:
manifest.json
{
"name": "test",
"version": "0.0.1",
"manifest_version": 2,
"description": "Test",
"background": {
"scripts": [
"background.js"
]
},
"permissions": [],
"browser_action": {
"default_popup": "popup.html"
}
}
popup.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="report"></div>
<script src="popup.js"></script>
</body>
</html>
popup.js
var reportContainer = document.querySelector('#report');
chrome.runtime.sendMessage({action: 'report'}, function (response) {
reportContainer.innerHTML = response;
});
background.js
var report = '<h1>hello from background</h1>';
chrome.runtime.onMessage.addListener(
function(data, sender, sendResponse) {
if(data.action === 'report') {
sendResponse(report);
}
}
);
Browser extensions: Send messages (with response) between browser-action-popup and background-script
From the docs:
If sending to your extension, the runtime.onMessage event will be fired in every frame of your extension (except for the sender's frame)
So you don't have to worry for a message from the popup triggering the onMessage event on the popup.
You also ask for another mean of communication by sharing variables. From the popup you can call chrome.runtime.getBackgroundPage
, that to get the JavaScript window object for the background page. And from the background page you can call chrome.extension.getViews with {type: 'popup'}
to access the window object for the popup, if it is open.
The Firefox documentation for getBackgroundPage
states:
This provides a convenient way for other privileged add-on scripts to get direct access to the background script's scope. This enables them to access variables or call functions defined in that scope. "Privileged script" here includes scripts running in options pages, or scripts running in browser action or page action popups, but does not include content scripts.
Messages intended for one script in the background context are received by all
Messages sent by runtime.sendMessage()
are received in all scripts in the background context which have a runtime.onMessage
listener registered, except the script from which the message was sent.1 There is no way for you to restrict the recipients of such messages.
Thus, you have to create some methodology to determine if the message is intended for each script which receives it. This can be done in a wide variety of ways, but all of them are based on either:
- The contents of the
message
and/or - The
sender
Both of these are provided as arguments to the runtime.onMessage()
listener.
Using message
To use the message
you have to choose to impose some structure on the message
. The message
can be any JSON-ifiable data you choose to send. Imposing some structure allows you to more easily use messages more complex and more reliably communicate information between your scripts. There is nothing that is predefined. You can use whatever you desire. However, being consistent in your choices usually makes it easier to program and almost always makes code easier to maintain.
Personally, I'm usually sending messages for different reasons. For each extension, I'll usually choose to always send an Object with a particular format. That format may be different for each extension, but it usually looks something like:
var message = {
type: 'requestFoo',
//subType: 'Used if the type needs to be further split',
data: dataNeededForRequest
//You can add whatever other properties you want here.
};
If the extension has multiple possible recipients, then either A) only one recipient would understand how to handle a requestFoo
type
message, and all others would ignore such message
types
, or if there were multiple background context scripts which could handle requestFoo
types, then I would add a recipient
or destination
property. Thus, the message
would look like:
var message = {
type: 'requestFoo',
//subType: 'Used if the type needs to be further split',
recipient: 'page2.js',
data: dataNeededForRequest
//You can add whatever other properties you want here.
};
When each script received the message
they would check both that the recipient
matched the script which had received the message and that the code understood how to handle the type
of message
.
Keep in mind that the structure above is just what I happen to use. You can define whatever structure you desire which also fulfills your needs.
Using sender
If a script is never to act on messages received from a specific sender(s), or if it is to only act on messages from specific senders, then that script can check the sender
runtime.MessageSender
Object to see if the parameters match ones from a sender for which it is to act on the message
. If you use this methodology, you will most commonly be checking the sender.url
.
Basing a choice to act on a message
based solely on the sender
is significantly more limited than being based on the contents of the message
. However, it can be quite useful when used in addition to information provided in the message
. It also provides a way to know the sender of the message which can not be spoofed. In addition, it means that you don't need to communicate information as to which scope was the sender of the message, unless, of course, you have more than one possible sender in the scope (i.e. in the URL/page).
1. A bug in Firefox in versions prior to 51 results in messages being received in the script which sent them. If you expect your extension to be used in that version or earlier you must account for that possibility (i.e. ignore them), as some situations can result in locking up Firefox (e.g. if you always send a new message when you receive a message).
How to attach content script to a tab in webextensions
You can't inject content scripts into HTML pages from your extension and there's no need to do so.
HTML pages which are loaded from within your extension, be that as a tab, window, options page, popup, etc., are loaded into the background context. This is the same context in which your background script(s) run. However, scripts associated with such HTML pages (i.e. in separate JavaScript files referenced from <script>
tags) run in the scope of that page. They have access to all the chrome.*
/browser.*
APIs which are available to your background script(s). If they so desire, they can gain references to the other scopes within the background context. If you want more information about communicating between such scripts, please see: Communicate between scripts in the background context (background script, browser action, page action, options page, etc.)
Thus, there is no need to load a content script into such pages. The scripts running in the page already have access to the DOM of that page and all the privileged extension APIs. Such scripts are not restricted to only the APIs available to content scripts.
Side note: Content scripts are attached to the tab's content, not the tab
Content scripts are not attached to a tab, per se. They are injected into the content that is currently displayed in a tab. This happens either through a manifest.json content_scripts
entry having a matching URL, or through tabs.executeScript()
being called to inject it into a specified tab. If different content is loaded into the tab, then the content script is destroyed. Thus, it's attached to the content, not the actual tab. In other words, it does not get automatically reloaded when the tab changes to a new URL, unless that URL matches a manifest.json content_scripts
entry or tabs.executeScript()
is called again.
Related Topics
JavaScript Keycode VS Charcode
Capture Browser Console Logs with Capybara
Performance of Key Lookup in JavaScript Object
Jqgrid, How to Make a Column Editable in the Add Dialog But Not During (Inline) Edits
Convert Array of Strings into an Array of Objects
Template Language That Works on Both Server and Client
Calculating Page Load Time in JavaScript
JavaScript with Embedded Ruby: How to Safely Assign a Ruby Value to a JavaScript Variable
Using Socket.Io in Express 4 and Express-Generator's /Bin/Www
Detect Blocked Popup in Chrome
Rails 5 - Turbolinks 5,Some Js Not Loaded on Page Render
How to Update Single Value Inside Specific Array Item in Redux
How to Update Window.Location.Hash Without Jumping the Document
How to Read a Httponly Cookie Using JavaScript