Merge m-c to fx-team
authorPanos Astithas <past@mozilla.com>
Wed, 19 Dec 2012 10:03:48 +0200
changeset 125539 bfd85c9652fa63023a338c5d425d5ab319418d45
parent 125530 287a7d7cf7f09fef3ddf43e39af48f9cb0a65593 (current diff)
parent 125538 8ba98799b6bbf1a6aa20470d7f22780fc72ad300 (diff)
child 125540 1b6ab3a080d81bb9519e09304abd0713489eebba
child 125741 6b168eb45722e7082cbbdba612674abf2e615181
child 138106 36e72b98353002166ed11ced1847c2deb85b4bbd
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone20.0a1
first release with
nightly linux32
bfd85c9652fa / 20.0a1 / 20121219030847 / files
nightly linux64
bfd85c9652fa / 20.0a1 / 20121219030847 / files
nightly mac
bfd85c9652fa / 20.0a1 / 20121219030847 / files
nightly win32
bfd85c9652fa / 20.0a1 / 20121219030847 / files
nightly win64
bfd85c9652fa / 20.0a1 / 20121219030847 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team
browser/devtools/profiler/ProfilerPanel.jsm
--- a/b2g/chrome/content/dbg-browser-actors.js
+++ b/b2g/chrome/content/dbg-browser-actors.js
@@ -98,69 +98,31 @@ DeviceRootActor.prototype.requestTypes =
  *        The browser instance that contains this tab.
  */
 function DeviceTabActor(connection, browser) {
   BrowserTabActor.call(this, connection, browser);
 }
 
 DeviceTabActor.prototype = new BrowserTabActor();
 
-DeviceTabActor.prototype.grip = function DTA_grip() {
-  dbg_assert(!this.exited,
-             'grip() should not be called on exited browser actor.');
-  dbg_assert(this.actorID,
-             'tab should have an actorID.');
-
-  let response = {
-    'actor': this.actorID,
-    'title': this.browser.title,
-    'url': this.browser.document.documentURI
-  };
-
-  // Walk over tab actors added by extensions and add them to a new ActorPool.
-  let actorPool = new ActorPool(this.conn);
-  this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
-  if (!actorPool.isEmpty()) {
-    this._tabActorPool = actorPool;
-    this.conn.addActorPool(this._tabActorPool);
-  }
-
-  this._appendExtraActors(response);
-  return response;
-};
-
-/**
- * Creates a thread actor and a pool for context-lifetime actors. It then sets
- * up the content window for debugging.
- */
-DeviceTabActor.prototype._pushContext = function DTA_pushContext() {
-  dbg_assert(!this._contextPool, "Can't push multiple contexts");
+Object.defineProperty(DeviceTabActor.prototype, "title", {
+  get: function() {
+    return this.browser.title;
+  },
+  enumerable: true,
+  configurable: false
+});
 
-  this._contextPool = new ActorPool(this.conn);
-  this.conn.addActorPool(this._contextPool);
-
-  this.threadActor = new ThreadActor(this, this.browser.wrappedJSObject);
-  this._contextPool.addActor(this.threadActor);
-};
-
-// Protocol Request Handlers
+Object.defineProperty(DeviceTabActor.prototype, "url", {
+  get: function() {
+    return this.browser.document.documentURI;
+  },
+  enumerable: true,
+  configurable: false
+});
 
