• Skip to main content
  • Select language
  • Skip to search
MDN Web Docs
  • Technologies
    • HTML
    • CSS
    • JavaScript
    • Graphics
    • HTTP
    • APIs / DOM
    • WebExtensions
    • MathML
  • References & Guides
    • Learn web development
    • Tutorials
    • References
    • Developer Guides
    • Accessibility
    • Game development
    • ...more docs
Add-ons
  1. MDN
  2. Mozilla
  3. Add-ons
  4. Add-on SDK
  5. Low-Level APIs
  6. dev/panel

dev/panel

In This Article
  1. Basic usage
    1. Defining the panel constructor
    2. Creating a Tool
    3. Example
  2. Panel document environment
  3. Communicating with the panel document
    1. Two-way messaging
  4. Communicating with the debugger server
    1. Volcan.js: a JavaScript API for the debugging protocol
      1. Connecting
      2. Actors

Add-ons using the techniques described in this document are considered a legacy technology in Firefox. Don't use these techniques to develop new add-ons. Use WebExtensions instead. If you maintain an add-on which uses the techniques described here, consider migrating it to use WebExtensions.

From Firefox 53 onwards, no new legacy add-ons will be accepted on addons.mozilla.org (AMO).

From Firefox 57 onwards, WebExtensions will be the only supported extension type, and Firefox will not load other types.

Even before Firefox 57, changes coming up in the Firefox platform will break many legacy extensions. These changes include multiprocess Firefox (e10s), sandboxing, and multiple content processes. Legacy extensions that are affected by these changes should migrate to WebExtensions if they can. See the "Compatibility Milestones" document for more.

A wiki page containing resources, migration paths, office hours, and more, is available to help developers transition to the new technologies.

This module 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 this API. We're working on removing this restriction.

Enables you to extend the Firefox Developer Tools. Most of the Firefox Developer Tools are hosted in a UI component called the Toolbox. Individual built-in tools, such as the JavaScript Debugger or the Web Console, occupy "panels" in the toolbox. With the dev/panel module, you can create your own panels in the Toolbox:

The panel gets a tab in the Toolbox toolbar which enables the user to open it:

You specify the panel's content and behavior using HTML, CSS, and JavaScript. When the panel's created, the framework passes it a debuggee: this is a MessagePort object that you can use to exchange JSON messages with the browser that the developer tools are currently debugging. The messages follow the remote debugging protocol.

For a simple walkthrough of using the dev/panel API to add a new tool, see Adding a panel to the toolbox.

Basic usage

Defining the panel constructor

To add a new tool you first need to define a constructor that inherits from the Panel class, and in that constructor you need to supply values for various properties .

You can set the constructor up manually if you like, or you can use the Add-on SDK core/heritage module to simplify the mechanics of inheriting from Panel. You can use the Class utility function:

const { Panel } = require("dev/panel");
const { Class } = require("sdk/core/heritage");
const MyPanel = Class({
  extends: Panel,
  label: "My Panel",
  tooltip: "My new devtool",
  icon: "./my-devtool.png",
  url: "./my-devtool.html",
  setup: function(options) {
    // my setup goes here
  },
  dispose: function() {
    // my teardown goes here
  },
  onReady: function() {
    // I can send messages to
    // the panel document here
  }
});

Alternatively, you can use the extend function:

const { extend } = require("sdk/core/heritage");
function MyPanel() {};
MyPanel.prototype = extend(Panel.prototype, {
  label: "My Panel",
  tooltip: "...",
  ....
});

In the constructor definition there are a number of mandatory and optional parameters for you to supply.

Name Type Description  
label String The string to display in the Toolbox toolbar. Mandatory
icon String

The icon to display in the Toolbox toolbar, specified as a resource:// URL pointing to an icon file, typically in your add-on's "data" directory.

You can use the notation "./my-icon.png" as an alias for the URL pointing to "data/my-icon.png".

Mandatory
url String

A resource:// URL pointing to an HTML file, typically in your add-on's "data" directory. This file contains the specification of the panel's user interface.

You can use the notation "./my-file.html" as an alias for the URL pointing to "data/my-file.html".

You can't directly manipulate the panel's content from main.js, but you can exchange messages with scripts running in the panel.

Mandatory
tooltip String A string that will be used as a tooltip in the Toolbox toolbar. Optional
setup Function

A function that will be 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.

Optional
dispose Function A function that will be called when the panel is about to be destroyed. You can use it to do any cleanup. Optional
onReady Function An event handler that will be called when the document in the panel becomes interactive. It's equivalent to document.readyState === "interactive". At this point you can send the panel document messages. Optional
onLoad Function An event handler that will be called after the document in the panel is fully loaded. It's equivalent to document.readyState === "complete". Optional

Once you've defined the panel's constructor you have to export it so it can be called by the framework.

Creating a Tool

Next, you need to create a new Tool using the dev/toolbox module, initializing it with the newly defined constructor.

Example

Here's a complete main.js:

// main.js
// require the SDK modules
const { Panel } = require("dev/panel");
const { Tool } = require("dev/toolbox");
const { Class } = require("sdk/core/heritage");
// define the panel constructor
const MyPanel = Class({
  extends: Panel,
  label: "My Panel",
  tooltip: "My new devtool's panel",
  icon: "./my-icon.png",
  url: "./my-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;
  },
  onReady: function() {
    // in this function you can communicate
    // with the panel document
  }
});
// export the constructor
exports.MyPanel = MyPanel;
// create a new tool, initialized
// with the new constructor
const myTool = new Tool({
  panels: { myPanel: MyPanel }
});

Panel document environment

The panel document loaded from the url property can of course include CSS and JavaScript just like a normal web page:

<html>
  <head>
    <meta charset="utf-8">
    <link href="./my-panel.css"rel="stylesheet"></link>
    <script src="resource://sdk/dev/volcan.js"></script>
  </head>
  <body>
      <div id="content"></div>
  </body>
  <script src="./my-panel.js"></script>
</html>

It doesn't have access to any privileged APIs, including the Add-on SDK APIs. However, it can receive messages from the add-on that created it, and the add-on can pass it the debuggee so it can communicate with the debugger server the developer tools are targeting.

Communicating with the panel document

The main add-on code can't directly access the panel document or any scripts loaded by the panel document. However it can send messages to the panel document using the postMessage method of Panel. This is closely modeled on window.postMessage. You can only send the panel document message after you've received the ready event.

In the main add-on code:

// main.js
// require the SDK modules
const { Panel } = require("dev/panel");
const { Tool } = require("dev/toolbox");
const { Class } = require("sdk/core/heritage");
const MyPanel = Class({
  extends: Panel,
  label: "My Panel",
  tooltip: "My new devtool's panel",
  icon: "./my-icon.png",
  url: "./my-panel.html",
  onReady: function() {
    this.postMessage("Message from the add-on");
  }
});
// export the constructor
exports.MyPanel = MyPanel;
// create a new tool, initialized
// with the new constructor
const myTool = new Tool({
  panels: { myPanel: MyPanel }
});

In the panel document script:

// my-panel.js
window.addEventListener("message", function(event) {
  var content = document.getElementById("content");
  content.textContent = event.data;
});

Note that at the moment you have to pass an array of ports into postMessage, even if you don't need to use them:

// main.js
// require the SDK modules
const { Panel } = require("dev/panel");
const { Tool } = require("dev/toolbox");
const { Class } = require("sdk/core/heritage");
const MyPanel = Class({
  extends: Panel,
  label: "My Panel",
  tooltip: "My new devtool's panel",
  icon: "./my-icon.png",
  url: "./my-panel.html",
  setup: function(options) {
    this.debuggee = options.debuggee;
  },
  dispose: function() {
    this.debuggee = null;
  },
  onReady: function() {
    this.postMessage("Message from the add-on", [this.debuggee]);
  }
});
// export the constructor
exports.MyPanel = MyPanel;
// create a new tool, initialized
// with the new constructor
const myTool = new Tool({
  panels: { myPanel: MyPanel }
});

This is being tracked as bug 1079540.

Two-way messaging

The panel document does not have an equivalent postMessage method, so if you want your panel document to communicate back to the add-on, you can use channel messaging.

In the add-on side:

  • require the sdk/messaging module
  • create a MessageChannel
  • keep one MessagePort in the add-on side for receiving messages from the panel document
  • pass the other MessagePort to the panel document in the ports argument to postMessage
// main.js
// require the SDK modules
const { Panel } = require("dev/panel");
const { Tool } = require("dev/toolbox");
const { Class } = require("sdk/core/heritage");
const { MessageChannel } = require("sdk/messaging");
const channel = new MessageChannel();
const addonSide = channel.port1;
const panelSide = channel.port2;
// messages from the panel arrive here
addonSide.onmessage = function(event) {
  console.log(event.data);
}
const MyPanel = Class({
  extends: Panel,
  label: "My Panel",
  tooltip: "My new devtool's panel",
  icon: "./my-icon.png",
  url: "./my-panel.html",
  onReady: function() {
    // send a port to the panel document
    this.postMessage("Message from the add-on", [panelSide]);
  }
});
// export the constructor
exports.MyPanel = MyPanel;
// create a new tool, initialized
// with the new constructor
const myTool = new Tool({
  panels: { myPanel: MyPanel }
});

In the panel document:

  • retrieve the MessagePort from the event, and use it to send messages to the add-on
// my-panel.js
window.addEventListener("message", function(event) {
  // retrieve the port
  var toAddon = event.ports[0];
  toAddon.start();
  toAddon.postMessage("Message from the panel document");
  console.log(toAddon);
});

Communicating with the debugger server

The Firefox developer tools use a client/server protocol: a tool, such as a JavaScript debugger or style editor, is the client, and the program being debugged, such as Firefox, is the server. Clients connect to the server and send it messages to examine and modify the state of the program being debugged. The Remote Debugging Protocol page describes the protocol in detail.

When the user opens a panel and the panel is created, the framework calls the panel's setup method. The setup method will be passed a debuggee object, which is a MessagePort that the add-on can use to exchange messages with the debugger server.

For example, here's a main.js which sends the listTabs message to the debugger server, and logs the response:

// main.js
// require the SDK modules
const { Panel } = require("dev/panel");
const { Tool } = require("dev/toolbox");
const { Class } = require("sdk/core/heritage");
const MyPanel = Class({
  extends: Panel,
  label: "My Panel",
  tooltip: "My new devtool's panel",
  icon: "./my-icon.png",
  url: "./my-panel.html",
  setup: function(options) {
    this.debuggee = options.debuggee;
    this.debuggee.start();
    this.debuggee.onmessage = function(event) {
      console.log(event.data);
    }
    this.debuggee.postMessage({
      "to":"root",
      "type":"listTabs"
    });
  }
});
// export the constructor
exports.MyPanel = MyPanel;
// create a new tool, initialized
// with the new constructor
const myTool = new Tool({
  panels: { myPanel: MyPanel }
});

More usefully, you can pass debuggee from the main add-on to the panel document using the ports argument to postMessage. Then the panel document can communicate with the debugger server.

In main.js:

// main.js
// require the SDK modules
const { Panel } = require("dev/panel");
const { Tool } = require("dev/toolbox");
const { Class } = require("sdk/core/heritage");
const MyPanel = Class({
  extends: Panel,
  label: "My Panel",
  tooltip: "My new devtool's panel",
  icon: "./my-icon.png",
  url: "./my-panel.html",
  setup: function(options) {
    this.debuggee = options.debuggee;
  },
  dispose: function() {
    this.debuggee = null;
  },
  onReady: function() {
    this.debuggee.start();
    this.postMessage("port", [this.debuggee]);
  }
});
// export the constructor
exports.MyPanel = MyPanel;
// create a new tool, initialized
// with the new constructor
const myTool = new Tool({
  panels: { myPanel: MyPanel }
});

In my-panel.js:

// my-panel.js
var content = document.getElementById("content");
window.addEventListener("message", function(event) {
  var debuggee = event.ports[0];
  console.log(debuggee);
  debuggee.onmessage = function(event) {
    content.textContent = JSON.stringify(event.data);
  }
  debuggee.postMessage({
    "to":"root",
    "type":"listTabs"
  });
});

If you do this, don't forget to call start() on the port before passing it over to the panel document.

Volcan.js: a JavaScript API for the debugging protocol

Communicating with the debugger server by exchanging JSON messages can be cumbersome. So we've also provided a library called volcan.js which gives you a JavaScript API to the remote debugging protocol.

Note that at the moment volcan.js does not support the complete remote debugging protocol. We're still working on documenting exactly which messages are supported by volcan.js.

To use volcan.js, you can just include it from your panel's HTML like this:

<html>
  <head>
    <meta charset="utf-8">
    <link href="./my-panel.css"rel="stylesheet"></link>
    <script src="resource://sdk/dev/volcan.js"></script>
  </head>
  <body>
      <div id = "content"></div>
  </body>
  <script src="./my-panel.js"></script>
</html>

Here's a script that uses volcan.js to get the selected tab and display its URL:

// my-panel.js
var content = document.getElementById("content");
window.addEventListener("message", function(event) {
  var debuggee = event.ports[0];
  volcan.connect(debuggee).
    then(listTabs).
    then(writeTabList);
});
function listTabs(root) {
  return root.listTabs();
}
function writeTabList(tabList) {
  content.textContent = tabList.tabs[tabList.selected].url;
}

We don't have detailed documentation for volcan.js, but it's coming soon. In the meantime, here's a quick guide.

Connecting

Volcan.js provides a global connect() function that takes a MessagePort connected to the debugger server, and returns a promise which is fulfilled with an object representing the root actor:

volcan.connect(debuggee).then(gotRoot);
function gotRoot(root) {
 // can use root actor here
}

Actors

Actors in the Remote Debugging Protocol are volcan.js objects. The messages actors can accept are methods of those objects. The message type becomes the method name. For example, the root actor object has a method listTabs and the stylesheet actor object has a method getStyleSheets. Any additional parameters sent in the message become arguments to the method.

If a message is expected to get a reply, then the method returns a promise which is fulfilled with the reply. If the message reply in the remote debugging protocol would contain an actor ID, then in volcan.js the object that fulfills the promise contains that actor instance. If the message reply contains some JSON object, then in volcan.js the object that fulfills the promise includes a corresponding JSON object.

If an actor can send notifications, as in the request/reply/notify pattern, then the corresponding volcan.js object emits an event, which you can listen for using addEventListener. Any data sent with the notification is available to the event listener in the event.data property.

Document Tags and Contributors

 Contributors to this page: wbamberg, jonaswjs, guidocalvano
 Last updated by: wbamberg, Dec 1, 2016, 10:29:00 AM
See also
  1. WebExtensions
  2. Getting started
    1. What are WebExtensions?
    2. Your first WebExtension
    3. Your second WebExtension
    4. Anatomy of a WebExtension
    5. Example WebExtensions
  3. How to
    1. Intercept HTTP requests
    2. Modify a web page
    3. Add a button to the toolbar
    4. Implement a settings page
  4. Concepts
    1. Using the JavaScript APIs
    2. User interface components
    3. Content scripts
    4. Match patterns
    5. Internationalization
    6. Content Security Policy
    7. Native messaging
  5. Porting
    1. Porting a Google Chrome extension
    2. Porting a legacy Firefox add-on
    3. Embedded WebExtensions
    4. Comparison with the Add-on SDK
    5. Comparison with XUL/XPCOM extensions
    6. Chrome incompatibilities
  6. Firefox workflow
    1. Temporary Installation in Firefox
    2. Debugging
    3. Getting started with web-ext
    4. web-ext command reference
    5. WebExtensions and the Add-on ID
    6. Publishing your WebExtension
  7. JavaScript APIs
    1. Browser support for JavaScript APIs
    2. alarms
    3. bookmarks
    4. browserAction
    5. browsingData
    6. commands
    7. contextMenus
    8. contextualIdentities
    9. cookies
    10. downloads
    11. events
    12. extension
    13. extensionTypes
    14. history
    15. i18n
    16. identity
    17. idle
    18. management
    19. notifications
    20. omnibox
    21. pageAction
    22. runtime
    23. sessions
    24. sidebarAction
    25. storage
    26. tabs
    27. topSites
    28. webNavigation
    29. webRequest
    30. windows
  8. Manifest keys
    1. applications
    2. author
    3. background
    4. browser_action
    5. chrome_url_overrides
    6. commands
    7. content_scripts
    8. content_security_policy
    9. default_locale
    10. description
    11. developer
    12. homepage_url
    13. icons
    14. manifest_version
    15. name
    16. omnibox
    17. options_ui
    18. page_action
    19. permissions
    20. short_name
    21. sidebar_action
    22. version
    23. web_accessible_resources
  9. Add-on SDK
  10. Getting started
    1. Installation
    2. Getting started
    3. Troubleshooting
  11. High-Level APIs
    1. addon-page
    2. base64
    3. clipboard
    4. context-menu
    5. hotkeys
    6. indexed-db
    7. l10n
    8. notifications
    9. page-mod
    10. page-worker
    11. panel
    12. passwords
    13. private-browsing
    14. querystring
    15. request
    16. selection
    17. self
    18. simple-prefs
    19. simple-storage
    20. system
    21. tabs
    22. timers
    23. ui
    24. url
    25. webextension
    26. widget
    27. windows
  12. Low-Level APIs
    1. /loader
    2. chrome
    3. console/plain-text
    4. console/traceback
    5. content/content
    6. content/loader
    7. content/mod
    8. content/symbiont
    9. content/worker
    10. core/heritage
    11. core/namespace
    12. core/promise
    13. dev/panel
    14. event/core
    15. event/target
    16. frame/hidden-frame
    17. frame/utils
    18. fs/path
    19. io/byte-streams
    20. io/file
    21. io/text-streams
    22. lang/functional
    23. lang/type
    24. loader/cuddlefish
    25. loader/sandbox
    26. net/url
    27. net/xhr
    28. places/bookmarks
    29. places/favicon
    30. places/history
    31. platform/xpcom
    32. preferences/event-target
    33. preferences/service
    34. remote/child
    35. remote/parent
    36. stylesheet/style
    37. stylesheet/utils
    38. system/child_process
    39. system/environment
    40. system/events
    41. system/runtime
    42. system/unload
    43. system/xul-app
    44. tabs/utils
    45. test/assert
    46. test/harness
    47. test/httpd
    48. test/runner
    49. test/utils
    50. ui/button/action
    51. ui/button/toggle
    52. ui/frame
    53. ui/id
    54. ui/sidebar
    55. ui/toolbar
    56. util/array
    57. util/collection
    58. util/deprecate
    59. util/list
    60. util/match-pattern
    61. util/object
    62. util/uuid
    63. window/utils
  13. Firefox for Android
  14. Getting started
    1. Walkthrough
    2. Debugging
    3. Code snippets
  15. APIs
    1. Accounts.jsm
    2. BrowserApp
    3. HelperApps.jsm
    4. Home.jsm
    5. HomeProvider.jsm
    6. JavaAddonManager.jsm
    7. NativeWindow
    8. Notifications.jsm
    9. PageActions.jsm
    10. Prompt.jsm
    11. RuntimePermissions.jsm
    12. Snackbars.jsm
    13. Sound.jsm
    14. Tab
  16. Legacy
  17. Restartless extensions
    1. Overview
  18. Overlay extensions
    1. Overview
  19. Themes
  20. Lightweight themes
    1. Overview
  21. Complete themes
    1. Overview
  22. Publishing add-ons
  23. Guides
    1. Signing and distribution overview
    2. Submit an add-on
    3. Review policies
    4. Developer agreement
    5. Featured add-ons
    6. Contact addons.mozilla.org
  24. Community and support
  25. Channels
    1. Add-ons blog
    2. Add-on forums
    3. Stack Overflow
    4. Development newsgroup
    5. IRC Channel