Extend DevTools

DevTools extensions add functionality to Chrome DevTools by accessing DevTools-specific extension APIs through a DevTools page added to the extension.

Architecture diagram showing DevTools page communicating with the
         inspected window and the service worker. The service worker is shown
         communicating with the content scripts and accessing extension APIs.
         The DevTools page has access to the DevTools APIs, for example, creating panels.
DevTools extension architecture.

The DevTools-specific extension APIs include the following:

The DevTools page

When a DevTools window opens, a DevTools extension creates an instance of its DevTools page that exists as long as the window is open. This page has access to the DevTools APIs and some other extension APIs, and can do the following:

The DevTools page can't use most extensions APIs directly. Instead, it uses the same subset of the extension and runtime APIs as content scripts do, and communicates with the service worker using message passing. For an example, see Inject a Content Script.

Create a DevTools extension

To create a DevTools page for your extension, add the devtools_page field in the extension manifest:

{
  "name": ...
  "version": "1.0",
  "devtools_page": "devtools.html",
  ...
}

The devtools_page field must point to an HTML page. Because the DevTools page must be local to your extension, we recommend specifying it it using a relative URL.

The members of the chrome.devtools API are available only to the pages loaded within the DevTools window while that window is open. Content scripts and other extension pages don't have access to these APIs.

DevTools UI elements: panels and sidebar panes

In addition to the usual extension UI elements, such as browser actions, context menus and popups, a DevTools extension can add UI elements to the DevTools window:

  • A panel is a top-level tab, like the Elements, Sources, and Network panels.
  • A sidebar pane presents supplementary UI related to a panel. The Styles, Computed Styles, and Event Listeners panes on the Elements panel are examples of sidebar panes. Depending on the version of Chrome you're using and where the DevTools window is docked, your sidebar panes might look like the following example image:
DevTools window showing Elements panel and Styles sidebar pane.
DevTools window showing Elements panel and Styles sidebar pane.

Each panel is its own HTML file, which can include other resources (JavaScript, CSS, images, and so on). To create a basic panel, use the following code:

chrome.devtools.panels.create("My Panel",
    "MyPanelIcon.png",
    "Panel.html",
    function(panel) {
      // code invoked on panel creation
    }
);

JavaScript executed in a panel or sidebar pane has access to the same APIs as the DevTools page.

To create a basic sidebar pane, use the following code:

chrome.devtools.panels.elements.createSidebarPane("My Sidebar",
    function(sidebar) {
        // sidebar initialization code here
        sidebar.setObject({ some_data: "Some data to show" });
});

There are several ways to display content in a sidebar pane:

  • HTML content: Call setPage() to specify an HTML page to display in the pane.
  • JSON data: Pass a JSON object to setObject().
  • JavaScript expression: Pass an expression to setExpression(). DevTools evaluates the expression in the context of the inspected page, then displays the return value.

For both setObject() and setExpression(), the pane displays the value as it would appear in the DevTools console. However, setExpression() lets you display DOM elements and arbitrary JavaScript objects, while setObject() only supports JSON objects.

Communicate between extension components

The following sections describe some helpful ways to allow DevTools extension components to communicate with each other.

Inject a content script

The DevTools page can't call scripting.executeScript() directly. To inject a content script from the DevTools page, you must retrieve the inspected window's tab ID using the inspectedWindow.tabId property and send a message to the background page. From the background page, call scripting.executeScript() to inject the script.

The following code snippets show how to inject a content script using executeScript.

// DevTools page -- devtools.js
// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
    name: "devtools-page"
});

backgroundPageConnection.onMessage.addListener(function (message) {
    // Handle responses from the background page, if any
});

// Relay the tab ID to the background page
backgroundPageConnection.postMessage({
    tabId: chrome.devtools.inspectedWindow.tabId,
    scriptToInject: "content_script.js"
});

Code for the background page:

// Background page -- background.js
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
    // assign the listener function to a variable so we can remove it later
    var devToolsListener = function(message, sender, sendResponse) {
        // Inject a content script into the identified tab
        chrome.scripting.executeScript({
          target: {tabId: message.tabId},
          files: [message.scriptToInject]
    });
    }
    // add the listener
    devToolsConnection.onMessage.addListener(devToolsListener);

    devToolsConnection.onDisconnect.addListener(function() {
         devToolsConnection.onMessage.removeListener(devToolsListener);
    });
});

If a content script has already been injected, you can add additional context scripts using the eval() method. See Pass the selected element to a content script for more information.

Evaluate JavaScript in the inspected window

You can use the inspectedWindow.eval() method to execute JavaScript code in the context of the inspected page. You can invoke the eval() method from a DevTools page, panel, or sidebar pane.

By default, the expression is evaluated in the context of the main frame of the page. inspectedWindow.eval() uses the same script execution context and options as code entered in the DevTools console, which allows access to DevTools Console Utilities API features when using eval(). For example, SOAK uses it for inspecting an element:

chrome.devtools.inspectedWindow.eval(
  "inspect($$('head script[data-soak=main]')[0])",
  function(result, isException) { }
);

You can also set the useContentScriptContext to true when calling inspectedWindow.eval() to evaluate the expression in the same context as the content scripts. To use this option, use a static content script declaration before calling eval(), either by calling executeScript() or by specifying a content script in the manifest.json file. After the context script context loads, you can also use this option to inject additional content scripts.

Pass the selected element to a content script

