Contexts and methods for communication between the browser action, background scripts, and content scripts of chrome extensions?

I think chrome extensions are overall quite simple and very powerful, but one thing that always confuses me is trying to communicate between the various script that code may run in. There's the code that runs when referenced from the "default_popup" page of the browser action, the code in the "scripts" property of "background" and the content scripts.

In what context are scripts in these categories run, and how can each communicate with the others?


Three different contexts

As a Chrome extension developer, you can distinguish three different environments.

  1. 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.
  2. Content scripts, running in the tab's process.
  3. 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.


The Google documentation has everything, but it's hard to pull all the information together. There are two main types of scripts:
1. Background scripts have full access to the Chrome api's, but can't interact with the target webpage.
2. Content scripts can interact with each other and with the webpage's DOM (but not its scripts, from which it's isolated), but have only limited access to the Chrome api's.
Both run whenever you load a new page (unless you've used "matches" to restrict where the content script runs).

You can communicate between the two via message passing. This is more easily done from content scripts than from background scripts because you need to know the tab's id for the latter.

Other scripts (browserAction.js, pageAction.js, optionsPage.js) only run when their corresponding html page is opened (as if you are opening the webpage in your browser window, which is what you're really doing). They are similar to background scripts in restrictions and abilities.

Try to avoid the need to interact with a page's scripts. The best way I know of is to interact through the shared DOM (literally writing javascript code inside html comments). But the target of your extension isn't designed for this, so you'll have to include you're own script that does this into the webpage. Use a content script to write the script element into the document (its src is
chrome.extension.getURL("myscript.js"),
and you'll need to have
"web_accessible_resources": ["myscript.js"]
in your manifest.