Home | History | Annotate | Download | only in articles
      1 <h1>Extending DevTools</h1>
      2 
      3 <h2 id="overview">Overview</h2>
      4 
      5 <p>A DevTools extension adds functionality to the Chrome DevTools. It can add new
      6 UI panels and sidebars, interact with the inspected page, get information about
      7 network requests, and more. DevTools extensions have access to an additional set
      8 of DevTools-specific extension APIs:</p>
      9 
     10 <ul>
     11 <li><code>$(ref:devtools.inspectedWindow)</code></li>
     12 <li><code>$(ref:devtools.network)</code></a></li>
     13 <li><code>$(ref:devtools.panels)</code></a></li>
     14 </ul>
     15 
     16 <p>A DevTools extension is structured like any other extension: it can have a
     17 background page, content scripts, and other items. In addition, each DevTools
     18 extension has a DevTools page, which has access to the DevTools APIs.</p>
     19 
     20 <p><img src="{{static}}/images/devtools-extension.png" alt="Architecture diagram
     21 showing DevTools page communicating with the inspected window and the background
     22 page. The background page is shown communicating with the content scripts and
     23 accessing extension APIs. The DevTools page has access to the DevTools APIs, for
     24 example, creating panels."/></p>
     25 
     26 <h2 id="devtools-page">The DevTools Page</h2>
     27 
     28 <p>An instance of the extension's DevTools page is created each time a DevTools
     29 window opens. The DevTools page exists for the lifetime of the DevTools window.
     30 The DevTools page has access to the DevTools APIs and a limited set of extension
     31 APIs. Specifically, the DevTools page can:</p>
     32 
     33 <ul>
     34 
     35 <li>Create and interact with panels using the <code>$(ref:devtools.panels)</code>
     36 APIs.</li>
     37 
     38 <li>Get information about the inspected window and evaluate code in the inspected
     39 window using the <code>$(ref:devtools.inspectedWindow)</code> APIs.</li>
     40 
     41 <li>Get information about network requests using the <code>$(ref:devtools.network)</code>
     42 APIs.</li>
     43 
     44 </ul>
     45 
     46 <p>The DevTools page cannot use most of the extensions APIs directly.  It has
     47 access to the same subset of the <code>$(ref:extension)</code>
     48 and <code>$(ref:runtime)</code>
     49 APIs that a content script has access to. Like a content script, a DevTools page
     50 can communicate with the background page using <a href="messaging.html">Message Passing</a>.
     51 For an example, see <a href="#injecting">Injecting a Content Script</a>.</p>
     52 
     53 <h2 id="creating">Creating a DevTools Extension</h2>
     54 
     55 <p>To create a DevTools page for your extension, add the <code>devtools_page</code>
     56 field in the extension manifest:</p>
     57 
     58 <pre>
     59 {
     60   "name": ...
     61   "version": "1.0",
     62   "minimum_chrome_version": "10.0",
     63   "devtools_page": "devtools.html",
     64   ...
     65 }
     66 </pre>
     67 
     68 <p>An instance of the <code>devtools_page</code> specified in your extension's
     69 manifest is created for every DevTools window opened. The page may add other
     70 extension pages as panels and sidebars to the DevTools window using the
     71 <code>$(ref:devtools.panels)</code> API.</p>
     72 
     73 <p class="note">The <code>devtools_page</code> field must point to an HTML page.
     74 This differs from the <code>background</code> field, used for specifying a background page,
     75 which lets you specify JavaScript files directly.</p>
     76 
     77 <p>The <code>chrome.devtools.*</code> API modules are available only to the pages
     78 loaded within the DevTools window. Content scripts and other extension pages do not
     79 have these APIs. Thus, the APIs are available only through the lifetime of the
     80 DevTools window.</p>
     81 
     82 <p>
     83 There are also some DevTools APIs that are still experimental.
     84 Refer to <a href="http://developer.chrome.com/extensions/experimental.html">chrome.experimental.*
     85 APIs</a> for the list of
     86 experimental APIs and guidelines on how to use them.</p>
     87 
     88 <h2 id="devtools-ui">DevTools UI Elements: Panels and Sidebar Panes</h2>
     89 
     90 <p>
     91 In addition to the usual extension UI elements, such as browser actions, context
     92 menus and popups, a DevTools extension can add UI elements to the DevTools window:</p>
     93 
     94 <ul>
     95 
     96 <li>A <em>panel</em> is a top-level tab, like the Elements, Sources, and Network
     97 panels.</li>
     98 
     99 <li>A <em>sidebar pane</em> presents supplementary UI related to a panel. The
    100 Styles, Computed Styles, and Event Listeners panes on the Elements panel are
    101 examples of sidebar panes. Currently your extension can only add sidebar panes to
    102 the Elements panel. (Note that the appearance of sidebar panes may not match the
    103 image, depending on the version of Chrome you're using, and where the DevTools
    104 window is docked.)</li>
    105 
    106 </ul>
    107 
    108 <img src="{{static}}/images/devtools-extension-ui.png"
    109     alt="DevTools window showing Elements panel and Styles sidebar pane." />
    110 <p>
    111 Each panel is its own HTML file, which can include other resources (JavaScript, CSS,
    112 images, and so on). Creating a basic panel looks like this:
    113 </p>
    114 <pre>
    115 chrome.devtools.panels.create("My Panel",
    116     "MyPanelIcon.png",
    117     "Panel.html",
    118     function(panel) {
    119       // code invoked on panel creation
    120     }
    121 );
    122 </pre>
    123 
    124 <p>JavaScript executed in a panel or sidebar pane has access to the the same APIs
    125 as the DevTools page.</p>
    126 
    127 <p>Creating a basic sidebar pane for the Elements panel looks like this:</p>
    128 <pre>
    129 chrome.devtools.panels.elements.createSidebarPane("My Sidebar",
    130     function(sidebar) {
    131         // sidebar initialization code here
    132         sidebar.setObject({ some_data: "Some data to show" });
    133 });</pre>
    134 <p>There are several ways to display content in a sidebar pane:</p>
    135 
    136 <ul>
    137 
    138 <li><p>HTML content. Call
    139 <code>$(ref:devtools.panels.ExtensionSidebarPane.setPage setPage)</code> to specify
    140 an HTML page to display in the pane.</p></li>
    141 
    142 
    143 <li><p>JSON data. Pass a JSON object to
    144 <code>$(ref:devtools.panels.ExtensionSidebarPane.setObject setObject)</code>.
    145 </p></li>
    146 
    147 <li><p>JavaScript expression.  Pass an expression to
    148 <code>$(ref:devtools.panels.ExtensionSidebarPane.setExpression setExpression)</code>.
    149 DevTools evaluates the expression in the context of the inspected page, and
    150 displays the return value.</p></li>
    151 
    152 </ul>
    153 
    154 <p>For both <code>setObject</code> and <code>setExpression</code>,
    155 the pane displays the value as it would appear in the DevTools console.
    156 However, <code>setExpression</code> lets you display DOM elements and arbitrary
    157 JavaScript objects, while <code>setObject</code> only supports JSON objects.</p>
    158 
    159 <h2 id="solutions">Communicating Between Extension Components</h2>
    160 <p>The following sections describe some typical scenarios for communicating between
    161 the different components of a DevTools extension.</p>
    162 
    163 <h3 id="injecting">Injecting a Content Script</h3>
    164 
    165 <p>The DevTools page can't call <code>$(ref:tabs.executeScript)</code> directly.
    166 To inject a content script from the DevTools page, you must retrieve the ID
    167 of the inspected window's tab using the <code>$(ref:inspectedWindow.tabId)</code>
    168 property and send a message to the background page. From the background page,
    169 call <code>$(ref:tabs.executeScript)</code> to inject the script.</p>
    170 
    171 <p class="note">If a content script has already been injected, you can add
    172 additional context scripts using the <code>eval</code> method. See
    173 <a href="#selected-element">Passing the Selected Element to a Content Script</a>
    174 for more information.</p>
    175 
    176 <p>The following code snippets show how to inject a content script using
    177 <code>executeScript</code>.
    178 </p>
    179 
    180 <pre>
    181 // DevTools page -- devtools.js
    182 // Create a connection to the background page
    183 var backgroundPageConnection = chrome.runtime.connect({
    184     name: "devtools-page"
    185 });
    186 
    187 backgroundPageConnection.onMessage.addListener(function (message) {
    188     // Handle responses from the background page, if any
    189 });
    190 
    191 // Relay the tab ID to the background page
    192 chrome.runtime.sendMessage({
    193     tabId: chrome.devtools.inspectedWindow.tabId,
    194     scriptToInject: "content_script.js"
    195 });
    196 </pre>
    197 
    198 <p>Code for the background page:</p>
    199 
    200 <pre>
    201 // Background page -- background.js
    202 chrome.runtime.onConnect.addListener(function(devToolsConnection) {
    203     // assign the listener function to a variable so we can remove it later
    204     var devToolsListener = function(message, sender, sendResponse) {
    205         // Inject a content script into the identified tab
    206         chrome.tabs.executeScript(message.tabId,
    207             { file: message.scriptToInject });
    208     }
    209     // add the listener
    210     devToolsConnection.onMessage.addListener(devToolsListener);
    211 
    212     devToolsConnection.onDisconnect(function() {
    213          devToolsConnection.onMessage.removeListener(devToolsListener);
    214     });
    215 }
    216 </pre>
    217 
    218 <h3 id="evaluating-js">Evaluating JavaScript in the Inspected Window</h3>
    219 
    220 <p>You can use the <code>$(ref:inspectedWindow.eval)</code> method to execute
    221 JavaScript code in the context of the inspected page. You can invoke the
    222 <code>eval</code> method from a DevTools page, panel or sidebar pane.</p>
    223 
    224 <p>By default, the expression is evaluated in the context of the main frame of the
    225 page. Use the <code>useContentScriptContext: true</code> option to evaluate the
    226 expression in the same context as the content scripts.</p>
    227 
    228 <p>Calling <code>eval</code> with <code>useContentScriptContext: true</code> does
    229 not <em>create</em> a content script context, so you must load a context script
    230 before calling <code>eval</code>, either by calling <code>executeScript</code> or
    231 by specifying a content script in the <code>manifest.json</code> file.</p>
    232 
    233 <p>Once the context script context exists, you can use this option to inject
    234 additional content scripts.</p>
    235 
    236 <p class="warning">The <code>eval</code> method is powerful when used in the right
    237 context and dangerous when used inappropriately. Use the
    238 <code>$(ref:tabs.executeScript)</code> method if you don't need access to the
    239 JavaScript context of the inspected page. For detailed cautions and a comparison
    240 of the two methods, see <code>$(ref:inspectedWindow)</code>.</p>
    241 
    242 <h3 id="selected-element">Passing the Selected Element to a Content Script</h3>
    243 
    244 <p>The content script doesn't have direct access to the current selected element.
    245 However, any code you execute using <code>$(ref:inspectedWindow.eval)</code> has
    246 access to the DevTools console and command-line APIs. For example, in evaluated code
    247 you can use <code>$0</code> to access the selected element.</p>
    248 
    249 <p>To pass the selected element to a content script:</p>
    250 
    251 <ul>
    252 
    253 <li>Create a method in the content script that takes the selected element as
    254 an argument.</li>
    255 
    256 <li>Call the method from the DevTools page using <code>$(ref:inspectedWindow.eval)</code>
    257 with the <code>useContentScriptContext: true</code> option. </li>
    258 
    259 </ul>
    260 
    261 <p>The code in your content script might look something like this:</p>
    262 
    263 <pre>
    264 function setSelectedElement(el) {
    265     // do something with the selected element
    266 }
    267 </pre>
    268 
    269 <p>Invoke the method from the DevTools page like this:</p>
    270 
    271 <pre>
    272 chrome.devtools.inspectedWindow.eval("setSelectedElement($0)",
    273     { useContentScriptContext: true });
    274 </pre>
    275 
    276 <p>The <code>useContentScriptContext: true</code> option specifies that the
    277 expression must be evaluated in the same context as the content scripts, so it can
    278 access the <code>setSelectedElement</code> method.</p>
    279 
    280 
    281 <h3 id="content-script-to-devtools">Messaging from Content Scripts to the DevTools Page</h3>
    282 
    283 <p>Messaging between the DevTools page and content scripts is indirect, by way of
    284 the background page. </p>
    285 
    286 <p>When sending a message <em>to</em> a content script, the background page can use
    287 the <code>$(ref:tabs.sendMessage)</code> method, which directs a message to the
    288 content scripts in a specific tab, as shown in
    289 <a href="#injecting">Injecting a Content Script</a>.</p>
    290 
    291 <p>When sending a message <em>from</em> a content script, there is no ready-made
    292 method to deliver a message to the correct DevTools page instance associated with
    293 the current tab.  As a workaround, you can have the DevTools page establish a
    294 long-lived connection with the background page, and have the background page keep a
    295 map of tab IDs to connections, so it can route each message to the correct
    296 connection.</p>
    297 
    298 <pre>// background.js
    299 var connections = {};
    300 
    301 chrome.runtime.onConnect.addListener(function (port) {
    302 
    303     var extensionListener = function (message, sender, sendResponse) {
    304 
    305         // The original connection event doesn't include the tab ID of the
    306         // DevTools page, so we need to send it explicitly.
    307         if (message.name == "init") {
    308           connections[message.tabId] = port;
    309           return;
    310         }
    311 
    312 	// other message handling
    313     }
    314 
    315     // Listen to messages sent from the DevTools page
    316     port.onMessage.addListener(extensionListener);
    317 
    318     port.onDisconnect.addListener(function(port) {
    319         port.onMessage.removeListener(extensionListener);
    320 
    321         var tabs = Object.keys(connections);
    322         for (var i=0, len=tabs.length; i &lt; len; i++) {
    323           if (connections[tabs[i]] == port) {
    324             delete connections[tabs[i]]
    325             break;
    326           }
    327         }
    328     });
    329 });
    330 
    331 // Receive message from content script and relay to the devTools page for the
    332 // current tab
    333 chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    334     // Messages from content scripts should have sender.tab set
    335     if (sender.tab) {
    336       var tabId = sender.tab.id;
    337       if (tabId in connections) {
    338         connections[tabId].postMessage(request);
    339       } else {
    340         console.log("Tab not found in connection list.");
    341       }
    342     } else {
    343       console.log("sender.tab not defined.");
    344     }
    345     return true;
    346 });
    347 </pre>
    348 
    349 <p>The DevTools page (or panel or sidebar pane) establishes the connection like
    350 this:</p>
    351 
    352 <pre>
    353 // Create a connection to the background page
    354 var backgroundPageConnection = chrome.runtime.connect({
    355     name: "panel"
    356 });
    357 
    358 backgroundPageConnection.postMessage({
    359     name: 'init',
    360     tabId: chrome.devtools.inspectedWindow.tabId
    361 });
    362 </pre>
    363 
    364 <h3 id="detecting-open-close">Detecting When DevTools Opens and Closes</h3>
    365 
    366 <p>If your extension needs to track whether the DevTools window is open, you can
    367 add an $(ref:runtime.onConnect onConnect) listener to the background page, and call
    368 $(ref:runtime.connect connect) from the DevTools page. Since each tab can have its
    369 own DevTools window open, you may receive multiple connect events. To track whether
    370 any DevTools window is open, you need to count the connect and disconnect events as
    371 shown below:</p>
    372 
    373 <pre>// background.js
    374 var openCount = 0;
    375 chrome.runtime.onConnect.addListener(function (port) {
    376     if (port.name == "devtools-page") {
    377       if (openCount == 0) {
    378         alert("DevTools window opening.");
    379       }
    380       openCount++;
    381    
    382       port.onDisconnect.addListener(function(port) {
    383           openCount--;
    384           if (openCount == 0) {
    385             alert("Last DevTools window closing.");
    386           }
    387       });
    388     }
    389 });</pre>
    390 
    391 <p>The DevTools page creates a connection like this:</p>
    392 
    393 <pre>
    394 // devtools.js
    395 
    396 // Create a connection to the background page
    397 var backgroundPageConnection = chrome.runtime.connect({
    398     name: "devtools-page"
    399 });
    400 </pre>
    401 
    402 <h2 id="more">More information</h2>
    403 
    404 <p>For information on the standard APIs that extensions can use, see
    405 <a href="http://developer.chrome.com/extensions/api_index.html">chrome.* APIs</a>
    406 and <a href="http://developer.chrome.com/extensions/api_other.html">Other APIs</a>.
    407 </p>
    408 
    409 <p>
    410 <a href="http://groups.google.com/group/google-chrome-developer-tools/topics">Give
    411 us feedback!</a>
    412 Your comments and suggestions help us improve the APIs.</p>
    413 
    414 <h2 id="examples">Examples</h2>
    415 
    416 <p>You can find examples that use DevTools APIs in
    417 <a href="http://developer.chrome.com/extensions/samples.html#devtools">Samples</a>.
    418 </p>
    419