| <h1>Extending DevTools</h1> |
| |
| <h2 id="overview">Overview</h2> |
| |
| <p>A DevTools extension adds functionality to the Chrome DevTools. It can add new |
| UI panels and sidebars, interact with the inspected page, get information about |
| network requests, and more. DevTools extensions have access to an additional set |
| of DevTools-specific extension APIs:</p> |
| |
| <ul> |
| <li><code>$(ref:devtools.inspectedWindow)</code></li> |
| <li><code>$(ref:devtools.network)</code></a></li> |
| <li><code>$(ref:devtools.panels)</code></a></li> |
| </ul> |
| |
| <p>A DevTools extension is structured like any other extension: it can have a |
| background page, content scripts, and other items. In addition, each DevTools |
| extension has a DevTools page, which has access to the DevTools APIs.</p> |
| |
| <p><img src="{{static}}/images/devtools-extension.png" alt="Architecture diagram |
| showing DevTools page communicating with the inspected window and the background |
| page. The background page is shown communicating with the content scripts and |
| accessing extension APIs. The DevTools page has access to the DevTools APIs, for |
| example, creating panels."/></p> |
| |
| <h2 id="devtools-page">The DevTools Page</h2> |
| |
| <p>An instance of the extension's DevTools page is created each time a DevTools |
| window opens. The DevTools page exists for the lifetime of the DevTools window. |
| The DevTools page has access to the DevTools APIs and a limited set of extension |
| APIs. Specifically, the DevTools page can:</p> |
| |
| <ul> |
| |
| <li>Create and interact with panels using the <code>$(ref:devtools.panels)</code> |
| APIs.</li> |
| |
| <li>Get information about the inspected window and evaluate code in the inspected |
| window using the <code>$(ref:devtools.inspectedWindow)</code> APIs.</li> |
| |
| <li>Get information about network requests using the <code>$(ref:devtools.network)</code> |
| APIs.</li> |
| |
| </ul> |
| |
| <p>The DevTools page cannot use most of the extensions APIs directly. It has |
| access to the same subset of the <code>$(ref:extension)</code> |
| and <code>$(ref:runtime)</code> |
| APIs that a content script has access to. Like a content script, a DevTools page |
| can communicate with the background page using <a href="messaging.html">Message Passing</a>. |
| For an example, see <a href="#injecting">Injecting a Content Script</a>.</p> |
| |
| <h2 id="creating">Creating a DevTools Extension</h2> |
| |
| <p>To create a DevTools page for your extension, add the <code>devtools_page</code> |
| field in the extension manifest:</p> |
| |
| <pre> |
| { |
| "name": ... |
| "version": "1.0", |
| "minimum_chrome_version": "10.0", |
| "devtools_page": "devtools.html", |
| ... |
| } |
| </pre> |
| |
| <p>An instance of the <code>devtools_page</code> specified in your extension's |
| manifest is created for every DevTools window opened. The page may add other |
| extension pages as panels and sidebars to the DevTools window using the |
| <code>$(ref:devtools.panels)</code> API.</p> |
| |
| <p class="note">The <code>devtools_page</code> field must point to an HTML page. |
| This differs from the <code>background</code> field, used for specifying a background page, |
| which lets you specify JavaScript files directly.</p> |
| |
| <p>The <code>chrome.devtools.*</code> API modules are available only to the pages |
| loaded within the DevTools window. Content scripts and other extension pages do not |
| have these APIs. Thus, the APIs are available only through the lifetime of the |
| DevTools window.</p> |
| |
| <p> |
| There are also some DevTools APIs that are still experimental. |
| Refer to <a href="http://developer.chrome.com/extensions/experimental.html">chrome.experimental.* |
| APIs</a> for the list of |
| experimental APIs and guidelines on how to use them.</p> |
| |
| <h2 id="devtools-ui">DevTools UI Elements: Panels and Sidebar Panes</h2> |
| |
| <p> |
| 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:</p> |
| |
| <ul> |
| |
| <li>A <em>panel</em> is a top-level tab, like the Elements, Sources, and Network |
| panels.</li> |
| |
| <li>A <em>sidebar pane</em> presents supplementary UI related to a panel. The |
| Styles, Computed Styles, and Event Listeners panes on the Elements panel are |
| examples of sidebar panes. Currently your extension can only add sidebar panes to |
| the Elements panel. (Note that the appearance of sidebar panes may not match the |
| image, depending on the version of Chrome you're using, and where the DevTools |
| window is docked.)</li> |
| |
| </ul> |
| |
| <img src="{{static}}/images/devtools-extension-ui.png" |
| alt="DevTools window showing Elements panel and Styles sidebar pane." /> |
| <p> |
| Each panel is its own HTML file, which can include other resources (JavaScript, CSS, |
| images, and so on). Creating a basic panel looks like this: |
| </p> |
| <pre> |
| chrome.devtools.panels.create("My Panel", |
| "MyPanelIcon.png", |
| "Panel.html", |
| function(panel) { |
| // code invoked on panel creation |
| } |
| ); |
| </pre> |
| |
| <p>JavaScript executed in a panel or sidebar pane has access to the the same APIs |
| as the DevTools page.</p> |
| |
| <p>Creating a basic sidebar pane for the Elements panel looks like this:</p> |
| <pre> |
| chrome.devtools.panels.elements.createSidebarPane("My Sidebar", |
| function(sidebar) { |
| // sidebar initialization code here |
| sidebar.setObject({ some_data: "Some data to show" }); |
| });</pre> |
| <p>There are several ways to display content in a sidebar pane:</p> |
| |
| <ul> |
| |
| <li><p>HTML content. Call |
| <code>$(ref:devtools.panels.ExtensionSidebarPane.setPage setPage)</code> to specify |
| an HTML page to display in the pane.</p></li> |
| |
| |
| <li><p>JSON data. Pass a JSON object to |
| <code>$(ref:devtools.panels.ExtensionSidebarPane.setObject setObject)</code>. |
| </p></li> |
| |
| <li><p>JavaScript expression. Pass an expression to |
| <code>$(ref:devtools.panels.ExtensionSidebarPane.setExpression setExpression)</code>. |
| DevTools evaluates the expression in the context of the inspected page, and |
| displays the return value.</p></li> |
| |
| </ul> |
| |
| <p>For both <code>setObject</code> and <code>setExpression</code>, |
| the pane displays the value as it would appear in the DevTools console. |
| However, <code>setExpression</code> lets you display DOM elements and arbitrary |
| JavaScript objects, while <code>setObject</code> only supports JSON objects.</p> |
| |
| <h2 id="solutions">Communicating Between Extension Components</h2> |
| <p>The following sections describe some typical scenarios for communicating between |
| the different components of a DevTools extension.</p> |
| |
| <h3 id="injecting">Injecting a Content Script</h3> |
| |
| <p>The DevTools page can't call <code>$(ref:tabs.executeScript)</code> directly. |
| To inject a content script from the DevTools page, you must retrieve the ID |
| of the inspected window's tab using the <code>$(ref:inspectedWindow.tabId)</code> |
| property and send a message to the background page. From the background page, |
| call <code>$(ref:tabs.executeScript)</code> to inject the script.</p> |
| |
| <p class="note">If a content script has already been injected, you can add |
| additional context scripts using the <code>eval</code> method. See |
| <a href="#selected-element">Passing the Selected Element to a Content Script</a> |
| for more information.</p> |
| |
| <p>The following code snippets show how to inject a content script using |
| <code>executeScript</code>. |
| </p> |
| |
| <pre> |
| // 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 |
| chrome.runtime.sendMessage({ |
| tabId: chrome.devtools.inspectedWindow.tabId, |
| scriptToInject: "content_script.js" |
| }); |
| </pre> |
| |
| <p>Code for the background page:</p> |
| |
| <pre> |
| // 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.tabs.executeScript(message.tabId, |
| { file: message.scriptToInject }); |
| } |
| // add the listener |
| devToolsConnection.onMessage.addListener(devToolsListener); |
| |
| devToolsConnection.onDisconnect(function() { |
| devToolsConnection.onMessage.removeListener(devToolsListener); |
| }); |
| } |
| </pre> |
| |
| <h3 id="evaluating-js">Evaluating JavaScript in the Inspected Window</h3> |
| |
| <p>You can use the <code>$(ref:inspectedWindow.eval)</code> method to execute |
| JavaScript code in the context of the inspected page. You can invoke the |
| <code>eval</code> method from a DevTools page, panel or sidebar pane.</p> |
| |
| <p>By default, the expression is evaluated in the context of the main frame of the |
| page. Use the <code>useContentScriptContext: true</code> option to evaluate the |
| expression in the same context as the content scripts.</p> |
| |
| <p>Calling <code>eval</code> with <code>useContentScriptContext: true</code> does |
| not <em>create</em> a content script context, so you must load a context script |
| before calling <code>eval</code>, either by calling <code>executeScript</code> or |
| by specifying a content script in the <code>manifest.json</code> file.</p> |
| |
| <p>Once the context script context exists, you can use this option to inject |
| additional content scripts.</p> |
| |
| <p class="warning">The <code>eval</code> method is powerful when used in the right |
| context and dangerous when used inappropriately. Use the |
| <code>$(ref:tabs.executeScript)</code> method if you don't need access to the |
| JavaScript context of the inspected page. For detailed cautions and a comparison |
| of the two methods, see <code>$(ref:inspectedWindow)</code>.</p> |
| |
| <h3 id="selected-element">Passing the Selected Element to a Content Script</h3> |
| |
| <p>The content script doesn't have direct access to the current selected element. |
| However, any code you execute using <code>$(ref:inspectedWindow.eval)</code> has |
| access to the DevTools console and command-line APIs. For example, in evaluated code |
| you can use <code>$0</code> to access the selected element.</p> |
| |
| <p>To pass the selected element to a content script:</p> |
| |
| <ul> |
| |
| <li>Create a method in the content script that takes the selected element as |
| an argument.</li> |
| |
| <li>Call the method from the DevTools page using <code>$(ref:inspectedWindow.eval)</code> |
| with the <code>useContentScriptContext: true</code> option. </li> |
| |
| </ul> |
| |
| <p>The code in your content script might look something like this:</p> |
| |
| <pre> |
| function setSelectedElement(el) { |
| // do something with the selected element |
| } |
| </pre> |
| |
| <p>Invoke the method from the DevTools page like this:</p> |
| |
| <pre> |
| chrome.devtools.inspectedWindow.eval("setSelectedElement($0)", |
| { useContentScriptContext: true }); |
| </pre> |
| |
| <p>The <code>useContentScriptContext: true</code> option specifies that the |
| expression must be evaluated in the same context as the content scripts, so it can |
| access the <code>setSelectedElement</code> method.</p> |
| |
| |
| <h3 id="content-script-to-devtools">Messaging from Content Scripts to the DevTools Page</h3> |
| |
| <p>Messaging between the DevTools page and content scripts is indirect, by way of |
| the background page. </p> |
| |
| <p>When sending a message <em>to</em> a content script, the background page can use |
| the <code>$(ref:tabs.sendMessage)</code> method, which directs a message to the |
| content scripts in a specific tab, as shown in |
| <a href="#injecting">Injecting a Content Script</a>.</p> |
| |
| <p>When sending a message <em>from</em> a content script, there is no ready-made |
| method to deliver a message to the correct DevTools page instance associated with |
| the current tab. As a workaround, you can have the DevTools page establish a |
| long-lived connection with the background page, and have the background page keep a |
| map of tab IDs to connections, so it can route each message to the correct |
| connection.</p> |
| |
| <pre>// 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; |
| }); |
| </pre> |
| |
| <p>The DevTools page (or panel or sidebar pane) establishes the connection like |
| this:</p> |
| |
| <pre> |
| // Create a connection to the background page |
| var backgroundPageConnection = chrome.runtime.connect({ |
| name: "panel" |
| }); |
| |
| backgroundPageConnection.postMessage({ |
| name: 'init', |
| tabId: chrome.devtools.inspectedWindow.tabId |
| }); |
| </pre> |
| |
| |
| <h3 id="evaluated-scripts-to-devtools">Messaging from Injected Scripts to the DevTools Page</h3> |
| |
| <p>While the above solution works for content scripts, code that is injected |
| directly into the page (e.g. through appending a <code><script></code> tag |
| or through <code>$(ref:inspectedWindow.eval)</code>) requires a different strategy. |
| In this context, <code>$(ref:runtime.sendMessage)</code> will not pass messages |
| to the background script as expected.</p> |
| |
| <p>As a workaround, you can combine your injected script with a content script |
| that acts as an intermediary. To pass messages to the content script, you can |
| use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage"> |
| <code>window.postMessage</code></a> API. Here's an example, assuming the |
| background script from the previous section:</p> |
| |
| <pre> |
| // injected-script.js |
| |
| window.postMessage({ |
| greeting: 'hello there!', |
| source: 'my-devtools-extension' |
| }, '*'); |
| </pre> |
| |
| <pre> |
| // 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); |
| }); |
| </pre> |
| |
| Your message will now flow from the injected script, to the content script, to |
| the background script, and finally to the DevTools page. |
| |
| <h3 id="detecting-open-close">Detecting When DevTools Opens and Closes</h3> |
| |
| <p>If your extension needs to track whether the DevTools window is open, you can |
| add an $(ref:runtime.onConnect onConnect) listener to the background page, and call |
| $(ref:runtime.connect connect) from the DevTools page. Since each tab can have its |
| own DevTools window open, you may receive multiple connect events. To track whether |
| any DevTools window is open, you need to count the connect and disconnect events as |
| shown below:</p> |
| |
| <pre>// 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."); |
| } |
| }); |
| } |
| });</pre> |
| |
| <p>The DevTools page creates a connection like this:</p> |
| |
| <pre> |
| // devtools.js |
| |
| // Create a connection to the background page |
| var backgroundPageConnection = chrome.runtime.connect({ |
| name: "devtools-page" |
| }); |
| </pre> |
| |
| <h2 id="more">More information</h2> |
| |
| <p>For information on the standard APIs that extensions can use, see |
| <a href="http://developer.chrome.com/extensions/api_index.html">chrome.* APIs</a> |
| and <a href="http://developer.chrome.com/extensions/api_other.html">Other APIs</a>. |
| </p> |
| |
| <p> |
| <a href="http://groups.google.com/group/google-chrome-developer-tools/topics">Give |
| us feedback!</a> |
| Your comments and suggestions help us improve the APIs.</p> |
| |
| <h2 id="examples">Examples</h2> |
| |
| <p>You can find examples that use DevTools APIs in |
| <a href="http://developer.chrome.com/extensions/samples.html#devtools">Samples</a>. |
| </p> |