merge last green changeset from fx-team to m-c
authorRob Campbell <rcampbell@mozilla.com>
Fri, 24 Aug 2012 14:57:23 -0400
changeset 103336 35431a5588e0186b2a176d51927f9962d47e0044
parent 103333 f2146a6c104e87e943d08c89b89d1dbac6f544f9 (current diff)
parent 103335 73622d728d4cef8a8550c866f1d814683151b284 (diff)
child 103337 45204dc49ac2e75e33170539fe251e48507cefcc
push id13945
push userryanvm@gmail.com
push dateFri, 24 Aug 2012 20:18:14 +0000
treeherdermozilla-inbound@b3c861bd1e2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone17.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 last green changeset from fx-team to m-c
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -180,31 +180,24 @@ let DebuggerController = {
     this.client.close();
 
     this.client = null;
     this.tabClient = null;
     this.activeThread = null;
   },
 
   /**
-   * Starts debugging the current tab. This function is called on each location
-   * change in this tab.
+   * This function is called on each location change in this tab.
    */
   _onTabNavigated: function DC__onTabNavigated(aNotification, aPacket) {
-    let client = this.client;
-
-    client.activeThread.detach(function() {
-      client.activeTab.detach(function() {
-        client.listTabs(function(aResponse) {
-          let tab = aResponse.tabs[aResponse.selected];
-          this._startDebuggingTab(client, tab);
-          this.dispatchEvent("Debugger:Connecting");
-        }.bind(this));
-      }.bind(this));
-    }.bind(this));
+    DebuggerController.ThreadState._handleTabNavigation(function() {
+      DebuggerController.StackFrames._handleTabNavigation(function() {
+        DebuggerController.SourceScripts._handleTabNavigation();
+      });
+    });
   },
 
   /**
    * Stops debugging the current tab.
    */
   _onTabDetached: function DC__onTabDetached() {
     this.dispatchEvent("Debugger:Close");
   },
