Extensiones de Chrome: entorno de background y paso de mensajes

Caution: Consider using event pages instead. Learn more.

A common need for extensions is to have a single long-running script to manage some task or state. Background pages to the rescue.

As the architecture overview explains, the background page is an HTML page that runs in the extension process. It exists for the lifetime of your extension, and only one instance of it at a time is active.

In a typical extension with a background page, the UI — for example, the browser action or page action and any options page — is implemented by dumb views. When the view needs some state, it requests the state from the background page. When the background page notices a state change, the background page tells the views to update.

Manifest

Register your background page in the extension manifest. In the common case, a background page does not require any HTML markup. These kind of background pages can be implemented using JavaScript files alone, like this:

{
  "name": "My extension",
  ...
  "background": {
    "scripts": ["background.js"]
  },
  ...
}

A background page will be generated by the extension system that includes each of the files listed in the scripts property.

If you need to specify HTML in your background page, you can do that using the page property instead:

{
  "name": "My extension",
  ...
  "background": {
    "page": "background.html"
  },
  ...
}

If you need the browser to start up early—so you can display notifications, for example—then you might also want to specify the“background” permission.

Details

You can communicate between your various pages using direct script calls, similar to how frames can communicate. The extension.getViews method returns a list of window objects for every active page belonging to your extension, and the extension.getBackgroundPage method returns the background page.

Example

The following code snippet demonstrates how the background page can interact with other pages in the extension. It also shows how you can use the background page to handle events such as user clicks.

The extension in this example has a background page and multiple pages created (with tabs.create) from a file named image.html.

//In background.js:
// React when a browser action's icon is clicked.
chrome.browserAction.onClicked.addListener(function(tab) {
  var viewTabUrl = chrome.extension.getURL('image.html');
  var imageUrl = /* an image's URL */;

  // Look through all the pages in this extension to find one we can use.
  var views = chrome.extension.getViews();
  for (var i = 0; i < views.length; i++) {
    var view = views[i];

    // If this view has the right URL and hasn't been used yet...
    if (view.location.href == viewTabUrl && !view.imageAlreadySet) {

      // ...call one of its functions and set a property.
      view.setImageUrl(imageUrl);
      view.imageAlreadySet = true;
      break; // we're done
    }
  }
});

//In image.html:
<html>
  <script>
    function setImageUrl(url) {
      document.getElementById('target').src = url;
    }
  </script>

  <body>
    <p>
    Image here:
    </p>

    <img id="target" src="white.png" width="640" height="480">

  </body>
</html>

Message Passing

Since content scripts run in the context of a web page and not the extension, they often need some way of communicating with the rest of the extension. For example, an RSS reader extension might use content scripts to detect the presence of an RSS feed on a page, then notify the background page in order to display a page action icon for that page.

Communication between extensions and their content scripts works by using message passing. Either side can listen for messages sent from the other end, and respond on the same channel. A message can contain any valid JSON object (null, boolean, number, string, array, or object). There is a simple API for one-time requests and a more complex API that allows you to have long-lived connections for exchanging multiple messages with a shared context. It is also possible to send a message to another extension if you know its ID, which is covered in the cross-extension messages section.

Simple one-time requests

If you only need to send a single message to another part of your extension (and optionally get a response back), you should use the simplified runtime.sendMessage or tabs.sendMessage methods. This lets you send a one-time JSON-serializable message from a content script to extension, or vice versa, respectively. An optional callback parameter allows you handle the response from the other side, if there is one.

Sending a request from a content script looks like this:

contentscript.js
================
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
  console.log(response.farewell);
});

Sending a request from the extension to a content script looks very similar, except that you need to specify which tab to send it to. This example demonstrates sending a message to the content script in the selected tab.

background.html
===============
chrome.tabs.getSelected(null, function(tab) {
  chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
    console.log(response.farewell);
  });
});

On the receiving end, you need to set up an runtime.onMessage event listener to handle the message. This looks the same from a content script or extension page.

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting == "hello")
      sendResponse({farewell: "goodbye"});
  });

Note: If multiple pages are listening for onMessage events, only the first to call sendResponse() for a particular event will succeed in sending the response. All other responses to that event will be ignored.

