Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 08 Mar 2013 14:30:34 -0500
changeset 124254 b4bfc1c0829cea02bf7447460f7878fcdbd7b957
parent 124242 4740975a1063d58f1e092f5eccd0f9b1a7843d7b (current diff)
parent 124253 af5f267cd6a1a1745b9d4393208b87e6d5892fda (diff)
child 124255 8c5d7b67d20dca1982ae4f22a2d35d8cefc299a1
push id24315
push userryanvm@gmail.com
push dateFri, 08 Mar 2013 19:30:04 +0000
treeherdermozilla-inbound@b4bfc1c0829c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1141,16 +1141,19 @@ pref("devtools.editor.expandtab", true);
 // Tells which component you want to use for source editing in developer tools.
 //
 // Available components:
 //   "orion" - this is the Orion source code editor from the Eclipse project. It
 //   provides programmer-specific editor features such as syntax highlighting,
 //   indenting and bracket recognition.
 pref("devtools.editor.component", "orion");
 
+// Enable the Font Inspector
+pref("devtools.fontinspector.enabled", true);
+
 // Whether the character encoding menu is under the main Firefox button. This
 // preference is a string so that localizers can alter it.
 pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
 
 // Allow using tab-modal prompts when possible.
 pref("prompts.tab_modal.enabled", true);
 // Whether the Panorama should animate going in/out of tabs
 pref("browser.panorama.animate_zoom", true);
