Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / common / extensions / docs / templates / articles / devtools.html
blob4ad7cbbf58f0f114aa136856debd0308442a248d
1 <h1>Extending DevTools</h1>
3 <h2 id="overview">Overview</h2>
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. View <a href="https://developer.chrome.com/devtools/docs/extensions-gallery">featured DevTools extensions</a>. DevTools extensions have access to an additional set
8 of DevTools-specific extension APIs:</p>
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>
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>
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>
26 <h2 id="devtools-page">The DevTools Page</h2>
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>
33 <ul>
35 <li>Create and interact with panels using the <code>$(ref:devtools.panels)</code>
36 APIs.</li>
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>
41 <li>Get information about network requests using the <code>$(ref:devtools.network)</code>
42 APIs.</li>
44 </ul>
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>
53 <h2 id="creating">Creating a DevTools Extension</h2>
55 <p>To create a DevTools page for your extension, add the <code>devtools_page</code>
56 field in the extension manifest:</p>
58 <pre>
60 "name": ...
61 "version": "1.0",
62 "minimum_chrome_version": "10.0",
63 "devtools_page": "devtools.html",
64 ...
66 </pre>
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>
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>
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>
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>
88 <h2 id="devtools-ui">DevTools UI Elements: Panels and Sidebar Panes</h2>
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>
94 <ul>
96 <li>A <em>panel</em> is a top-level tab, like the Elements, Sources, and Network
97 panels.</li>
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>
106 </ul>
108 <img src="{{static}}/images/devtools-extension-ui.png"
109 alt="DevTools window showing Elements panel and Styles sidebar pane." />
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
122 </pre>
124 <p>JavaScript executed in a panel or sidebar pane has access to the the same APIs
125 as the DevTools page.</p>
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>
136 <ul>
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>
143 <li><p>JSON data. Pass a JSON object to
144 <code>$(ref:devtools.panels.ExtensionSidebarPane.setObject setObject)</code>.
145 </p></li>
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>
152 </ul>
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>
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>
163 <h3 id="injecting">Injecting a Content Script</h3>
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>
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>
176 <p>The following code snippets show how to inject a content script using
177 <code>executeScript</code>.
178 </p>
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"
187 backgroundPageConnection.onMessage.addListener(function (message) {
188 // Handle responses from the background page, if any
191 // Relay the tab ID to the background page
192 chrome.runtime.sendMessage({
193 tabId: chrome.devtools.inspectedWindow.tabId,
194 scriptToInject: "content_script.js"
196 </pre>
198 <p>Code for the background page:</p>
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 });
209 // add the listener
210 devToolsConnection.onMessage.addListener(devToolsListener);
212 devToolsConnection.onDisconnect.addListener(function() {
213 devToolsConnection.onMessage.removeListener(devToolsListener);
216 </pre>
218 <h3 id="evaluating-js">Evaluating JavaScript in the Inspected Window</h3>
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>
224 <p>By default, the expression is evaluated in the context of the main frame of the
225 page. Now, you may be familiar with the DevTools <a href="https://developer.chrome.com/devtools/docs/commandline-api">commandline API</a> features like element inspection (<code>inspect(elem)</code>), breaking on functions (<code>debug(fn)</code>), copying to clipboard (<code>copy()</code>) and more. <code>inspectedWindow.eval()</code> uses the same script execution context and options as the code typed at the DevTools console, which allows access to these APIs within the eval. For example, <a href="https://github.com/RedRibbon/SOAK/blob/ffdfad68ffb6051fa2d4e9db0219b3d234ac1ae8/pages/devtools.js#L6-L8">SOAK</a> uses it for inspecting an element:
227 <pre>
228 chrome.devtools.inspectedWindow.eval(
229 "inspect($$('head script[data-soak=main]')[0])",
230 function(result, isException) { }
232 </pre>
235 <p>Alternatively, use the <code>useContentScriptContext: true</code> option for <code>inspectedWindow.eval()</code> to evaluate the
236 expression in the same context as the content scripts. Calling <code>eval</code> with <code>useContentScriptContext: true</code> does
237 not <em>create</em> a content script context, so you must load a context script
238 before calling <code>eval</code>, either by calling <code>executeScript</code> or
239 by specifying a content script in the <code>manifest.json</code> file.</p>
241 <p>Once the context script context exists, you can use this option to inject
242 additional content scripts.</p>
244 <p class="warning">The <code>eval</code> method is powerful when used in the right
245 context and dangerous when used inappropriately. Use the
246 <code>$(ref:tabs.executeScript)</code> method if you don't need access to the
247 JavaScript context of the inspected page. For detailed cautions and a comparison
248 of the two methods, see <code>$(ref:inspectedWindow)</code>.</p>
250 <h3 id="selected-element">Passing the Selected Element to a Content Script</h3>
252 <p>The content script doesn't have direct access to the current selected element.
253 However, any code you execute using <code>$(ref:inspectedWindow.eval)</code> has
254 access to the DevTools console and command-line APIs. For example, in evaluated code
255 you can use <code>$0</code> to access the selected element.</p>
257 <p>To pass the selected element to a content script:</p>
259 <ul>
261 <li>Create a method in the content script that takes the selected element as
262 an argument.</li>
264 <li>Call the method from the DevTools page using <code>$(ref:inspectedWindow.eval)</code>
265 with the <code>useContentScriptContext: true</code> option. </li>
267 </ul>
269 <p>The code in your content script might look something like this:</p>
271 <pre>
272 function setSelectedElement(el) {
273 // do something with the selected element
275 </pre>
277 <p>Invoke the method from the DevTools page like this:</p>
279 <pre>
280 chrome.devtools.inspectedWindow.eval("setSelectedElement($0)",
281 { useContentScriptContext: true });
282 </pre>
284 <p>The <code>useContentScriptContext: true</code> option specifies that the
285 expression must be evaluated in the same context as the content scripts, so it can
286 access the <code>setSelectedElement</code> method.</p>
289 <h3 id="panel-window">Getting a reference panel's <code>window</code></h3>
291 To <code>postMessage</code> from a devtools panel, you'll need a reference to its <code>window</code> object. Get a panel's iframe window in from the the <a href="http://developer.chrome.com/extensions/devtools.panels.html#event-ExtensionPanel-onShown"><code>panel.onShown</code></a> event handler:
293 <pre>
294 onShown.addListener(function callback)
295 extensionPanel.onShown.addListener(function (extPanelWindow) {
296 extPanelWindow instanceof Window; // true
297 extPanelWindow.postMessage( // …
299 </pre>
302 <h3 id="content-script-to-devtools">Messaging from Content Scripts to the DevTools Page</h3>
304 <p>Messaging between the DevTools page and content scripts is indirect, by way of
305 the background page. </p>
307 <p>When sending a message <em>to</em> a content script, the background page can use
308 the <code>$(ref:tabs.sendMessage)</code> method, which directs a message to the
309 content scripts in a specific tab, as shown in
310 <a href="#injecting">Injecting a Content Script</a>.</p>
312 <p>When sending a message <em>from</em> a content script, there is no ready-made
313 method to deliver a message to the correct DevTools page instance associated with
314 the current tab. As a workaround, you can have the DevTools page establish a
315 long-lived connection with the background page, and have the background page keep a
316 map of tab IDs to connections, so it can route each message to the correct
317 connection.</p>
319 <pre>// background.js
320 var connections = {};
322 chrome.runtime.onConnect.addListener(function (port) {
324 var extensionListener = function (message, sender, sendResponse) {
326 // The original connection event doesn't include the tab ID of the
327 // DevTools page, so we need to send it explicitly.
328 if (message.name == "init") {
329 connections[message.tabId] = port;
330 return;
333 // other message handling
336 // Listen to messages sent from the DevTools page
337 port.onMessage.addListener(extensionListener);
339 port.onDisconnect.addListener(function(port) {
340 port.onMessage.removeListener(extensionListener);
342 var tabs = Object.keys(connections);
343 for (var i=0, len=tabs.length; i &lt; len; i++) {
344 if (connections[tabs[i]] == port) {
345 delete connections[tabs[i]]
346 break;
352 // Receive message from content script and relay to the devTools page for the
353 // current tab
354 chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
355 // Messages from content scripts should have sender.tab set
356 if (sender.tab) {
357 var tabId = sender.tab.id;
358 if (tabId in connections) {
359 connections[tabId].postMessage(request);
360 } else {
361 console.log("Tab not found in connection list.");
363 } else {
364 console.log("sender.tab not defined.");
366 return true;
368 </pre>
370 <p>The DevTools page (or panel or sidebar pane) establishes the connection like
371 this:</p>
373 <pre>
374 // Create a connection to the background page
375 var backgroundPageConnection = chrome.runtime.connect({
376 name: "panel"
379 backgroundPageConnection.postMessage({
380 name: 'init',
381 tabId: chrome.devtools.inspectedWindow.tabId
383 </pre>
386 <h3 id="evaluated-scripts-to-devtools">Messaging from Injected Scripts to the DevTools Page</h3>
388 <p>While the above solution works for content scripts, code that is injected
389 directly into the page (e.g. through appending a <code>&lt;script&gt;</code> tag
390 or through <code>$(ref:inspectedWindow.eval)</code>) requires a different strategy.
391 In this context, <code>$(ref:runtime.sendMessage)</code> will not pass messages
392 to the background script as expected.</p>
394 <p>As a workaround, you can combine your injected script with a content script
395 that acts as an intermediary. To pass messages to the content script, you can
396 use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage">
397 <code>window.postMessage</code></a> API. Here's an example, assuming the
398 background script from the previous section:</p>
400 <pre>
401 // injected-script.js
403 window.postMessage({
404 greeting: 'hello there!',
405 source: 'my-devtools-extension'
406 }, '*');
407 </pre>
409 <pre>
410 // content-script.js
412 window.addEventListener('message', function(event) {
413 // Only accept messages from the same frame
414 if (event.source !== window) {
415 return;
418 var message = event.data;
420 // Only accept messages that we know are ours
421 if (typeof message !== 'object' || message === null ||
422 !message.source === 'my-devtools-extension') {
423 return;
426 chrome.runtime.sendMessage(message);
428 </pre>
430 <p>Your message will now flow from the injected script, to the content script, to
431 the background script, and finally to the DevTools page.</p>
433 <p>You can also consider <a href="https://github.com/GoogleChrome/devtools-docs/issues/143">two alternative message passing techniques here</a>.</p>
436 <h3 id="detecting-open-close">Detecting When DevTools Opens and Closes</h3>
438 <p>If your extension needs to track whether the DevTools window is open, you can
439 add an $(ref:runtime.onConnect onConnect) listener to the background page, and call
440 $(ref:runtime.connect connect) from the DevTools page. Since each tab can have its
441 own DevTools window open, you may receive multiple connect events. To track whether
442 any DevTools window is open, you need to count the connect and disconnect events as
443 shown below:</p>
445 <pre>// background.js
446 var openCount = 0;
447 chrome.runtime.onConnect.addListener(function (port) {
448 if (port.name == "devtools-page") {
449 if (openCount == 0) {
450 alert("DevTools window opening.");
452 openCount++;
454 port.onDisconnect.addListener(function(port) {
455 openCount--;
456 if (openCount == 0) {
457 alert("Last DevTools window closing.");
461 });</pre>
463 <p>The DevTools page creates a connection like this:</p>
465 <pre>
466 // devtools.js
468 // Create a connection to the background page
469 var backgroundPageConnection = chrome.runtime.connect({
470 name: "devtools-page"
472 </pre>
475 <h2 id="examples">DevTools extension examples</h2>
477 <p>Browse the source of these DevTools extension examples:</p>
479 <ul>
481 <li><a href="https://github.com/PolymerLabs/polymer-devtools-extension">Polymer Devtools Extension</a> - uses many helpers running in the host page to query DOM/JS state to send back to the custom panel.
482 <li><a href="https://github.com/facebook/react-devtools">React DevTools Extension</a> - Uses a submodule of Blink to reuse DevTools UI components.
483 <li><a href="https://github.com/emberjs/ember-inspector">Ember Inspector</a> - Shared extension core with adapters for both Chrome and Firefox.
484 <li><a href="https://github.com/thomasboyt/coquette-inspect">Coquette-inspect</a> - A clean React-based extension with a debugging agent injected into the host page.
485 <li>our <a href="https://developer.chrome.com/devtools/docs/extensions-gallery">DevTools Extension Gallery</a> and <a href="https://developer.chrome.com/devtools/docs/sample-extensions">Sample Extensions</a> have more worthwhile apps to install, try out, and learn from.
488 </ul>
491 <h2 id="more">More information</h2>
493 <p>For information on the standard APIs that extensions can use, see
494 <a href="http://developer.chrome.com/extensions/api_index.html">chrome.* APIs</a>
495 and <a href="http://developer.chrome.com/extensions/api_other.html">Other APIs</a>.
496 </p>
499 <a href="http://groups.google.com/group/google-chrome-developer-tools/topics">Give
500 us feedback!</a>
501 Your comments and suggestions help us improve the APIs.</p>
503 <h2 id="examples">Examples</h2>
505 <p>You can find examples that use DevTools APIs in
506 <a href="http://developer.chrome.com/extensions/samples.html#devtools">Samples</a>.
507 </p>