Long-lived connections

Sometimes it’s useful to have a conversation that lasts longer than a single request and response. In this case, you can open a long-lived channel from your content script to an extension page, or vice versa, using runtime.connect or tabs.connect respectively. The channel can optionally have a name, allowing you to distinguish between different types of connections.

One use case might be an automatic form fill extension. The content script could open a channel to the extension page for a particular login, and send a message to the extension for each input element on the page to request the form data to fill in. The shared connection allows the extension to keep shared state linking the several messages coming from the content script.

When establishing a connection, each end is given a runtime.Port object which is used for sending and receiving messages through that connection.

Here is how you open a channel from a content script, and send and listen for messages:

contentscript.js
================
var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
  if (msg.question == "Who's there?")
    port.postMessage({answer: "Madame"});
  else if (msg.question == "Madame who?")
    port.postMessage({answer: "Madame... Bovary"});
});

Sending a request from the extension to a content script looks very similar, except that you need to specify which tab to connect to. Simply replace the call to connect in the above example with tabs.connect.

In order to handle incoming connections, you need to set up a runtime.onConnect event listener. This looks the same from a content script or an extension page. When another part of your extension calls “connect()”, this event is fired, along with the runtime.Portobject you can use to send and receive messages through the connection. Here’s what it looks like to respond to incoming connections:

chrome.runtime.onConnect.addListener(function(port) {
  console.assert(port.name == "knockknock");
  port.onMessage.addListener(function(msg) {
    if (msg.joke == "Knock knock")
      port.postMessage({question: "Who's there?"});
    else if (msg.answer == "Madame")
      port.postMessage({question: "Madame who?"});
    else if (msg.answer == "Madame... Bovary")
      port.postMessage({question: "I don't get it."});
  });
});

You may want to find out when a connection is closed, for example if you are maintaining separate state for each open port. For this you can listen to the runtime.Port event. This event is fired either when the other side of the channel manually calls runtime.Port, or when the page containing the port is unloaded (for example if the tab is navigated). onDisconnect is guaranteed to be fired only once for any given port.

Cross-extension messaging

In addition to sending messages between different components in your extension, you can use the messaging API to communicate with other extensions. This lets you expose a public API that other extensions can take advantage of.

Listening for incoming requests and connections is similar to the internal case, except you use the runtime.onMessageExternal orruntime.onConnectExternal methods. Here’s an example of each:

// For simple requests:
chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.id == blacklistedExtension)
      return;  // don't allow this extension access
    else if (request.getTargetData)
      sendResponse({targetData: targetData});
    else if (request.activateLasers) {
      var success = activateLasers();
      sendResponse({activateLasers: success});
    }
  });

// For long-lived connections:
chrome.runtime.onConnectExternal.addListener(function(port) {
  port.onMessage.addListener(function(msg) {
    // See other examples for sample onMessage handlers.
  });
});

Likewise, sending a message to another extension is similar to sending one within your extension. The only difference is that you must pass the ID of the extension you want to communicate with. For example:

// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// Make a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
  function(response) {
    if (targetInRange(response.targetData))
      chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
  });

// Start a long-running conversation:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);

Security considerations

When receiving a message from a content script or another extension, your background page should be careful not to fall victim to cross-site scripting. Specifically, avoid using dangerous APIs such as the below:

background.html
===============
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be evaluating an evil script!
  var resp = eval("(" + response.farewell + ")");
});

background.html
===============
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be injecting a malicious script!
  document.getElementById("resp").innerHTML = response.farewell;
});

Instead, prefer safer APIs that do not run scripts:

background.html
===============
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // JSON.parse does not evaluate the attacker's scripts.
  var resp = JSON.parse(response.farewell);
});

background.html
===============
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // innerText does not let the attacker inject HTML elements.
  document.getElementById("resp").innerText = response.farewell;
});

Examples

You can find simple examples of communication via messages in the examples/api/messaging directory. Also see the contentscript_xhr example, in which a content script and its parent extension exchange messages, so that the parent extension can perform cross-site requests on behalf of the content script. For more examples and for help in viewing the source code, see Samples.