--- a/browser/devtools/commandline/gcli.jsm
+++ b/browser/devtools/commandline/gcli.jsm
@@ -7017,18 +7017,18 @@ define('gcli/promise', ['require', 'expo
 });
 define("text!gcli/ui/intro.html", [], "\n" +
   "<div>\n" +
   "  <p>${l10n.introTextOpening2}</p>\n" +
   "\n" +
   "  <p>\n" +
   "    ${l10n.introTextCommands}\n" +
   "    <span class=\"gcli-out-shortcut\" onclick=\"${onclick}\"\n" +
-  "        ondblclick=\"${ondblclick}\" data-command=\"help\">help</span>\n" +
-  "    ${l10n.introTextKeys2} <code>${l10n.introTextF1Escape}</code>.\n" +
+  "        ondblclick=\"${ondblclick}\" data-command=\"help\">help</span>${l10n.introTextKeys2}\n" +
+  "    <code>${l10n.introTextF1Escape}</code>.\n" +
   "  </p>\n" +
   "\n" +
   "  <button onclick=\"${onGotIt}\" if=\"${showHideButton}\">${l10n.introTextGo}</button>\n" +
   "</div>\n" +
   "");
 
 /*
  * Copyright 2012, Mozilla Foundation and contributors
--- a/browser/devtools/commandline/test/browser_dbg_cmd.js
+++ b/browser/devtools/commandline/test/browser_dbg_cmd.js
@@ -42,23 +42,31 @@ function testCommands(dbg, cmd) {
                   is(output.value, "step over", "debugger stepped over");
                   cmd("dbg step out", function() {
                     is(output.value, "step out", "debugger stepped out");
                     cmd("dbg continue", function() {
                       cmd("dbg continue", function() {
                         is(output.value, "dbg continue", "debugger continued");
                         DeveloperToolbarTest.exec({
                           typed: "dbg close",
+                          completed: false,
                           blankOutput: true
                         });
 
                         let target = TargetFactory.forTab(gBrowser.selectedTab);
-                        ok(!gDevTools.getToolbox(target),
-                          "Debugger was closed.");
-                        finish();
+                        let toolbox = gDevTools.getToolbox(target);
+                        if (!toolbox) {
+                          ok(true, "Debugger was closed.");
+                          finish();
+                        } else {
+                          toolbox.on("destroyed", function () {
+                            ok(true, "Debugger was closed.");
+                            finish();
+                          });
+                        }
                       });
                     });
                   });
                 });
               });
             });
           });
         });
--- a/browser/devtools/debugger/DebuggerPanel.jsm
+++ b/browser/devtools/debugger/DebuggerPanel.jsm
@@ -5,19 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 this.EXPORTED_SYMBOLS = ["DebuggerPanel"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+    "resource://gre/modules/commonjs/sdk/core/promise.js");
+
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
   "resource://gre/modules/devtools/dbg-server.jsm");
 
 function DebuggerPanel(iframeWindow, toolbox) {
   this.panelWin = iframeWindow;
   this._toolbox = toolbox;
 
   this._controller = this.panelWin.DebuggerController;
@@ -32,23 +34,16 @@ DebuggerPanel.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
   open: function DebuggerPanel_open() {
     let deferred = Promise.defer();
 
     this._ensureOnlyOneRunningDebugger();
 
-    if (!this.target.isRemote) {
-      if (!DebuggerServer.initialized) {
-        DebuggerServer.init();
-        DebuggerServer.addBrowserActors();
-      }
-    }
-
     let onDebuggerLoaded = function () {
       this.panelWin.removeEventListener("Debugger:Loaded",
                                         onDebuggerLoaded, true);
       this._isReady = true;
       this.emit("ready");
       deferred.resolve(this);
     }.bind(this);
 
@@ -57,25 +52,35 @@ DebuggerPanel.prototype = {
                                         onDebuggerConnected, true);
       this.emit("connected");
     }.bind(this);
 
     this.panelWin.addEventListener("Debugger:Loaded", onDebuggerLoaded, true);
     this.panelWin.addEventListener("Debugger:Connected",
                                    onDebuggerConnected, true);
 
-    return deferred.promise;
+    // Remote debugging gets the debuggee from a RemoteTarget object.
+    if (this.target.isRemote) {
+      this.panelWin._remoteFlag = true;
+      return deferred.promise;
+    }
+
+    // Local debugging needs to convert the TabTarget to a RemoteTarget.
+    return this.target.makeRemote().then(function success() {
+      return deferred.promise;
+    });
   },
 
   // DevToolPanel API
   get target() this._toolbox.target,
 
   get isReady() this._isReady,
 
   destroy: function() {
+    this.emit("destroyed");
     return Promise.resolve(null);
   },
 
   // DebuggerPanel API
 
   addBreakpoint: function() {
     this._bkp.addBreakpoint.apply(this._bkp, arguments);
   },
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -94,17 +94,17 @@ let DebuggerController = {
       window.dispatchEvent("Debugger:Unloaded");
       window._isChromeDebugger && this._quitApp();
     }.bind(this));
   },
 
   /**
    * Prepares the hostname and port number for a remote debugger connection
    * and handles connection retries and timeouts.
-   *
+   * XXX: remove all this (bug 823577)
    * @return boolean
    *         True if connection should proceed normally, false otherwise.
    */
   _prepareConnection: function DC__prepareConnection() {
     // If we exceeded the total number of connection retries, bail.
     if (this._remoteConnectionTry === Prefs.remoteConnectionRetries) {
       Services.prompt.alert(null,
         L10N.getStr("remoteDebuggerPromptTitle"),
@@ -160,71 +160,59 @@ let DebuggerController = {
    * Initializes a debugger client and connects it to the debugger server,
    * wiring event handlers as necessary.
    */
   _connect: function DC__connect() {
     function callback() {
       window.dispatchEvent("Debugger:Connected");
     }
 
-    let client;
-
-    // Remote debugging gets the debuggee from a RemoteTarget object.
-    if (this._target && this._target.isRemote) {
-      window._isRemoteDebugger = true;
-
-      client = this.client = this._target.client;
+    if (!window._isChromeDebugger) {
+      let client = this.client = this._target.client;
       this._target.on("close", this._onTabDetached);
       this._target.on("navigate", this._onTabNavigated);
+      this._target.on("will-navigate", this._onTabNavigated);
 
       if (this._target.chrome) {
         let dbg = this._target.form.chromeDebugger;
         this._startChromeDebugging(client, dbg, callback);
       } else {
         this._startDebuggingTab(client, this._target.form, callback);
       }
       return;
     }
 
-    // Content or chrome debugging can connect directly to the debuggee.
-    // TODO: convert this to use a TabTarget.
-    let transport = window._isChromeDebugger
-      ? debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort)
-      : DebuggerServer.connectPipe();
+    // Chrome debugging needs to make the connection to the debuggee.
+    let transport = debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort);
 
-    client = this.client = new DebuggerClient(transport);
+    let client = this.client = new DebuggerClient(transport);
     client.addListener("tabNavigated", this._onTabNavigated);
     client.addListener("tabDetached", this._onTabDetached);
 
     client.connect(function(aType, aTraits) {
       client.listTabs(function(aResponse) {
-        if (window._isChromeDebugger) {
-          let dbg = aResponse.chromeDebugger;
-          this._startChromeDebugging(client, dbg, callback);
-        } else {
-          let tab = aResponse.tabs[aResponse.selected];
-          this._startDebuggingTab(client, tab, callback);
-        }
+        this._startChromeDebugging(client, aResponse.chromeDebugger, callback);
       }.bind(this));
     }.bind(this));
   },
 
   /**
    * Disconnects the debugger client and removes event handlers as necessary.
    */
   _disconnect: function DC__disconnect() {
     // Return early if the client didn't even have a chance to instantiate.
     if (!this.client) {
       return;
     }
     this.client.removeListener("tabNavigated", this._onTabNavigated);
     this.client.removeListener("tabDetached", this._onTabDetached);
 
-    // When remote debugging, the connection is closed by the RemoteTarget.
-    if (!window._isRemoteDebugger) {
+    // When debugging content or a remote instance, the connection is closed by
+    // the RemoteTarget.
+    if (window._isChromeDebugger) {
       this.client.close();
     }
 
     this.client = null;
     this.tabClient = null;
     this.activeThread = null;
   },
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/Makefile.in
@@ -0,0 +1,13 @@
+#
+# 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/.
+
+DEPTH		= @DEPTH@
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/font-inspector.css
@@ -0,0 +1,16 @@
+/* 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/. */
+
+.dim > #root,
+.font:not(.has-code) .font-css-code,
+.font-is-local,
+.font-is-remote,
+.font.is-local .font-format-url {
+  display: none;
+}
+
+.font.is-remote .font-is-remote,
+.font.is-local .font-is-local {
+  display: inline;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/font-inspector.js
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+
+function FontInspector(inspector, window)
+{
+  this.inspector = inspector;
+  this.chromeDoc = window.document;
+  this.init();
+}
+
+FontInspector.prototype = {
+  init: function FI_init() {
+    this.update = this.update.bind(this);
+    this.onNewNode = this.onNewNode.bind(this);
+    this.onHighlighterLocked = this.onHighlighterLocked.bind(this);
+    this.inspector.selection.on("new-node", this.onNewNode);
+    this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
+    if (this.inspector.highlighter) {
+      this.inspector.highlighter.on("locked", this.onHighlighterLocked);
+    }
+    this.update();
+  },
+
+  /**
+   * Is the fontinspector visible in the sidebar?
+   */
+  isActive: function FI_isActive() {
+    return this.inspector.sidebar &&
+           this.inspector.sidebar.getCurrentTabID() == "fontinspector";
+  },
+
+  /**
+   * Remove listeners.
+   */
+  destroy: function FI_destroy() {
+    this.chromeDoc = null;
+    this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
+    this.inspector.selection.off("new-node", this.onNewNode);
+    if (this.inspector.highlighter) {
+      this.inspector.highlighter.off("locked", this.onHighlighterLocked);
+    }
+  },
+
+  /**
+   * Selection 'new-node' event handler.
+   */
+  onNewNode: function FI_onNewNode() {
+    if (this.isActive() &&
+        this.inspector.selection.isConnected() &&
+        this.inspector.selection.isElementNode() &&
+        this.inspector.selection.reason != "highlighter") {
+      this.undim();
+      this.update();
+    } else {
+      this.dim();
+    }
+  },
+
+  /**
+   * Highlighter 'locked' event handler
+   */
+  onHighlighterLocked: function FI_onHighlighterLocked() {
+    this.undim();
+    this.update();
+  },
+
+  /**
+   * Hide the font list. No node are selected.
+   */
+  dim: function FI_dim() {
+    this.chromeDoc.body.classList.add("dim");
+    this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
+  },
+
+  /**
+   * Show the font list. A node is selected.
+   */
+  undim: function FI_undim() {
+    this.chromeDoc.body.classList.remove("dim");
+  },
+
+  /**
+   * Retrieve all the font related info we have for the selected
+   * node and display them.
+   */
+  update: function FI_update() {
+    if (!this.isActive() ||
+        !this.inspector.selection.isConnected() ||
+        !this.inspector.selection.isElementNode() ||
+        this.chromeDoc.body.classList.contains("dim")) {
+      return;
+    }
+
+    let node = this.inspector.selection.node;
+    let contentDocument = node.ownerDocument;
+
+    // We don't get fonts for a node, but for a range
+    let rng = contentDocument.createRange();
+    rng.selectNode(node);
+    let fonts = DOMUtils.getUsedFontFaces(rng);
+    let fontsArray = [];
+    for (let i = 0; i < fonts.length; i++) {
+      fontsArray.push(fonts.item(i));
+    }
+    fontsArray = fontsArray.sort(function(a, b) {
+      return a.srcIndex < b.srcIndex;
+    });
+    this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
+    for (let f of fontsArray) {
+      this.render(f, contentDocument);
+    }
+  },
+
+  /**
+   * Display the information of one font.
+   */
+  render: function FI_render(font, document) {
+    let s = this.chromeDoc.querySelector("#template > section");
+    s = s.cloneNode(true);
+
+    s.querySelector(".font-name").textContent = font.name;
+    s.querySelector(".font-css-name").textContent = font.CSSFamilyName;
+    s.querySelector(".font-format").textContent = font.format;
+
+    if (font.srcIndex == -1) {
+      s.classList.add("is-local");
+    } else {
+      s.classList.add("is-remote");
+    }
+
+    s.querySelector(".font-url").value = font.URI;
+
+    let iframe = s.querySelector(".font-preview");
+    if (font.rule) {
+      // This is the @font-face{…} code.
+      let cssText = font.rule.style.parentRule.cssText;
+
+      s.classList.add("has-code");
+      s.querySelector(".font-css-code").textContent = cssText;
+
+      // We guess the base URL of the stylesheet to make
+      // sure the font will be accessible in the preview.
+      // If the font-face is in an inline <style>, we get
+      // the location of the page.
+      let origin = font.rule.style.parentRule.parentStyleSheet.href;
+      if (!origin) { // Inline stylesheet
+        origin = document.location.href;
+      }
+      // We remove the last part of the URL to get a correct base.
+      let base = origin.replace(/\/[^\/]*$/,"/")
+
+      // From all this information, we build a preview.
+      this.buildPreview(iframe, font.CSSFamilyName, cssText, base);
+    } else {
+      this.buildPreview(iframe, font.CSSFamilyName, "", "");
+    }
+
+    this.chromeDoc.querySelector("#all-fonts").appendChild(s);
+  },
+
+  /**
+   * Show a preview of the font in an iframe.
+   */
+  buildPreview: function FI_buildPreview(iframe, name, cssCode, base) {
+    /* The HTML code of the preview is:
+     *   <!DOCTYPE HTML>
+     *   <head>
+     *    <base href="{base}"></base>
+     *   </head>
+     *   <style>
+     *   p {font-family: {name};}
+     *   * {font-size: 40px;line-height:60px;padding:0 10px;margin:0};
+     *   </style>
+     *   <p contenteditable>Abc</p>
+     */
+    let extraCSS = "* {padding:0;margin:0}";
+    extraCSS += "p {font-family: '" + name + "';}";
+    extraCSS += "p {font-size: 40px;line-height:60px;padding:0 10px;margin:0;}";
+    cssCode += extraCSS;
+    let src = "data:text/html;charset=utf-8,<!DOCTYPE HTML><head><base></base></head><style></style><p contenteditable>Abc</p>";
+    iframe.addEventListener("load", function onload() {
+      iframe.removeEventListener("load", onload, true);
+      let doc = iframe.contentWindow.document;
+      // We could have done that earlier, but we want to avoid any URL-encoding
+      // nightmare.
+      doc.querySelector("base").href = base;
+      doc.querySelector("style").textContent = cssCode;
+    }, true);
+    iframe.src = src;
+  },
+
+  /**
+   * Select the <body> to show all the fonts included in the document.
+   */
+  showAll: function FI_showAll() {
+    if (!this.isActive() ||
+        !this.inspector.selection.isConnected() ||
+        !this.inspector.selection.isElementNode()) {
+      return;
+    }
+    let node = this.inspector.selection.node;
+    let contentDocument = node.ownerDocument;
+    let root = contentDocument.documentElement;
+    if (contentDocument.body) {
+      root = contentDocument.body;
+    }
+    this.inspector.selection.setNode(root, "fontinspector");
+  },
+}
+
+
+window.setPanel = function(panel) {
+  window.fontInspector = new FontInspector(panel, window);
+}
+
+window.onunload = function() {
+  window.fontInspector.destroy();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/font-inspector.xhtml
@@ -0,0 +1,41 @@
+<!-- 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 html [
+<!ENTITY % fontinspectorDTD SYSTEM "chrome://browser/locale/devtools/font-inspector.dtd" >
+ %fontinspectorDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>&title;</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <link rel="stylesheet" href="font-inspector.css" type="text/css"/>
+    <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
+    <link rel="stylesheet" href="chrome://browser/skin/devtools/font-inspector.css" type="text/css"/>
+  </head>
+  <body class="devtools-monospace" role="application">
+    <script type="application/javascript;version=1.8" src="font-inspector.js"></script>
+    <div id="root">
+      <ul id="all-fonts"></ul>
+      <button id="showall" onclick="fontInspector.showAll()">&showAllFonts;</button>
+    </div>
+    <div id="template" style="display:none">
+      <section class="font">
+        <iframe sandbox="allow-same-origin" class="font-preview"></iframe>
+        <div class="font-info">
+          <h1 class="font-name"></h1>
+          <span class="font-is-local">&system;</span>
+          <span class="font-is-remote">&remote;</span>
+          <p class="font-format-url">
+            <input readonly="readonly" class="font-url"></input>
+            (<span class="font-format"></span>)
+          </p>
+          <p class="font-css">&usedAs; "<span class="font-css-name"></span>"</p>
+          <pre class="font-css-code"></pre>
+        </div>
+      </section>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/moz.build
@@ -0,0 +1,6 @@
+# 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/.
+
+TEST_DIRS += ['test']
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/test/Makefile.in
@@ -0,0 +1,22 @@
+# 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/.
+
+DEPTH     = @DEPTH@
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_BROWSER_FILES = \
+		browser_fontinspector.js \
+		browser_fontinspector.html \
+		browser_font.woff \
+		$(NULL)
+
+
+libs::	$(_BROWSER_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e8440843b48adbc99a20c5e0a1642a36ca7178de
GIT binary patch
literal 4704
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
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+
+<style>
+  @font-face {
+    font-family: bar;
+    src: url(bad/font/name.ttf), url(browser_font.woff) format("woff");
+  }
+  body{
+    font-family:Arial;
+  }
+  div {
+    font-family:Arial;
+    font-family:bar;
+  }
+</style>
+
+<body>
+  BODY
+  <div>DIV</div>
+</body>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let tempScope = {};
+Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
+let TargetFactory = tempScope.TargetFactory;
+
+function test() {
+  waitForExplicitFinish();
+
+  let doc;
+  let node;
+  let view;
+  let inspector;
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    doc = content.document;
+    waitForFocus(setupTest, content);
+  }, true);
+
+  content.location = "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_fontinspector.html";
+
+  function setupTest() {
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
+    gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+      openFontInspector(toolbox.getCurrentPanel());
+    });
+  }
+
+  function openFontInspector(aInspector) {
+    inspector = aInspector;
+
+    info("Inspector open");
+
+    inspector.selection.setNode(doc.body);
+    inspector.sidebar.select("fontinspector");
+    inspector.sidebar.once("fontinspector-ready", viewReady);
+  }
+
+  function viewReady() {
+    info("Font Inspector ready");
+
+    view = inspector.sidebar.getWindowForTab("fontinspector");
+
+    ok(!!view.fontInspector, "Font inspector document is alive.");
+
+    let d = view.document;
+
+    let s = d.querySelectorAll("#all-fonts > section");
+    is(s.length, 2, "Found 2 fonts");
+
+    is(s[0].querySelector(".font-name").textContent,
+       "DeLarge Bold", "font 0: Right font name");
+    ok(s[0].classList.contains("is-remote"),
+       "font 0: is remote");
+    is(s[0].querySelector(".font-url").value,
+       "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_font.woff",
+       "font 0: right url");
+    is(s[0].querySelector(".font-format").textContent,
+       "woff", "font 0: right font format");
+    is(s[0].querySelector(".font-css-name").textContent,
+       "bar", "font 0: right css name");
+
+
+    let font1Name = s[1].querySelector(".font-name").textContent;
+    let font1CssName = s[1].querySelector(".font-css-name").textContent;
+
+    // On Linux test machines, the Arial font doesn't exist.
+    // The fallback is "Liberation Sans"
+
+    ok((font1Name == "Arial") || (font1Name == "Liberation Sans"),
+       "font 1: Right font name");
+    ok(s[1].classList.contains("is-local"), "font 1: is local");
+    ok((font1CssName == "Arial") || (font1CssName == "Liberation Sans"),
+       "Arial", "font 1: right css name");
+
+    executeSoon(function() {
+      gDevTools.once("toolbox-destroyed", finishUp);
+      inspector._toolbox.destroy();
+    });
+  }
+
+  function finishUp() {
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/test/moz.build
@@ -0,0 +1,5 @@
+# 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/.
+
--- a/browser/devtools/framework/Target.jsm
+++ b/browser/devtools/framework/Target.jsm
@@ -7,27 +7,34 @@
 this.EXPORTED_SYMBOLS = [ "TargetFactory" ];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
+  "resource://gre/modules/devtools/dbg-server.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
+  "resource://gre/modules/devtools/dbg-client.jsm");
 
 const targets = new WeakMap();
 
 /**
  * Functions for creating Targets
  */
 this.TargetFactory = {
   /**
    * Construct a Target
-   * @param {XULTab} tab
-   *        The tab to use in creating a new target
+   * @param {XULTab} | {Object} tab
+   *        The tab to use in creating a new target, or an options object in
+   *        case of remote targets.
+   *
    * @return A target object
    */
   forTab: function TF_forTab(tab) {
     let target = targets.get(tab);
     if (target == null) {
       target = new TabTarget(tab);
       targets.set(tab, target);
     }
@@ -56,37 +63,17 @@ this.TargetFactory = {
     if (target == null) {
       target = new WindowTarget(window);
       targets.set(window, target);
     }
     return target;
   },
 
   /**
-   * Construct a Target for a remote global
-   * @param {Object} form
-   *        The serialized form of a debugging protocol actor.
-   * @param {DebuggerClient} client
-   *        The debuger client instance to communicate with the server.
-   * @param {boolean} chrome
-   *        A flag denoting that the debugging target is the remote process as a
-   *        whole and not a single tab.
-   * @return A target object
-   */
-  forRemote: function TF_forRemote(form, client, chrome) {
-    let target = targets.get(form);
-    if (target == null) {
-      target = new RemoteTarget(form, client, chrome);
-      targets.set(form, target);
-    }
-    return target;
-  },
-
-  /**
-   * Get all of the targets known to some browser instance (local if null)
+   * Get all of the targets known to the local browser instance
    * @return An array of target objects
    */
   allTargets: function TF_allTargets() {
     let windows = [];
     let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                        .getService(Components.interfaces.nsIWindowMediator);
     let en = wm.getXULWindowEnumerator(null);
     while (en.hasMoreElements()) {
@@ -166,66 +153,161 @@ Object.defineProperty(Target.prototype, 
 
 
 /**
  * A TabTarget represents a page living in a browser tab. Generally these will
  * be web pages served over http(s), but they don't have to be.
  */
 function TabTarget(tab) {
   EventEmitter.decorate(this);
-  this._tab = tab;
-  this._setupListeners();
+  this.destroy = this.destroy.bind(this);
+  this._handleThreadState = this._handleThreadState.bind(this);
+  this.on("thread-resumed", this._handleThreadState);
+  this.on("thread-paused", this._handleThreadState);
+  // Only real tabs need initialization here. Placeholder objects for remote
+  // targets will be initialized after a makeRemote method call.
+  if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
+    this._tab = tab;
+    this._setupListeners();
+  }
 }
 
 TabTarget.prototype = {
   _webProgressListener: null,
 
   supports: supports,
   get version() { return getVersion(); },
 
   get tab() {
     return this._tab;
   },
 
+  get form() {
+    return this._form;
+  },
+
+  get client() {
+    return this._client;
+  },
+
+  get chrome() {
+    return this._chrome;
+  },
+
   get window() {
-    return this._tab.linkedBrowser.contentWindow;
+    // Be extra careful here, since this may be called by HS_getHudByWindow
+    // during shutdown.
+    if (this._tab && this._tab.linkedBrowser) {
+      return this._tab.linkedBrowser.contentWindow;
+    }
   },
 
   get name() {
-    return this._tab.linkedBrowser.contentDocument.title;
+    return this._tab ? this._tab.linkedBrowser.contentDocument.title :
+                       this._form.title;
   },
 
   get url() {
-    return this._tab.linkedBrowser.contentDocument.location.href;
+    return this._tab ? this._tab.linkedBrowser.contentDocument.location.href :
+                       this._form.url;
   },
 
   get isRemote() {
-    return false;
+    return !this.isLocalTab;
   },
 
   get isLocalTab() {
-    return true;
+    return !!this._tab;
   },
 
   get isThreadPaused() {
     return !!this._isThreadPaused;
   },
 
   /**
+   * Adds remote protocol capabilities to the target, so that it can be used
+   * for tools that support the Remote Debugging Protocol even for local
+   * connections.
+   *
+   * @param object aOptions
+   *        An optional object containing remote connection options that is
+   *        supplied when connecting to another instance.
+   */
+  makeRemote: function TabTarget_makeRemote(aOptions) {
+    if (this._remote) {
+      return this._remote.promise;
+    }
+
+    this._remote = Promise.defer();
+
+    if (aOptions) {
+      this._form = aOptions.form;
+      this._client = aOptions.client;
+      this._chrome = aOptions.chrome;
+    } else {
+      // Since a remote protocol connection will be made, let's start the
+      // DebuggerServer here, once and for all tools.
+      if (!DebuggerServer.initialized) {
+        DebuggerServer.init();
+        DebuggerServer.addBrowserActors();
+      }
+
+      this._client = new DebuggerClient(DebuggerServer.connectPipe());
+      // A local TabTarget will never perform chrome debugging.
+      this._chrome = false;
+    }
+
+    this._setupRemoteListeners();
+
+    if (aOptions) {
+      // In the remote debugging case, the protocol connection will have been
+      // already initialized in the connection screen code.
+      this._remote.resolve(null);
+    } else {
+      this._client.connect(function(aType, aTraits) {
+        this._client.listTabs(function(aResponse) {
+          this._form = aResponse.tabs[aResponse.selected];
+          this._remote.resolve(null);
+        }.bind(this));
+      }.bind(this));
+    }
+
+    return this._remote.promise;
+  },
+
+  /**
    * Listen to the different events.
    */
   _setupListeners: function TabTarget__setupListeners() {
     this._webProgressListener = new TabWebProgressListener(this);
     this.tab.linkedBrowser.addProgressListener(this._webProgressListener);
     this.tab.addEventListener("TabClose", this);
     this.tab.parentNode.addEventListener("TabSelect", this);
     this.tab.ownerDocument.defaultView.addEventListener("unload", this);
-    this._handleThreadState = this._handleThreadState.bind(this);
-    this.on("thread-resumed", this._handleThreadState);
-    this.on("thread-paused", this._handleThreadState);
+  },
+
+  /**
+   * Setup listeners for remote debugging, updating existing ones as necessary.
+   */
+  _setupRemoteListeners: function TabTarget__setupRemoteListeners() {
+    // Reset any conflicting event handlers that were set before makeRemote().
+    if (this._webProgressListener) {
+      this._webProgressListener.destroy();
+    }
+
+    this.client.addListener("tabDetached", this.destroy);
+
+    this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
+      if (aPacket.state == "start") {
+        this.emit("will-navigate", aPacket);
+      } else {
+        this.emit("navigate", aPacket);
+      }
+    }.bind(this);
+    this.client.addListener("tabNavigated", this._onTabNavigated);
   },
 
   /**
    * Handle tabs events.
    */
   handleEvent: function (event) {
     switch (event.type) {
       case "TabClose":
@@ -255,38 +337,76 @@ TabTarget.prototype = {
         break;
     }
   },
 
   /**
    * Target is not alive anymore.
    */
   destroy: function() {
-    if (!this._destroyed) {
-      this._destroyed = true;
+    // If several things call destroy then we give them all the same
+    // destruction promise so we're sure to destroy only once
+    if (this._destroyer) {
+      return this._destroyer.promise;
+    }
+
+    this._destroyer = Promise.defer();
+
+    // Before taking any action, notify listeners that destruction is imminent.
+    this.emit("close");
 
-      this.tab.linkedBrowser.removeProgressListener(this._webProgressListener)
-      this._webProgressListener.target = null;
-      this._webProgressListener = null;
-      this.tab.ownerDocument.defaultView.removeEventListener("unload", this);
-      this.tab.removeEventListener("TabClose", this);
-      this.tab.parentNode.removeEventListener("TabSelect", this);
-      this.off("thread-resumed", this._handleThreadState);
-      this.off("thread-paused", this._handleThreadState);
-      this.emit("close");
+    // First of all, do cleanup tasks that pertain to both remoted and
+    // non-remoted targets.
+    this.off("thread-resumed", this._handleThreadState);
+    this.off("thread-paused", this._handleThreadState);
+
+    if (this._tab) {
+      this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
+      this._tab.removeEventListener("TabClose", this);
+      this._tab.parentNode.removeEventListener("TabSelect", this);
+    }
+
+    // If this target was not remoted, the promise will be resolved before the
+    // function returns.
+    // if (!this._remote) {
+    if (this._tab && !this._client) {
+      if (this._webProgressListener) {
+        this._webProgressListener.destroy();
+      }
 
       targets.delete(this._tab);
       this._tab = null;
+      this._client = null;
+      this._form = null;
+      this._remote = null;
+
+      this._destroyer.resolve(null);
+    } else if (this._client) {
+      // If, on the other hand, this target was remoted, the promise will be
+      // resolved after the remote connection is closed.
+      this.client.removeListener("tabNavigated", this._onTabNavigated);
+      this.client.removeListener("tabDetached", this.destroy);
+
+      this._client.close(function onClosed() {
+        let key = this._tab ? this._tab : this._form;
+        targets.delete(key);
+        this._client = null;
+        this._tab = null;
+        this._form = null;
+        this._remote = null;
+
+        this._destroyer.resolve(null);
+      }.bind(this));
     }
 
-    return Promise.resolve(null);
+    return this._destroyer.promise;
   },
 
   toString: function() {
-    return 'TabTarget:' + this.tab;
+    return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
   },
 };
 
 
 /**
  * WebProgressListener for TabTarget.
  *
  * @param object aTarget
@@ -317,23 +437,34 @@ TabWebProgressListener.prototype = {
       this.target.emit("will-navigate", request);
     }
   },
 
   onProgressChange: function() {},
   onSecurityChange: function() {},
   onStatusChange: function() {},
 
-  onLocationChange: function TwPL_onLocationChange(webProgress, request, URI, flags) {
+  onLocationChange: function TWPL_onLocationChange(webProgress, request, URI, flags) {
     if (this.target &&
         !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
       let window = webProgress.DOMWindow;
       this.target.emit("navigate", window);
     }
   },
+
+  /**
+   * Destroy the progress listener instance.
+   */
+  destroy: function TWPL_destroy() {
+    if (this.target.tab) {
+      this.target.tab.linkedBrowser.removeProgressListener(this);
+    }
+    this.target._webProgressListener = null;
+    this.target = null;
+  }
 };
 
 
 /**
  * A WindowTarget represents a page living in a xul window or panel. Generally
  * these will have a chrome: URL
  */
 function WindowTarget(window) {
@@ -407,106 +538,8 @@ WindowTarget.prototype = {
 
     return Promise.resolve(null);
   },
 
   toString: function() {
     return 'WindowTarget:' + this.window;
   },
 };
-
-/**
- * A RemoteTarget represents a page living in a remote Firefox instance.
- */
-function RemoteTarget(form, client, chrome) {
-  EventEmitter.decorate(this);
-  this._client = client;
-  this._form = form;
-  this._chrome = chrome;
-  this._setupListeners();
-}
-
-RemoteTarget.prototype = {
-  supports: supports,
-  get version() getVersion(),
-
-  get isRemote() true,
-
-  get chrome() this._chrome,
-
-  get name() this._form.title,
-
-  get url() this._form.url,
-
-  get client() this._client,
-
-  get form() this._form,
-
-  get isLocalTab() false,
-
-  get isThreadPaused() !!this._isThreadPaused,
-
-  /**
-   * Listen to the different events.
-   */
-  _setupListeners: function() {
-    this.destroy = this.destroy.bind(this);
-    this.client.addListener("tabDetached", this.destroy);
-
-    this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
-      if (aPacket.state == "start") {
-        this.emit("will-navigate", aPacket);
-      } else {
-        this.emit("navigate", aPacket);
-      }
-    }.bind(this);
-    this.client.addListener("tabNavigated", this._onTabNavigated);
-
-    this._handleThreadState = this._handleThreadState.bind(this);
-    this.on("thread-resumed", this._handleThreadState);
-    this.on("thread-paused", this._handleThreadState);
-  },
-
-  /**
-   * Handle script status.
-   */
-  _handleThreadState: function(event) {
-    switch (event) {
-      case "thread-resumed":
-        this._isThreadPaused = false;
-        break;
-      case "thread-paused":
-        this._isThreadPaused = true;
-        break;
-    }
-  },
-
-  /**
-   * Target is not alive anymore.
-   */
-  destroy: function RT_destroy() {
-    // If several things call destroy then we give them all the same
-    // destruction promise so we're sure to destroy only once
-    if (this._destroyer) {
-      return this._destroyer.promise;
-    }
-
-    this._destroyer = Promise.defer();
-
-    this.client.removeListener("tabNavigated", this._onTabNavigated);
-    this.client.removeListener("tabDetached", this.destroy);
-
-    this._client.close(function onClosed() {
-      this._client = null;
-      this.off("thread-resumed", this._handleThreadState);
-      this.off("thread-paused", this._handleThreadState);
-      this.emit("close");
-
-      this._destroyer.resolve(null);
-    }.bind(this));
-
-    return this._destroyer.promise;
-  },
-
-  toString: function() {
-    return 'RemoteTarget:' + this.form.actor;
-  },
-};
--- a/browser/devtools/framework/Toolbox.jsm
+++ b/browser/devtools/framework/Toolbox.jsm
@@ -121,17 +121,17 @@ Promise.all = Promise.promised(Array);
 this.Toolbox = function Toolbox(target, selectedTool, hostType) {
   this._target = target;
   this._toolPanels = new Map();
 
   this._toolRegistered = this._toolRegistered.bind(this);
   this._toolUnregistered = this._toolUnregistered.bind(this);
   this.destroy = this.destroy.bind(this);
 
-  this._target.once("close", this.destroy);
+  this._target.on("close", this.destroy);
 
   if (!hostType) {
     hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
   }
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
   }
   let definitions = gDevTools.getToolDefinitionMap();
@@ -427,22 +427,23 @@ Toolbox.prototype = {
 
   /**
    * Switch to the tool with the given id
    *
    * @param {string} id
    *        The id of the tool to switch to
    */
   selectTool: function TBOX_selectTool(id) {
+    let deferred = Promise.defer();
+
     if (this._currentToolId == id) {
-      return;
+      // Return the existing panel in order to have a consistent return value.
+      return Promise.resolve(this._toolPanels.get(id));
     }
 
-    let deferred = Promise.defer();
-
     if (!this.isReady) {
       throw new Error("Can't select tool, wait for toolbox 'ready' event");
     }
     let tab = this.doc.getElementById("toolbox-tab-" + id);
 
     if (!tab) {
       throw new Error("No tool found");
     }
@@ -681,32 +682,34 @@ Toolbox.prototype = {
     // method is only executed once.
     let deferred = Promise.defer();
     this._destroyer = deferred.promise;
 
     this._target.off("navigate", this._refreshHostTitle);
     this.off("select", this._refreshHostTitle);
     this.off("host-changed", this._refreshHostTitle);
 
-    let outstanding = [];
+    gDevTools.off("tool-registered", this._toolRegistered);
+    gDevTools.off("tool-unregistered", this._toolUnregistered);
 
-    // Remote targets need to be notified that the toolbox is being torn down.
-    if (this._target && this._target.isRemote) {
-      outstanding.push(this._target.destroy());
-    }
-    this._target = null;
+    let outstanding = [];
 
     for (let [id, panel] of this._toolPanels) {
       outstanding.push(panel.destroy());
     }
 
     outstanding.push(this._host.destroy());
 
-    gDevTools.off("tool-registered", this._toolRegistered);
-    gDevTools.off("tool-unregistered", this._toolUnregistered);
+    // Targets need to be notified that the toolbox is being torn down, so that
+    // remote protocol connections can be gracefully terminated.
+    if (this._target) {
+      this._target.off("close", this.destroy);
+      outstanding.push(this._target.destroy());
+    }
+    this._target = null;
 
     Promise.all(outstanding).then(function() {
       this.emit("destroyed");
       deferred.resolve();
     }.bind(this));
 
     return this._destroyer;
   }
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -159,12 +159,19 @@ function handleConnectionTimeout() {
   showError("timeout");
 }
 
 /**
  * The user clicked on one of the buttons.
  * Opens the toolbox.
  */
 function openToolbox(form, chrome=false) {
-  let target = TargetFactory.forRemote(form, gClient, chrome);
-  gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
-  window.close();
+  let options = {
+    form: form,
+    client: gClient,
+    chrome: chrome
+  };
+  let target = TargetFactory.forTab(options);
+  target.makeRemote(options).then(function() {
+    gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
+    window.close();
+  });
 }
--- a/browser/devtools/framework/test/browser_new_activation_workflow.js
+++ b/browser/devtools/framework/test/browser_new_activation_workflow.js
@@ -46,24 +46,28 @@ function selectAndCheckById(id) {
   return toolbox.selectTool(id).then(function() {
     let tab = doc.getElementById("toolbox-tab-" + id);
     is(tab.selected, true, "The " + id + " tab is selected");
   });
 }
 
 function testToggle() {
   toolbox.once("destroyed", function() {
-    gDevTools.showToolbox(target, "styleeditor").then(function() {
+    // Cannot reuse a target after it's destroyed.
+    target = TargetFactory.forTab(gBrowser.selectedTab);
+    gDevTools.showToolbox(target, "styleeditor").then(function(aToolbox) {
+      toolbox = aToolbox;
       is(toolbox.currentToolId, "styleeditor", "The style editor is selected");
       finishUp();
     });
   }.bind(this));
 
   toolbox.destroy();
 }
 
 function finishUp() {
-  toolbox.destroy();
-  toolbox = null;
-  target = null;
-  gBrowser.removeCurrentTab();
-  finish();
+  toolbox.destroy().then(function() {
+    toolbox = null;
+    target = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  });
 }
--- a/browser/devtools/framework/test/browser_target_events.js
+++ b/browser/devtools/framework/test/browser_target_events.js
@@ -36,17 +36,19 @@ function onHidden() {
 function onVisible() {
   ok(true, "Visible event received");
   target.once("will-navigate", onWillNavigate);
   gBrowser.contentWindow.location = "data:text/html,test navigation";
 }
 
 function onWillNavigate(event, request) {
   ok(true, "will-navigate event received");
-  target.once("navigate", onNavigate);
+  // Wait for navigation handling to complete before removing the tab, in order
+  // to avoid triggering assertions.
+  target.once("navigate", executeSoon.bind(null, onNavigate));
 }
 
 function onNavigate() {
   ok(true, "navigate event received");
   target.once("close", onClose);
   gBrowser.removeCurrentTab();
 }
 
--- a/browser/devtools/framework/test/browser_toolbox_hosts.js
+++ b/browser/devtools/framework/test/browser_toolbox_hosts.js
@@ -85,22 +85,24 @@ function testToolSelect()
 {
   // make sure we can load a tool after switching hosts
   toolbox.selectTool("inspector").then(testDestroy);
 }
 
 function testDestroy()
 {
   toolbox.destroy().then(function() {
+    target = TargetFactory.forTab(gBrowser.selectedTab);
     gDevTools.showToolbox(target).then(testRememberHost);
   });
 }
 
-function testRememberHost()
+function testRememberHost(aToolbox)
 {
+  toolbox = aToolbox;
   // last host was the window - make sure it's the same when re-opening
   is(toolbox.hostType, Toolbox.HostType.WINDOW, "host remembered");
 
   let win = Services.wm.getMostRecentWindow("devtools:toolbox");
   ok(win, "toolbox separate window exists");
 
   cleanup();
 }
@@ -118,13 +120,14 @@ function checkToolboxLoaded(iframe)
   let tabs = iframe.contentDocument.getElementById("toolbox-tabs");
   ok(tabs, "toolbox UI has been loaded into iframe");
 }
 
 function cleanup()
 {
   Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM);
 
-  toolbox.destroy();
-  DevTools = Toolbox = toolbox = target = null;
-  gBrowser.removeCurrentTab();
-  finish();
-}
+  toolbox.destroy().then(function() {
+    DevTools = Toolbox = toolbox = target = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  });
+ }
--- a/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
+++ b/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
@@ -61,14 +61,15 @@ function selectCB(event, id) {
 
   is(toolIDs.indexOf(id), idIndex,
      "Correct tool is selected on pressing the shortcut for " + id);
 
   testShortcuts(toolbox, idIndex + 1);
 }
 
 function tidyUp() {
-  toolbox.destroy();
-  gBrowser.removeCurrentTab();
+  toolbox.destroy().then(function() {
+    gBrowser.removeCurrentTab();
 
-  toolbox = toolIDs = idIndex = Toolbox = null;
-  finish();
+    toolbox = toolIDs = idIndex = Toolbox = null;
+    finish();
+  });
 }
--- a/browser/devtools/framework/test/browser_toolbox_window_title_changes.js
+++ b/browser/devtools/framework/test/browser_toolbox_window_title_changes.js
@@ -49,33 +49,42 @@ function test() {
         target.once("navigate", function () deferred.resolve());
         gBrowser.loadURI(URL_2);
         return deferred.promise;
       })
       .then(checkTitle.bind(null, LABEL_2, URL_2, "url changed"))
 
     // destroy toolbox, create new one hosted in a window (with a
     // different tool id), and check title
-      .then(function () toolbox.destroy())
-      .then(function () gDevTools.showToolbox(target, null,
-                                              Toolbox.HostType.WINDOW))
-      .then(function (aToolbox) { toolbox = aToolbox; })
-      .then(function () toolbox.selectTool(TOOL_ID_1))
-      .then(checkTitle.bind(null, LABEL_1, URL_2,
-                            "toolbox destroyed and recreated"))
+      .then(function () {
+        // Give the tools a chance to handle the navigation event before
+        // destroying the toolbox.
+        executeSoon(function() {
+          toolbox.destroy()
+            .then(function () {
+              // After destroying the toolbox, a fresh target is required.
+              target = TargetFactory.forTab(gBrowser.selectedTab);
+              return gDevTools.showToolbox(target, null, Toolbox.HostType.WINDOW);
+            })
+            .then(function (aToolbox) { toolbox = aToolbox; })
+            .then(function () toolbox.selectTool(TOOL_ID_1))
+            .then(checkTitle.bind(null, LABEL_1, URL_2,
+                                  "toolbox destroyed and recreated"))
 
-    // clean up
-      .then(function () toolbox.destroy())
-      .then(function () {
-        toolbox = null;
-        gBrowser.removeCurrentTab();
-        Services.prefs.clearUserPref("devtools.toolbox.host");
-        Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
-        Services.prefs.clearUserPref("devtools.toolbox.sideEnabled");
-        finish();
+            // clean up
+            .then(function () toolbox.destroy())
+            .then(function () {
+              toolbox = null;
+              gBrowser.removeCurrentTab();
+              Services.prefs.clearUserPref("devtools.toolbox.host");
+              Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
+              Services.prefs.clearUserPref("devtools.toolbox.sideEnabled");
+              finish();
+            });
+        });
       });
   });
 }
 
 function checkTitle(toolLabel, url, context) {
   let win = Services.wm.getMostRecentWindow("devtools:toolbox");
   let definitions = gDevTools.getToolDefinitionMap();
   let expectedTitle = toolLabel + " - " + url;
--- a/browser/devtools/inspector/InspectorPanel.jsm
+++ b/browser/devtools/inspector/InspectorPanel.jsm
@@ -214,16 +214,22 @@ InspectorPanel.prototype = {
     this.sidebar.addTab("ruleview",
                         "chrome://browser/content/devtools/cssruleview.xul",
                         "ruleview" == defaultTab);
 
     this.sidebar.addTab("computedview",
                         "chrome://browser/content/devtools/csshtmltree.xul",
                         "computedview" == defaultTab);
 
+    if (Services.prefs.getBoolPref("devtools.fontinspector.enabled")) {
+      this.sidebar.addTab("fontinspector",
+                          "chrome://browser/content/devtools/fontinspector/font-inspector.xhtml",
+                          "fontinspector" == defaultTab);
+    }
+
     this.sidebar.addTab("layoutview",
                         "chrome://browser/content/devtools/layoutview/view.xhtml",
                         "layoutview" == defaultTab);
 
     let ruleViewTab = this.sidebar.getTab("ruleview");
     ruleViewTab.addEventListener("mouseover", this.toggleHighlighter, false);
     ruleViewTab.addEventListener("mouseout", this.toggleHighlighter, false);
 
@@ -407,16 +413,17 @@ InspectorPanel.prototype = {
     this.target = null;
     this.panelDoc = null;
     this.panelWin = null;
     this.breadcrumbs = null;
     this.lastNodemenuItem = null;
     this.nodemenu = null;
     this.searchBox = null;
     this.highlighter = null;
+    this._searchResults = null;
 
     return Promise.resolve(null);
   },
 
   /**
    * The command callback for the HTML search box. This function is
    * automatically invoked as the user is typing.
    */
--- a/browser/devtools/inspector/test/browser_inspector_initialization.js
+++ b/browser/devtools/inspector/test/browser_inspector_initialization.js
@@ -15,17 +15,17 @@ function createDocument()
   doc.body.innerHTML = '<div id="first" style="{ margin: 10em; ' +
     'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA}">\n' +
     '<h1>Some header text</h1>\n' +
     '<p id="salutation" style="{font-size: 12pt}">hi.</p>\n' +
     '<p id="body" style="{font-size: 12pt}">I am a test-case. This text exists ' +
     'solely to provide some things to test the inspector initialization.</p>\n' +
     'If you are reading this, you should go do something else instead. Maybe ' +
     'read a book. Or better yet, write some test-cases for another bit of code. ' +
-    '<span style="{font-style: italic}">Maybe more inspector test-cases!</span></p>\n' +
+    '<span style="{font-style: italic}">Inspector\'s!</span></p>\n' +
     '<p id="closing">end transmission</p>\n' +
     '</div>';
   doc.title = "Inspector Initialization Test";
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
     startInspectorTests(toolbox);
   }).then(null, console.error);
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -14,16 +14,19 @@ browser.jar:
     content/browser/styleeditor.xul               (styleeditor/styleeditor.xul)
     content/browser/styleeditor.css               (styleeditor/styleeditor.css)
     content/browser/devtools/csshtmltree.xul      (styleinspector/csshtmltree.xul)
     content/browser/devtools/cssruleview.xul      (styleinspector/cssruleview.xul)
     content/browser/devtools/styleinspector.css   (styleinspector/styleinspector.css)
     content/browser/devtools/layoutview/view.js   (layoutview/view.js)
     content/browser/devtools/layoutview/view.xhtml  (layoutview/view.xhtml)
     content/browser/devtools/layoutview/view.css  (layoutview/view.css)
+    content/browser/devtools/fontinspector/font-inspector.js    (fontinspector/font-inspector.js)
+    content/browser/devtools/fontinspector/font-inspector.xhtml (fontinspector/font-inspector.xhtml)
+    content/browser/devtools/fontinspector/font-inspector.css   (fontinspector/font-inspector.css)
     content/browser/orion.js                      (sourceeditor/orion/orion.js)
 *   content/browser/source-editor-overlay.xul     (sourceeditor/source-editor-overlay.xul)
     content/browser/debugger.xul                  (debugger/debugger.xul)
     content/browser/debugger.css                  (debugger/debugger.css)
     content/browser/debugger-controller.js        (debugger/debugger-controller.js)
     content/browser/debugger-view.js              (debugger/debugger-view.js)
     content/browser/debugger-toolbar.js           (debugger/debugger-toolbar.js)
     content/browser/debugger-panes.js             (debugger/debugger-panes.js)
--- a/browser/devtools/layoutview/view.js
+++ b/browser/devtools/layoutview/view.js
@@ -99,17 +99,17 @@ LayoutView.prototype = {
    */
   destroy: function LV_destroy() {
     this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
     this.inspector.selection.off("new-node", this.onNewNode);
     if (this.browser) {
       this.browser.removeEventListener("MozAfterPaint", this.update, true);
     }
     if (this.inspector.highlighter) {
-      this.inspector.highlighter.on("locked", this.onHighlighterLocked);
+      this.inspector.highlighter.off("locked", this.onHighlighterLocked);
     }
     this.sizeHeadingLabel = null;
     this.sizeLabel = null;
     this.inspector = null;
     this.doc = null;
   },
 
   /**
--- a/browser/devtools/markupview/MarkupView.jsm
+++ b/browser/devtools/markupview/MarkupView.jsm
@@ -639,17 +639,17 @@ MarkupView.prototype = {
   {
     this.undo.destroy();
     delete this.undo;
 
     this._frame.removeEventListener("focus", this._boundFocus, false);
     delete this._boundFocus;
 
     this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true);
-    this._frame.contentWindow.removeEventListener("resize", this._boundUpdatePreview, true);
+    this._frame.contentWindow.removeEventListener("resize", this._boundResizePreview, true);
     this._frame.contentWindow.removeEventListener("overflow", this._boundResizePreview, true);
     this._frame.contentWindow.removeEventListener("underflow", this._boundResizePreview, true);
     delete this._boundUpdatePreview;
 
     this._frame.contentWindow.removeEventListener("keydown", this._boundKeyDown, true);
     delete this._boundKeyDown;
 
     this._inspector.selection.off("new-node", this._boundOnNewSelection);
--- a/browser/devtools/moz.build
+++ b/browser/devtools/moz.build
@@ -14,9 +14,10 @@ DIRS += [
     'tilt',
     'scratchpad',
     'debugger',
     'layoutview',
     'shared',
     'responsivedesign',
     'framework',
     'profiler',
+    'fontinspector',
 ]
--- a/browser/devtools/profiler/ProfilerController.jsm
+++ b/browser/devtools/profiler/ProfilerController.jsm
@@ -18,58 +18,33 @@ XPCOMUtils.defineLazyGetter(this, "Debug
   return DebuggerServer;
 });
 
 /**
  * Object acting as a mediator between the ProfilerController and
  * DebuggerServer.
  */
 function ProfilerConnection(client) {
-  if (!DebuggerServer.initialized) {
-    DebuggerServer.init();
-    DebuggerServer.addBrowserActors();
-  }
-
-  this.isRemote = true;
-
-  if (!client) {
-    let transport = DebuggerServer.connectPipe();
-    client = new DebuggerClient(transport);
-    this.isRemote = false;
-  }
-
   this.client = client;
 }
 
 ProfilerConnection.prototype = {
   actor: null,
 
   /**
    * Connects to a debugee and executes a callback when ready.
    *
    * @param function aCallback
    *        Function to be called once we're connected to the client.
    */
   connect: function PCn_connect(aCallback) {
-    let client = this.client;
-
-    let listTabs = function () {
-      client.listTabs(function (aResponse) {
-        this.actor = aResponse.profilerActor;
-        aCallback();
-      }.bind(this));
-    }.bind(this);
-
-    if (this.isRemote) {
-      return void listTabs();
-    }
-
-    client.connect(function (aType, aTraits) {
-      listTabs();
-    });
+    this.client.listTabs(function (aResponse) {
+      this.actor = aResponse.profilerActor;
+      aCallback();
+    }.bind(this));
   },
 
   /**
    * Sends a message to check if the profiler is currently active.
    *
    * @param function aCallback
    *        Function to be called once we have a response from
    *        the client. It will be called with a single argument
@@ -124,48 +99,45 @@ ProfilerConnection.prototype = {
     var message = { to: this.actor, type: "getProfile" };
     this.client.request(message, aCallback);
   },
 
   /**
    * Cleanup.
    */
   destroy: function PCn_destroy() {
-    this.client.close(function () {
-      this.client = null;
-    }.bind(this));
+    this.client = null;
   }
 };
 
 /**
  * Object defining the profiler controller components.
  */
 function ProfilerController(target) {
-  let client;
-
-  if (target.isRemote) {
-    client = target.client;
+  this.profiler = new ProfilerConnection(target.client);
+  // Chrome debugging targets have already obtained a reference to the profiler
+  // actor.
+  this._connected = !!target.chrome;
+  if (target.chrome) {
+    this.profiler.actor = target.form.profilerActor;
   }
-
-  this.profiler = new ProfilerConnection(client);
-  this._connected = false;
 }
 
 ProfilerController.prototype = {
   /**
    * Connects to the client unless we're already connected.
    *
    * @param function aCallback
    *        Function to be called once we're connected. If
    *        the controller is already connected, this function
    *        will be called immediately (synchronously).
    */
   connect: function (aCallback) {
     if (this._connected) {
-      aCallback();
+      return void aCallback();
     }
 
     this.profiler.connect(function onConnect() {
       this._connected = true;
       aCallback();
     }.bind(this));
   },
 
--- a/browser/devtools/profiler/ProfilerPanel.jsm
+++ b/browser/devtools/profiler/ProfilerPanel.jsm
@@ -4,22 +4,24 @@
 
 "use strict";
 
 const Cu = Components.utils;
 
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
 Cu.import("resource:///modules/devtools/ProfilerController.jsm");
 Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
-Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["ProfilerPanel"];
 
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+    "resource://gre/modules/commonjs/sdk/core/promise.js");
+
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
   "resource://gre/modules/devtools/dbg-server.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 
 /**
  * An instance of a profile UI. Profile UI consists of
@@ -200,17 +202,16 @@ ProfileUI.prototype = {
  *   - profileSwitched: after user switches to a different
  *                      profile.
  */
 function ProfilerPanel(frame, toolbox) {
   this.isReady = false;
   this.window = frame.window;
   this.document = frame.document;
   this.target = toolbox.target;
-  this.controller = new ProfilerController(this.target);
 
   this.profiles = new Map();
   this._uid = 0;
 
   EventEmitter.decorate(this);
 }
 
 ProfilerPanel.prototype = {
@@ -251,33 +252,49 @@ ProfilerPanel.prototype = {
 
   /**
    * Open a debug connection and, on success, switch to the newly created
    * profile.
    *
    * @return Promise
    */
   open: function PP_open() {
-    let deferred = Promise.defer();
+    let promise;
+    // Local profiling needs to make the target remote.
+    if (!this.target.isRemote) {
+      promise = this.target.makeRemote();
+    } else {
+      promise = Promise.resolve(this.target);
+    }
 
-    this.controller.connect(function onConnect() {
-      let create = this.document.getElementById("profiler-create");
-      create.addEventListener("click", this.createProfile.bind(this), false);
-      create.removeAttribute("disabled");
+    return promise
+      .then(function(target) {
+        let deferred = Promise.defer();
+        this.controller = new ProfilerController(this.target);
 
-      let profile = this.createProfile();
-      this.switchToProfile(profile, function () {
-        this.isReady = true;
-        this.emit("ready");
+        this.controller.connect(function onConnect() {
+          let create = this.document.getElementById("profiler-create");
+          create.addEventListener("click", this.createProfile.bind(this), false);
+          create.removeAttribute("disabled");
+
+          let profile = this.createProfile();
+          this.switchToProfile(profile, function () {
+            this.isReady = true;
+            this.emit("ready");
 
-        deferred.resolve(this);
+            deferred.resolve(this);
+          }.bind(this))
+        }.bind(this));
+
+        return deferred.promise;
       }.bind(this))
-    }.bind(this));
-
-    return deferred.promise;
+      .then(null, function onError(reason) {
+        Cu.reportError("ProfilerPanel open failed. " +
+                       reason.error + ": " + reason.message);
+      });
   },
 
   /**
    * Creates a new profile instance (see ProfileUI) and
    * adds an appropriate item to the sidebar. Note that
    * this method doesn't automatically switch user to
    * the newly created profile, they have do to switch
    * explicitly.
--- a/browser/devtools/responsivedesign/CmdResize.jsm
+++ b/browser/devtools/responsivedesign/CmdResize.jsm
@@ -48,20 +48,22 @@ gcli.addCommand({
     },
     onChange: function(aTarget, aChangeHandler) {
       let browserWindow = aTarget.tab.ownerDocument.defaultView;
       let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
       mgr.on("on", aChangeHandler);
       mgr.on("off", aChangeHandler);
     },
     offChange: function(aTarget, aChangeHandler) {
-      let browserWindow = aTarget.tab.ownerDocument.defaultView;
-      let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
-      mgr.off("on", aChangeHandler);
-      mgr.off("off", aChangeHandler);
+      if (aTarget.tab) {
+        let browserWindow = aTarget.tab.ownerDocument.defaultView;
+        let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
+        mgr.off("on", aChangeHandler);
+        mgr.off("off", aChangeHandler);
+      }
     },
   },
   exec: gcli_cmd_resize
 });
 
 gcli.addCommand({
   name: 'resize to',
   description: gcli.lookup('resizeModeToDesc'),
--- a/browser/devtools/tilt/CmdTilt.jsm
+++ b/browser/devtools/tilt/CmdTilt.jsm
@@ -52,19 +52,21 @@ gcli.addCommand({
       return !!TiltManager.getTiltForBrowser(browserWindow).currentInstance;
     },
     onChange: function(aTarget, aChangeHandler) {
       let browserWindow = aTarget.tab.ownerDocument.defaultView;
       let tilt = TiltManager.getTiltForBrowser(browserWindow);
       tilt.on("change", aChangeHandler);
     },
     offChange: function(aTarget, aChangeHandler) {
-      let browserWindow = aTarget.tab.ownerDocument.defaultView;
-      let tilt = TiltManager.getTiltForBrowser(browserWindow);
-      tilt.off("change", aChangeHandler);
+      if (aTarget.tab) {
+        let browserWindow = aTarget.tab.ownerDocument.defaultView;
+        let tilt = TiltManager.getTiltForBrowser(browserWindow);
+        tilt.off("change", aChangeHandler);
+      }
     },
   },
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     Tilt.toggle();
   }
 });
--- a/browser/devtools/webconsole/AutocompletePopup.jsm
+++ b/browser/devtools/webconsole/AutocompletePopup.jsm
@@ -220,17 +220,19 @@ AutocompletePopup.prototype = {
   /**
    * Setter for the selected index.
    *
    * @param number aIndex
    *        The number (index) of the item you want to select in the list.
    */
   set selectedIndex(aIndex) {
     this._list.selectedIndex = aIndex;
-    this._list.ensureIndexIsVisible(this._list.selectedIndex);
+    if (this._list.ensureIndexIsVisible) {
+      this._list.ensureIndexIsVisible(this._list.selectedIndex);
+    }
   },
 
   /**
    * Getter for the selected item.
    * @type object
    */
   get selectedItem() {
     return this._list.selectedItem ?
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -232,22 +232,21 @@ WebConsole.prototype = {
     let onFailure = function(aReason) {
       deferred.reject(aReason);
     };
 
     let win, doc;
     if ((win = this.iframe.contentWindow) &&
         (doc = win.document) &&
         doc.readyState == "complete") {
-      this.iframe.addEventListener("load", onIframeLoad, true);
+      initUI();
     }
     else {
-      initUI();
+      this.iframe.addEventListener("load", onIframeLoad, true);
     }
-
     return deferred.promise;
   },
 
   /**
    * Retrieve the Web Console panel title.
    *
    * @return string
    *         The Web Console panel title.
@@ -340,51 +339,110 @@ WebConsole.prototype = {
         return;
       }
     }
     // Open view source if style editor fails.
     this.viewSource(aSourceURL, aSourceLine);
   },
 
   /**
+   * Tries to open a JavaScript file related to the web page for the web console
+   * instance in the Script Debugger. If the file is not found, it is opened in
+   * source view instead.
+   *
+   * @param string aSourceURL
+   *        The URL of the file.
+   * @param integer aSourceLine
+   *        The line number which you want to place the caret.
+   */
+  viewSourceInDebugger:
+  function WC_viewSourceInDebugger(aSourceURL, aSourceLine)
+  {
+    let self = this;
+    let panelWin = null;
+    let debuggerWasOpen = true;
+    let toolbox = gDevTools.getToolbox(this.target);
+
+    if (!toolbox.getPanel("jsdebugger")) {
+      debuggerWasOpen = false;
+      let toolboxWin = toolbox.doc.defaultView;
+      toolboxWin.addEventListener("Debugger:AfterSourcesAdded",
+                                  function afterSourcesAdded() {
+        toolboxWin.removeEventListener("Debugger:AfterSourcesAdded",
+                                       afterSourcesAdded);
+        loadScript();
+      });
+    }
+
+    toolbox.selectTool("jsdebugger").then(function onDebuggerOpen(dbg) {
+      panelWin = dbg.panelWin;
+      if (debuggerWasOpen) {
+        loadScript();
+      }
+    });
+
+    function loadScript() {
+      let debuggerView = panelWin.DebuggerView;
+      if (!debuggerView.Sources.containsValue(aSourceURL)) {
+        toolbox.selectTool("webconsole");
+        self.viewSource(aSourceURL, aSourceLine);
+        return;
+      }
+      if (debuggerWasOpen && debuggerView.Sources.selectedValue == aSourceURL) {
+        debuggerView.editor.setCaretPosition(aSourceLine - 1);
+        return;
+      }
+
+      panelWin.addEventListener("Debugger:SourceShown", onSource, false);
+      debuggerView.Sources.preferredSource = aSourceURL;
+    }
+
+    function onSource(aEvent) {
+      if (aEvent.detail.url != aSourceURL) {
+        return;
+      }
+      panelWin.removeEventListener("Debugger:SourceShown", onSource, false);
+      panelWin.DebuggerView.editor.setCaretPosition(aSourceLine - 1);
+    }
+  },
+
+  /**
    * Destroy the object. Call this method to avoid memory leaks when the Web
    * Console is closed.
    *
    * @return object
    *         A Promise object that is resolved once the Web Console is closed.
    */
   destroy: function WC_destroy()
   {
     if (this._destroyer) {
       return this._destroyer.promise;
     }
 
     delete HUDService.hudReferences[this.hudId];
 
-    let tabWindow = this.target.isLocalTab ? this.target.window : null;
-
     this._destroyer = Promise.defer();
 
     let popupset = this.mainPopupSet;
     let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
     for (let panel of panels) {
       panel.hidePopup();
     }
 
     let onDestroy = function WC_onDestroyUI() {
       try {
+        let tabWindow = this.target.isLocalTab ? this.target.window : null;
         tabWindow && tabWindow.focus();
       }
       catch (ex) {
-        // Tab focus can fail if the tab is closed.
+        // Tab focus can fail if the tab or target is closed.
       }
 
       let id = WebConsoleUtils.supportsString(this.hudId);
       Services.obs.notifyObservers(id, "web-console-destroyed", null);
-
       this._destroyer.resolve(null);
     }.bind(this);
 
     if (this.ui) {
       this.ui.destroy().then(onDestroy);
     }
     else {
       onDestroy();
--- a/browser/devtools/webconsole/WebConsolePanel.jsm
+++ b/browser/devtools/webconsole/WebConsolePanel.jsm
@@ -5,16 +5,19 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "WebConsolePanel" ];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+    "resource://gre/modules/commonjs/sdk/core/promise.js");
+
 XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
     "resource:///modules/HUDService.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
     "resource:///modules/devtools/EventEmitter.jsm");
 
 /**
  * A DevToolPanel that controls the Web Console.
@@ -33,27 +36,41 @@ WebConsolePanel.prototype = {
    *
    * @return object
    *         A Promise that is resolved when the Web Console completes opening.
    */
   open: function WCP_open()
   {
     let parentDoc = this._toolbox.doc;
     let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
-    let promise = HUDService.openWebConsole(this.target, iframe);
+    let promise;
+
+    // Local debugging needs to make the target remote.
+    if (!this.target.isRemote) {
+      promise = this.target.makeRemote();
+    } else {
+      promise = Promise.resolve(this.target);
+    }
 
-    return promise.then(function onSuccess(aWebConsole) {
-      this.hud = aWebConsole;
-      this._isReady = true;
-      this.emit("ready");
-      return this;
-    }.bind(this), function onError(aReason) {
-      Cu.reportError("WebConsolePanel open failed. " +
-                     aReason.error + ": " + aReason.message);
-    });
+    return promise
+      .then(function(aTarget) {
+        this._frameWindow._remoteTarget = aTarget;
+        return HUDService.openWebConsole(this.target, iframe);
+      }.bind(this))
+      .then(function onSuccess(aWebConsole) {
+        this.hud = aWebConsole;
+        this._isReady = true;
+        this.emit("ready");
+        return this;
+      }.bind(this), function onError(aReason) {
+        let msg = "WebConsolePanel open failed. " +
+                  aReason.error + ": " + aReason.message;
+        dump(msg + "\n");
+        Cu.reportError(msg);
+      });
   },
 
   get target() this._toolbox.target,
 
   _isReady: false,
   get isReady() this._isReady,
 
   destroy: function WCP_destroy()
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -99,16 +99,17 @@ MOCHITEST_BROWSER_FILES = \
 	browser_webconsole_bug_659907_console_dir.js \
 	browser_webconsole_bug_664131_console_group.js \
 	browser_webconsole_bug_704295.js \
 	browser_webconsole_bug_658368_time_methods.js \
 	browser_webconsole_bug_764572_output_open_url.js \
 	browser_webconsole_bug_622303_persistent_filters.js \
 	browser_webconsole_bug_770099_bad_policyuri.js \
 	browser_webconsole_bug_770099_violation.js \
+	browser_webconsole_bug_766001_JS_Console_in_Debugger.js \
 	browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js \
 	browser_cached_messages.js \
 	browser_bug664688_sandbox_update_after_navigation.js \
 	browser_result_format_as_string.js \
 	browser_webconsole_bug_737873_mixedcontent.js \
 	browser_output_breaks_after_console_dir_uninspectable.js \
 	browser_console_log_inspectable_object.js \
 	browser_bug_638949_copy_link_location.js \
@@ -205,11 +206,14 @@ MOCHITEST_BROWSER_FILES += \
 	test-for-of.html \
 	test_bug_770099_violation.html \
 	test_bug_770099_violation.html^headers^ \
 	test_bug_770099_bad_policy_uri.html \
 	test_bug_770099_bad_policy_uri.html^headers^ \
 	test-result-format-as-string.html \
 	test-bug-737873-mixedcontent.html \
 	test-repeated-messages.html \
+	test-bug-766001-console-log.js \
+	test-bug-766001-js-console-links.html \
+	test-bug-766001-js-errors.js \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js
@@ -72,31 +72,31 @@ function tab2Loaded(aEvent) {
     }
   }
 
   function closeConsoles() {
     Services.obs.addObserver(onWebConsoleClose, "web-console-destroyed", false);
 
     try {
       let target1 = TargetFactory.forTab(tab1);
-      gDevTools.closeToolbox(target1);
+      gDevTools.closeToolbox(target1).then(function() {
+        try {
+          let target2 = TargetFactory.forTab(tab2);
+          gDevTools.closeToolbox(target2);
+        }
+        catch (ex) {
+          ok(false, "gDevTools.closeToolbox(target2) exception: " + ex);
+          noErrors = false;
+        }
+      });
     }
     catch (ex) {
       ok(false, "gDevTools.closeToolbox(target1) exception: " + ex);
       noErrors = false;
     }
-
-    try {
-      let target2 = TargetFactory.forTab(tab2);
-      gDevTools.closeToolbox(target2);
-    }
-    catch (ex) {
-      ok(false, "gDevTools.closeToolbox(target2) exception: " + ex);
-      noErrors = false;
-    }
   }
 
   function testEnd() {
     ok(noErrors, "there were no errors");
 
     Array.forEach(win1.gBrowser.tabs, function(aTab) {
       win1.gBrowser.removeTab(aTab);
     });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
@@ -0,0 +1,113 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test" +
+                 "/test-bug-766001-js-console-links.html";
+
+let nodes, dbg, toolbox, target, index = 0, src, line;
+
+function test()
+{
+  expectUncaughtException();
+  requestLongerTimeout(2);
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, testViewSource);
+  }, true);
+}
+
+function testViewSource(aHud)
+{
+  registerCleanupFunction(function() {
+    nodes = dbg = toolbox = target = index = src = line = null;
+  });
+
+  let JSSelector = ".webconsole-msg-exception .webconsole-location";
+  let consoleSelector = ".webconsole-msg-console .webconsole-location";
+
+  waitForSuccess({
+    name: "find the location node",
+    validatorFn: function()
+    {
+      return aHud.outputNode.querySelector(JSSelector) &&
+             aHud.outputNode.querySelector(consoleSelector);
+    },
+    successFn: function()
+    {
+      nodes = [aHud.outputNode.querySelector(JSSelector),
+               aHud.outputNode.querySelector(consoleSelector)];
+
+      target = TargetFactory.forTab(gBrowser.selectedTab);
+      toolbox = gDevTools.getToolbox(target);
+      toolbox.once("jsdebugger-selected", checkLineAndClickNext);
+
+      EventUtils.sendMouseEvent({ type: "click" }, nodes[index%2]);
+    },
+    failureFn: finishTest,
+  });
+}
+
+function checkLineAndClickNext(aEvent, aPanel)
+{
+  if (index == 3) {
+    finishTest();
+    return;
+  }
+  info(aEvent + " event fired for index " + index);
+
+  dbg = aPanel;
+
+  src = nodes[index%2].getAttribute("title");
+  ok(src, "source url found for index " + index);
+  line = nodes[index%2].sourceLine;
+  ok(line, "found source line for index " + index);
+
+  info("Waiting for the correct script to be selected for index " + index);
+  dbg.panelWin.addEventListener("Debugger:SourceShown", onSource, false);
+}
+
+function onSource(aEvent) {
+  if (aEvent.detail.url != src) {
+    return;
+  }
+  dbg.panelWin.removeEventListener("Debugger:SourceShown", onSource, false);
+
+  ok(true, "Correct script is selected for index " + index);
+
+  checkCorrectLine(function() {
+    gDevTools.showToolbox(target, "webconsole").then(function() {
+      index++;
+      info("webconsole selected for index " + index);
+
+      toolbox.once("jsdebugger-selected", checkLineAndClickNext);
+
+      EventUtils.sendMouseEvent({ type: "click" }, nodes[index%2]);
+    });
+  });
+}
+
+function checkCorrectLine(aCallback)
+{
+  waitForSuccess({
+    name: "correct source and line test for debugger for index " + index,
+    validatorFn: function()
+    {
+      let debuggerView = dbg.panelWin.DebuggerView;
+      if (debuggerView.editor &&
+          debuggerView.editor.getCaretPosition().line == line - 1) {
+        return true;
+      }
+      return false;
+    },
+    successFn: function()
+    {
+      aCallback && executeSoon(aCallback);
+    },
+    failureFn: finishTest,
+    timeout: 10000,
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-766001-console-log.js
@@ -0,0 +1,8 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+window.addEventListener("load", function() {
+  console.log("Blah Blah");
+}, false);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-766001-js-console-links.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Web Console test for bug 766001 : Open JS/Console call Links in Debugger</title>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+    <script type="text/javascript" src="test-bug-766001-js-errors.js"></script>
+    <script type="text/javascript" src="test-bug-766001-console-log.js"></script>
+  </head>
+  <body>
+    <p>Web Console test for bug 766001 : Open JS/Console call Links in Debugger.</p>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-766001-js-errors.js
@@ -0,0 +1,7 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+window.addEventListener("load", function() {
+  document.bar();
+}, false);
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -10,25 +10,16 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
-                                  "resource://gre/modules/devtools/dbg-server.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
-                                  "resource://gre/modules/devtools/dbg-client.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "debuggerSocketConnect",
-                                  "resource://gre/modules/devtools/dbg-client.jsm");
-
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyPanel",
                                   "resource:///modules/PropertyPanel.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyTreeView",
@@ -2414,16 +2405,20 @@ WebConsoleFrame.prototype = {
             win.focus();
             return;
           }
         }
       }
       else if (locationNode.parentNode.category == CATEGORY_CSS) {
         this.owner.viewSourceInStyleEditor(aSourceURL, aSourceLine);
       }
+      else if (locationNode.parentNode.category == CATEGORY_JS ||
+               locationNode.parentNode.category == CATEGORY_WEBDEV) {
+        this.owner.viewSourceInDebugger(aSourceURL, aSourceLine);
+      }
       else {
         this.owner.viewSource(aSourceURL, aSourceLine);
       }
     }.bind(this), true);
 
     return locationNode;
   },
 
@@ -3983,17 +3978,16 @@ function WebConsoleConnectionProxy(aWebC
   this.target = aTarget;
 
   this._onPageError = this._onPageError.bind(this);
   this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onFileActivity = this._onFileActivity.bind(this);
   this._onTabNavigated = this._onTabNavigated.bind(this);
-  this._onListTabs = this._onListTabs.bind(this);
   this._onAttachTab = this._onAttachTab.bind(this);
   this._onAttachConsole = this._onAttachConsole.bind(this);
   this._onCachedMessages = this._onCachedMessages.bind(this);
   this._connectionTimeout = this._connectionTimeout.bind(this);
 }
 
 WebConsoleConnectionProxy.prototype = {
   /**
@@ -4068,27 +4062,16 @@ WebConsoleConnectionProxy.prototype = {
    * Tells if the window.console object of the remote web page is the native
    * object or not.
    * @private
    * @type boolean
    */
   _hasNativeConsoleAPI: false,
 
   /**
-   * Initialize the debugger server.
-   */
-  initServer: function WCCP_initServer()
-  {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
-      DebuggerServer.addBrowserActors();
-    }
-  },
-
-  /**
    * Initialize a debugger client and connect it to the debugger server.
    *
    * @return object
    *         A Promise object that is resolved/rejected based on the success of
    *         the connection initialization.
    */
   connect: function WCCP_connect()
   {
@@ -4106,49 +4089,33 @@ WebConsoleConnectionProxy.prototype = {
     let promise = this._connectDefer.promise;
     promise.then(function _onSucess() {
       this._connectTimer.cancel();
       this._connectTimer = null;
     }.bind(this), function _onFailure() {
       this._connectTimer = null;
     }.bind(this));
 
-    // TODO: convert the non-remote path to use the target API as well.
-    let transport, client;
-    if (this.target.isRemote) {
-      client = this.client = this.target.client;
-    }
-    else {
-      this.initServer();
-      transport = DebuggerServer.connectPipe();
-      client = this.client = new DebuggerClient(transport);
-    }
+    let client = this.client = this.target.client;
 
     client.addListener("pageError", this._onPageError);
     client.addListener("consoleAPICall", this._onConsoleAPICall);
     client.addListener("networkEvent", this._onNetworkEvent);
     client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
     client.addListener("fileActivity", this._onFileActivity);
     client.addListener("tabNavigated", this._onTabNavigated);
 
-    if (this.target.isRemote) {
-      if (!this.target.chrome) {
-        // target.form is a TabActor grip
-        this._attachTab(this.target.form);
-      }
-      else {
-        // target.form is a RootActor grip
-        this._consoleActor = this.target.form.consoleActor;
-        this._attachConsole();
-      }
+    if (!this.target.chrome) {
+      // target.form is a TabActor grip
+      this._attachTab(this.target.form);
     }
     else {
-      client.connect(function(aType, aTraits) {
-        client.listTabs(this._onListTabs);
-      }.bind(this));
+      // target.form is a RootActor grip
+      this._consoleActor = this.target.form.consoleActor;
+      this._attachConsole();
     }
 
     return promise;
   },
 
   /**
    * Connection timeout handler.
    * @private
@@ -4159,35 +4126,16 @@ WebConsoleConnectionProxy.prototype = {
       error: "timeout",
       message: l10n.getStr("connectionTimeout"),
     };
 
     this._connectDefer.reject(error);
   },
 
   /**
-   * The "listTabs" response handler.
-   *
-   * @private
-   * @param object aResponse
-   *        The JSON response object received from the server.
-   */
-  _onListTabs: function WCCP__onListTabs(aResponse)
-  {
-    if (aResponse.error) {
-      Cu.reportError("listTabs failed: " + aResponse.error + " " +
-                     aResponse.message);
-      this._connectDefer.reject(aResponse);
-      return;
-    }
-
-    this._attachTab(aResponse.tabs[aResponse.selected]);
-  },
-
-  /**
    * Attach to the tab actor.
    *
    * @private
    * @param object aTab
    *        Grip for the tab to attach to.
    */
   _attachTab: function WCCP__attachTab(aTab)
   {
@@ -4428,60 +4376,30 @@ WebConsoleConnectionProxy.prototype = {
 
     this._disconnecter = Promise.defer();
 
     if (!this.client) {
       this._disconnecter.resolve(null);
       return this._disconnecter.promise;
     }
 
-    let onDisconnect = function() {
-      if (timer) {
-        timer.cancel();
-        timer = null;
-        this._disconnecter.resolve(null);
-      }
-    }.bind(this);
-
-    let timer = null;
-    let remoteTarget = this.target.isRemote;
-    if (!remoteTarget) {
-      timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-      timer.initWithCallback(onDisconnect, 1500, Ci.nsITimer.TYPE_ONE_SHOT);
-    }
-
     this.client.removeListener("pageError", this._onPageError);
     this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
     this.client.removeListener("networkEvent", this._onNetworkEvent);
     this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
     this.client.removeListener("fileActivity", this._onFileActivity);
     this.client.removeListener("tabNavigated", this._onTabNavigated);
 
-    let client = this.client;
-
     this.client = null;
     this.webConsoleClient = null;
     this.tabClient = null;
     this.target = null;
     this.connected = false;
     this.owner = null;
-
-    if (!remoteTarget) {
-      try {
-        client.close(onDisconnect);
-      }
-      catch (ex) {
-        Cu.reportError("Web Console disconnect exception: " + ex);
-        Cu.reportError(ex.stack);
-        onDisconnect();
-      }
-    }
-    else {
-      onDisconnect();
-    }
+    this._disconnecter.resolve(null);
 
     return this._disconnecter.promise;
   },
 };
 
 function gSequenceId()
 {
   return gSequenceId.n++;
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/font-inspector.dtd
@@ -0,0 +1,12 @@
+<!-- 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/. -->
+
+<!-- LOCALIZATION NOTE : FILE This file contains the Font Inspector strings.
+  - The Font Inspector is the panel accessible in the Inspector sidebar. -->
+
+<!ENTITY title "Fonts">
+<!ENTITY showAllFonts "See all the fonts used in the page">
+<!ENTITY usedAs "Used as: ">
+<!ENTITY system "system">
+<!ENTITY remote "remote">
--- a/browser/locales/en-US/chrome/browser/devtools/gcli.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gcli.properties
@@ -328,39 +328,39 @@ prefOutputValue=Value
 # name, which is why it should be as short as possible. See introManual for a
 # fuller description of what it does.
 introDesc=Show the opening message
 
 # LOCALIZATION NOTE (introManual): A fuller description of the 'intro'
 # command. Displayed when the user asks for help on what it does.
 introManual=Redisplay the message that is shown to new users until they click the 'Got it!' button
 
-# LOCALIZATION NOTE (introTextOpening): The 'intro text' opens when the user
+# LOCALIZATION NOTE (introTextOpening2): The 'intro text' opens when the user
 # first opens the developer toolbar to explain the command line, and is shown
 # each time it is opened until the user clicks the 'Got it!' button. This
 # string is the opening paragraph of the intro text.
 introTextOpening2=This command line is designed for developers. It focuses on speed of input over JavaScript syntax and a rich display over monospace output.
 
 # LOCALIZATION NOTE (introTextCommands): For information about the 'intro
-# text' see introTextOpening. The second paragraph is in 2 sections, the first
-# section points the user to the 'help' command.
+# text' see introTextOpening2. The second paragraph is in 2 sections, the
+# first section points the user to the 'help' command.
 introTextCommands=For a list of commands type
 
 # LOCALIZATION NOTE (introTextKeys2): For information about the 'intro text'
-# see introTextOpening. The second section in the second paragraph points the
+# see introTextOpening2. The second section in the second paragraph points the
 # user to the F1/Escape keys which show and hide hints.
 introTextKeys2=, or to show/hide command hints press
 
 # LOCALIZATION NOTE (introTextF1Escape): For information about the 'intro
-# text' see introTextOpening. This string is used with introTextKeys, and
+# text' see introTextOpening2. This string is used with introTextKeys2, and
 # contains the keys that are pressed to open and close hints.
 introTextF1Escape=F1/Escape
 
 # LOCALIZATION NOTE (introTextGo): For information about the 'intro text' see
-# introTextOpening. The text on the button that dismisses the intro text.
+# introTextOpening2. The text on the button that dismisses the intro text.
 introTextGo=Got it!
 
 # LOCALIZATION NOTE (hideIntroDesc): Short description of the 'hideIntro'
 # setting. Displayed when the user asks for help on the settings.
 hideIntroDesc=Show the initial welcome message
 
 # LOCALIZATION NOTE (eagerHelperDesc): Short description of the 'eagerHelper'
 # setting. Displayed when the user asks for help on the settings.
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
@@ -1,8 +1,12 @@
+# 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/.
+
 toolboxDockButtons.bottom.tooltip=Dock to bottom of browser window
 toolboxDockButtons.side.tooltip=Dock to side of browser window
 toolboxDockButtons.window.tooltip=Show in separate window
 
 # LOCALIZATION NOTE (toolboxToggleButton.errors): Semi-colon list of plural
 # forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of errors in the current web page
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -42,16 +42,17 @@
     locale/browser/devtools/profiler.properties       (%chrome/browser/devtools/profiler.properties)
     locale/browser/devtools/layoutview.dtd            (%chrome/browser/devtools/layoutview.dtd)
     locale/browser/devtools/responsiveUI.properties   (%chrome/browser/devtools/responsiveUI.properties)
     locale/browser/devtools/toolbox.dtd            (%chrome/browser/devtools/toolbox.dtd)
     locale/browser/devtools/toolbox.properties     (%chrome/browser/devtools/toolbox.properties)
     locale/browser/devtools/inspector.dtd          (%chrome/browser/devtools/inspector.dtd)
     locale/browser/devtools/connection-screen.dtd  (%chrome/browser/devtools/connection-screen.dtd)
     locale/browser/devtools/connection-screen.properties (%chrome/browser/devtools/connection-screen.properties)
+    locale/browser/devtools/font-inspector.dtd     (%chrome/browser/devtools/font-inspector.dtd)
     locale/browser/newTab.dtd                      (%chrome/browser/newTab.dtd)
     locale/browser/newTab.properties               (%chrome/browser/newTab.properties)
     locale/browser/openLocation.dtd                (%chrome/browser/openLocation.dtd)
     locale/browser/openLocation.properties         (%chrome/browser/openLocation.properties)
     locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
     locale/browser/safeMode.dtd                    (%chrome/browser/safeMode.dtd)
new file mode 100644
--- /dev/null
+++ b/browser/themes/linux/devtools/font-inspector.css
@@ -0,0 +1,79 @@
+* {
+  -moz-box-sizing: border-box;
+}
+
+body {
+  background: #F9F9F9;
+  margin: 0;
+  padding-bottom: 20px;
+}
+
+#all-fonts {
+  padding: 0 5px;
+  margin: 0;
+}
+
+#showall {
+  border-radius: 0;
+  border: 1px solid black;
+  margin: 3px;
+  cursor: pointer;
+  position: fixed;
+  bottom: 0;
+  right: 0;
+}
+
+.font {
+  border-bottom: 1px solid #DDD;
+  padding: 10px 5px;
+  font-size: 0;
+}
+
+.font:last-of-type {
+  border-bottom: 0;
+}
+
+.font:nth-child(even) {
+  background: #F4F4F4;
+}
+
+.font-preview {
+  height: 60px;
+  width: 100%;
+  border: 0;
+  display: block;
+}
+
+.font-info {
+  font-size: 1rem;
+  display: block;
+}
+
+.font-name {
+  display: inline;
+}
+
+.font-css-code {
+  max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  background: white;
+  padding: 5px;
+  border: 1px dotted #CCC;
+}
+
+.font-is-local,
+.font-is-remote,
+.font-format-url,
+.font-css {
+  color: #999
+}
+
+.font-url {
+  border: 1px solid #CCC;
+  color: #888;
+}
+
+.font-url:focus {
+  color: black;
+}
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -185,16 +185,17 @@ browser.jar:
   skin/classic/browser/devtools/toolbox.css               (devtools/toolbox.css)
   skin/classic/browser/devtools/tool-webconsole.png       (devtools/tool-webconsole.png)
   skin/classic/browser/devtools/tool-debugger.png         (devtools/tool-debugger.png)
   skin/classic/browser/devtools/tool-inspector.png        (devtools/tool-inspector.png)
   skin/classic/browser/devtools/tool-styleeditor.png      (devtools/tool-styleeditor.png)
   skin/classic/browser/devtools/tool-profiler.png         (devtools/tool-profiler.png)
   skin/classic/browser/devtools/close.png                 (devtools/close.png)
   skin/classic/browser/devtools/undock.png                (devtools/undock.png)
+  skin/classic/browser/devtools/font-inspector.css        (devtools/font-inspector.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-24-throbber.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
new file mode 100644
--- /dev/null
+++ b/browser/themes/osx/devtools/font-inspector.css
@@ -0,0 +1,79 @@
+* {
+  -moz-box-sizing: border-box;
+}
+
+body {
+  background: #F9F9F9;
+  margin: 0;
+  padding-bottom: 20px;
+}
+
+#all-fonts {
+  padding: 0 5px;
+  margin: 0;
+}
+
+#showall {
+  border-radius: 0;
+  border: 1px solid black;
+  margin: 3px;
+  cursor: pointer;
+  position: fixed;
+  bottom: 0;
+  right: 0;
+}
+
+.font {
+  border-bottom: 1px solid #DDD;
+  padding: 10px 5px;
+  font-size: 0;
+}
+
+.font:last-of-type {
+  border-bottom: 0;
+}
+
+.font:nth-child(even) {
+  background: #F4F4F4;
+}
+
+.font-preview {
+  height: 60px;
+  width: 100%;
+  border: 0;
+  display: block;
+}
+
+.font-info {
+  font-size: 1rem;
+  display: block;
+}
+
+.font-name {
+  display: inline;
+}
+
+.font-css-code {
+  max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  background: white;
+  padding: 5px;
+  border: 1px dotted #CCC;
+}
+
+.font-is-local,
+.font-is-remote,
+.font-format-url,
+.font-css {
+  color: #999
+}
+
+.font-url {
+  border: 1px solid #CCC;
+  color: #888;
+}
+
+.font-url:focus {
+  color: black;
+}
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -266,16 +266,17 @@ browser.jar:
   skin/classic/browser/devtools/toolbox.css                 (devtools/toolbox.css)
   skin/classic/browser/devtools/tool-webconsole.png         (devtools/tool-webconsole.png)
   skin/classic/browser/devtools/tool-debugger.png           (devtools/tool-debugger.png)
   skin/classic/browser/devtools/tool-inspector.png          (devtools/tool-inspector.png)
   skin/classic/browser/devtools/tool-styleeditor.png        (devtools/tool-styleeditor.png)
   skin/classic/browser/devtools/tool-profiler.png           (devtools/tool-profiler.png)
   skin/classic/browser/devtools/close.png                   (devtools/close.png)
   skin/classic/browser/devtools/undock.png                  (devtools/undock.png)
+  skin/classic/browser/devtools/font-inspector.css          (devtools/font-inspector.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
new file mode 100644
--- /dev/null
+++ b/browser/themes/windows/devtools/font-inspector.css
@@ -0,0 +1,79 @@
+* {
+  -moz-box-sizing: border-box;
+}
+
+body {
+  background: #F9F9F9;
+  margin: 0;
+  padding-bottom: 20px;
+}
+
+#all-fonts {
+  padding: 0 5px;
+  margin: 0;
+}
+
+#showall {
+  border-radius: 0;
+  border: 1px solid black;
+  margin: 3px;
+  cursor: pointer;
+  position: fixed;
+  bottom: 0;
+  right: 0;
+}
+
+.font {
+  border-bottom: 1px solid #DDD;
+  padding: 10px 5px;
+  font-size: 0;
+}
+
+.font:last-of-type {
+  border-bottom: 0;
+}
+
+.font:nth-child(even) {
+  background: #F4F4F4;
+}
+
+.font-preview {
+  height: 60px;
+  width: 100%;
+  border: 0;
+  display: block;
+}
+
+.font-info {
+  font-size: 1rem;
+  display: block;
+}
+
+.font-name {
+  display: inline;
+}
+
+.font-css-code {
+  max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  background: white;
+  padding: 5px;
+  border: 1px dotted #CCC;
+}
+
+.font-is-local,
+.font-is-remote,
+.font-format-url,
+.font-css {
+  color: #999
+}
+
+.font-url {
+  border: 1px solid #CCC;
+  color: #888;
+}
+
+.font-url:focus {
+  color: black;
+}
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -212,16 +212,17 @@ browser.jar:
         skin/classic/browser/devtools/toolbox.css                   (devtools/toolbox.css)
         skin/classic/browser/devtools/tool-webconsole.png           (devtools/tool-webconsole.png)
         skin/classic/browser/devtools/tool-debugger.png             (devtools/tool-debugger.png)
         skin/classic/browser/devtools/tool-inspector.png            (devtools/tool-inspector.png)
         skin/classic/browser/devtools/tool-styleeditor.png          (devtools/tool-styleeditor.png)
         skin/classic/browser/devtools/tool-profiler.png             (devtools/tool-profiler.png)
         skin/classic/browser/devtools/close.png                     (devtools/close.png)
         skin/classic/browser/devtools/undock.png                    (devtools/undock.png)
+        skin/classic/browser/devtools/font-inspector.css            (devtools/font-inspector.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/sync-throbber.png
         skin/classic/browser/sync-16.png
         skin/classic/browser/sync-32.png
         skin/classic/browser/sync-128.png
         skin/classic/browser/sync-bg.png
         skin/classic/browser/sync-desktopIcon.png
         skin/classic/browser/sync-mobileIcon.png
@@ -440,16 +441,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/toolbox.css               (devtools/toolbox.css)
         skin/classic/aero/browser/devtools/tool-webconsole.png       (devtools/tool-webconsole.png)
         skin/classic/aero/browser/devtools/tool-debugger.png         (devtools/tool-debugger.png)
         skin/classic/aero/browser/devtools/tool-inspector.png        (devtools/tool-inspector.png)
         skin/classic/aero/browser/devtools/tool-styleeditor.png      (devtools/tool-styleeditor.png)
         skin/classic/aero/browser/devtools/tool-profiler.png         (devtools/tool-profiler.png)
         skin/classic/aero/browser/devtools/close.png                 (devtools/close.png)
         skin/classic/aero/browser/devtools/undock.png                (devtools/undock.png)
+        skin/classic/aero/browser/devtools/font-inspector.css        (devtools/font-inspector.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/sync-throbber.png
         skin/classic/aero/browser/sync-16.png
         skin/classic/aero/browser/sync-32.png
         skin/classic/aero/browser/sync-128.png
         skin/classic/aero/browser/sync-bg.png
         skin/classic/aero/browser/sync-desktopIcon.png
         skin/classic/aero/browser/sync-mobileIcon.png
--- a/toolkit/devtools/debugger/server/dbg-browser-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-browser-actors.js
@@ -674,17 +674,16 @@ DebuggerProgressListener.prototype = {
         this._tabActor.threadActor.dbg.enabled = false;
         this._tabActor._pendingNavigation = aRequest;
       }
 
       this._tabActor.conn.send({
         from: this._tabActor.actorID,
         type: "tabNavigated",
         url: aRequest.URI.spec,
-        title: "",
         nativeConsoleAPI: true,
         state: "start",
       });
     } else if (isStop) {
       if (this._tabActor.threadActor.state == "running") {
         this._tabActor.threadActor.dbg.enabled = true;
       }