The content script doesn't have direct access to the current selected element. However, any code you execute using inspectedWindow.eval() has access to the DevTools console and Console Utilities APIs. For example, in evaluated code you can use $0 to access the selected element.

To pass the selected element to a content script:

  1. Create a method in the content script that takes the selected element as an argument.
  function setSelectedElement(el) {
      // do something with the selected element
  }
  1. Call the method from the DevTools page using inspectedWindow.eval() with the useContentScriptContext: true option.

    chrome.devtools.inspectedWindow.eval("setSelectedElement($0)",
        { useContentScriptContext: true });
    

The useContentScriptContext: true option specifies that the expression must be evaluated in the same context as the content scripts, so it can access the setSelectedElement method.

Get a reference panel's window

To call postMessage() from a devtools panel, you'll need a reference to its window object. Get a panel's iframe window from the panel.onShown event handler:

extensionPanel.onShown.addListener(function (extPanelWindow) {
    extPanelWindow instanceof Window; // true
    extPanelWindow.postMessage( // …
});

Send messages between content scripts and the DevTools page

Use the service worker to send messages between the DevTools page and content scripts.

To send a message to a content script, the service worker calls tabs.sendMessage(), which directs a message to the content scripts in a specific tab, as shown in Injecting a Content Script.

To send a message from a content script, you'll need to establish a connection between the DevTools page and the service worker, and create a map of tab IDs to connections so the service worker can route each message to the correct connection.

// background.js
var connections = {};

chrome.runtime.onConnect.addListener(function (port) {

    var extensionListener = function (message, sender, sendResponse) {

        // The original connection event doesn't include the tab ID of the
        // DevTools page, so we need to send it explicitly.
        if (message.name == "init") {
          connections[message.tabId] = port;
          return;
        }

    // other message handling
    }

    // Listen to messages sent from the DevTools page
    port.onMessage.addListener(extensionListener);

    port.onDisconnect.addListener(function(port) {
        port.onMessage.removeListener(extensionListener);

        var tabs = Object.keys(connections);
        for (var i=0, len=tabs.length; i < len; i++) {
          if (connections[tabs[i]] == port) {
            delete connections[tabs[i]]
            break;
          }
        }
    });
});

// Receive message from content script and relay to the devTools page for the
// current tab
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    // Messages from content scripts should have sender.tab set
    if (sender.tab) {
      var tabId = sender.tab.id;
      if (tabId in connections) {
        connections[tabId].postMessage(request);
      } else {
        console.log("Tab not found in connection list.");
      }
    } else {
      console.log("sender.tab not defined.");
    }
    return true;
});

The DevTools page (or panel or sidebar pane) establishes the connection like this:

// Create a connection to the service worker
var backgroundPageConnection = chrome.runtime.connect({
    name: "panel"
});

backgroundPageConnection.postMessage({
    name: 'init',
    tabId: chrome.devtools.inspectedWindow.tabId
});

Send messages from injected scripts to the DevTools page

Code injected directly into the page without a content script, including by appending a <script> tag or calling inspectedWindow.eval(), can't send messages to the DevTools page using runtime.sendMessage(). Instead, we recommend combining your injected script wtih a content script that can act as an intermediary, and using the window.postMessage() method. The following example uses the background script from the previous section:

// injected-script.js

window.postMessage({
  greeting: 'hello there!',
  source: 'my-devtools-extension'
}, '*');
// content-script.js

window.addEventListener('message', function(event) {
  // Only accept messages from the same frame
  if (event.source !== window) {
    return;
  }

  var message = event.data;

  // Only accept messages that we know are ours
  if (typeof message !== 'object' || message === null ||
      message.source !== 'my-devtools-extension') {
    return;
  }

  chrome.runtime.sendMessage(message);
});

The message is sent from the injected script, to the content script, to the background script, and finally to the DevTools page.

Other alternative message-passing techniques can be found here.

Detect when DevTools opens and closes

To track whether the DevTools window is open, add an onConnect listener to the service worker and call connect() from the DevTools page. Because each tab can have its own DevTools window open, you might receive multiple connect events. To track whether any DevTools window is open, count the connect and disconnect events as shown in the following example:

// background.js
var openCount = 0;
chrome.runtime.onConnect.addListener(function (port) {
    if (port.name == "devtools-page") {
      if (openCount == 0) {
        alert("DevTools window opening.");
      }
      openCount++;

      port.onDisconnect.addListener(function(port) {
          openCount--;
          if (openCount == 0) {
            alert("Last DevTools window closing.");
          }
      });
    }
});

The DevTools page creates a connection like this:

// devtools.js

// Create a connection to the service worker
var backgroundPageConnection = chrome.runtime.connect({
    name: "devtools-page"
});

DevTools extension examples

The examples on this page come from the following pages:

  • Polymer Devtools Extension - Uses many helpers running in the host page to query DOM/JS state to send back to the custom panel.
  • React DevTools Extension - Uses a submodule of the renderer to reuse DevTools UI components.
  • Ember Inspector - Shared extension core with adapters for both Chrome and Firefox.
  • Coquette-inspect - A clean React-based extension with a debugging agent injected into the host page.
  • Sample Extensions have more worthwhile extensions to install, try out, and learn from.

More information

For information on the standard APIs that extensions can use, see chrome.* APIs and web APIs.

Give us feedback! Your comments and suggestions help us improve the APIs.

Examples

You can find examples that use DevTools APIs in Samples.