-/**
- * Prepare to enter a nested event loop by disabling debuggee events.
- */
-DeviceTabActor.prototype.preNest = function DTA_preNest() {
-  let windowUtils = this.browser
-                        .QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIDOMWindowUtils);
-  windowUtils.suppressEventHandling(true);
-  windowUtils.suspendTimeouts();
-};
-
-/**
- * Prepare to exit a nested event loop by enabling debuggee events.
- */
-DeviceTabActor.prototype.postNest = function DTA_postNest(aNestData) {
-  let windowUtils = this.browser
-                        .QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIDOMWindowUtils);
-  windowUtils.resumeTimeouts();
-  windowUtils.suppressEventHandling(false);
-};
+Object.defineProperty(DeviceTabActor.prototype, "contentWindow", {
+  get: function() {
+    return this.browser;
+  },
+  enumerable: true,
+  configurable: false
+});
--- a/browser/devtools/debugger/DebuggerPanel.jsm
+++ b/browser/devtools/debugger/DebuggerPanel.jsm
@@ -20,17 +20,17 @@ function DebuggerPanel(iframeWindow, too
   this.panelWin = iframeWindow;
   this._toolbox = toolbox;
 
   this._controller = this.panelWin.DebuggerController;
   this._view = this.panelWin.DebuggerView;
   this._controller._target = this.target;
   this._bkp = this._controller.Breakpoints;
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 }
 
 DebuggerPanel.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
   open: function DebuggerPanel_open() {
     let deferred = Promise.defer();
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -210,19 +210,28 @@ let DebuggerController = {
 
     this.client = null;
     this.tabClient = null;
     this.activeThread = null;
   },
 
   /**
    * Called for each location change in the debugged tab.
+   *
+   * @param string aType
+   *        Packet type.
+   * @param object aPacket
+   *        Packet received from the server.
    */
-  _onTabNavigated: function DC__onTabNavigated() {
-    DebuggerView._handleTabNavigation();
+  _onTabNavigated: function DC__onTabNavigated(aType, aPacket) {
+    if (aPacket.state == "start") {
+      DebuggerView._handleTabNavigation();
+      return;
+    }
+
     this.ThreadState._handleTabNavigation();
     this.StackFrames._handleTabNavigation();
     this.SourceScripts._handleTabNavigation();
   },
 
   /**
    * Called when the debugged tab is closed.
    */
--- a/browser/devtools/debugger/test/browser_dbg_bfcache.js
+++ b/browser/devtools/debugger/test/browser_dbg_bfcache.js
@@ -35,17 +35,24 @@ function testInitialLoad() {
   });
 
   gDebuggee.firstCall();
 }
 
 function testLocationChange()
 {
   gDebugger.DebuggerController.activeThread.resume(function() {
-    gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+    gDebugger.DebuggerController.client.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
+      dump("tabNavigated state " + aPacket.state + "\n");
+      if (aPacket.state == "start") {
+        return;
+      }
+
+      gDebugger.DebuggerController.client.removeListener("tabNavigated", onTabNavigated);
+
       ok(true, "tabNavigated event was fired.");
       info("Still attached to the tab.");
 
       gDebugger.addEventListener("Debugger:AfterScriptsAdded", function _onEvent(aEvent) {
         gDebugger.removeEventListener(aEvent.type, _onEvent);
 
         executeSoon(function() {
           validateSecondPage();
@@ -54,17 +61,24 @@ function testLocationChange()
       });
     });
     content.location = STACK_URL;
   });
 }
 
 function testBack()
 {
-  gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+  gDebugger.DebuggerController.client.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
+    dump("tabNavigated state " + aPacket.state + "\n");
+    if (aPacket.state == "start") {
+      return;
+    }
+
+    gDebugger.DebuggerController.client.removeListener("tabNavigated", onTabNavigated);
+
     ok(true, "tabNavigated event was fired after going back.");
     info("Still attached to the tab.");
 
     gDebugger.addEventListener("Debugger:AfterScriptsAdded", function _onEvent(aEvent) {
       gDebugger.removeEventListener(aEvent.type, _onEvent);
 
       executeSoon(function() {
         validateFirstPage();
@@ -74,17 +88,24 @@ function testBack()
   });
 
   info("Going back.");
   content.history.back();
 }
 
 function testForward()
 {
-  gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+  gDebugger.DebuggerController.client.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
+    dump("tabNavigated state " + aPacket.state + "\n");
+    if (aPacket.state == "start") {
+      return;
+    }
+
+    gDebugger.DebuggerController.client.removeListener("tabNavigated", onTabNavigated);
+
     ok(true, "tabNavigated event was fired after going forward.");
     info("Still attached to the tab.");
 
     gDebugger.addEventListener("Debugger:AfterScriptsAdded", function _onEvent(aEvent) {
       gDebugger.removeEventListener(aEvent.type, _onEvent);
 
       executeSoon(function() {
         validateSecondPage();
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-blank.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-blank.js
@@ -68,17 +68,23 @@ function testSimpleCall() {
     "The source editor text should not be 'Loading...'");
 
   testLocationChange();
 }
 
 function testLocationChange()
 {
   gDebugger.DebuggerController.activeThread.resume(function() {
-    gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+    gDebugger.DebuggerController.client.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
+      dump("tabNavigated state " + aPacket.state + "\n");
+      if (aPacket.state == "start") {
+        return;
+      }
+      gDebugger.DebuggerController.client.removeListener("tabNavigated", onTabNavigated);
+
       ok(true, "tabNavigated event was fired.");
       info("Still attached to the tab.");
 
       gDebugger.addEventListener("Debugger:AfterScriptsAdded", function _onEvent(aEvent) {
         gDebugger.removeEventListener(aEvent.type, _onEvent);
 
         is(gDebugger.DebuggerView.Sources.selectedValue, null,
           "There should be no selected script.");
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-new.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-new.js
@@ -68,17 +68,23 @@ function testSimpleCall() {
     "The source editor text should not be 'Loading...'");
 
   testLocationChange();
 }
 
 function testLocationChange()
 {
   gDebugger.DebuggerController.activeThread.resume(function() {
-    gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+    gDebugger.DebuggerController.client.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
+      dump("tabNavigated state " + aPacket.state + "\n");
+      if (aPacket.state == "start") {
+        return;
+      }
+      gDebugger.DebuggerController.client.removeListener("tabNavigated", onTabNavigated);
+
       ok(true, "tabNavigated event was fired.");
       info("Still attached to the tab.");
 
       gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
         gDebugger.removeEventListener(aEvent.type, _onEvent);
 
         isnot(gDebugger.DebuggerView.Sources.selectedValue, null,
           "There should be a selected script.");
--- a/browser/devtools/debugger/test/browser_dbg_location-changes.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes.js
@@ -45,17 +45,23 @@ function testSimpleCall() {
   });
 
   gDebuggee.simpleCall();
 }
 
 function testLocationChange()
 {
   gDebugger.DebuggerController.activeThread.resume(function() {
-    gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+    gDebugger.DebuggerController.client.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
+      dump("tabNavigated state " + aPacket.state + "\n");
+      if (aPacket.state == "start") {
+        return;
+      }
+      gDebugger.DebuggerController.client.removeListener("tabNavigated", onTabNavigated);
+
       ok(true, "tabNavigated event was fired.");
       info("Still attached to the tab.");
 
       closeDebuggerAndFinish();
     });
     content.location = TAB1_URL;
   });
 }
--- a/browser/devtools/debugger/test/browser_dbg_nav-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_nav-01.js
@@ -21,17 +21,23 @@ function test()
 }
 
 function get_tab()
 {
   gTab1 = addTab(TAB1_URL, function() {
     get_tab_actor_for_url(gClient, TAB1_URL, function(aGrip) {
       gTab1Actor = aGrip.actor;
       gClient.request({ to: aGrip.actor, type: "attach" }, function(aResponse) {
-        gClient.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+        gClient.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
+          dump("onTabNavigated state " + aPacket.state + "\n");
+          if (aPacket.state == "start") {
+            return;
+          }
+          gClient.removeListener("tabNavigated", onTabNavigated);
+
           is(aPacket.url, TAB2_URL, "Got a tab navigation notification.");
           gClient.addOneTimeListener("tabDetached", function (aEvent, aPacket) {
             ok(true, "Got a tab detach notification.");
             finish_test();
           });
           removeTab(gTab1);
         });
         gTab1.linkedBrowser.loadURI(TAB2_URL);
--- a/browser/devtools/framework/Sidebar.jsm
+++ b/browser/devtools/framework/Sidebar.jsm
@@ -21,17 +21,17 @@ const XULNS = "http://www.mozilla.org/ke
  *  <tabbox> node;
  * @param {ToolPanel} panel
  *  Related ToolPanel instance;
  * @param {Boolean} showTabstripe
  *  Show the tabs.
  */
 this.ToolSidebar = function ToolSidebar(tabbox, panel, showTabstripe=true)
 {
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 
   this._tabbox = tabbox;
   this._panelDoc = this._tabbox.ownerDocument;
   this._toolPanel = panel;
 
   this._tabbox.tabpanels.addEventListener("select", this, true);
 
   this._tabs = new Map();
--- a/browser/devtools/framework/Target.jsm
+++ b/browser/devtools/framework/Target.jsm
@@ -165,17 +165,17 @@ 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) {
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
   this._tab = tab;
   this._setupListeners();
 }
 
 TabTarget.prototype = {
   _webProgressListener: null,
 
   supports: supports,
@@ -196,16 +196,20 @@ TabTarget.prototype = {
   get url() {
     return this._tab.linkedBrowser.contentDocument.location.href;
   },
 
   get isRemote() {
     return false;
   },
 
+  get isLocalTab() {
+    return true;
+  },
+
   /**
    * Listen to the different tabs 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);
@@ -300,17 +304,17 @@ TabWebProgressListener.prototype = {
 };
 
 
 /**
  * A WindowTarget represents a page living in a xul window or panel. Generally
  * these will have a chrome: URL
  */
 function WindowTarget(window) {
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
   this._window = window;
 }
 
 WindowTarget.prototype = {
   supports: supports,
   get version() { return getVersion(); },
 
   get window() {
@@ -324,16 +328,20 @@ WindowTarget.prototype = {
   get url() {
     return this._window.document.location.href;
   },
 
   get isRemote() {
     return false;
   },
 
+  get isLocalTab() {
+    return false;
+  },
+
   /**
    * Target is not alive anymore.
    */
   destroy: function() {
     if (!this._destroyed) {
       this._destroyed = true;
 
       this.emit("close");
@@ -349,46 +357,52 @@ WindowTarget.prototype = {
     return 'WindowTarget:' + this.window;
   },
 };
 
 /**
  * A RemoteTarget represents a page living in a remote Firefox instance.
  */
 function RemoteTarget(form, client, chrome) {
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
   this._client = client;
   this._form = form;
   this._chrome = chrome;
 
   this.destroy = this.destroy.bind(this);
   this.client.addListener("tabDetached", this.destroy);
 
-  this._onTabNavigated = function onRemoteTabNavigated() {
-    this.emit("navigate");
+  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);
 }
 
 RemoteTarget.prototype = {
   supports: supports,
   get version() getVersion(),
 
   get isRemote() true,
 
   get chrome() this._chrome,
 
-  get name() this._form._title,
+  get name() this._form.title,
 
-  get url() this._form._url,
+  get url() this._form.url,
 
   get client() this._client,
 
   get form() this._form,
 
+  get isLocalTab() false,
+
   /**
    * 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;
--- a/browser/devtools/framework/Toolbox.jsm
+++ b/browser/devtools/framework/Toolbox.jsm
@@ -122,17 +122,17 @@ this.Toolbox = function Toolbox(target, 
   let definitions = gDevTools.getToolDefinitions();
   if (!definitions.get(selectedTool)) {
     selectedTool = "webconsole";
   }
   this._defaultToolId = selectedTool;
 
   this._host = this._createHost(hostType);
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 
   gDevTools.on("tool-registered", this._toolRegistered);
   gDevTools.on("tool-unregistered", this._toolUnregistered);
 }
 
 /**
  * The toolbox can be 'hosted' either embedded in a browser window
  * or in a separate window.
@@ -265,16 +265,20 @@ Toolbox.prototype = {
    */
   _buildDockButtons: function TBOX_createDockButtons() {
     let dockBox = this.doc.getElementById("toolbox-dock-buttons");
 
     while (dockBox.firstChild) {
       dockBox.removeChild(dockBox.firstChild);
     }
 
+    if (!this._target.isLocalTab) {
+      return;
+    }
+
     let sideEnabled = Services.prefs.getBoolPref(this._prefs.SIDE_ENABLED);
 
     for each (let position in this.HostType) {
       if (position == this.hostType ||
          (!sideEnabled && position == this.HostType.SIDE)) {
         continue;
       }
 
@@ -471,16 +475,20 @@ Toolbox.prototype = {
    * @param {string} hostType
    *        The host type of the new host object
    */
   switchHost: function TBOX_switchHost(hostType) {
     if (hostType == this._host.type) {
       return;
     }
 
+    if (!this._target.isLocalTab) {
+      return;
+    }
+
     let newHost = this._createHost(hostType);
     return newHost.open().then(function(iframe) {
       // change toolbox document's parent to the new host
       iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
       iframe.swapFrameLoaders(this.frame);
 
       this._host.off("window-closed", this.destroy);
       this._host.destroy();
--- a/browser/devtools/framework/ToolboxHosts.jsm
+++ b/browser/devtools/framework/ToolboxHosts.jsm
@@ -28,17 +28,17 @@ this.Hosts = {
 }
 
 /**
  * Host object for the dock on the bottom of the browser
  */
 function BottomHost(hostTab) {
   this.hostTab = hostTab;
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 }
 
 BottomHost.prototype = {
   type: "bottom",
 
   heightPref: "devtools.toolbox.footer.height",
 
   /**
@@ -96,17 +96,17 @@ BottomHost.prototype = {
 
 
 /**
  * Host object for the in-browser sidebar
  */
 function SidebarHost(hostTab) {
   this.hostTab = hostTab;
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 }
 
 SidebarHost.prototype = {
   type: "side",
 
   widthPref: "devtools.toolbox.sidebar.width",
 
   /**
@@ -161,17 +161,17 @@ SidebarHost.prototype = {
 }
 
 /**
  * Host object for the toolbox in a separate window
  */
 function WindowHost() {
   this._boundUnload = this._boundUnload.bind(this);
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 }
 
 WindowHost.prototype = {
   type: "window",
 
   WINDOW_URL: "chrome://browser/content/devtools/framework/toolbox-window.xul",
 
   /**
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -26,17 +26,17 @@ const FORBIDDEN_IDS = new Set("toolbox",
  */
 this.DevTools = function DevTools() {
   this._tools = new Map();
   this._toolboxes = new Map();
 
   // destroy() is an observer's handler so we need to preserve context.
   this.destroy = this.destroy.bind(this);
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 
   Services.obs.addObserver(this.destroy, "quit-application", false);
 
   // Register the set of default tools
   for (let definition of defaultTools) {
     this.registerTool(definition);
   }
 }
--- a/browser/devtools/framework/test/browser_devtools_api.js
+++ b/browser/devtools/framework/test/browser_devtools_api.js
@@ -80,17 +80,17 @@ function finishUp() {
 * When a Toolbox is started it creates a DevToolPanel for each of the tools
 * by calling toolDefinition.build(). The returned object should
 * at least implement these functions. They will be used by the ToolBox.
 *
 * There may be no benefit in doing this as an abstract type, but if nothing
 * else gives us a place to write documentation.
 */
 function DevToolPanel(iframeWindow, toolbox) {
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 
   this._toolbox = toolbox;
 
   /*let doc = iframeWindow.document
   let label = doc.createElement("label");
   let textNode = doc.createTextNode("Some Tool");
 
   label.appendChild(textNode);
--- a/browser/devtools/framework/toolbox.xul
+++ b/browser/devtools/framework/toolbox.xul
@@ -8,20 +8,28 @@
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/toolbox.css" type="text/css"?>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/source-editor-overlay.xul"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <notificationbox id="toolbox-notificationbox" flex="1">
     <toolbar class="devtools-tabbar">
+#ifdef XP_MACOSX
       <hbox id="toolbox-controls">
         <toolbarbutton id="toolbox-close" class="devtools-closebutton"></toolbarbutton>
         <hbox id="toolbox-dock-buttons"/>
       </hbox>
+#endif
       <radiogroup id="toolbox-tabs" orient="horizontal">
       </radiogroup>
       <hbox id="toolbox-buttons" flex="1" pack="end"/>
+#ifndef XP_MACOSX
+      <hbox id="toolbox-controls">
+        <hbox id="toolbox-dock-buttons"/>
+        <toolbarbutton id="toolbox-close" class="devtools-closebutton"></toolbarbutton>
+      </hbox>
+#endif
     </toolbar>
     <deck id="toolbox-deck" flex="1">
     </deck>
   </notificationbox>
 </window>
--- a/browser/devtools/inspector/Highlighter.jsm
+++ b/browser/devtools/inspector/Highlighter.jsm
@@ -79,17 +79,17 @@ this.Highlighter = function Highlighter(
   this.target = aTarget;
   this.tab = aTarget.tab;
   this.toolbox = aToolbox;
   this.browser = this.tab.linkedBrowser;
   this.chromeDoc = this.tab.ownerDocument;
   this.chromeWin = this.chromeDoc.defaultView;
   this.inspector = aInspector
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 
   this._init();
 }
 
 Highlighter.prototype = {
   get selection() {
     return this.inspector.selection;
   },
@@ -134,18 +134,23 @@ Highlighter.prototype = {
     this.selection.on("new-node", this.highlight);
     this.selection.on("new-node", this.updateInfobar);
     this.selection.on("detached", this.highlight);
     this.selection.on("pseudoclass", this.updateInfobar);
     this.selection.on("attribute-changed", this.updateInfobar);
 
     this.onToolSelected = function(event, id) {
       if (id != "inspector") {
+        this.chromeWin.clearTimeout(this.pageEventsMuter);
+        this.detachMouseListeners();
         this.hide();
       } else {
+        if (!this.locked) {
+          this.attachMouseListeners();
+        }
         this.show();
       }
     }.bind(this);
     this.toolbox.on("select", this.onToolSelected);
 
     this.hidden = true;
     this.highlight();
   },
--- a/browser/devtools/inspector/InspectorPanel.jsm
+++ b/browser/devtools/inspector/InspectorPanel.jsm
@@ -37,17 +37,17 @@ this.InspectorPanel = function Inspector
   this._target = toolbox._target;
   this.panelDoc = iframeWindow.document;
   this.panelWin = iframeWindow;
   this.panelWin.inspector = this;
 
   this.tabTarget = (this.target.tab != null);
   this.winTarget = (this.target.window != null);
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 }
 
 InspectorPanel.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
   open: function InspectorPanel_open() {
     let deferred = Promise.defer();
--- a/browser/devtools/inspector/Selection.jsm
+++ b/browser/devtools/inspector/Selection.jsm
@@ -53,17 +53,17 @@ this.EXPORTED_SYMBOLS = ["Selection"];
  *
  * @param node Inner node.
  *    Can be null. Can be (un)set in the future via the "node" property;
  * @param trackAttribute Tell if events should be fired when the attributes of
  *    the ndoe change.
  *
  */
 this.Selection = function Selection(node=null, track={attributes:true,detached:true}) {
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
   this._onMutations = this._onMutations.bind(this);
   this.track = track;
   this.setNode(node);
 }
 
 Selection.prototype = {
   _node: null,
 
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -44,15 +44,15 @@ browser.jar:
     content/browser/devtools/profiler/cleopatra/images/noise.png        (profiler/cleopatra/images/noise.png)
     content/browser/devtools/profiler/cleopatra/images/showall.png      (profiler/cleopatra/images/showall.png)
     content/browser/devtools/profiler/cleopatra/images/throbber.svg     (profiler/cleopatra/images/throbber.svg)
     content/browser/devtools/profiler/cleopatra/images/treetwisty.svg   (profiler/cleopatra/images/treetwisty.svg)
     content/browser/devtools/commandline.css      (commandline/commandline.css)
     content/browser/devtools/commandlineoutput.xhtml  (commandline/commandlineoutput.xhtml)
     content/browser/devtools/commandlinetooltip.xhtml  (commandline/commandlinetooltip.xhtml)
     content/browser/devtools/framework/toolbox-window.xul    (framework/toolbox-window.xul)
-    content/browser/devtools/framework/toolbox.xul           (framework/toolbox.xul)
+*   content/browser/devtools/framework/toolbox.xul           (framework/toolbox.xul)
     content/browser/devtools/framework/toolbox.css           (framework/toolbox.css)
     content/browser/devtools/inspector/inspector.xul         (inspector/inspector.xul)
     content/browser/devtools/inspector/inspector.css         (inspector/inspector.css)
     content/browser/devtools/connect.xhtml  (framework/connect/connect.xhtml)
     content/browser/devtools/connect.css    (framework/connect/connect.css)
     content/browser/devtools/connect.js     (framework/connect/connect.js)
--- a/browser/devtools/markupview/MarkupView.jsm
+++ b/browser/devtools/markupview/MarkupView.jsm
@@ -53,17 +53,17 @@ this.MarkupView = function MarkupView(aI
 
   this._observer = new this.doc.defaultView.MutationObserver(this._mutationObserver.bind(this));
 
   this._boundOnNewSelection = this._onNewSelection.bind(this);
   this._inspector.selection.on("new-node", this._boundOnNewSelection);
   this._onNewSelection();
 
   this._boundKeyDown = this._onKeyDown.bind(this);
-  this._frame.addEventListener("keydown", this._boundKeyDown, false);
+  this._frame.contentWindow.addEventListener("keydown", this._boundKeyDown, false);
 
   this._boundFocus = this._onFocus.bind(this);
   this._frame.addEventListener("focus", this._boundFocus, false);
 
   this._initPreview();
 }
 
 MarkupView.prototype = {
@@ -485,17 +485,17 @@ MarkupView.prototype = {
     delete this._boundFocus;
 
     this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true);
     this._frame.contentWindow.removeEventListener("resize", this._boundUpdatePreview, true);
     this._frame.contentWindow.removeEventListener("overflow", this._boundResizePreview, true);
     this._frame.contentWindow.removeEventListener("underflow", this._boundResizePreview, true);
     delete this._boundUpdatePreview;
 
-    this._frame.removeEventListener("keydown", this._boundKeyDown, true);
+    this._frame.contentWindow.removeEventListener("keydown", this._boundKeyDown, true);
     delete this._boundKeyDown;
 
     this._inspector.selection.off("new-node", this._boundOnNewSelection);
     delete this._boundOnNewSelection;
 
     delete this._elt;
 
     delete this._containers;
--- a/browser/devtools/profiler/ProfilerPanel.jsm
+++ b/browser/devtools/profiler/ProfilerPanel.jsm
@@ -35,17 +35,17 @@ XPCOMUtils.defineLazyGetter(this, "Debug
  *   Unique ID for this profile.
  * @param ProfilerPanel panel
  *   A reference to the container panel.
  */
 function ProfileUI(uid, panel) {
   let doc = panel.document;
   let win = panel.window;
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 
   this.isReady = false;
   this.panel = panel;
   this.uid = uid;
 
   this.iframe = doc.createElement("iframe");
   this.iframe.setAttribute("flex", "1");
   this.iframe.setAttribute("id", "profiler-cleo-" + uid);
@@ -178,17 +178,17 @@ function ProfilerPanel(frame, toolbox) {
   this.window = frame.window;
   this.document = frame.document;
   this.target = toolbox.target;
   this.controller = new ProfilerController();
 
   this.profiles = new Map();
   this._uid = 0;
 
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 }
 
 ProfilerPanel.prototype = {
   isReady:    null,
   window:     null,
   document:   null,
   target:     null,
   controller: null,
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -64,25 +64,20 @@ this.ResponsiveUIManager = {
         if (aTab.__responsiveUI) {
           aTab.__responsiveUI.close();
         }
         break;
       case "resize toggle":
           this.toggle(aWindow, aTab);
       default:
     }
-  },
+  }
+}
 
-  get events() {
-    if (!this._eventEmitter) {
-      this._eventEmitter = new EventEmitter();
-    }
-    return this._eventEmitter;
-  },
-}
+EventEmitter.decorate(ResponsiveUIManager);
 
 let presets = [
   // Phones
   {key: "320x480", width: 320, height: 480},    // iPhone, B2G, with <meta viewport>
   {key: "360x640", width: 360, height: 640},    // Android 4, phones, with <meta viewport>
 
   // Tablets
   {key: "768x1024", width: 768, height: 1024},   // iPad, with <meta viewport>
@@ -170,17 +165,17 @@ function ResponsiveUI(aWindow, aTab)
     if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
       this.rotate();
     }
   } catch(e) {}
 
   if (this._floatingScrollbars)
     switchToFloatingScrollbars(this.tab);
 
-  ResponsiveUIManager.events.emit("on", this.tab, this);
+  ResponsiveUIManager.emit("on", this.tab, this);
 }
 
 ResponsiveUI.prototype = {
   _transitionsEnabled: true,
   _floatingScrollbars: false, // See bug 799471
   get transitionsEnabled() this._transitionsEnabled,
   set transitionsEnabled(aValue) {
     this._transitionsEnabled = aValue;
@@ -227,17 +222,17 @@ ResponsiveUI.prototype = {
     this.stack.removeChild(this.resizer);
     this.stack.removeChild(this.resizeBar);
 
     // Unset the responsive mode.
     this.container.removeAttribute("responsivemode");
     this.stack.removeAttribute("responsivemode");
 
     delete this.tab.__responsiveUI;
-    ResponsiveUIManager.events.emit("off", this.tab, this);
+    ResponsiveUIManager.emit("off", this.tab, this);
   },
 
   /**
    * Handle keypressed.
    *
    * @param aEvent
    */
   onKeypress: function RUI_onKeypress(aEvent) {
--- a/browser/devtools/responsivedesign/test/browser_responsiveui.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveui.js
@@ -1,28 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   let instance, widthBeforeClose, heightBeforeClose;
-  let events = ResponsiveUI.ResponsiveUIManager.events;
+  let mgr = ResponsiveUI.ResponsiveUIManager;
 
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     waitForFocus(startTest, content);
   }, true);
 
   content.location = "data:text/html,mop";
 
   function startTest() {
     document.getElementById("Tools:ResponsiveUI").removeAttribute("disabled");
-    events.once("on", function() {executeSoon(onUIOpen)});
+    mgr.once("on", function() {executeSoon(onUIOpen)});
     synthesizeKeyFromKeyTag("key_responsiveUI");
   }
 
   function onUIOpen() {
     // Is it open?
     let container = gBrowser.getBrowserContainer();
     is(container.getAttribute("responsivemode"), "true", "In responsive mode.");
 
@@ -116,36 +116,36 @@ function test() {
     is(content.innerHeight, initialWidth, "The height is now the width.");
     let [width, height] = extractSizeFromString(instance.menulist.firstChild.firstChild.getAttribute("label"));
     is(width, initialHeight, "Label updated (width).");
     is(height, initialWidth, "Label updated (height).");
 
     widthBeforeClose = content.innerWidth;
     heightBeforeClose = content.innerHeight;
 
-    events.once("off", function() {executeSoon(restart)});
+    mgr.once("off", function() {executeSoon(restart)});
     EventUtils.synthesizeKey("VK_ESCAPE", {});
   }
 
   function restart() {
-    events.once("on", function() {executeSoon(onUIOpen2)});
+    mgr.once("on", function() {executeSoon(onUIOpen2)});
     synthesizeKeyFromKeyTag("key_responsiveUI");
   }
 
   function onUIOpen2() {
     let container = gBrowser.getBrowserContainer();
     is(container.getAttribute("responsivemode"), "true", "In responsive mode.");
 
     // Menus are correctly updated?
     is(document.getElementById("Tools:ResponsiveUI").getAttribute("checked"), "true", "menus checked");
 
     is(content.innerWidth, widthBeforeClose, "width restored.");
     is(content.innerHeight, heightBeforeClose, "height restored.");
 
-    events.once("off", function() {executeSoon(finishUp)});
+    mgr.once("off", function() {executeSoon(finishUp)});
     EventUtils.synthesizeKey("VK_ESCAPE", {});
   }
 
   function finishUp() {
 
     // Menus are correctly updated?
     is(document.getElementById("Tools:ResponsiveUI").getAttribute("checked"), "false", "menu unchecked");
 
--- a/browser/devtools/shared/EventEmitter.jsm
+++ b/browser/devtools/shared/EventEmitter.jsm
@@ -1,29 +1,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 this.EXPORTED_SYMBOLS = ["EventEmitter"];
 
 /**
  * EventEmitter.
- *
- * @param Object aObjectToExtend
- *        If aObjectToExtend is not null, the public methods of EventEmitter
- *        are bound to the object.
  */
-this.EventEmitter = function EventEmitter(aObjectToExtend) {
-  if (aObjectToExtend) {
-    aObjectToExtend.on = this.on.bind(this);
-    aObjectToExtend.off = this.off.bind(this);
-    aObjectToExtend.once = this.once.bind(this);
-    aObjectToExtend.emit = this.emit.bind(this);
-  }
-}
+this.EventEmitter = function EventEmitter() {};
+
+/**
+ * Decorate an object with event emitter functionality.
+ *
+ * @param Object aObjectToDecorate
+ *        Bind all public methods of EventEmitter to
+ *        the aObjectToDecorate object.
+ */
+EventEmitter.decorate = function EventEmitter_decorate (aObjectToDecorate) {
+  let emitter = new EventEmitter();
+  aObjectToDecorate.on = emitter.on.bind(emitter);
+  aObjectToDecorate.off = emitter.off.bind(emitter);
+  aObjectToDecorate.once = emitter.once.bind(emitter);
+  aObjectToDecorate.emit = emitter.emit.bind(emitter);
+};
 
 EventEmitter.prototype = {
   /**
    * Connect a listener.
    *
    * @param string aEvent
    *        The event name to which we're connecting.
    * @param function aListener
@@ -98,10 +102,10 @@ EventEmitter.prototype = {
         catch (ex) {
           // Prevent a bad listener from interfering with the others.
           let msg = ex + ": " + ex.stack;
           Components.utils.reportError(msg);
           dump(msg + "\n");
         }
       }
     }
-  },
-}
+  }
+};
--- a/browser/devtools/shared/test/browser_eventemitter_basic.js
+++ b/browser/devtools/shared/test/browser_eventemitter_basic.js
@@ -9,17 +9,17 @@ function test() {
 
 function testEmitter(aObject) {
   Cu.import("resource:///modules/devtools/EventEmitter.jsm", this);
 
   let emitter;
 
   if (aObject) {
     emitter = aObject;
-    new EventEmitter(emitter);
+    EventEmitter.decorate(emitter);
   } else {
     emitter = new EventEmitter();
   }
 
   ok(emitter, "We have an event emitter");
 
   emitter.on("next", next);
   emitter.emit("next", "abc", "def");
--- a/browser/devtools/styleeditor/StyleEditorPanel.jsm
+++ b/browser/devtools/styleeditor/StyleEditorPanel.jsm
@@ -11,17 +11,17 @@ this.EXPORTED_SYMBOLS = ["StyleEditorPan
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "StyleEditorChrome",
                         "resource:///modules/devtools/StyleEditorChrome.jsm");
 
 this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 
   this._toolbox = toolbox;
   this._target = toolbox.target;
 
   this.reset = this.reset.bind(this);
   this.newPage = this.newPage.bind(this);
   this.destroy = this.destroy.bind(this);
 
--- a/browser/devtools/tilt/TiltVisualizer.jsm
+++ b/browser/devtools/tilt/TiltVisualizer.jsm
@@ -172,20 +172,23 @@ TiltVisualizer.prototype = {
                              false);
     Services.obs.addObserver(this.onNewNodeFromTilt,
                              this.presenter.NOTIFICATIONS.UNHIGHLIGHTING,
                              false);
 
     let target = TargetFactory.forTab(aTab);
     let toolbox = gDevTools.getToolbox(target);
     if (toolbox) {
-      this.inspector = toolbox.getPanel("inspector");
-      this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
-      this.inspector.selection.on("detached", this.onNewNodeFromInspector);
-      this.onNewNodeFromInspector();
+      let panel = toolbox.getPanel("inspector");
+      if (panel) {
+        this.inspector = panel;
+        this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
+        this.inspector.selection.on("detached", this.onNewNodeFromInspector);
+        this.onNewNodeFromInspector();
+      }
     }
   },
 
   /**
    * Unregister inspector event listeners.
    */
   unbindInspector: function TV_unbindInspector()
   {
--- a/browser/devtools/webconsole/WebConsolePanel.jsm
+++ b/browser/devtools/webconsole/WebConsolePanel.jsm
@@ -17,17 +17,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/HUDService.jsm");
 
 /**
  * A DevToolPanel that controls the Web Console.
  */
 function WebConsolePanel(iframeWindow, toolbox) {
   this._frameWindow = iframeWindow;
   this._toolbox = toolbox;
-  new EventEmitter(this);
+  EventEmitter.decorate(this);
 }
 
 WebConsolePanel.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
   open: function StyleEditor_open() {
     let tab = this._toolbox._getHostTab();
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -112,16 +112,22 @@ MOCHITEST_BROWSER_FILES = \
 	browser_output_breaks_after_console_dir_uninspectable.js \
 	browser_console_log_inspectable_object.js \
 	browser_bug_638949_copy_link_location.js \
 	browser_output_longstring_expand.js \
 	browser_netpanel_longstring_expand.js \
 	head.js \
 	$(NULL)
 
+ifeq ($(OS_ARCH), Darwin)
+MOCHITEST_BROWSER_FILES += \
+        browser_webconsole_bug_804845_ctrl_key_nav.js \
+        $(NULL)
+endif
+
 ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
 MOCHITEST_BROWSER_FILES += \
         browser_webconsole_bug_618311_private_browsing.js \
         $(NULL)
 endif
 
 MOCHITEST_BROWSER_FILES += \
 	test-console.html \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_804845_ctrl_key_nav.js
@@ -0,0 +1,214 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* 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/
+ *
+ * Contributor(s):
+ *  zmgmoz <zmgmoz@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Test navigation of webconsole contents via ctrl-a, ctrl-e, ctrl-p, ctrl-n
+// see https://bugzilla.mozilla.org/show_bug.cgi?id=804845
+
+let jsterm, inputNode;
+function test() {
+  addTab("data:text/html;charset=utf-8,Web Console test for bug 804845 and bug 619598");
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, doTests);
+  }, true);
+}
+
+function doTests(HUD) {
+  jsterm = HUD.jsterm;
+  inputNode = jsterm.inputNode;
+  ok(!jsterm.inputNode.value, "inputNode.value is empty");
+  is(jsterm.inputNode.selectionStart, 0);
+  is(jsterm.inputNode.selectionEnd, 0);
+
+  testSingleLineInputNavNoHistory();
+  testMultiLineInputNavNoHistory();
+  testNavWithHistory();
+
+  jsterm = inputNode = null;
+  executeSoon(finishTest);
+}
+
+function testSingleLineInputNavNoHistory() {
+  // Single char input
+  EventUtils.synthesizeKey("1", {});
+  is(inputNode.selectionStart, 1, "caret location after single char input");
+
+  // nav to start/end with ctrl-a and ctrl-e;
+  EventUtils.synthesizeKey("a", { ctrlKey: true });
+  is(inputNode.selectionStart, 0, "caret location after single char input and ctrl-a");
+
+  EventUtils.synthesizeKey("e", { ctrlKey: true });
+  is(inputNode.selectionStart, 1, "caret location after single char input and ctrl-e");
+
+  // Second char input
+  EventUtils.synthesizeKey("2", {});
+  // nav to start/end with up/down keys; verify behaviour using ctrl-p/ctrl-n
+  EventUtils.synthesizeKey("VK_UP", {});
+  is(inputNode.selectionStart, 0, "caret location after two char input and VK_UP");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  is(inputNode.selectionStart, 2, "caret location after two char input and VK_DOWN");
+
+  EventUtils.synthesizeKey("a", { ctrlKey: true });
+  is(inputNode.selectionStart, 0, "move caret to beginning of 2 char input with ctrl-a");
+  EventUtils.synthesizeKey("a", { ctrlKey: true });
+  is(inputNode.selectionStart, 0, "no change of caret location on repeat ctrl-a");
+  EventUtils.synthesizeKey("p", { ctrlKey: true });
+  is(inputNode.selectionStart, 0, "no change of caret location on ctrl-p from beginning of line");
+
+  EventUtils.synthesizeKey("e", { ctrlKey: true });
+  is(inputNode.selectionStart, 2, "move caret to end of 2 char input with ctrl-e");
+  EventUtils.synthesizeKey("e", { ctrlKey: true });
+  is(inputNode.selectionStart, 2, "no change of caret location on repeat ctrl-e");
+  EventUtils.synthesizeKey("n", { ctrlKey: true });
+  is(inputNode.selectionStart, 2, "no change of caret location on ctrl-n from end of line");
+
+  EventUtils.synthesizeKey("p", { ctrlKey: true });
+  is(inputNode.selectionStart, 0, "ctrl-p moves to start of line");
+
+  EventUtils.synthesizeKey("n", { ctrlKey: true });
+  is(inputNode.selectionStart, 2, "ctrl-n moves to end of line");
+}
+
+function testMultiLineInputNavNoHistory() {
+  let lineValues = ["one", "2", "something longer", "", "", "three!"];
+  jsterm.setInputValue("");
+  // simulate shift-return
+  for (let i = 0; i < lineValues.length; i++) {
+    jsterm.setInputValue(inputNode.value + lineValues[i]);
+    EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true });
+  }
+  let inputValue = inputNode.value;
+  is(inputNode.selectionStart, inputNode.selectionEnd);
+  is(inputNode.selectionStart, inputValue.length, "caret at end of multiline input");
+
+  // possibility newline is represented by one ('\r', '\n') or two ('\r\n') chars
+  let newlineString = inputValue.match(/(\r\n?|\n\r?)$/)[0];
+
+  // Ok, test navigating within the multi-line string!
+  EventUtils.synthesizeKey("VK_UP", {});
+  let expectedStringAfterCarat = lineValues[5]+newlineString;
+  is(inputNode.value.slice(inputNode.selectionStart), expectedStringAfterCarat,
+     "up arrow from end of multiline");
+
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  is(inputNode.value.slice(inputNode.selectionStart), "",
+     "down arrow from within multiline");
+
+  // navigate up through input lines
+  EventUtils.synthesizeKey("p", { ctrlKey: true });
+  is(inputNode.value.slice(inputNode.selectionStart), expectedStringAfterCarat,
+     "ctrl-p from end of multiline");
+
+  for (let i = 4; i >= 0; i--) {
+    EventUtils.synthesizeKey("p", { ctrlKey: true });
+    expectedStringAfterCarat = lineValues[i] + newlineString + expectedStringAfterCarat;
+    is(inputNode.value.slice(inputNode.selectionStart), expectedStringAfterCarat,
+       "ctrl-p from within line " + i + " of multiline input");
+  }
+  EventUtils.synthesizeKey("p", { ctrlKey: true });
+  is(inputNode.selectionStart, 0, "reached start of input");
+  is(inputNode.value, inputValue,
+     "no change to multiline input on ctrl-p from beginning of multiline");
+
+  // navigate to end of first line
+  EventUtils.synthesizeKey("e", { ctrlKey: true });
+  let caretPos = inputNode.selectionStart;
+  let expectedStringBeforeCarat = lineValues[0];
+  is(inputNode.value.slice(0, caretPos), expectedStringBeforeCarat,
+     "ctrl-e into multiline input");
+  EventUtils.synthesizeKey("e", { ctrlKey: true });
+  is(inputNode.selectionStart, caretPos,
+     "repeat ctrl-e doesn't change caret position in multiline input");
+
+  // navigate down one line; ctrl-a to the beginning; ctrl-e to end
+  for (let i = 1; i < lineValues.length; i++) {
+    EventUtils.synthesizeKey("n", { ctrlKey: true });
+    EventUtils.synthesizeKey("a", { ctrlKey: true });
+    caretPos = inputNode.selectionStart;
+    expectedStringBeforeCarat += newlineString;
+    is(inputNode.value.slice(0, caretPos), expectedStringBeforeCarat,
+       "ctrl-a to beginning of line " + (i+1) + " in multiline input");
+
+    EventUtils.synthesizeKey("e", { ctrlKey: true });
+    caretPos = inputNode.selectionStart;
+    expectedStringBeforeCarat += lineValues[i];
+    is(inputNode.value.slice(0, caretPos), expectedStringBeforeCarat,
+       "ctrl-e to end of line " + (i+1) + "in multiline input");
+  }
+}
+
+function testNavWithHistory() {
+  // NOTE: Tests does NOT currently define behaviour for ctrl-p/ctrl-n with
+  // caret placed _within_ single line input
+  let values = ['"single line input"',
+                '"a longer single-line input to check caret repositioning"',
+                ['"multi-line"', '"input"', '"here!"'].join("\n"),
+               ];
+  // submit to history
+  for (let i = 0; i < values.length; i++) {
+    jsterm.setInputValue(values[i]);
+    jsterm.execute();
+  }
+  is(inputNode.selectionStart, 0, "caret location at start of empty line");
+
+  EventUtils.synthesizeKey("p", { ctrlKey: true });
+  is(inputNode.selectionStart, values[values.length-1].length,
+     "caret location correct at end of last history input");
+
+  // Navigate backwards history with ctrl-p
+  for (let i = values.length-1; i > 0; i--) {
+    let match = values[i].match(/(\n)/g);
+    if (match) {
+      // multi-line inputs won't update from history unless caret at beginning
+      EventUtils.synthesizeKey("a", { ctrlKey: true });
+      for (let i = 0; i < match.length; i++) {
+        EventUtils.synthesizeKey("p", { ctrlKey: true });
+      }
+      EventUtils.synthesizeKey("p", { ctrlKey: true });
+    } else {
+      // single-line inputs will update from history from end of line
+      EventUtils.synthesizeKey("p", { ctrlKey: true });
+    }
+    is(inputNode.value, values[i-1],
+       "ctrl-p updates inputNode from backwards history values[" + i-1 + "]");
+  }
+  let inputValue = inputNode.value;
+  EventUtils.synthesizeKey("p", { ctrlKey: true });
+  is(inputNode.selectionStart, 0,
+     "ctrl-p at beginning of history moves caret location to beginning of line");
+  is(inputNode.value, inputValue,
+     "no change to input value on ctrl-p from beginning of line");
+
+  // Navigate forwards history with ctrl-n
+  for (let i = 1; i<values.length; i++) {
+    EventUtils.synthesizeKey("n", { ctrlKey: true });
+    is(inputNode.value, values[i],
+       "ctrl-n updates inputNode from forwards history values[" + i + "]");
+    is(inputNode.selectionStart, values[i].length,
+       "caret location correct at end of history input for values[" + i + "]");
+  }
+  EventUtils.synthesizeKey("n", { ctrlKey: true });
+  ok(!inputNode.value, "ctrl-n at end of history updates to empty input");
+
+  // Simulate editing multi-line
+  inputValue = "one\nlinebreak";
+  jsterm.setInputValue(inputValue);
+
+  // Attempt nav within input
+  EventUtils.synthesizeKey("p", { ctrlKey: true });
+  is(inputNode.value, inputValue,
+     "ctrl-p from end of multi-line does not trigger history");
+
+  EventUtils.synthesizeKey("a", { ctrlKey: true });
+  EventUtils.synthesizeKey("p", { ctrlKey: true });
+  is(inputNode.value, values[values.length-1],
+     "ctrl-p from start of multi-line triggers history");
+}
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -373,16 +373,17 @@ WebConsoleFrame.prototype = {
     this.proxy = new WebConsoleConnectionProxy(this, this.owner.target);
 
     let timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT);
     this._connectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     this._connectTimer.initWithCallback(this._connectionTimeout,
                                         timeout, Ci.nsITimer.TYPE_ONE_SHOT);
 
     this.proxy.connect(function() {
+      // Don't complete connection if the connection timed-out.
       if (this._connectTimer) {
         this._connectTimer.cancel();
         this._connectTimer = null;
         this.saveRequestAndResponseBodies = this._saveRequestAndResponseBodies;
         this._onInitComplete();
       }
     }.bind(this));
   },
@@ -3176,31 +3177,82 @@ JSTerm.prototype = {
   /**
    * The inputNode "keypress" event handler.
    *
    * @param nsIDOMEvent aEvent
    */
   keyPress: function JSTF_keyPress(aEvent)
   {
     if (aEvent.ctrlKey) {
+      let inputNode = this.inputNode;
+      let closePopup = false;
       switch (aEvent.charCode) {
         case 97:
           // control-a
-          this.inputNode.setSelectionRange(0, 0);
+          let lineBeginPos = 0;
+          if (this.hasMultilineInput()) {
+            // find index of closest newline <= to cursor
+            for (let i = inputNode.selectionStart-1; i >= 0; i--) {
+              if (inputNode.value.charAt(i) == "\r" ||
+                  inputNode.value.charAt(i) == "\n") {
+                lineBeginPos = i+1;
+                break;
+              }
+            }
+          }
+          inputNode.setSelectionRange(lineBeginPos, lineBeginPos);
           aEvent.preventDefault();
+          closePopup = true;
           break;
         case 101:
           // control-e
-          this.inputNode.setSelectionRange(this.inputNode.value.length,
-                                           this.inputNode.value.length);
+          let lineEndPos = inputNode.value.length;
+          if (this.hasMultilineInput()) {
+            // find index of closest newline >= cursor
+            for (let i = inputNode.selectionEnd; i<lineEndPos; i++) {
+              if (inputNode.value.charAt(i) == "\r" ||
+                  inputNode.value.charAt(i) == "\n") {
+                lineEndPos = i;
+                break;
+              }
+            }
+          }
+          inputNode.setSelectionRange(lineEndPos, lineEndPos);
           aEvent.preventDefault();
           break;
+        case 110:
+          // Control-N differs from down arrow: it ignores autocomplete state.
+          // Note that we preserve the default 'down' navigation within
+          // multiline text.
+          if (Services.appinfo.OS == "Darwin" &&
+              this.canCaretGoNext() &&
+              this.historyPeruse(HISTORY_FORWARD)) {
+            aEvent.preventDefault();
+          }
+          closePopup = true;
+          break;
+        case 112:
+          // Control-P differs from up arrow: it ignores autocomplete state.
+          // Note that we preserve the default 'up' navigation within
+          // multiline text.
+          if (Services.appinfo.OS == "Darwin" &&
+              this.canCaretGoPrevious() &&
+              this.historyPeruse(HISTORY_BACK)) {
+            aEvent.preventDefault();
+          }
+          closePopup = true;
+          break;
         default:
           break;
       }
+      if (closePopup) {
+        if (this.autocompletePopup.isOpen) {
+          this.clearCompletion();
+        }
+      }
       return;
     }
     else if (aEvent.shiftKey &&
         aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
       // shift return
       // TODO: expand the inputNode height by one line
       return;
     }
@@ -3312,16 +3364,27 @@ JSTerm.prototype = {
     else {
       throw new Error("Invalid argument 0");
     }
 
     return true;
   },
 
   /**
+   * Test for multiline input.
+   *
+   * @return boolean
+   *         True if CR or LF found in node value; else false.
+   */
+  hasMultilineInput: function JST_hasMultilineInput()
+  {
+    return /[\r\n]/.test(this.inputNode.value);
+  },
+
+  /**
    * Check if the caret is at a location that allows selecting the previous item
    * in history when the user presses the Up arrow key.
    *
    * @return boolean
    *         True if the caret is at a location that allows selecting the
    *         previous item in history when the user presses the Up arrow key,
    *         otherwise false.
    */
@@ -3978,59 +4041,79 @@ function WebConsoleConnectionProxy(aWebC
   this.owner = aWebConsole;
   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._onLocationChange = this._onLocationChange.bind(this);
+  this._onTabNavigated = this._onTabNavigated.bind(this);
 }
 
 WebConsoleConnectionProxy.prototype = {
   /**
    * The owning Web Console instance.
    *
    * @see WebConsoleFrame
    * @type object
    */
   owner: null,
 
   /**
+   * The target that the console connects to.
+   * @type RemoteTarget
+   */
+  target: null,
+
+  /**
    * The DebuggerClient object.
    *
    * @see DebuggerClient
    * @type object
    */
   client: null,
 
   /**
    * The WebConsoleClient object.
    *
    * @see WebConsoleClient
    * @type object
    */
   webConsoleClient: null,
 
   /**
+   * The TabClient instance we use.
+   * @type object
+   */
+  tabClient: null,
+
+  /**
    * Tells if the connection is established.
    * @type boolean
    */
   connected: false,
 
   /**
    * The WebConsoleActor ID.
    *
    * @private
    * @type string
    */
   _consoleActor: null,
 
   /**
+   * The TabActor ID.
+   *
+   * @private
+   * @type string
+   */
+  _tabActor: null,
+
+  /**
    * Tells if the window.console object of the remote web page is the native
    * object or not.
    * @private
    * @type boolean
    */
   _hasNativeConsoleAPI: false,
 
   /**
@@ -4064,59 +4147,108 @@ WebConsoleConnectionProxy.prototype = {
       client = this.client = new DebuggerClient(transport);
     }
 
     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("locationChange", this._onLocationChange);
+    client.addListener("tabNavigated", this._onTabNavigated);
 
     if (this.target.isRemote) {
-      this._consoleActor = this.target.form.consoleActor;
       if (!this.target.chrome) {
-        this.owner.onLocationChange(this.target.url, this.target.name);
+        // target.form is a TabActor grip
+        this._attachTab(this.target.form, aCallback);
       }
-
-      let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
-                       "FileActivity", "LocationChange"];
-      this.client.attachConsole(this._consoleActor, listeners,
-                                this._onAttachConsole.bind(this, aCallback));
-      return;
+      else {
+        // target.form is a RootActor grip
+        this._consoleActor = this.target.form.consoleActor;
+        this._attachConsole(aCallback);
+      }
     }
-    client.connect(function(aType, aTraits) {
-      client.listTabs(this._onListTabs.bind(this, aCallback));
-    }.bind(this));
+    else {
+      client.connect(function(aType, aTraits) {
+        client.listTabs(this._onListTabs.bind(this, aCallback));
+      }.bind(this));
+    }
   },
 
   /**
    * The "listTabs" response handler.
    *
    * @private
    * @param function [aCallback]
    *        Optional function to invoke once the connection is established.
    * @param object aResponse
    *        The JSON response object received from the server.
    */
   _onListTabs: function WCCP__onListTabs(aCallback, aResponse)
   {
-    let selectedTab = aResponse.tabs[aResponse.selected];
-    if (selectedTab) {
-      this._consoleActor = selectedTab.consoleActor;
-      this.owner.onLocationChange(selectedTab.url, selectedTab.title);
+    if (aResponse.error) {
+      Cu.reportError("listTabs failed: " + aResponse.error + " " +
+                     aResponse.message);
+      return;
     }
-    else {
-      this._consoleActor = aResponse.consoleActor;
+
+    this._attachTab(aResponse.tabs[aResponse.selected], aCallback);
+  },
+
+  /**
+   * Attach to the tab actor.
+   *
+   * @private
+   * @param object aTab
+   *        Grip for the tab to attach to.
+   * @param function aCallback
+   *        Function to invoke when the connection is established.
+   */
+  _attachTab: function WCCP__attachTab(aTab, aCallback)
+  {
+    this._consoleActor = aTab.consoleActor;
+    this._tabActor = aTab.actor;
+    this.owner.onLocationChange(aTab.url, aTab.title);
+    this.client.attachTab(this._tabActor,
+                          this._onAttachTab.bind(this, aCallback));
+  },
+
+  /**
+   * The "attachTab" response handler.
+   *
+   * @private
+   * @param function [aCallback]
+   *        Optional function to invoke once the connection is established.
+   * @param object aResponse
+   *        The JSON response object received from the server.
+   * @param object aTabClient
+   *        The TabClient instance for the attached tab.
+   */
+  _onAttachTab: function WCCP__onAttachTab(aCallback, aResponse, aTabClient)
+  {
+    if (aResponse.error) {
+      Cu.reportError("attachTab failed: " + aResponse.error + " " +
+                     aResponse.message);
+      return;
     }
 
-    this.owner._resetConnectionTimeout();
-
+    this.tabClient = aTabClient;
+    this._attachConsole(aCallback);
+  },
+
+  /**
+   * Attach to the Web Console actor.
+   *
+   * @private
+   * @param function aCallback
+   *        Function to invoke when the connection is established.
+   */
+  _attachConsole: function WCCP__attachConsole(aCallback)
+  {
     let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
-                     "FileActivity", "LocationChange"];
+                     "FileActivity"];
     this.client.attachConsole(this._consoleActor, listeners,
                               this._onAttachConsole.bind(this, aCallback));
   },
 
   /**
    * The "attachConsole" response handler.
    *
    * @private
@@ -4255,32 +4387,35 @@ WebConsoleConnectionProxy.prototype = {
   _onFileActivity: function WCCP__onFileActivity(aType, aPacket)
   {
     if (this.owner && aPacket.from == this._consoleActor) {
       this.owner.handleFileActivity(aPacket.uri);
     }
   },
 
   /**
-   * The "locationChange" message type handler. We redirect any message to
+   * The "tabNavigated" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param string aType
    *        Message type.
    * @param object aPacket
    *        The message received from the server.
    */
-  _onLocationChange: function WCCP__onLocationChange(aType, aPacket)
+  _onTabNavigated: function WCCP__onTabNavigated(aType, aPacket)
   {
-    if (!this.owner || aPacket.from != this._consoleActor) {
+    if (!this.owner || aPacket.from != this._tabActor) {
       return;
     }
 
-    this.owner.onLocationChange(aPacket.uri, aPacket.title);
+    if (aPacket.url) {
+      this.owner.onLocationChange(aPacket.url, aPacket.title);
+    }
+
     if (aPacket.state == "stop" && !aPacket.nativeConsoleAPI) {
       this.owner.logWarningAboutReplacedAPI();
     }
   },
 
   /**
    * Release an object actor.
    *
@@ -4314,43 +4449,52 @@ WebConsoleConnectionProxy.prototype = {
       }
       if (aOnDisconnect) {
         aOnDisconnect();
         aOnDisconnect = null;
       }
     };
 
     let timer = null;
-    if (aOnDisconnect) {
+    let remoteTarget = this.target.isRemote;
+    if (aOnDisconnect && !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("locationChange", this._onLocationChange);
+    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;
 
     try {
-      if (!this.target.isRemote) {
-        this.client.close(onDisconnect);
+      if (!remoteTarget) {
+        client.close(onDisconnect);
       }
     }
     catch (ex) {
       Cu.reportError("Web Console disconnect exception: " + ex);
       Cu.reportError(ex.stack);
       onDisconnect();
     }
 
-    this.client = null;
-    this.webConsoleClient = null;
-    this.connected = false;
-    this.owner = null;
+    if (remoteTarget) {
+      onDisconnect();
+    }
   },
 };
 
 function gSequenceId()
 {
   return gSequenceId.n++;
 }
 gSequenceId.n = 0;
--- a/toolkit/devtools/debugger/dbg-client.jsm
+++ b/toolkit/devtools/debugger/dbg-client.jsm
@@ -475,19 +475,20 @@ DebuggerClient.prototype = {
       }
 
       // Packets that indicate thread state changes get special treatment.
       if (aPacket.type in ThreadStateTypes &&
           aPacket.from in this._threadClients) {
         this._threadClients[aPacket.from]._onThreadState(aPacket);
       }
       // On navigation the server resumes, so the client must resume as well.
-      // We achive that by generating a fake resumption packet that triggers
+      // We achieve that by generating a fake resumption packet that triggers
       // the client's thread state change listeners.
-      if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
+      if (this.activeThread &&
+          aPacket.type == UnsolicitedNotifications.tabNavigated &&
           aPacket.from in this._tabClients) {
         let resumption = { from: this.activeThread._actor, type: "resumed" };
         this.activeThread._onThreadState(resumption);
       }
       this.notify(aPacket.type, aPacket);
 
       if (onResponse) {
         onResponse(aPacket);
--- a/toolkit/devtools/debugger/server/dbg-browser-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-browser-actors.js
@@ -349,34 +349,61 @@ BrowserTabActor.prototype = {
    */
   removeFromParentPool: function BTA_removeFromParentPool(aActor) {
     this.conn.removeActor(aActor);
   },
 
   // A constant prefix that will be used to form the actor ID by the server.
   actorPrefix: "tab",
 
+  /**
+   * Getter for the tab title.
+   * @return string
+   *         Tab title.
+   */
+  get title() {
+    let title = this.browser.contentTitle;
+    // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
+    // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
+    // as the title.
+    if (!title && this._tabbrowser) {
+      title = this._tabbrowser
+                  ._getTabForContentWindow(this.contentWindow).label;
+    }
+    return title;
+  },
+
+  /**
+   * Getter for the tab URL.
+   * @return string
+   *         Tab URL.
+   */
+  get url() {
+    return this.browser.currentURI.spec;
+  },
+
+  /**
+   * Getter for the tab content window.
+   * @return nsIDOMWindow
+   *         Tab content window.
+   */
+  get contentWindow() {
+    return this.browser.contentWindow;
+  },
+
   grip: function BTA_grip() {
     dbg_assert(!this.exited,
                "grip() shouldn't be called on exited browser actor.");
     dbg_assert(this.actorID,
                "tab should have an actorID.");
 
-    let title = this.browser.contentTitle;
-    // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
-    // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
-    // as the title.
-    if (!title && this._tabbrowser) {
-      title = this._tabbrowser
-                  ._getTabForContentWindow(this.browser.contentWindow).label;
-    }
     let response = {
       actor: this.actorID,
-      title: title,
-      url: this.browser.currentURI.spec
+      title: this.title,
+      url: this.url,
     };
 
     // Walk over tab actors added by extensions and add them to a new ActorPool.
     let actorPool = new ActorPool(this.conn);
     this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
     if (!actorPool.isEmpty()) {
       this._tabActorPool = actorPool;
       this.conn.addActorPool(this._tabActorPool);
@@ -386,20 +413,16 @@ BrowserTabActor.prototype = {
     return response;
   },
 
   /**
    * Called when the actor is removed from the connection.
    */
   disconnect: function BTA_disconnect() {
     this._detach();
-
-    if (this._progressListener) {
-      this._progressListener.destroy();
-    }
     this._extraActors = null;
   },
 
   /**
    * Called by the root actor when the underlying tab is closed.
    */
   exit: function BTA_exit() {
     if (this.exited) {
@@ -407,19 +430,16 @@ BrowserTabActor.prototype = {
     }
 
     if (this.attached) {
       this._detach();
       this.conn.send({ from: this.actorID,
                        type: "tabDetached" });
     }
 
-    if (this._progressListener) {
-      this._progressListener.destroy();
-    }
     this._browser = null;
     this._tabbrowser = null;
   },
 
   /**
    * Does the actual work of attching to a tab.
    */
   _attach: function BTA_attach() {
@@ -450,17 +470,17 @@ BrowserTabActor.prototype = {
    * up the content window for debugging.
    */
   _pushContext: function BTA_pushContext() {
     dbg_assert(!this._contextPool, "Can't push multiple contexts");
 
     this._contextPool = new ActorPool(this.conn);
     this.conn.addActorPool(this._contextPool);
 
-    this.threadActor = new ThreadActor(this, this.browser.contentWindow.wrappedJSObject);
+    this.threadActor = new ThreadActor(this, this.contentWindow.wrappedJSObject);
     this._contextPool.addActor(this.threadActor);
   },
 
   /**
    * Exits the current thread actor and removes the context-lifetime actor pool.
    * The content window is no longer being debugged after this call.
    */
   _popContext: function BTA_popContext() {
@@ -475,26 +495,30 @@ BrowserTabActor.prototype = {
   /**
    * Does the actual work of detaching from a tab.
    */
   _detach: function BTA_detach() {
     if (!this.attached) {
       return;
     }
 
+    if (this._progressListener) {
+      this._progressListener.destroy();
+    }
+
     this.browser.removeEventListener("DOMWindowCreated", this._onWindowCreated, true);
     this.browser.removeEventListener("pageshow", this._onWindowCreated, true);
 
     this._popContext();
 
     // Shut down actors that belong to this tab's pool.
     this.conn.removeActorPool(this._tabPool);
     this._tabPool = null;
     if (this._tabActorPool) {
-      this.conn.removeActorPool(this._tabActorPool);
+      this.conn.removeActorPool(this._tabActorPool, true);
       this._tabActorPool = null;
     }
 
     this._attached = false;
   },
 
   // Protocol Request Handlers
 
@@ -510,47 +534,43 @@ BrowserTabActor.prototype = {
 
   onDetach: function BTA_onDetach(aRequest) {
     if (!this.attached) {
       return { error: "wrongState" };
     }
 
     this._detach();
 
-    if (this._progressListener) {
-      this._progressListener.destroy();
-    }
-
     return { type: "detached" };
   },
 
   /**
    * Prepare to enter a nested event loop by disabling debuggee events.
    */
   preNest: function BTA_preNest() {
     if (!this.browser) {
       // The tab is already closed.
       return;
     }
-    let windowUtils = this.browser.contentWindow
+    let windowUtils = this.contentWindow
                           .QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
     windowUtils.suppressEventHandling(true);
     windowUtils.suspendTimeouts();
   },
 
   /**
    * Prepare to exit a nested event loop by enabling debuggee events.
    */
   postNest: function BTA_postNest(aNestData) {
     if (!this.browser) {
       // The tab is already closed.
       return;
     }
-    let windowUtils = this.browser.contentWindow
+    let windowUtils = this.contentWindow
                           .QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
     windowUtils.resumeTimeouts();
     windowUtils.suppressEventHandling(false);
     if (this._pendingNavigation) {
       this._pendingNavigation.resume();
       this._pendingNavigation = null;
     }
@@ -567,31 +587,45 @@ BrowserTabActor.prototype = {
       if (evt.type == "pageshow" && !evt.persisted) {
         return;
       }
       if (this._attached) {
         this.threadActor.clearDebuggees();
         if (this.threadActor.dbg) {
           this.threadActor.dbg.enabled = true;
         }
-        if (this._progressListener) {
-          delete this._progressListener._needsTabNavigated;
-        }
-        this.conn.send({ from: this.actorID, type: "tabNavigated",
-                         url: this.browser.contentDocument.URL });
       }
     }
 
     if (this._attached) {
       this.threadActor.global = evt.target.defaultView.wrappedJSObject;
       if (this.threadActor.attached) {
         this.threadActor.findGlobals();
       }
     }
-  }
+  },
+
+  /**
+   * Tells if the window.console object is native or overwritten by script in
+   * the page.
+   *
+   * @param nsIDOMWindow aWindow
+   *        The window object you want to check.
+   * @return boolean
+   *         True if the window.console object is native, or false otherwise.
+   */
+  hasNativeConsoleAPI: function BTA_hasNativeConsoleAPI(aWindow) {
+    let isNative = false;
+    try {
+      let console = aWindow.wrappedJSObject.console;
+      isNative = "__mozillaConsole__" in console;
+    }
+    catch (ex) { }
+    return isNative;
+  },
 };
 
 /**
  * The request types this actor can handle.
  */
 BrowserTabActor.prototype.requestTypes = {
   "attach": BrowserTabActor.prototype.onAttach,
   "detach": BrowserTabActor.prototype.onDetach
@@ -617,49 +651,65 @@ DebuggerProgressListener.prototype = {
     let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START;
     let isStop = aFlag & Ci.nsIWebProgressListener.STATE_STOP;
     let isDocument = aFlag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
     let isNetwork = aFlag & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
     let isRequest = aFlag & Ci.nsIWebProgressListener.STATE_IS_REQUEST;
     let isWindow = aFlag & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
 
     // Skip non-interesting states.
-    if (isStart && isDocument && isRequest && isNetwork) {
+    if (!isWindow || !isNetwork ||
+        aProgress.DOMWindow != this._tabActor.contentWindow) {
+      return;
+    }
+
+    if (isStart && aRequest instanceof Ci.nsIChannel) {
       // If the request is about to happen in a new window, we are not concerned
       // about the request.
-      if (aProgress.DOMWindow != this._tabActor.browser.contentWindow) {
-        return;
+
+      // Proceed normally only if the debuggee is not paused.
+      if (this._tabActor.threadActor.state == "paused") {
+        aRequest.suspend();
+        this._tabActor.threadActor.onResume();
+        this._tabActor.threadActor.dbg.enabled = false;
+        this._tabActor._pendingNavigation = aRequest;
       }
 
-      // If the debuggee is not paused, then proceed normally.
-      if (this._tabActor.threadActor.state != "paused") {
-        return;
-      }
-
-      aRequest.suspend();
-      this._tabActor.threadActor.onResume();
-      this._tabActor.threadActor.dbg.enabled = false;
-      this._tabActor._pendingNavigation = aRequest;
-      this._needsTabNavigated = true;
-    } else if (isStop && isWindow && isNetwork && this._needsTabNavigated) {
-      delete this._needsTabNavigated;
-      this._tabActor.threadActor.dbg.enabled = true;
       this._tabActor.conn.send({
         from: this._tabActor.actorID,
         type: "tabNavigated",
-        url: this._tabActor.browser.contentDocument.URL
+        url: aRequest.URI.spec,
+        title: "",
+        nativeConsoleAPI: true,
+        state: "start",
       });
+    } else if (isStop) {
+      if (this._tabActor.threadActor.state == "running") {
+        this._tabActor.threadActor.dbg.enabled = true;
+      }
 
-      this.destroy();
+      let window = this._tabActor.contentWindow;
+      this._tabActor.conn.send({
+        from: this._tabActor.actorID,
+        type: "tabNavigated",
+        url: this._tabActor.url,
+        title: this._tabActor.title,
+        nativeConsoleAPI: this._tabActor.hasNativeConsoleAPI(window),
+        state: "stop",
+      });
     }
   },
 
   /**
    * Destroy the progress listener instance.
    */
   destroy: function DPL_destroy() {
     if (this._tabActor._tabbrowser.removeProgressListener) {
-      this._tabActor._tabbrowser.removeProgressListener(this);
+      try {
+        this._tabActor._tabbrowser.removeProgressListener(this);
+      } catch (ex) {
+        // This can throw during browser shutdown.
+      }
     }
     this._tabActor._progressListener = null;
     this._tabActor = null;
   }
 };
--- a/toolkit/devtools/debugger/server/dbg-server.js
+++ b/toolkit/devtools/debugger/server/dbg-server.js
@@ -536,21 +536,30 @@ DebuggerServerConnection.prototype = {
    * Add a map of actor IDs to the connection.
    */
   addActorPool: function DSC_addActorPool(aActorPool) {
     this._extraPools.push(aActorPool);
   },
 
   /**
    * Remove a previously-added pool of actors to the connection.
+   *
+   * @param ActorPool aActorPool
+   *        The ActorPool instance you want to remove.
+   * @param boolean aCleanup
+   *        True if you want to disconnect each actor from the pool, false
+   *        otherwise.
    */
-  removeActorPool: function DSC_removeActorPool(aActorPool) {
+  removeActorPool: function DSC_removeActorPool(aActorPool, aCleanup) {
     let index = this._extraPools.lastIndexOf(aActorPool);
     if (index > -1) {
-      this._extraPools.splice(index, 1);
+      let pool = this._extraPools.splice(index, 1);
+      if (aCleanup) {
+        pool.map(function(p) { p.cleanup(); });
+      }
     }
   },
 
   /**
    * Add an actor to the default actor pool for this connection.
    */
   addActor: function DSC_addActor(aActor) {
     this._actorPool.addActor(aActor);
--- a/toolkit/devtools/webconsole/dbg-webconsole-actors.js
+++ b/toolkit/devtools/webconsole/dbg-webconsole-actors.js
@@ -155,33 +155,17 @@ WebConsoleActor.prototype =
 
   actorPrefix: "console",
 
   grip: function WCA_grip()
   {
     return { actor: this.actorID };
   },
 
-  /**
-   * Tells if the window.console object is native or overwritten by script in
-   * the page.
-   *
-   * @return boolean
-   *         True if the window.console object is native, or false otherwise.
-   */
-  hasNativeConsoleAPI: function WCA_hasNativeConsoleAPI()
-  {
-    let isNative = false;
-    try {
-      let consoleObject = WebConsoleUtils.unwrap(this.window).console;
-      isNative = "__mozillaConsole__" in consoleObject;
-    }
-    catch (ex) { }
-    return isNative;
-  },
+  hasNativeConsoleAPI: BrowserTabActor.prototype.hasNativeConsoleAPI,
 
   /**
    * Destroy the current WebConsoleActor instance.
    */
   disconnect: function WCA_disconnect()
   {
     if (this.pageErrorListener) {
       this.pageErrorListener.destroy();
@@ -331,30 +315,21 @@ WebConsoleActor.prototype =
           if (!this.consoleProgressListener) {
             this.consoleProgressListener =
               new ConsoleProgressListener(this.window, this);
           }
           this.consoleProgressListener.startMonitor(this.consoleProgressListener.
                                                     MONITOR_FILE_ACTIVITY);
           startedListeners.push(listener);
           break;
-        case "LocationChange":
-          if (!this.consoleProgressListener) {
-            this.consoleProgressListener =
-              new ConsoleProgressListener(this.window, this);
-          }
-          this.consoleProgressListener.startMonitor(this.consoleProgressListener.
-                                                    MONITOR_LOCATION_CHANGE);
-          startedListeners.push(listener);
-          break;
       }
     }
     return {
       startedListeners: startedListeners,
-      nativeConsoleAPI: this.hasNativeConsoleAPI(),
+      nativeConsoleAPI: this.hasNativeConsoleAPI(this.window),
     };
   },
 
   /**
    * Handler for the "stopListeners" request.
    *
    * @param object aRequest
    *        The JSON request object received from the Web Console client.
@@ -365,17 +340,17 @@ WebConsoleActor.prototype =
   onStopListeners: function WCA_onStopListeners(aRequest)
   {
     let stoppedListeners = [];
 
     // If no specific listeners are requested to be detached, we stop all
     // listeners.
     let toDetach = aRequest.listeners ||
                    ["PageError", "ConsoleAPI", "NetworkActivity",
-                    "FileActivity", "LocationChange"];
+                    "FileActivity"];
 
     while (toDetach.length > 0) {
       let listener = toDetach.shift();
       switch (listener) {
         case "PageError":
           if (this.pageErrorListener) {
             this.pageErrorListener.destroy();
             this.pageErrorListener = null;
@@ -398,23 +373,16 @@ WebConsoleActor.prototype =
           break;
         case "FileActivity":
           if (this.consoleProgressListener) {
             this.consoleProgressListener.stopMonitor(this.consoleProgressListener.
                                                      MONITOR_FILE_ACTIVITY);
           }
           stoppedListeners.push(listener);
           break;
-        case "LocationChange":
-          if (this.consoleProgressListener) {
-            this.consoleProgressListener.stopMonitor(this.consoleProgressListener.
-                                                     MONITOR_LOCATION_CHANGE);
-          }
-          stoppedListeners.push(listener);
-          break;
       }
     }
 
     return { stoppedListeners: stoppedListeners };
   },
 
   /**
    * Handler for the "getCachedMessages" request. This method sends the cached
@@ -732,44 +700,16 @@ WebConsoleActor.prototype =
     let packet = {
       from: this.actorID,
       type: "fileActivity",
       uri: aFileURI,
     };
     this.conn.send(packet);
   },
 
-  /**
-   * Handler for location changes. This method sends the new browser location
-   * to the remote Web Console client.
-   *
-   * @see ConsoleProgressListener
-   * @param string aState
-   *        Tells the location change state:
-   *        - "start" means a load has begun.
-   *        - "stop" means load completed.
-   * @param string aURI
-   *        The new browser URI.
-   * @param string aTitle
-   *        The new page title URI.
-   */
-  onLocationChange: function WCA_onLocationChange(aState, aURI, aTitle)
-  {
-    // TODO: Bug 792062 - Make the tabNavigated notification reusable by the Web Console
-    let packet = {
-      from: this.actorID,
-      type: "locationChange",
-      uri: aURI,
-      title: aTitle,
-      state: aState,
-      nativeConsoleAPI: this.hasNativeConsoleAPI(),
-    };
-    this.conn.send(packet);
-  },
-
   //////////////////
   // End of event handlers for various listeners.
   //////////////////
 
   /**
    * Prepare a message from the console API to be sent to the remote Web Console
    * instance.
    *