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.

  1. Extension code, runs in the process of your Chrome extension
  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="https://stackoverflow.com/questions/17246133/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.

Leave a Comment