@@ -322,17 +315,17 @@ ThreadState.prototype = {
    * @param function aCallback
    *        The next function in the initialization sequence.
    */
   connect: function TS_connect(aCallback) {
     this.activeThread.addListener("paused", this._update);
     this.activeThread.addListener("resumed", this._update);
     this.activeThread.addListener("detached", this._update);
 
-    this._update();
+    this._handleTabNavigation();
 
     aCallback && aCallback();
   },
 
   /**
    * Disconnect from the client.
    */
   disconnect: function TS_disconnect() {
@@ -340,16 +333,25 @@ ThreadState.prototype = {
       return;
     }
     this.activeThread.removeListener("paused", this._update);
     this.activeThread.removeListener("resumed", this._update);
     this.activeThread.removeListener("detached", this._update);
   },
 
   /**
+   * Handles any initialization on a tab navigation event issued by the client.
+   */
+  _handleTabNavigation: function TS__handleTabNavigation(aCallback) {
+    DebuggerView.StackFrames.updateState(this.activeThread.state);
+
+    aCallback && aCallback();
+  },
+
+  /**
    * Update the UI after a thread state change.
    */
   _update: function TS__update(aEvent) {
     DebuggerView.StackFrames.updateState(this.activeThread.state);
   }
 };
 
 /**
@@ -392,55 +394,64 @@ StackFrames.prototype = {
   /**
    * Watch the given thread client.
    *
    * @param function aCallback
    *        The next function in the initialization sequence.
    */
   connect: function SF_connect(aCallback) {
     window.addEventListener("Debugger:FetchedVariables", this._onFetchedVars, false);
-
     this.activeThread.addListener("paused", this._onPaused);
     this.activeThread.addListener("resumed", this._onResume);
     this.activeThread.addListener("framesadded", this._onFrames);
     this.activeThread.addListener("framescleared", this._onFramesCleared);
 
+    this._handleTabNavigation();
     this.updatePauseOnExceptions(this.pauseOnExceptions);
 
     aCallback && aCallback();
   },
 
   /**
    * Disconnect from the client.
    */
   disconnect: function SF_disconnect() {
-    window.removeEventListener("Debugger:FetchedVariables", this._onFetchedVars, false);
-
     if (!this.activeThread) {
       return;
     }
+    window.removeEventListener("Debugger:FetchedVariables", this._onFetchedVars, false);
     this.activeThread.removeListener("paused", this._onPaused);
     this.activeThread.removeListener("resumed", this._onResume);
     this.activeThread.removeListener("framesadded", this._onFrames);
     this.activeThread.removeListener("framescleared", this._onFramesCleared);
   },
 
   /**
+   * Handles any initialization on a tab navigation event issued by the client.
+   */
+  _handleTabNavigation: function SF__handleTabNavigation(aCallback) {
+    // Nothing to do here yet.
+
+    aCallback && aCallback();
+  },
+
+  /**
    * Handler for the thread client's paused notification.
    *
    * @param string aEvent
    *        The name of the notification ("paused" in this case).
    * @param object aPacket
    *        The response packet.
    */
   _onPaused: function SF__onPaused(aEvent, aPacket) {
     // In case the pause was caused by an exception, store the exception value.
     if (aPacket.why.type == "exception") {
       this.exception = aPacket.why.exception;
     }
+
     this.activeThread.fillFrames(this.pageSize);
     DebuggerView.editor.focus();
   },
 
   /**
    * Handler for the thread client's resumed notification.
    */
   _onResume: function SF__onResume() {
@@ -829,17 +840,16 @@ StackFrames.prototype = {
 
 /**
  * Keeps the source script list up-to-date, using the thread client's
  * source script cache.
  */
 function SourceScripts() {
   this._onNewScript = this._onNewScript.bind(this);
   this._onScriptsAdded = this._onScriptsAdded.bind(this);
-  this._onScriptsCleared = this._onScriptsCleared.bind(this);
   this._onShowScript = this._onShowScript.bind(this);
   this._onLoadSource = this._onLoadSource.bind(this);
   this._onLoadSourceFinished = this._onLoadSourceFinished.bind(this);
 }
 
 SourceScripts.prototype = {
 
   /**
@@ -864,91 +874,107 @@ SourceScripts.prototype = {
   /**
    * Watch the given thread client.
    *
    * @param function aCallback
    *        The next function in the initialization sequence.
    */
   connect: function SS_connect(aCallback) {
     window.addEventListener("Debugger:LoadSource", this._onLoadSource, false);
-
     this.debuggerClient.addListener("newScript", this._onNewScript);
-    this.activeThread.addListener("scriptsadded", this._onScriptsAdded);
-    this.activeThread.addListener("scriptscleared", this._onScriptsCleared);
 
-    this._clearLabelsCache();
-    this._onScriptsCleared();
-
-    // Retrieve the list of scripts known to the server from before the client
-    // was ready to handle new script notifications.
-    this.activeThread.fillScripts();
+    this._handleTabNavigation();
 
     aCallback && aCallback();
   },
 
   /**
    * Disconnect from the client.
    */
-  disconnect: function TS_disconnect() {
-    window.removeEventListener("Debugger:LoadSource", this._onLoadSource, false);
-
+  disconnect: function SS_disconnect() {
     if (!this.activeThread) {
       return;
     }
+    window.removeEventListener("Debugger:LoadSource", this._onLoadSource, false);
     this.debuggerClient.removeListener("newScript", this._onNewScript);
-    this.activeThread.removeListener("scriptsadded", this._onScriptsAdded);
-    this.activeThread.removeListener("scriptscleared", this._onScriptsCleared);
+  },
+
+  /**
+   * Handles any initialization on a tab navigation event issued by the client.
+   */
+  _handleTabNavigation: function SS__handleTabNavigation(aCallback) {
+    this._clearLabelsCache();
+    this._onScriptsCleared();
+
+    // Retrieve the list of scripts known to the server from before the client
+    // was ready to handle new script notifications.
+    this.activeThread.getScripts(this._onScriptsAdded);
+
+    aCallback && aCallback();
   },
 
   /**
    * Handler for the debugger client's unsolicited newScript notification.
    */
   _onNewScript: function SS__onNewScript(aNotification, aPacket) {
     // Ignore scripts generated from 'clientEvaluate' packets.
     if (aPacket.url == "debugger eval code") {
       return;
     }
 
     this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true);
 
-    // Select the script if it's the preferred one.
+    let preferredScriptUrl = DebuggerView.Scripts.preferredScriptUrl;
+
+    // Select this script if it's the preferred one.
     if (aPacket.url === DebuggerView.Scripts.preferredScriptUrl) {
       DebuggerView.Scripts.selectScript(aPacket.url);
     }
+    // ..or the first entry if there's not one selected yet.
+    else if (!DebuggerView.Scripts.selected) {
+      DebuggerView.Scripts.selectIndex(0);
+    }
 
     // If there are any stored breakpoints for this script, display them again,
     // both in the editor and the pane.
     for each (let breakpoint in DebuggerController.Breakpoints.store) {
       if (breakpoint.location.url == aPacket.url) {
         DebuggerController.Breakpoints.displayBreakpoint(breakpoint);
       }
     }
+
+    DebuggerController.dispatchEvent("Debugger:AfterNewScript");
   },
 
   /**
-   * Handler for the thread client's scriptsadded notification.
+   * Callback for the getScripts() method.
    */
-  _onScriptsAdded: function SS__onScriptsAdded() {
-    for each (let script in this.activeThread.cachedScripts) {
+  _onScriptsAdded: function SS__onScriptsAdded(aResponse) {
+    for each (let script in aResponse.scripts) {
       this._addScript(script, false);
     }
     DebuggerView.Scripts.commitScripts();
     DebuggerController.Breakpoints.updatePaneBreakpoints();
 
-    // Select the preferred script if one exists, the first entry otherwise.
     let preferredScriptUrl = DebuggerView.Scripts.preferredScriptUrl;
+
+    // Select the preferred script if it exists and was part of the response.
     if (preferredScriptUrl && DebuggerView.Scripts.contains(preferredScriptUrl)) {
       DebuggerView.Scripts.selectScript(preferredScriptUrl);
-    } else {
+    }
+    // ..or the first entry if there's not one selected yet.
+    else if (!DebuggerView.Scripts.selected) {
       DebuggerView.Scripts.selectIndex(0);
     }
+
+    DebuggerController.dispatchEvent("Debugger:AfterScriptsAdded");
   },
 
   /**
-   * Handler for the thread client's scriptscleared notification.
+   * Called during navigation to clear the currently-loaded scripts.
    */
   _onScriptsCleared: function SS__onScriptsCleared() {
     DebuggerView.Scripts.empty();
     DebuggerView.Breakpoints.emptyText();
     DebuggerView.editor.setText("");
   },
 
   /**
--- a/browser/devtools/debugger/test/browser_dbg_bfcache.js
+++ b/browser/devtools/debugger/test/browser_dbg_bfcache.js
@@ -35,50 +35,62 @@ function testInitialLoad() {
   });
 
   gDebuggee.firstCall();
 }
 
 function testLocationChange()
 {
   gDebugger.DebuggerController.activeThread.resume(function() {
-    gDebugger.DebuggerController.client.addOneTimeListener("tabAttached", function(aEvent, aPacket) {
-      ok(true, "Successfully reattached to the tab again.");
-      gDebugger.DebuggerController.client.addOneTimeListener("resumed", function(aEvent, aPacket) {
+    gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+      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();
           testBack();
         });
       });
     });
     content.location = STACK_URL;
   });
 }
 
 function testBack()
 {
-  gDebugger.DebuggerController.client.addOneTimeListener("tabAttached", function(aEvent, aPacket) {
-    ok(true, "Successfully reattached to the tab after going back.");
-    gDebugger.DebuggerController.client.addOneTimeListener("resumed", function(aEvent, aPacket) {
+  gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+    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();
         testForward();
       });
     });
   });
 
   info("Going back.");
   content.history.back();
 }
 
 function testForward()
 {
-  gDebugger.DebuggerController.client.addOneTimeListener("tabAttached", function(aEvent, aPacket) {
-    ok(true, "Successfully reattached to the tab after going forward.");
-    gDebugger.DebuggerController.client.addOneTimeListener("resumed", function(aEvent, aPacket) {
+  gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
+    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();
         closeDebuggerAndFinish();
       });
     });
   });
 
   info("Going forward.");
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-blank.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-blank.js
@@ -52,35 +52,34 @@ function testSimpleCall() {
   gDebuggee.simpleCall();
 }
 
 function testLocationChange()
 {
   gDebugger.DebuggerController.activeThread.resume(function() {
     gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
       ok(true, "tabNavigated event was fired.");
-      gDebugger.DebuggerController.client.addOneTimeListener("tabAttached", function(aEvent, aPacket) {
-        ok(true, "Successfully reattached to the tab again.");
+      info("Still attached to the tab.");
 
-        // Wait for the initial resume...
-        gDebugger.gClient.addOneTimeListener("resumed", function() {
-          is(gDebugger.DebuggerView.Scripts.selected, null,
-            "There should be no selected script.");
-          is(gDebugger.editor.getText().length, 0,
-            "The source editor not have any text displayed.");
+      gDebugger.addEventListener("Debugger:AfterScriptsAdded", function _onEvent(aEvent) {
+        gDebugger.removeEventListener(aEvent.type, _onEvent);
 
-          let menulist = gDebugger.DebuggerView.Scripts._scripts;
-          let noScripts = gDebugger.L10N.getStr("noScriptsText");
-          is(menulist.getAttribute("label"), noScripts,
-            "The menulist should display a notice that there are no scripts availalble.");
-          is(menulist.getAttribute("tooltiptext"), "",
-            "The menulist shouldn't have any tooltip text attributed when there are no scripts available.");
+        is(gDebugger.DebuggerView.Scripts.selected, null,
+          "There should be no selected script.");
+        is(gDebugger.editor.getText().length, 0,
+          "The source editor not have any text displayed.");
 
-          closeDebuggerAndFinish();
-        });
+        let menulist = gDebugger.DebuggerView.Scripts._scripts;
+        let noScripts = gDebugger.L10N.getStr("noScriptsText");
+        is(menulist.getAttribute("label"), noScripts,
+          "The menulist should display a notice that there are no scripts availalble.");
+        is(menulist.getAttribute("tooltiptext"), "",
+          "The menulist shouldn't have any tooltip text attributed when there are no scripts available.");
+
+        closeDebuggerAndFinish();
       });
     });
     content.location = "about:blank";
   });
 }
 
 registerCleanupFunction(function() {
   removeTab(gTab);
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-new.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-new.js
@@ -52,35 +52,34 @@ function testSimpleCall() {
   gDebuggee.simpleCall();
 }
 
 function testLocationChange()
 {
   gDebugger.DebuggerController.activeThread.resume(function() {
     gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
       ok(true, "tabNavigated event was fired.");
-      gDebugger.DebuggerController.client.addOneTimeListener("tabAttached", function(aEvent, aPacket) {
-        ok(true, "Successfully reattached to the tab again.");
+      info("Still attached to the tab.");
 
-        // Wait for the initial resume...
-        gDebugger.gClient.addOneTimeListener("resumed", function() {
-          isnot(gDebugger.DebuggerView.Scripts.selected, null,
-            "There should be a selected script.");
-          isnot(gDebugger.editor.getText().length, 0,
-            "The source editor should have some text displayed.");
+      gDebugger.addEventListener("Debugger:AfterScriptsAdded", function _onEvent(aEvent) {
+        gDebugger.removeEventListener(aEvent.type, _onEvent);
 
-          let menulist = gDebugger.DebuggerView.Scripts._scripts;
-          let noScripts = gDebugger.L10N.getStr("noScriptsText");
-          isnot(menulist.getAttribute("label"), noScripts,
-            "The menulist should not display a notice that there are no scripts availalble.");
-          isnot(menulist.getAttribute("tooltiptext"), "",
-            "The menulist should have a tooltip text attributed.");
+        isnot(gDebugger.DebuggerView.Scripts.selected, null,
+          "There should be a selected script.");
+        isnot(gDebugger.editor.getText().length, 0,
+          "The source editor should have some text displayed.");
 
-          closeDebuggerAndFinish();
-        });
+        let menulist = gDebugger.DebuggerView.Scripts._scripts;
+        let noScripts = gDebugger.L10N.getStr("noScriptsText");
+        isnot(menulist.getAttribute("label"), noScripts,
+          "The menulist should not display a notice that there are no scripts availalble.");
+        isnot(menulist.getAttribute("tooltiptext"), "",
+          "The menulist should have a tooltip text attributed.");
+
+        closeDebuggerAndFinish();
       });
     });
     content.location = EXAMPLE_URL + "browser_dbg_iframes.html";
   });
 }
 
 registerCleanupFunction(function() {
   removeTab(gTab);
--- a/browser/devtools/debugger/test/browser_dbg_location-changes.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes.js
@@ -47,21 +47,19 @@ function testSimpleCall() {
   gDebuggee.simpleCall();
 }
 
 function testLocationChange()
 {
   gDebugger.DebuggerController.activeThread.resume(function() {
     gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
       ok(true, "tabNavigated event was fired.");
-      gDebugger.DebuggerController.client.addOneTimeListener("tabAttached", function(aEvent, aPacket) {
-        ok(true, "Successfully reattached to the tab again.");
+      info("Still attached to the tab.");
 
-        closeDebuggerAndFinish();
-      });
+      closeDebuggerAndFinish();
     });
     content.location = TAB1_URL;
   });
 }
 
 registerCleanupFunction(function() {
   removeTab(gTab);
   gPane = null;
--- a/toolkit/devtools/debugger/dbg-client.jsm
+++ b/toolkit/devtools/debugger/dbg-client.jsm
@@ -173,16 +173,27 @@ const ThreadStateTypes = {
  */
 const UnsolicitedNotifications = {
   "newScript": "newScript",
   "tabDetached": "tabDetached",
   "tabNavigated": "tabNavigated"
 };
 
 /**
+ * Set of pause types that are sent by the server and not as an immediate
+ * response to a client request.
+ */
+const UnsolicitedPauses = {
+  "resumeLimit": "resumeLimit",
+  "debuggerStatement": "debuggerStatement",
+  "breakpoint": "breakpoint",
+  "watchpoint": "watchpoint"
+};
+
+/**
  * Set of debug protocol request types that specify the protocol request being
  * sent to the server.
  */
 const DebugProtocolTypes = {
   "assign": "assign",
   "attach": "attach",
   "clientEvaluate": "clientEvaluate",
   "delete": "delete",
@@ -395,28 +406,38 @@ DebuggerClient.prototype = {
     try {
       if (!aPacket.from) {
         Cu.reportError("Server did not specify an actor, dropping packet: " +
                        JSON.stringify(aPacket));
         return;
       }
 
       let onResponse;
-      // Don't count unsolicited notifications as responses.
+      // Don't count unsolicited notifications or pauses as responses.
       if (aPacket.from in this._activeRequests &&
-          !(aPacket.type in UnsolicitedNotifications)) {
+          !(aPacket.type in UnsolicitedNotifications) &&
+          !(aPacket.type == ThreadStateTypes.paused &&
+            aPacket.why.type in UnsolicitedPauses)) {
         onResponse = this._activeRequests[aPacket.from].onResponse;
         delete this._activeRequests[aPacket.from];
       }
 
-      // paused/resumed/detached get special treatment...
+      // 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
+      // the client's thread state change listeners.
+      if (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);
       }
     } catch(ex) {
       dumpn("Error handling response: " + ex + " - stack:\n" + ex.stack);
       Cu.reportError(ex);
@@ -720,22 +741,30 @@ ThreadClient.prototype = {
    * @param aOnResponse integer
    *        Called with the thread's response.
    */
   getScripts: function TC_getScripts(aOnResponse) {
     let packet = { to: this._actor, type: DebugProtocolTypes.scripts };
     this._client.request(packet, aOnResponse);
   },
 
-  /**
-   * A cache of source scripts. Clients can observe the scriptsadded and
-   * scriptscleared event to keep up to date on changes to this cache,
-   * and can fill it using the fillScripts method.
-   */
-  get cachedScripts() { return this._scriptCache; },
+  _doInterrupted: function TC_doInterrupted(aAction, aError) {
+    if (this.paused) {
+      aAction();
+      return;
+    }
+    this.interrupt(function(aResponse) {
+      if (aResponse) {
+        aError(aResponse);
+        return;
+      }
+      aAction();
+      this.resume(function() {});
+    }.bind(this));
+  },
 
   /**
    * Ensure that source scripts have been loaded in the
    * ThreadClient's source script cache. A scriptsadded event will be
    * sent when the source script cache is updated.
    *
    * @returns true if a scriptsadded notification should be expected.
    */
--- a/toolkit/devtools/debugger/server/dbg-browser-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-browser-actors.js
@@ -441,20 +441,29 @@ BrowserTabActor.prototype = {
   onWindowCreated: function BTA_onWindowCreated(evt) {
     if (evt.target === this.browser.contentDocument) {
       // pageshow events for non-persisted pages have already been handled by a
       // prior DOMWindowCreated event.
       if (evt.type == "pageshow" && !evt.persisted) {
         return;
       }
       if (this._attached) {
+        this.threadActor.clearDebuggees();
+        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._addDebuggees(evt.target.defaultView.wrappedJSObject);
+    }
   }
 };
 
 /**
  * The request types this actor can handle.
  */
 BrowserTabActor.prototype.requestTypes = {
   "attach": BrowserTabActor.prototype.onAttach,
@@ -494,20 +503,23 @@ DebuggerProgressListener.prototype = {
       }
 
       // 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._tabActor._detach();
       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
       });
 
       this.destroy();
     }
--- a/toolkit/devtools/debugger/server/dbg-script-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-script-actors.js
@@ -46,35 +46,56 @@ ThreadActor.prototype = {
   get threadLifetimePool() {
     if (!this._threadLifetimePool) {
       this._threadLifetimePool = new ActorPool(this.conn);
       this.conn.addActorPool(this._threadLifetimePool);
     }
     return this._threadLifetimePool;
   },
 
+  clearDebuggees: function TA_clearDebuggees() {
+    if (this._dbg) {
+      let debuggees = this._dbg.getDebuggees();
+      for (let debuggee of debuggees) {
+        this._dbg.removeDebuggee(debuggee);
+      }
+    }
+    this.conn.removeActorPool(this._threadLifetimePool || undefined);
+    this._threadLifetimePool = null;
+    // Unless we carefully take apart the scripts table this way, we end up
+    // leaking documents. It would be nice to track this down carefully, once
+    // we have the appropriate tools.
+    for (let url in this._scripts) {
+      delete this._scripts[url];
+    }
+    this._scripts = {};
+  },
+
   /**
    * Add a debuggee global to the Debugger object.
    */
   addDebuggee: function TA_addDebuggee(aGlobal) {
     // Use the inspector xpcom component to turn on debugging
     // for aGlobal's compartment.  Ideally this won't be necessary
     // medium- to long-term, and will be managed by the engine
     // instead.
 
     if (!this._dbg) {
       this._dbg = new Debugger();
+      this._dbg.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
+      this._dbg.onDebuggerStatement = this.onDebuggerStatement.bind(this);
+      this._dbg.onNewScript = this.onNewScript.bind(this);
+      // Keep the debugger disabled until a client attaches.
+      this.dbg.enabled = this._state != "detached";
     }
 
     this.dbg.addDebuggee(aGlobal);
-    this.dbg.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
-    this.dbg.onDebuggerStatement = this.onDebuggerStatement.bind(this);
-    this.dbg.onNewScript = this.onNewScript.bind(this);
-    // Keep the debugger disabled until a client attaches.
-    this.dbg.enabled = false;
+    for (let s of this.dbg.findScripts()) {
+      this._addScript(s);
+    }
   },
 
   /**
    * Remove a debuggee global from the JSInspector.
    */
   removeDebugee: function TA_removeDebuggee(aGlobal) {
     try {
       this.dbg.removeDebuggee(aGlobal);
@@ -85,29 +106,24 @@ ThreadActor.prototype = {
   },
 
   disconnect: function TA_disconnect() {
     if (this._state == "paused") {
       this.onResume();
     }
 
     this._state = "exited";
-    if (this.dbg) {
-      this.dbg.enabled = false;
-      this._dbg = null;
+
+    this.clearDebuggees();
+
+    if (!this._dbg) {
+      return;
     }
-    this.conn.removeActorPool(this._threadLifetimePool || undefined);
-    this._threadLifetimePool = null;
-    // Unless we carefully take apart the scripts table this way, we end up
-    // leaking documents. It would be nice to track this down carefully, once
-    // we have the appropriate tools.
-    for (let url in this._scripts) {
-      delete this._scripts[url];
-    }
-    this._scripts = {};
+    this._dbg.enabled = false;
+    this._dbg = null;
   },
 
   /**
    * Disconnect the debugger and put the actor in the exited state.
    */
   exit: function TA_exit() {
     this.disconnect();
   },
--- a/toolkit/devtools/debugger/server/dbg-server.js
+++ b/toolkit/devtools/debugger/server/dbg-server.js
@@ -361,17 +361,20 @@ DebuggerServerConnection.prototype = {
   addActorPool: function DSC_addActorPool(aActorPool) {
     this._extraPools.push(aActorPool);
   },
 
   /**
    * Remove a previously-added pool of actors to the connection.
    */
   removeActorPool: function DSC_removeActorPool(aActorPool) {
-    let index = this._extraPools.splice(this._extraPools.lastIndexOf(aActorPool), 1);
+    let index = this._extraPools.lastIndexOf(aActorPool);
+    if (index > -1) {
+      this._extraPools.splice(index, 1);
+    }
   },
 
   /**
    * Add an actor to the default actor pool for this connection.
    */
   addActor: function DSC_addActor(aActor) {
     this._actorPool.addActor(aActor);
   },
--- a/toolkit/devtools/debugger/tests/unit/test_listscripts-01.js
+++ b/toolkit/devtools/debugger/tests/unit/test_listscripts-01.js
@@ -23,23 +23,28 @@ function run_test()
   do_test_pending();
 }
 
 function test_simple_listscripts()
 {
   gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
     var path = getFilePath('test_listscripts-01.js');
     gThreadClient.getScripts(function (aResponse) {
-        // Check the return value.
-        do_check_eq(aResponse.scripts[0].url, path);
-        do_check_eq(aResponse.scripts[0].startLine, 41);
-        do_check_eq(aResponse.scripts[0].lineCount, 4);
-        gThreadClient.resume(function () {
-          finishClient(gClient);
-        });
+      let script = aResponse.scripts
+        .filter(function (s) {
+          return s.url.match(/test_listscripts-01.js$/);
+        })[0];
+      // Check the return value.
+      do_check_true(!!script);
+      do_check_eq(script.url, path);
+      do_check_eq(script.startLine, 46);
+      do_check_eq(script.lineCount, 4);
+      gThreadClient.resume(function () {
+        finishClient(gClient);
+      });
     });
   });
 
   gDebuggee.eval("var line0 = Error().lineNumber;\n" +
        "debugger;\n" +   // line0 + 1
        "var a = 1;\n" +  // line0 + 2
        "var b = 2;\n");  // line0 + 3
 }