How to write an add-on that adds a new tool to the toolbox.
This feature is new in Firefox 34.
Note that at the moment you can't debug remote targets (for example, Firefox OS, the Firefox OS Simulator, or Firefox for Android) using tools developed with these APIs. We're working on removing this restriction.
This article explains how to write an add-on that adds a new tool to the Toolbox. The tool we'll create is a minimal REPL for the remote debugging protocol, which enables you to send messages to the debugger server and displays the response from the server:
You can find the complete code for the add-on on GitHub: https://github.com/mdn/repl-panel.
Prerequisites
If you haven't used the Add-on SDK before, you'll first need to install the SDK and follow the Getting Started tutorial.
Once you've done that, create a new directory, navigate to it in the command line, and type jpm init
to create the skeleton structure for the new add-on.
You'll also need Firefox 34 or later.
Writing the add-on
To add the new tool we'll define a REPLPanel
class that extends the Panel
class in dev/panel.
A Panel
represents a panel in the toolbox, like the panels occupied by the built-in JavaScript Debugger or the Web Console. You specify the panel's content and behavior using HTML, CSS, and JavaScript. When a panel is created, the framework passes it a debuggee
object. The panel can use debuggee
to exchange JSON messages with the debugger server running inside Firefox.
Our add-on will define a REPLPanel
, which passes the debuggee
into a script running inside the panel's document. The panel's document will define a textarea
for the user to enter JSON messages to send the server, and a pre
block to display the response. The script will use debuggee
to:
- send the contents of the
textarea
to the server - listen for messages from the server, and display the results in the
pre
block
main.js
The SDK's jpm init
command creates a file called "main.js" in the "lib" directory under the add-on's root. Open this file and replace its contents with this:
// require the SDK modules const { Panel } = require("dev/panel"); const { Tool } = require("dev/toolbox"); const { Class } = require("sdk/core/heritage"); const self = require("sdk/self"); // define a REPLPanel class // that inherits from dev/panel const REPLPanel = Class({ extends: Panel, label: "REPL", tooltip: "Firefox debugging protocol REPL", icon: self.data.url("plane_64.png"), url: self.data.url("repl-panel.html"), // when the panel is created, // take a reference to the debuggee setup: function(options) { this.debuggee = options.debuggee; }, dispose: function() { this.debuggee = null; }, // when the panel's script is ready, // send it a message containing // the debuggee onReady: function() { this.debuggee.start(); this.postMessage("init", [this.debuggee]); } }); exports.REPLPanel = REPLPanel; // create a new tool, // initialized with the // REPLPanel's constructor const replTool = new Tool({ panels: { repl: REPLPanel } });
First, we're defining a REPLPanel
constructor, supplying:
- four properties:
icon
,label
,url
, and atooltip
- two functions:
setup()
anddispose()
- one event handler:
onReady
The icon
, label
, and tooltip
properties are all used to help identify the panel to the user in the Toolbox toolbar. The icon
property is a resource:// URL pointing to an icon stored in the add-on's "data" directory. The url
property is also a resource:// URL pointing to a resource in the "data" directory, but in this case it's an HTML source file, "repl-panel.html", containing the specification for the panel's user interface.
The setup()
function is called when the panel is created. It's passed an options
object containing a single property debuggee
. debuggee
is a MessagePort
object that you can use to exchange messages with the debugger server. In this setup
we're just keeping a reference to debuggee
. The dispose()
function is called when the panel is about to be destroyed. You can use it to do any cleanup.
The ready
event is dispatched after the document in the panel becomes interactive. It's equivalent to document.readyState === "interactive"
. At this point you can send the panel document messages. In this ready event handler we're sending debuggee
to the panel document. The panel will then be able to use debuggee
to exchange messages with the debugger server.
Finally, we create a new tool, initializing it with the newly defined constructor.
plane_64.png
In this example we've used the "plane_64.png" icon for the new tool. This was created by https://handdrawngoods.com/ and is used under the Creative Commons Attribution 3.0 Unported license (CC BY 3.0): https://creativecommons.org/licenses/by/3.0/. You can find in the example add-on's GitHub repository.
Whether you use this icon or a different one, save it in the "data" directory under your add-on's root directory, and make sure it's correctly referenced by the icon
property in the REPLPanel
constructor.
repl-panel.html
Create a file in the add-on's "data" directory called "repl-panel.html", and give it the following contents:
<html> <head> <meta charset="utf-8"> <link href="./repl-panel.css"rel="stylesheet"></link> </head> <body> <pre id="response"></pre> <textarea name="request" id="request" rows="10" cols="40"></textarea> </body> <script src="./repl-panel.js"></script> </html>
The HTML for the panel defines a textarea
element for the user to type a JSON request to send the server, and a pre
block to display the response from the server. It also includes CSS and JavaScript source files.
repl-panel.js
Create a file in the add-on's "data" directory called "repl-panel.js". It looks like this:
window.addEventListener("message", function(event) { window.port = event.ports[0]; window.port.onmessage = receive; }); var request = document.getElementById("request"); var response = document.getElementById("response"); request.addEventListener("keyup", send, false); function send(event) { if ((event.keyCode == 13) && (event.getModifierState("Control") == true)) { window.port.postMessage(JSON.parse(request.value)); } } function receive(event) { response.textContent = JSON.stringify(event.data, undefined, 2); }
The script loaded into the panel, "repl-panel.js", listens for the message from main.js that contains debuggee
. The debuggee
is a communications channel with the debugger server. Messages we send it are sent to the server as remote debugging protocol requests. Messages we get from it are responses to those requests.
So when "repl-panel.js" receives the debuggee
object, it starts listening for messages from the debugger server, and displays the results in the "response" element. It also listens to keyup
events in the "request" element, and on Control+Enter we send its contents to the debugger server.
repl-panel.css
Finally, create a file in the add-on's "data" directory called "repl-panel.css" that looks like this:
#request { position: fixed; width: 40%; } #response { float: right; width: 50%; color: blue; font-family: monospace; }
Final setup
Your directory structure should now look like:
- repl-panel
- data
- plane_64.png
- repl-panel.css
- repl-planel.html
- repl-panel.js
- lib
- main.js
- package.json
- data
Running the add-on
Back at the command prompt, run:
jpm run
You should see Firefox launch. Open the developer tools, and you'll see a new tab labeled "REPL". Switch to that tab, enter a JSON request, and press Ctrl+Enter to send it: