Bug 1279834 - Integrate devtools client UI into Thunderbird. r=mkmelin
authorPhilipp Kewisch <mozilla@kewis.ch>
Sat, 24 Sep 2016 13:24:37 +0200
changeset 46057 f85659ef3cf0f7064d8aad906fd70ee14ea8d9d9
parent 46056 26d7f9ff1aba6d7b0ac94e5c87b78dd66b245e99
child 46058 84406e2b6d12d521277649822c2b773b86f22041
push id3983
push useraleth@instantbird.org
push dateTue, 15 Nov 2016 14:21:41 +0000
treeherdertry-comm-central@4f779553b351 [default view] [failures only]
reviewersmkmelin
bugs1279834
Bug 1279834 - Integrate devtools client UI into Thunderbird. r=mkmelin MozReview-Commit-ID: Bo994FlkJQz
mail/app/profile/all-thunderbird.js
mail/base/content/baseMenuOverlay.xul
mail/base/content/contentAreaClick.js
mail/base/content/mailCore.js
mail/base/content/mailWindowOverlay.js
mail/base/content/mailWindowOverlay.xul
mail/components/devtools/content/dbg-messenger-overlay.js
mail/components/devtools/content/dbg-messenger-overlay.xul
mail/components/devtools/content/webconsole-overlay.xul
mail/components/devtools/devtools-loader.js
mail/components/devtools/devtools-loader.manifest
mail/components/devtools/extension/Makefile.in
mail/components/devtools/extension/bootstrap.js
mail/components/devtools/extension/content/options.js
mail/components/devtools/extension/content/options.xul
mail/components/devtools/extension/install.rdf
mail/components/devtools/extension/jar.mn
mail/components/devtools/extension/moz.build
mail/components/devtools/jar.mn
mail/components/devtools/modules/RemoteDebuggerServer.jsm
mail/components/devtools/modules/XULRootActor.js
mail/components/devtools/moz.build
mail/components/devtools/tb-root-actor.js
mail/components/devtools/toolbox-process-overlay.xul
mail/components/devtools/webconsole-overlay.xul
mail/locales/en-US/chrome/messenger/messenger.dtd
--- a/mail/app/profile/all-thunderbird.js
+++ b/mail/app/profile/all-thunderbird.js
@@ -804,16 +804,17 @@ pref("browser.search.order.3", "chrome:/
 pref("browser.search.defaultenginename.US", "data:text/plain,browser.search.defaultenginename.US=Bing");
 pref("browser.search.order.US.1", "data:text/plain,browser.search.defaultenginename.US=Bing");
 pref("browser.search.order.US.2", "data:text/plain,browser.search.defaultenginename.US=Yahoo");
 pref("browser.search.order.US.3", "data:text/plain,browser.search.defaultenginename.US=");
 
 // Developer Tools related preferences
 pref("devtools.debugger.log", false);
 pref("devtools.chrome.enabled", true);
+pref("devtools.debugger.remote-enabled", true);
 pref("devtools.selfxss.count", 5);
 
 pref("mail.chat.enabled", true);
 // Whether to show chat notifications or not.
 pref("mail.chat.show_desktop_notifications", true);
 // Decide how much information is to be shown in the notification.
 // 0 == Show all info (sender, chat message message preview),
 // 1 == Show sender's info only (not message preview),
--- a/mail/base/content/baseMenuOverlay.xul
+++ b/mail/base/content/baseMenuOverlay.xul
@@ -14,16 +14,19 @@
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript"
           src="chrome://communicator/content/utilityOverlay.js"/>
 
   <script type="application/javascript"
           src="chrome://messenger/content/mailCore.js"/>
 
+  <script type="application/javascript"
+          src="chrome://global/content/viewSourceUtils.js"/>
+
 #ifdef XP_WIN
   <menu id="helpMenu"
         label="&helpMenuWin.label;" accesskey="&helpMenuWin.accesskey;">
 #else
   <menu id="helpMenu"
         label="&helpMenu.label;" accesskey="&helpMenu.accesskey;">
 #endif
     <menupopup id="menu_HelpPopup">
--- a/mail/base/content/contentAreaClick.js
+++ b/mail/base/content/contentAreaClick.js
@@ -183,8 +183,17 @@ function openLinkExternally(url)
       transitionType: Components.interfaces.nsINavHistoryService.TRANSITION_LINK
     }]
   });
 
   Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
             .getService(Components.interfaces.nsIExternalProtocolService)
             .loadUrl(uri);
 }
+
+/**
+ * Compatibility to Firefox, used for example by devtools to open links. Defer
+ * this to the external browser for now, since in most cases this is meant to
+ * open an actionable tab.
+ */
+function openUILinkIn(url, where, options) {
+  openLinkExternally(url);
+}
--- a/mail/base/content/mailCore.js
+++ b/mail/base/content/mailCore.js
@@ -318,21 +318,25 @@ function onViewToolbarsPopupShowing(aEve
         menuItem.addEventListener("command", onMenuItemCommand, false);
       }
     }
   }
 }
 
 function toJavaScriptConsole()
 {
-  let { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
-  let HUDService = require("devtools/client/webconsole/hudservice");
   HUDService.openBrowserConsoleOrFocus();
 }
 
+function openAboutDebugging(hash)
+{
+  let url = "about:debugging" + (hash ? "#" + hash : "");
+  document.getElementById('tabmail').openTab("contentTab", { contentPage: url });
+}
+
 function toOpenWindowByType(inType, uri)
 {
   var topWindow = Services.wm.getMostRecentWindow(inType);
   if (topWindow)
     topWindow.focus();
   else
     window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
 }
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -6,16 +6,28 @@
 Components.utils.import("resource:///modules/FeedUtils.jsm");
 Components.utils.import("resource:///modules/gloda/dbview.js");
 Components.utils.import("resource:///modules/MailConsts.js");
 Components.utils.import("resource:///modules/mailServices.js");
 Components.utils.import("resource:///modules/MailUtils.js");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/PluralForm.jsm");
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager","resource://devtools/client/scratchpad/scratchpad-manager.jsm");
+Object.defineProperty(this, "HUDService", {
+  get: function HUDService_getter() {
+    let devtools = Components.utils.import("resource://devtools/shared/Loader.jsm", {}).devtools;
+    return devtools.require("devtools/client/webconsole/hudservice");
+  },
+  configurable: true,
+  enumerable: true
+});
 
 var ADDR_DB_LARGE_COMMIT       = 1;
 
 var kClassicMailLayout = 0;
 var kWideMailLayout = 1;
 var kVerticalMailLayout = 2;
 var kMailLayoutCommandMap =
 {
--- a/mail/base/content/mailWindowOverlay.xul
+++ b/mail/base/content/mailWindowOverlay.xul
@@ -523,16 +523,29 @@
   <key id="key_searchMail"
        key="&searchMailCmd.key;"
        oncommand="goDoCommand('cmd_search')"
        modifiers="accel,shift"/>
   <key id="key_errorConsole"
        key="&errorConsoleCmd.commandkey;"
        oncommand="toJavaScriptConsole();"
        modifiers="accel,shift"/>
+  <key id="key_devtoolsToolbox"
+       key="&devToolboxCmd.commandkey;"
+#ifdef XP_MACOSX
+       modifiers="accel,alt"
+#else
+       modifiers="accel,shift"
+#endif
+       oncommand="BrowserToolboxProcess.init();"/>
+  <key id="key_scratchpad"
+       keycode="&scratchpadCmd.keycode;"
+       keytext="&scratchpadCmd.keytext;"
+       modifiers="shift"
+       oncommand="ScratchpadManager.openScratchpad({ executionContext: 2 });"/>
   <key id="key_sanitizeHistory"
        keycode="VK_DELETE"
        oncommand="toSanitize();"
        modifiers="accel,shift"/>
 #ifdef XP_MACOSX
   <key id="key_sanitizeHistory_mac"
        keycode="VK_BACK"
        oncommand="toSanitize();"
@@ -2107,20 +2120,46 @@
                       command="cmd_runJunkControls"/>
             <menuitem id="appmenu_deleteJunk"
                       label="&deleteJunk.label;"
                       command="cmd_deleteJunk"/>
             <menuseparator id="tasksMenuAfterDeleteSeparator"/>
             <menuitem id="appmenu_import"
                       label="&importCmd.label;"
                       oncommand="toImport();"/>
-            <menuitem id="appmenu_javascriptConsole"
-                      label="&errorConsoleCmd.label;"
-                      key="key_errorConsole"
-                      oncommand="toJavaScriptConsole();"/>
+            <menu id="appmenu_devtoolsMenu"
+                  label="&devtoolsMenu.label;"
+                  accesskey="&devtoolsMenu.accesskey;">
+              <menupopup id="appmenu_devtoolsPopup">
+                <menuitem id="appmenu_devtoolsToolbox"
+                          label="&devToolboxCmd.label;"
+                          accesskey="&devToolboxCmd.accesskey;"
+                          key="key_devtoolsToolbox"
+                          oncommand="BrowserToolboxProcess.init();"/>
+                <menuitem id="appmenu_addonDebugging"
+                          label="&addonDebugCmd.label;"
+                          accesskey="&addonDebugCmd.accesskey;"
+                          oncommand="openAboutDebugging('addons')"/>
+                <menuitem id="appmenu_tabsDebugging"
+                          label="&tabsDebugCmd.label;"
+                          accesskey="&tabsDebugCmd.accesskey;"
+                          oncommand="openAboutDebugging('tabs')"/>
+                <menuseparator id="appmenu_debuggingSeparator"/>
+                <menuitem id="appmenu_javascriptConsole"
+                          label="&errorConsoleCmd.label;"
+                          accesskey="&errorConsoleCmd.accesskey;"
+                          key="key_errorConsole"
+                          oncommand="toJavaScriptConsole();"/>
+                <menuitem id="appmenu_openScratchpad"
+                          label="&scratchpadCmd.label;"
+                          accesskey="&scratchpadCmd.accesskey;"
+                          key="key_scratchpad"
+                          oncommand="ScratchpadManager.openScratchpad({ executionContext: 2 });"/>
+              </menupopup>
+            </menu>
             <menuitem id="appmenu_sanitizeHistory"
                       label="&clearRecentHistory.label;"
                       key="key_sanitizeHistory"
                       oncommand="toSanitize();"/>
           </menupopup>
         </menu>
         <!-- Help menu -->
         <splitmenu id="appmenu_help"
@@ -2992,21 +3031,44 @@
         <menuitem id="deleteJunk"
             label="&deleteJunk.label;"
             accesskey="&deleteJunk.accesskey;"
             command="cmd_deleteJunk"/>
         <menuseparator id="tasksMenuAfterDeleteSeparator"/>
         <menuitem id="menu_import" label="&importCmd.label;"
                   accesskey="&importCmd.accesskey;"
                   oncommand="toImport();"/>
-        <menuitem id="javascriptConsole"
-                  label="&errorConsoleCmd.label;"
-                  accesskey="&errorConsoleCmd.accesskey;"
-                  key="key_errorConsole"
-                  oncommand="toJavaScriptConsole();"/>
+        <menu id="devtoolsMenu" label="&devtoolsMenu.label;" accesskey="&devtoolsMenu.accesskey;">
+          <menupopup id="devtoolsPopup">
+            <menuitem id="devtoolsToolbox"
+                      label="&devToolboxCmd.label;"
+                      accesskey="&devToolboxCmd.accesskey;"
+                      key="key_devtoolsToolbox"
+                      oncommand="BrowserToolboxProcess.init();"/>
+            <menuitem id="addonDebugging"
+                      label="&addonDebugCmd.label;"
+                      accesskey="&addonDebugCmd.accesskey;"
+                      oncommand="openAboutDebugging('addons')"/>
+            <menuitem id="tabsDebugging"
+                      label="&tabsDebugCmd.label;"
+                      accesskey="&tabsDebugCmd.accesskey;"
+                      oncommand="openAboutDebugging('tabs')"/>
+            <menuseparator id="debuggingSeparator"/>
+            <menuitem id="javascriptConsole"
+                      label="&errorConsoleCmd.label;"
+                      accesskey="&errorConsoleCmd.accesskey;"
+                      key="key_errorConsole"
+                      oncommand="toJavaScriptConsole();"/>
+            <menuitem id="openScratchpad"
+                      label="&scratchpadCmd.label;"
+                      accesskey="&scratchpadCmd.accesskey;"
+                      key="key_scratchpad"
+                      oncommand="ScratchpadManager.openScratchpad({ executionContext: 2 });"/>
+          </menupopup>
+        </menu>
         <menuitem id="sanitizeHistory"
                   label="&clearRecentHistory.label;"
                   accesskey="&clearRecentHistory.accesskey;"
                   key="key_sanitizeHistory"
                   oncommand="toSanitize();"/>
 #ifndef XP_UNIX
         <menuseparator id="prefSep"/>
         <menuitem id="menu_accountmgr"
deleted file mode 100644
--- a/mail/components/devtools/content/dbg-messenger-overlay.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource:///modules/RemoteDebuggerServer.jsm");
-
-/**
- * Handler to call when the checked state of the menuitem is toggled. Sets the
- * remote-enabled preference of the debugger and starts the debugger if needed.
- */
-function toggleDebugger() {
-  let shouldEnable = document.getElementById("devtoolsDebugger").getAttribute("checked") == "true";
-  Services.prefs.setBoolPref("devtools.debugger.remote-enabled", shouldEnable);
-
-  RemoteDebuggerServer.extraInit = function(DebuggerServer) {
-    DebuggerServer.registerModule("resource:///modules/XULRootActor.js");
-  };
-  RemoteDebuggerServer.startstop(shouldEnable);
-}
-
-/**
- * Intialize the checked state, to be used when the view menu is opened.
- */
-function initDebuggerToolsMenu() {
-  let debuggerEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
-  document.getElementById("devtoolsDebugger")
-          .setAttribute("checked", debuggerEnabled);
-}
-
-/**
- * Intialize everything needed on load in the window for the debugger, i.e
- * menuitem checked state listeners.
- */
-function loadDebugger() {
-  let viewPopup = document.getElementById("taskPopup");
-  viewPopup.addEventListener("popupshowing", initDebuggerToolsMenu, false);
-
-  // Call these functions once to start or stop the debugger on startup.
-  initDebuggerToolsMenu();
-  toggleDebugger();
-}
-
-/**
- * Shutdown everything needed on load in the window for the debugger, i.e
- * menuitem checked state listeners.
- */
-function unloadDebugger() {
-  let viewPopup = document.getElementById("taskPopup");
-  viewPopup.removeEventListener("popupshowing", initDebuggerToolsMenu, false);
-}
-
-// Load and unload the debugger when the window loads/unloads.
-window.addEventListener("load", loadDebugger, {capture: false, once: true});
-window.addEventListener("unload", unloadDebugger, {capture: false, once: true});
deleted file mode 100644
--- a/mail/components/devtools/content/dbg-messenger-overlay.xul
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/messenger.dtd">
-
-<overlay id="devtools-overlay"
-         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script type="application/javascript" src="chrome://messenger/content/devtools/dbg-messenger-overlay.js"/>
-
-  <menupopup id="taskPopup">
-    <menuitem id="devtoolsDebugger"
-              type="checkbox"
-              insertafter="menu_inspector,javascriptConsole,javaScriptConsole"
-              label="&allowRemoteDebugging.label;"
-              accesskey="&allowRemoteDebugging.accesskey;"
-              oncommand="toggleDebugger()"/>
-  </menupopup>
-</overlay>
deleted file mode 100644
--- a/mail/components/devtools/content/webconsole-overlay.xul
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/messenger.dtd">
-
-<overlay id="tb-webconsole-overlay"
-         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <window id="devtools-webconsole" browserConsoleTitle="&errorConsoleCmd.label;">
-    <!--  Workaround since devtools hardcodes navigator:browser, which makes
-          browserWindow null, which means it falls back to using the
-          webconsole.xul window to look for gViewSourceUtils. -->
-    <script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
-    <script type="application/javascript"><![CDATA[
-        function openUILinkIn(url) {
-            var mailWindow = Services.wm.getMostRecentWindow("mail:3pane");
-            return mailWindow.openLinkExternally(url);
-        }
-    ]]></script>
-  </window>
-</overlay>
new file mode 100644
--- /dev/null
+++ b/mail/components/devtools/devtools-loader.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+
+function DevToolsStartup() {}
+
+DevToolsStartup.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+  classID: Components.ID("{089694e9-106a-4704-abf7-62a88545e194}"),
+
+  helpInfo: "",
+  handle: function (cmdLine) {
+    this.initialize();
+
+    // We want to overwrite the -devtools flag and open the toolbox instead
+    let devtoolsFlag = cmdLine.handleFlag("devtools", false);
+    if (devtoolsFlag) {
+        this.handleDevToolsFlag(cmdLine);
+    }
+  },
+
+  handleDevToolsFlag: function (cmdLine) {
+    Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm");
+    BrowserToolboxProcess.init();
+
+    if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
+      cmdLine.preventDefault = true;
+    }
+  },
+
+  initialize: function() {
+    var { devtools, require, DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+    var { DebuggerServer } = require("devtools/server/main");
+    var { gDevTools } = require("devtools/client/framework/devtools");
+    var HUDService = require("devtools/client/webconsole/hudservice");
+
+    if (DebuggerServer.chromeWindowType != "mail:3pane") {
+      // Set up the server chrome window type, make sure it can't be set
+      Object.defineProperty(DebuggerServer, "chromeWindowType", {
+        get: () => "mail:3pane",
+        set: () => {},
+        configurable: true
+      });
+    }
+
+    if (gDevTools.chromeWindowType != "mail:3pane") {
+      // Set up the client chrome window type, make sure it can't be set
+      Object.defineProperty(gDevTools, "chromeWindowType", {
+        get: () => "mail:3pane",
+        set: () => {},
+        configurable: true
+      });
+    }
+
+    // Make the loader visible to the debugger by default and for the already
+    // loaded instance. Thunderbird now also provides the Browser Toolbox for
+    // chrome debugging, which uses its own separate loader instance.
+    DevToolsLoader.prototype.invisibleToDebugger = false;
+    devtools.invisibleToDebugger = false;
+
+    if (!DebuggerServer.initialized) {
+      // Initialize and load the toolkit/browser actors
+      DebuggerServer.init();
+      DebuggerServer.addBrowserActors("mail:3pane");
+    }
+
+    if (!DebuggerServer.createRootActor.isMailRootActor) {
+      // Register the Thunderbird root actor
+      DebuggerServer.registerModule("resource:///modules/tb-root-actor.js");
+    }
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DevToolsStartup]);
new file mode 100644
--- /dev/null
+++ b/mail/components/devtools/devtools-loader.manifest
@@ -0,0 +1,5 @@
+component {089694e9-106a-4704-abf7-62a88545e194} devtools-loader.js
+contract @mozilla.org/messenger/devtools-startup-clh;1 {089694e9-106a-4704-abf7-62a88545e194}
+# This command line handler needs to be initialized before the devtools command line
+# handler, which currently uses "m-devtools". See details in nsICommandLineHandler.idl.
+category command-line-handler m-aaa-tb-devtools @mozilla.org/messenger/devtools-startup-clh;1
deleted file mode 100644
--- a/mail/components/devtools/extension/Makefile.in
+++ /dev/null
@@ -1,8 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-XPI_PKGNAME = dbgserver-$(MOZILLA_VERSION).$(AB_CD)
-relativesrcdir = mail/locales
-
-DEFINES += -DLOCALE_SRCDIR=$(LOCALE_SRCDIR)
deleted file mode 100644
--- a/mail/components/devtools/extension/bootstrap.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-function startup(aData, aReasion) {
-  // Register the resource:// location
-  let resource = Services.io
-                         .getProtocolHandler("resource")
-                         .QueryInterface(Ci.nsIResProtocolHandler);
-  resource.setSubstitution("dbgserver", aData.resourceURI);
-
-  // Load the debug server and start it if enabled.
-  Cu.import("resource://dbgserver/modules/RemoteDebuggerServer.jsm");
-  let remoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
-
-  RemoteDebuggerServer.extraInit = function(DebuggerServer) {
-    DebuggerServer.registerModule("resource://dbgserver/modules/XULRootActor.js");
-  };
-  RemoteDebuggerServer.startstop(remoteEnabled);
-}
-
-function shutdown(aData, aReason) {
-  if (aReason == APP_SHUTDOWN) return;
-
-  // Make sure to stop the debug server on disable and uninstall
-  if (aReason == ADDON_DISABLE || aReason == ADDON_UNINSTALL) {
-    RemoteDebuggerServer.stop();
-  }
-
-  // Unload our debug server
-  Cu.unload("resource://dbgserver/modules/RemoteDebuggerServer.jsm");
-
-  // Unregister the dbgserve resource:// location
-  let resource = Services.io
-                         .getProtocolHandler("resource")
-                         .QueryInterface(Ci.nsIResProtocolHandler);
-  resource.setSubstitution("dbgserver", null);
-}
-
-function install(aData, aReason) {}
-function uninstall(aData, aReason) {}
deleted file mode 100644
--- a/mail/components/devtools/extension/content/options.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-Components.utils.import("resource://dbgserver/modules/RemoteDebuggerServer.jsm");
-Components.utils.import("resource://gre/modules/PluralForm.jsm");
-
-/**
- * Toggle the debugger and reinitialize the UI
- */
-function toggleDebugger() {
-  applyChanges();
-  RemoteDebuggerServer.startstop(!RemoteDebuggerServer.listening);
-  updateState();
-}
-
-/**
- * Initialize the options pane
- */
-function initPane() {
-  RemoteDebuggerServer.onConnectionChange = updateState;
-  window.addEventListener("unload", function() {
-    RemoteDebuggerServer.onConnectionChange = null;
-  }, false);
-  updateState();
-}
-
-/**
- * Update the label states, i.e on connection change
- */
-function updateState() {
-  let buttonKey = "";
-  let statusKey = "";
-  let statusArg = null;
-
-  if (RemoteDebuggerServer.supported) {
-    if (RemoteDebuggerServer.listening) {
-      buttonKey = "stop";
-      if (RemoteDebuggerServer.connections > 0) {
-        statusKey = "connected";
-        statusArg = RemoteDebuggerServer.connections;
-      } else {
-        statusKey = "listening";
-      }
-    } else {
-      buttonKey = "start";
-      statusKey = "idle";
-    }
-  } else {
-    buttonKey = "start";
-    statusKey = "unsupported";
-  }
-
-  let strings = document.getElementById("strings");
-  let btn = document.getElementById("toggleDebugger-button");
-  btn.label = strings.getString("options." + buttonKey + ".label");
-  btn.disabled = !RemoteDebuggerServer.supported;
-
-  let lbl = document.getElementById("status-label");
-  if (statusArg) {
-    let status = strings.getFormattedString("options." + statusKey + ".label", [statusArg]);
-    lbl.textContent = PluralForm.get(statusArg, status).replace("#1", statusArg);
-  } else {
-    lbl.textContent = strings.getString("options." + statusKey + ".label");
-  }
-  lbl.setAttribute("tooltiptext", strings.getString("options." + statusKey + ".tooltip"));
-}
-
-/**
- * Apply the pref changes (useful for non-instant-apply platforms)
- */
-function applyChanges() {
-  document.getElementById("dbgserver-prefpane").writePreferences(false);
-  Components.classes["@mozilla.org/preferences-service;1"]
-            .getService(Components.interfaces.nsIPrefService)
-            .savePrefFile(null);
-}
deleted file mode 100644
--- a/mail/components/devtools/extension/content/options.xul
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
-
-<!DOCTYPE prefwindow SYSTEM "chrome://dbgserver/locale/dbgserver.dtd">
-
-<prefwindow id="dbgserver"
-            xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-            title="&options.title;"
-            buttons="accept"
-            width="300"
-            height="300"
-            ondialogaccept="return true;">
-  <prefpane id="dbgserver-prefpane" flex="1" onpaneload="initPane()">
-    <preferences>
-      <preference id="devtools.debugger.remote-port" name="devtools.debugger.remote-port" type="int" onchange="RemoteDebuggerServer.stop(); updateState();"/>
-      <preference id="devtools.debugger.force-local" name="devtools.debugger.force-local" type="bool" inverted="true"/>
-    </preferences>
-
-    <grid flex="1">
-      <columns>
-        <column/>
-        <column flex="1"/>
-      </columns>
-      <rows>
-        <row>
-          <label for="port">&options.port.label;</label>
-          <textbox type="number"
-                   id="port"
-                   preference="devtools.debugger.remote-port"/>
-        </row>
-        <row>
-          <label>&options.status.label;</label>
-          <label id="status-label"/>
-        </row>
-        <checkbox id="force-local"
-                  preference="devtools.debugger.force-local"
-                  label="&options.forcelocal.label;"/>
-      </rows>
-    </grid>
-    <button id="toggleDebugger-button" oncommand="toggleDebugger()"/>
-  </prefpane>
-  <script src="chrome://dbgserver/content/options.js" type="application/javascript"/>
-  <stringbundle id="strings" src="chrome://dbgserver/locale/dbgserver.properties"/>
-</prefwindow>
deleted file mode 100644
--- a/mail/components/devtools/extension/install.rdf
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-#filter substitution
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-  <Description about="urn:mozilla:install-manifest">
-
-    <em:id>remote-debugger@mozilla.org</em:id>
-    <em:version>@MOZILLA_VERSION_U@</em:version>
-    <em:type>2</em:type>
-    <em:bootstrap>true</em:bootstrap>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>toolkit@mozilla.org</em:id>
-        <em:minVersion>@MOZILLA_VERSION_U@</em:minVersion>
-        <em:maxVersion>@MOZILLA_VERSION_U@</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-    <em:name>Remote Developer Tools Server</em:name>
-    <em:description>Access for the Firefox Web Developer Tools</em:description>
-    <em:creator>Philipp Kewisch</em:creator>
-    <em:optionsURL>chrome://dbgserver/content/options.xul</em:optionsURL>
-  </Description>
-</RDF>
deleted file mode 100644
--- a/mail/components/devtools/extension/jar.mn
+++ /dev/null
@@ -1,13 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#filter substitution
-
-dbgserver.jar:
-% content dbgserver %content/
-    content/options.xul                          (content/options.xul)
-    content/options.js                           (content/options.js)
-
-% locale dbgserver @AB_CD@ %locale/@AB_CD@/
-    locale/@AB_CD@/dbgserver.properties          (%chrome/messenger/devtools/dbgserver.properties)
-    locale/@AB_CD@/dbgserver.dtd                 (%chrome/messenger/devtools/dbgserver.dtd)
deleted file mode 100644
--- a/mail/components/devtools/extension/moz.build
+++ /dev/null
@@ -1,21 +0,0 @@
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-EXTRA_JS_MODULES += ["../modules/RemoteDebuggerServer.jsm",
-                     "../modules/XULRootActor.js"]
-
-XPI_NAME = 'dbgserver'
-
-FINAL_TARGET_PP_FILES += [
-    'install.rdf',
-]
-
-FINAL_TARGET_FILES += [
-    'bootstrap.js',
-]
-
-JAR_MANIFESTS += ['jar.mn']
-
-USE_EXTENSION_MANIFEST = True
--- a/mail/components/devtools/jar.mn
+++ b/mail/components/devtools/jar.mn
@@ -1,10 +1,9 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 messenger.jar:
-% overlay chrome://messenger/content/messenger.xul chrome://messenger/content/devtools/dbg-messenger-overlay.xul
 % overlay chrome://devtools/content/webconsole/webconsole.xul chrome://messenger/content/devtools/webconsole-overlay.xul
-  content/messenger/devtools/dbg-messenger-overlay.xul     (content/dbg-messenger-overlay.xul)
-  content/messenger/devtools/dbg-messenger-overlay.js      (content/dbg-messenger-overlay.js)
-  content/messenger/devtools/webconsole-overlay.xul        (content/webconsole-overlay.xul)
+% overlay chrome://devtools/content/framework/toolbox-process-window.xul chrome://messenger/content/devtools/toolbox-process-overlay.xul
+  content/messenger/devtools/webconsole-overlay.xul        (webconsole-overlay.xul)
+  content/messenger/devtools/toolbox-process-overlay.xul   (toolbox-process-overlay.xul)
deleted file mode 100644
--- a/mail/components/devtools/modules/RemoteDebuggerServer.jsm
+++ /dev/null
@@ -1,209 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-this.EXPORTED_SYMBOLS = ["RemoteDebuggerServer"];
-
-/**
- * A module to wrap the devtools DebuggerServer with some extra methods.
- */
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-/** Load the debugger module, if its available. */
-var DebuggerServer = (function() {
-  try {
-    var { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
-    return require("devtools/server/main").DebuggerServer;
-  } catch (e) {}
-
-  return null;
-})();
-
-/** @return the list of main windows, see isMainWindow */
-function getMainWindows() {
-  let found = [];
-  let windows = Services.wm.getEnumerator(null);
-  while (windows.hasMoreElements()) {
-    let win = windows.getNext();
-    if (isMainWindow(win)) {
-      found.push(win);
-    }
-  }
-  return found;
-}
-
-/**
- * Check if the window is the "main window" by checking if the host part
- * matches the basename of the filename.
- *
- * @param aWindow       The window to check
- */
-function isMainWindow(aWindow) {
-  let urlParser = Components.classes["@mozilla.org/network/url-parser;1?auth=no"]
-                            .getService(Components.interfaces.nsIURLParser);
-  let baseName, bnpos = {}, bnlen = {};
-  let path = aWindow.location.pathname;
-  urlParser.parseFilePath(path, path.length, {}, {}, bnpos, bnlen, {}, {});
-  baseName = path.substr(bnpos.value, bnlen.value);
-  return (aWindow.location.hostname == baseName);
-}
-
-/**
- * The Frontend for the remote debugger, starts, stops and initializes the
- * actors.
- */
-var RemoteDebuggerServer = {
-  /** @return true if the debugger server is running */
-  get listening() { return DebuggerServer && DebuggerServer._listener != null; },
-
-  /** @return the number of connections to the debugger server */
-  get connections() { return DebuggerServer ? Object.keys(DebuggerServer._connections).length : 0; },
-
-  /** @return true if the debugger server could be loaded */
-  get supported() { return !!DebuggerServer; },
-
-  /**
-   * Get all windows that should be checked by the actors. The first one
-   * will be used as the chrome window type for DebuggerServer. If this is not
-   * set explicitly, it will be detected once from the open window urls.
-   */
-  get chromeWindowTypes() {
-    if (!this._chromeWindowTypes) {
-      let mainWindows = getMainWindows().map(function(win) {
-        return win.document.documentElement.getAttribute("windowtype");
-      });
-      this._chromeWindowTypes = mainWindows;
-    }
-
-    return this._chromeWindowTypes || [];
-  },
-  set chromeWindowTypes(v) { this._chromeWindowTypes = v; },
-
-  /**
-   * Set a function to wrap the DebuggerServer's onConnectionChange
-   * notification. This is not failsafe with multiple functions or other
-   * callers changing the DebuggerServer's function, so it should be used
-   * with caution.
-   */
-  get onConnectionChange() { return DebuggerServer.onConnectionChange; },
-  set onConnectionChange(aFunc) {
-    if (this.supported) {
-      if (aFunc) {
-        if (!this._origConnChange) {
-          this._origConnChange = DebuggerServer.onConnectionChange;
-        }
-        DebuggerServer.onConnectionChange = aFunc;
-      } else {
-        DebuggerServer.onConnectionChange = this._origConnChange;
-        this._origConnChange = null;
-      }
-    }
-    return aFunc;
-  },
-
-  /**
-   * Ensure the Remote Debugger is properly initialized.
-   */
-  _checkInit: function() {
-    if (DebuggerServer.initialized && DebuggerServer.createRootActor) {
-      return;
-    }
-
-    // Initialize the debugger, if non-local connections are permitted then
-    // have the default prompt kick in.
-    DebuggerServer.init(() => {
-      return Services.prefs.getBoolPref("devtools.debugger.force-local") ||
-             DebuggerServer._defaultAllowConnection();
-    });
-
-    // Load the toolkit actors first
-    DebuggerServer.addBrowserActors();
-
-    // Set up the chrome window type
-    DebuggerServer.chromeWindowType = this.chromeWindowTypes[0];
-
-    // Set up the extra actors. Pass DebuggerServer for convenience
-    this.extraInit(DebuggerServer);
-  },
-
-  extraInit: function(DebuggerServer) {
-    // Overwrite this to add your actors
-  },
-
-  /**
-   * Start or stop the server depending on the parameters
-   *
-   * @param start   If true, start the server, else stop.
-   * @return        True if the debugger server was started.
-   */
-  startstop: function(start) {
-    if (start) {
-      this.start();
-    } else {
-      this.stop();
-    }
-    return this.listening;
-  },
-
-  /**
-   * Start the debugger server
-   *
-   * @return    True, if the server was successfully started
-   */
-  start: function() {
-    if (!this.supported) {
-      return false;
-    }
-
-    this._checkInit();
-    Services.prefs.setBoolPref('devtools.debugger.remote-enabled', true);
-
-    // Make sure chrome debugging is enabled, no sense in starting otherwise.
-    DebuggerServer.allowChromeProcess = true;
-
-    let port = Services.prefs.getIntPref('devtools.debugger.remote-port') || 6000;
-    try {
-      let listener = DebuggerServer.createListener();
-      listener.portOrPath = port;
-
-      // Expose this listener via wifi discovery, if enabled.
-      if (Services.prefs.getBoolPref("devtools.remote.wifi.visible") &&
-          !Services.prefs.getBoolPref("devtools.debugger.force-local")) {
-        listener.discoverable = true;
-      }
-
-      listener.open();
-    } catch (e) {
-      Components.utils.reportError("Unable to start debugger server: " + e);
-      return false;
-    }
-    return true;
-  },
-
-  /**
-   * Stop the debugger server.
-   *
-   * @param aForce      If not passed or true, force debugger server shutdown
-   * @return            True, if the server was successfully stopped
-   */
-  stop: function(aForce=true) {
-    if (!this.supported) {
-      return false;
-    }
-
-    Services.prefs.setBoolPref('devtools.debugger.remote-enabled', false);
-    try {
-      DebuggerServer.closeAllListeners(aForce);
-    } catch (e) {
-      Components.utils.reportError("Unable to stop debugger server: " + e);
-      return false;
-    }
-    return true;
-  }
-};
-
-// Add this to DebuggerServer so the actors can make use of it without loading
-// this file, because the URL is different between loading as an extension and
-// directly.
-DebuggerServer.RemoteDebuggerServer = RemoteDebuggerServer;
deleted file mode 100644
--- a/mail/components/devtools/modules/XULRootActor.js
+++ /dev/null
@@ -1,238 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * Actors for the remote debugger server.
- *
- * NOTE: This file is used from both Thundebird and the Debugger Server
- * extension. Please don't introduce any Thunderbird-specific code
- */
-
-var { Ci, Cu } = require("chrome");
-var Services = require("Services");
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-var { RootActor } = require("devtools/server/actors/root");
-var { DebuggerServer } = require("devtools/server/main");
-var { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
-var { BrowserTabList, BrowserTabActor, BrowserAddonList } = require("devtools/server/actors/webbrowser");
-
-/**
- * Create the root actor for this XUL application.
- *
- * @param aConnection       The debugger connection to create the actor for.
- * @return                  The mail actor for the connection.
- */
-function createRootActor(aConnection) {
-  let parameters = {
-    tabList: new XulTabList(aConnection),
-    addonList: new BrowserAddonList(aConnection),
-    globalActorFactories: DebuggerServer.globalActorFactories,
-    onShutdown: sendShutdownEvent,
-  };
-
-  // Create the root actor and set the application type
-  let rootActor = new RootActor(aConnection, parameters);
-  if (DebuggerServer.chromeWindowType) {
-    rootActor.applicationType = DebuggerServer.chromeWindowType.split(":")[0];
-  } else {
-    rootActor.applicationType = "xulrunner";
-  }
-
-  return rootActor;
-}
-
-/**
- * Returns the window type of the passed window.
- */
-function appShellDOMWindowType(aWindow) {
-  /* This is what nsIWindowMediator's enumerator checks. */
-  return aWindow.document.documentElement.getAttribute('windowtype');
-}
-
-/**
- * Send a debugger shutdown event to all main windows.
- */
-function sendShutdownEvent() {
-  let windowTypes = DebuggerServer.RemoteDebuggerServer.chromeWindowTypes;
-  for (let type of windowTypes) {
-    let enumerator = Services.wm.getEnumerator(type);
-    while (enumerator.hasMoreElements()) {
-      let win = enumerator.getNext();
-      let evt = win.document.createEvent("Event");
-      evt.initEvent("Debugger:Shutdown", true, false);
-      win.document.documentElement.dispatchEvent(evt);
-    }
-  }
-}
-
-/**
- * The live list of tabs for a XUL Application. The term tab is taken from
- * Firefox tabs, where each browser tab shows up as a tab in the debugger.
- * Not all apps have the concept where each tab is a content tab, so we will
- * be iterating the content windows and presenting them as tabs instead.
- *
- * @param aConnection       The connection to create the tab list for.
- */
-function XulTabList(aConnection) {
-  this._connection = aConnection;
-  this._actorByBrowser = new Map();
-
-  // These windows should be checked for browser elements
-  this._checkedWindows = new Set(DebuggerServer.RemoteDebuggerServer.chromeWindowTypes);
-}
-
-XulTabList.prototype = {
-  _onListChanged: null,
-  _actorByBrowser: null,
-  _checkedWindows: null,
-  _mustNotify: false,
-  _listeningToMediator: false,
-
-  get onListChanged() {
-    return this._onListChanged;
-  },
-
-  set onListChanged(v) {
-    if (v !== null && typeof v !== 'function') {
-      throw Error("onListChanged property may only be set to 'null' or a function");
-    }
-    this._onListChanged = v;
-    this._checkListening();
-  },
-
-  _checkListening: function() {
-    let shouldListenToMediator =
-        ((this._onListChanged && this._mustNotify) ||
-         this._actorByBrowser.size > 0);
-
-    if (this._listeningToMediator !== shouldListenToMediator) {
-      let op = shouldListenToMediator ? "addListener" : "removeListener";
-      Services.wm[op](this);
-      this._listeningToMediator = shouldListenToMediator;
-    }
-  },
-
-  _notifyListChanged: function() {
-    if (this._onListChanged && this._mustNotify) {
-      this._onListChanged();
-      this._mustNotify = false;
-    }
-  },
-
-  _getTopWindow: function() {
-    let winIter = Services.wm.getZOrderDOMWindowEnumerator(null, true);
-    while (winIter.hasMoreElements()) {
-      let win = winIter.getNext();
-      if (this._checkedWindows.has(appShellDOMWindowType(win))) {
-        // This is one of our windows, return it
-        return win;
-      }
-    }
-    return null;
-  },
-
-  getList: function() {
-    let topWindow = this._getTopWindow();
-
-    // Look for all browser elements in all the windows we care about
-    for (let winName of this._checkedWindows) {
-      let winIter = Services.wm.getEnumerator(winName);
-      while (winIter.hasMoreElements()) {
-        let win = winIter.getNext();
-        let foundSelected = false;
-        // Check for browser elements and create a tab actor for each.
-        // This will catch content tabs, the message reader and the
-        // multi-message reader.
-        for (let browser of win.document.getElementsByTagName("browser")) {
-          if (browser.currentURI.spec == "about:blank") {
-            // about:blank is not particularly interesting. Don't
-            // add it to the list.
-            continue;
-          }
-          let actor = this._actorByBrowser.get(browser);
-          if (!actor) {
-            actor = new BrowserTabActor(this._connection,
-                                        browser, null);
-            this._actorByBrowser.set(browser, actor);
-          }
-
-          // Select the first visible browser in the top xul
-          // window.
-          let bo = browser.boxObject;
-          actor.selected = foundSelected =
-              win == topWindow &&
-              !foundSelected &&
-              bo.height > 0 &&
-              bo.width > 0;
-        }
-      }
-    }
-
-    this._mustNotify = true;
-    this._checkListening();
-
-    return Promise.resolve([...this._actorByBrowser.values()]);
-  },
-
-  onOpenWindow: DevToolsUtils.makeInfallible(function(aWindow) {
-    let handleLoad = DevToolsUtils.makeInfallible(() => {
-      if (this._checkedWindows.has(appShellDOMWindowType(aWindow))) {
-        // This is one of our windows, we need to check for browser
-        // elements. Notify is enough, iterate will do the actual actor
-        // creation.
-        this._notifyListChanged();
-      }
-    });
-
-    // You can hardly do anything at all with a XUL window at this point; it
-    // doesn't even have its document yet. Wait until its document has
-    // loaded, and then see what we've got. This also avoids
-    // nsIWindowMediator enumeration from within listeners (bug 873589).
-    aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                     .getInterface(Ci.nsIDOMWindow);
-    aWindow.addEventListener("load", handleLoad, {capture: false, once: true});
-  }, "XulTabList.prototype.onOpenWindow"),
-
-  onCloseWindow: DevToolsUtils.makeInfallible(function(aWindow) {
-    aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                     .getInterface(Ci.nsIDOMWindow);
-
-    // Only handle our window types
-    if (this._checkedWindows.has(appShellDOMWindowType(aWindow))) {
-      return;
-    }
-
-    // nsIWindowMediator deadlocks if you call its GetEnumerator method from
-    // a nsIWindowMediatorListener's onCloseWindow hook (bug 873589), so
-    // handle the close in a different tick.
-    Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
-      let shouldNotify = false;
-
-      // Scan the whole map for browsers that were in this window.
-      for (let [browser, actor] of this._actorByBrowser) {
-        // The browser document of a closed window has no default view.
-        if (!browser.ownerDocument.defaultView) {
-          this._actorByBrowser.delete(browser);
-          actor.exit();
-          shouldNotify = true;
-        }
-      }
-
-      if (shouldNotify) {
-        this._notifyListChanged();
-      }
-      this._checkListening();
-    }, "XulTabList.prototype.onCloseWindow's delayed body"), 0);
-  }, "XulTabList.prototype.onCloseWindow"),
-
-  onWindowTitleChange: function() {}
-};
-
-exports.register = function(handle) {
-  handle.setRootActor(createRootActor);
-};
-
-exports.unregister = function(handle) {
-  handle.setRootActor(null);
-};
--- a/mail/components/devtools/moz.build
+++ b/mail/components/devtools/moz.build
@@ -1,11 +1,15 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-DIRS += ["extension"]
+EXTRA_JS_MODULES += [
+    "tb-root-actor.js",
+]
 
-EXTRA_JS_MODULES += ["modules/RemoteDebuggerServer.jsm",
-                     "modules/XULRootActor.js"]
+EXTRA_COMPONENTS += [
+    "devtools-loader.js",
+    "devtools-loader.manifest",
+]
 
 JAR_MANIFESTS += ['jar.mn']
new file mode 100644
--- /dev/null
+++ b/mail/components/devtools/tb-root-actor.js
@@ -0,0 +1,271 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Actors for Thunderbird Developer Tools, for example the root actor or tab
+ * list actor.
+ */
+
+var { Ci, Cc } = require("chrome");
+var Services = require("Services");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var promise = require('promise');
+var { RootActor } = require("devtools/server/actors/root");
+var { DebuggerServer } = require("devtools/server/main");
+var { BrowserTabList, BrowserTabActor, BrowserAddonList } = require("devtools/server/actors/webbrowser");
+
+/**
+ * Create the root actor for Thunderbird.
+ *
+ * @param aConnection       The debugger connection to create the actor for.
+ * @return                  The mail actor for the connection.
+ */
+function createRootActor(aConnection) {
+  let parameters = {
+    tabList: new TBTabList(aConnection),
+    addonList: new BrowserAddonList(aConnection),
+    globalActorFactories: DebuggerServer.globalActorFactories,
+    onShutdown: sendShutdownEvent,
+  };
+
+  // Create the root actor and set the application type
+  let rootActor = new RootActor(aConnection, parameters);
+  if (DebuggerServer.chromeWindowType) {
+    rootActor.applicationType = DebuggerServer.chromeWindowType.split(":")[0];
+  }
+
+  return rootActor;
+}
+createRootActor.isMailRootActor = true;
+
+function getChromeWindowTypes() {
+  /** @return the list of main windows, see isMainWindow */
+  function getMainWindows() {
+    let found = [];
+    let windows = Services.wm.getEnumerator(null);
+    while (windows.hasMoreElements()) {
+      let win = windows.getNext();
+      if (isMainWindow(win)) {
+        found.push(win);
+      }
+    }
+    return found;
+  }
+
+  /**
+   * Check if the window is the "main window" by checking if the host part
+   * matches the basename of the filename.
+   *
+   * @param aWindow       The window to check
+   */
+  function isMainWindow(aWindow) {
+    let urlParser = Cc["@mozilla.org/network/url-parser;1?auth=no"]
+                      .getService(Ci.nsIURLParser);
+    let baseName, bnpos = {}, bnlen = {};
+    let path = aWindow.location.pathname;
+    urlParser.parseFilePath(path, path.length, {}, {}, bnpos, bnlen, {}, {});
+    baseName = path.substr(bnpos.value, bnlen.value);
+    return (aWindow.location.hostname == baseName);
+  }
+
+  return getMainWindows().map((win) => {
+    return win.document.documentElement.getAttribute("windowtype");
+  });
+}
+
+/**
+ * Returns the window type of the passed window.
+ */
+function appShellDOMWindowType(aWindow) {
+  // This is what nsIWindowMediator's enumerator checks.
+  return aWindow.document.documentElement.getAttribute('windowtype');
+}
+
+/**
+ * Send a debugger shutdown event to all main windows.
+ */
+function sendShutdownEvent() {
+  let windowTypes = getChromeWindowTypes();
+  for (let type of windowTypes) {
+    let enumerator = Services.wm.getEnumerator(type);
+    while (enumerator.hasMoreElements()) {
+      let win = enumerator.getNext();
+      let evt = win.document.createEvent("Event");
+      evt.initEvent("Debugger:Shutdown", true, false);
+      win.document.documentElement.dispatchEvent(evt);
+    }
+  }
+}
+
+/**
+ * The live list of tabs for Thunderbird. The term tab is taken from
+ * Firefox tabs, where each browser tab shows up as a tab in the debugger.
+ * This is not the case for Thunderbird, so we will be iterating the content
+ * windows and presenting them as tabs instead.
+ *
+ * @param aConnection       The connection to create the tab list for.
+ */
+function TBTabList(aConnection) {
+  this._connection = aConnection;
+  this._actorByBrowser = new Map();
+
+  // These windows should be checked for browser elements
+
+  this._checkedWindows = new Set(getChromeWindowTypes());
+}
+
+TBTabList.prototype = {
+  _onListChanged: null,
+  _actorByBrowser: null,
+  _checkedWindows: null,
+  _mustNotify: false,
+  _listeningToMediator: false,
+
+  get onListChanged() {
+    return this._onListChanged;
+  },
+
+  set onListChanged(v) {
+    if (v !== null && typeof v !== 'function') {
+      throw Error("onListChanged property may only be set to 'null' or a function");
+    }
+    this._onListChanged = v;
+    this._checkListening();
+  },
+
+  _checkListening: function() {
+    let shouldListenToMediator =
+        ((this._onListChanged && this._mustNotify) ||
+         this._actorByBrowser.size > 0);
+
+    if (this._listeningToMediator !== shouldListenToMediator) {
+      let op = shouldListenToMediator ? "addListener" : "removeListener";
+      Services.wm[op](this);
+      this._listeningToMediator = shouldListenToMediator;
+    }
+  },
+
+  _notifyListChanged: function() {
+    if (this._onListChanged && this._mustNotify) {
+      this._onListChanged();
+      this._mustNotify = false;
+    }
+  },
+
+  _getTopWindow: function() {
+    let winIter = Services.wm.getZOrderDOMWindowEnumerator(null, true);
+    while (winIter.hasMoreElements()) {
+      let win = winIter.getNext();
+      if (this._checkedWindows.has(appShellDOMWindowType(win))) {
+        // This is one of our windows, return it
+        return win;
+      }
+    }
+    return null;
+  },
+
+  getList: function() {
+    let topWindow = this._getTopWindow();
+
+    // Look for all browser elements in all the windows we care about
+    for (let winName of this._checkedWindows) {
+      let winIter = Services.wm.getEnumerator(winName);
+      while (winIter.hasMoreElements()) {
+        let win = winIter.getNext();
+        let foundSelected = false;
+        // Check for browser elements and create a tab actor for each.
+        // This will catch content tabs, the message reader and the
+        // multi-message reader.
+        for (let browser of win.document.getElementsByTagName("browser")) {
+          if (browser.currentURI.spec == "about:blank") {
+            // about:blank is not particularly interesting. Don't
+            // add it to the list.
+            continue;
+          }
+          let actor = this._actorByBrowser.get(browser);
+          if (!actor) {
+            actor = new BrowserTabActor(this._connection,
+                                        browser, null);
+            this._actorByBrowser.set(browser, actor);
+          }
+
+          // Select the first visible browser in the top xul
+          // window.
+          let bo = browser.boxObject;
+          actor.selected = foundSelected =
+              win == topWindow &&
+              !foundSelected &&
+              bo.height > 0 &&
+              bo.width > 0;
+        }
+      }
+    }
+
+    this._mustNotify = true;
+    this._checkListening();
+
+    return promise.resolve([...this._actorByBrowser.values()]);
+  },
+
+  onOpenWindow: DevToolsUtils.makeInfallible(function(aWindow) {
+    let handleLoad = DevToolsUtils.makeInfallible(() => {
+      if (this._checkedWindows.has(appShellDOMWindowType(aWindow))) {
+        // This is one of our windows, we need to check for browser
+        // elements. Notify is enough, iterate will do the actual actor
+        // creation.
+        this._notifyListChanged();
+      }
+    });
+
+    // You can hardly do anything at all with a XUL window at this point; it
+    // doesn't even have its document yet. Wait until its document has
+    // loaded, and then see what we've got. This also avoids
+    // nsIWindowMediator enumeration from within listeners (bug 873589).
+    aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                     .getInterface(Ci.nsIDOMWindow);
+    aWindow.addEventListener("load", handleLoad, {capture: false, once: true});
+  }, "TBTabList.prototype.onOpenWindow"),
+
+  onCloseWindow: DevToolsUtils.makeInfallible(function(aWindow) {
+    aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                     .getInterface(Ci.nsIDOMWindow);
+
+    // Only handle our window types
+    if (this._checkedWindows.has(appShellDOMWindowType(aWindow))) {
+      return;
+    }
+
+    // nsIWindowMediator deadlocks if you call its GetEnumerator method from
+    // a nsIWindowMediatorListener's onCloseWindow hook (bug 873589), so
+    // handle the close in a different tick.
+    Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
+      let shouldNotify = false;
+
+      // Scan the whole map for browsers that were in this window.
+      for (let [browser, actor] of this._actorByBrowser) {
+        // The browser document of a closed window has no default view.
+        if (!browser.ownerDocument.defaultView) {
+          this._actorByBrowser.delete(browser);
+          actor.exit();
+          shouldNotify = true;
+        }
+      }
+
+      if (shouldNotify) {
+        this._notifyListChanged();
+      }
+      this._checkListening();
+    }, "TBTabList.prototype.onCloseWindow's delayed body"), 0);
+  }, "TBTabList.prototype.onCloseWindow"),
+
+  onWindowTitleChange: function() {}
+};
+
+exports.register = function(handle) {
+  handle.setRootActor(createRootActor);
+};
+
+exports.unregister = function(handle) {
+  handle.setRootActor(null);
+};
new file mode 100644
--- /dev/null
+++ b/mail/components/devtools/toolbox-process-overlay.xul
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<overlay id="tb-toolbox-process-overlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <!-- defines openUILinkIn -->
+  <script type="application/javascript" src="chrome://communicator/content/contentAreaClick.js"/>
+</overlay>
new file mode 100644
--- /dev/null
+++ b/mail/components/devtools/webconsole-overlay.xul
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/messenger.dtd">
+
+<overlay id="tb-webconsole-overlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <window id="devtools-webconsole" browserConsoleTitle="&errorConsoleCmd.label;"/>
+</overlay>
--- a/mail/locales/en-US/chrome/messenger/messenger.dtd
+++ b/mail/locales/en-US/chrome/messenger/messenger.dtd
@@ -478,30 +478,50 @@
 <!ENTITY filtersApplyToMessage.label "Run Filters on Message">
 <!ENTITY filtersApplyToMessage.accesskey "u">
 <!ENTITY runJunkControls.label "Run Junk Mail Controls on Folder">
 <!ENTITY runJunkControls.accesskey "C">
 <!ENTITY deleteJunk.label "Delete Mail Marked as Junk in Folder">
 <!ENTITY deleteJunk.accesskey "D">
 <!ENTITY importCmd.label "Import…">
 <!ENTITY importCmd.accesskey "m">
-<!ENTITY errorConsoleCmd.label "Error Console">
-<!ENTITY errorConsoleCmd.accesskey "E">
-<!ENTITY errorConsoleCmd.commandkey "j">
 <!ENTITY clearRecentHistory.label "Clear Recent History…">
 <!ENTITY clearRecentHistory.accesskey "H">
 <!ENTITY accountManagerCmd2.label "Account Settings">
 <!ENTITY accountManagerCmd2.accesskey "S">
 <!-- LOCALIZATION NOTE (accountManagerCmdUnix.accesskey):
      Belongs to accountManagerCmd.label, which is placed under the Edit menu
      on Unix systems
   -->
 <!ENTITY accountManagerCmdUnix2.accesskey "A">
-<!ENTITY allowRemoteDebugging.label "Allow Remote Debugging">
-<!ENTITY allowRemoteDebugging.accesskey "g">
+
+<!-- Developer Tools Submenu -->
+<!ENTITY devtoolsMenu.label "Developer Tools">
+<!ENTITY devtoolsMenu.accesskey "o">
+<!ENTITY devToolboxCmd.label "Developer Toolbox">
+<!ENTITY devToolboxCmd.accesskey "T">
+<!ENTITY devToolboxCmd.commandkey "i">
+<!ENTITY addonDebugCmd.label "Add-on Debugger">
+<!ENTITY addonDebugCmd.accesskey "A">
+<!ENTITY tabsDebugCmd.label "Content Frame Debugger">
+<!ENTITY tabsDebugCmd.accesskey "C">
+<!ENTITY errorConsoleCmd.label "Error Console">
+<!ENTITY errorConsoleCmd.accesskey "E">
+<!ENTITY errorConsoleCmd.commandkey "j">
+<!ENTITY scratchpadCmd.label "Scratchpad">
+<!ENTITY scratchpadCmd.accesskey "S">
+
+<!--LOCALIZATION NOTE (scratchpadCmd.keycode):
+    This should be a key code constant as per
+    https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Constants_for_keyCode_value
+    (but without the DOM_ prefix) -->
+<!ENTITY scratchpadCmd.keycode "VK_F4">
+<!--LOCALIZATION NOTE (scratchpadCmd.keytext):
+    This should be the text printed on the key defined in the previous string. -->
+<!ENTITY scratchpadCmd.keytext "F4">
 
 <!-- Mail Toolbar -->
 <!ENTITY getMsgButton1.label "Get Messages">
 <!ENTITY newMsgButton.label "Write">
 <!ENTITY replyButton.label "Reply">
 <!ENTITY replyAllButton.label "Reply All">
 <!ENTITY replyListButton.label "Reply to List">
 <!ENTITY forwardButton.label "Forward">