merge m-c to fx-team
authorTim Taubert <tim.taubert@gmx.de>
Fri, 10 Feb 2012 14:26:49 +0100
changeset 86731 157ec7365fb9d179d631e0a17232d2d9bcf6dd00
parent 86719 417f1756b45249f5eed3d7525512a283286b6f33 (current diff)
parent 86730 55243908fbdd04ef0d086e54ecf92c3ff46007f1 (diff)
child 86732 82612a816cc0f7af875639a9eb023fd040c8c21b
child 86768 2d409ed569e0fbf0c52cd3f35dc3e06089f025e1
child 86830 ac4190a91b2f1da305486d00c8048d520feb2c24
push id102
push userMs2ger@gmail.com
push dateFri, 10 Feb 2012 20:38:19 +0000
milestone13.0a1
merge m-c to fx-team
browser/base/content/browser.js
--- a/browser/base/content/browser-thumbnails.js
+++ b/browser/base/content/browser-thumbnails.js
@@ -72,20 +72,18 @@ let gBrowserThumbnails = {
   onStateChange: function Thumbnails_onStateChange(aBrowser, aWebProgress,
                                                    aRequest, aStateFlags, aStatus) {
     if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
         aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)
       this._delayedCapture(aBrowser);
   },
 
   _capture: function Thumbnails_capture(aBrowser) {
-    if (this._shouldCapture(aBrowser)) {
-      let canvas = this._pageThumbs.capture(aBrowser.contentWindow);
-      this._pageThumbs.store(aBrowser.currentURI.spec, canvas);
-    }
+    if (this._shouldCapture(aBrowser))
+      this._pageThumbs.captureAndStore(aBrowser);
   },
 
   _delayedCapture: function Thumbnails_delayedCapture(aBrowser) {
     if (this._timeouts.has(aBrowser))
       clearTimeout(this._timeouts.get(aBrowser));
     else
       aBrowser.addEventListener("scroll", this, true);
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1223,22 +1223,22 @@ function BrowserStartup() {
   //                      ignored).
   //                 [1]: character set (string)
   //                 [2]: referrer (nsIURI)
   //                 [3]: postData (nsIInputStream)
   //                 [4]: allowThirdPartyFixup (bool)
   if ("arguments" in window && window.arguments[0])
     uriToLoad = window.arguments[0];
 
-  var isLoadingBlank = uriToLoad == "about:blank";
+  var isLoadingBlank = isBlankPageURL(uriToLoad);
   var mustLoadSidebar = false;
 
   prepareForStartup();
 
-  if (uriToLoad && !isLoadingBlank) {
+  if (uriToLoad && uriToLoad != "about:blank") {
     if (uriToLoad instanceof Ci.nsISupportsArray) {
       let count = uriToLoad.Count();
       let specs = [];
       for (let i = 0; i < count; i++) {
         let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString);
         specs.push(urisstring.data);
       }
 
--- a/browser/components/thumbnails/PageThumbs.jsm
+++ b/browser/components/thumbnails/PageThumbs.jsm
@@ -67,21 +67,23 @@ let PageThumbs = {
    * @return The thumbnail image's url.
    */
   getThumbnailURL: function PageThumbs_getThumbnailURL(aUrl) {
     return this.scheme + "://" + this.staticHost +
            "?url=" + encodeURIComponent(aUrl);
   },
 
   /**
-   * Creates a canvas containing a thumbnail depicting the given window.
+   * Captures a thumbnail for the given window.
    * @param aWindow The DOM window to capture a thumbnail from.
-   * @return The newly created canvas containing the image data.
+   * @param aCallback The function to be called when the thumbnail has been
+   *                  captured. The first argument will be the data stream
+   *                  containing the image data.
    */
-  capture: function PageThumbs_capture(aWindow) {
+  capture: function PageThumbs_capture(aWindow, aCallback) {
     let telemetryCaptureTime = new Date();
     let [sw, sh, scale] = this._determineCropSize(aWindow);
 
     let canvas = this._createCanvas();
     let ctx = canvas.getContext("2d");
 
     // Scale the canvas accordingly.
     ctx.scale(scale, scale);
@@ -89,85 +91,65 @@ let PageThumbs = {
     try {
       // Draw the window contents to the canvas.
       ctx.drawWindow(aWindow, 0, 0, sw, sh, THUMBNAIL_BG_COLOR,
                      ctx.DRAWWINDOW_DO_NOT_FLUSH);
     } catch (e) {
       // We couldn't draw to the canvas for some reason.
     }
 
-    Services.telemetry.getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
+    let telemetry = Services.telemetry;
+    telemetry.getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
       .add(new Date() - telemetryCaptureTime);
 
-    return canvas;
+    canvas.mozFetchAsStream(aCallback, this.contentType);
   },
 
   /**
-   * Stores the image data contained in the given canvas to the underlying
-   * storage.
-   * @param aKey The key to use for the storage.
-   * @param aCanvas The canvas containing the thumbnail's image data.
-   * @param aCallback The function to be called when the canvas data has been
-   *                  stored (optional).
+   * Captures a thumbnail for the given browser and stores it to the cache.
+   * @param aBrowser The browser to capture a thumbnail for.
+   * @param aCallback The function to be called when finished (optional).
    */
-  store: function PageThumbs_store(aKey, aCanvas, aCallback) {
-    let telemetryStoreTime = new Date();
+  captureAndStore: function PageThumbs_captureAndStore(aBrowser, aCallback) {
+    this.capture(aBrowser.contentWindow, function (aInputStream) {
+      let telemetryStoreTime = new Date();
 
-    function finish(aSuccessful) {
-      if (aSuccessful) {
-        Services.telemetry.getHistogramById("FX_THUMBNAILS_STORE_TIME_MS")
-          .add(new Date() - telemetryStoreTime);
+      function finish(aSuccessful) {
+        if (aSuccessful) {
+          Services.telemetry.getHistogramById("FX_THUMBNAILS_STORE_TIME_MS")
+            .add(new Date() - telemetryStoreTime);
+        }
+
+        if (aCallback)
+          aCallback(aSuccessful);
       }
 
-      if (aCallback)
-        aCallback(aSuccessful);
-    }
-
-    let self = this;
+      // Get a writeable cache entry.
+      PageThumbsCache.getWriteEntry(aBrowser.currentURI.spec, function (aEntry) {
+        if (!aEntry) {
+          finish(false);
+          return;
+        }
 
-    // Get a writeable cache entry.
-    PageThumbsCache.getWriteEntry(aKey, function (aEntry) {
-      if (!aEntry) {
-        finish(false);
-        return;
-      }
-
-      // Extract image data from the canvas.
-      self._readImageData(aCanvas, function (aData) {
         let outputStream = aEntry.openOutputStream(0);
 
         // Write the image data to the cache entry.
-        NetUtil.asyncCopy(aData, outputStream, function (aResult) {
+        NetUtil.asyncCopy(aInputStream, outputStream, function (aResult) {
           let success = Components.isSuccessCode(aResult);
           if (success)
             aEntry.markValid();
 
           aEntry.close();
           finish(success);
         });
       });
     });
   },
 
   /**
-   * Reads the image data from a given canvas and passes it to the callback.
-   * @param aCanvas The canvas to read the image data from.
-   * @param aCallback The function that the image data is passed to.
-   */
-  _readImageData: function PageThumbs_readImageData(aCanvas, aCallback) {
-    let dataUri = aCanvas.toDataURL(PageThumbs.contentType, "");
-    let uri = Services.io.newURI(dataUri, "UTF8", null);
-
-    NetUtil.asyncFetch(uri, function (aData, aResult) {
-      if (Components.isSuccessCode(aResult) && aData && aData.available())
-        aCallback(aData);
-    });
-  },
-
-  /**
    * Determines the crop size for a given content window.
    * @param aWindow The content window.
    * @return An array containing width, height and scale.
    */
   _determineCropSize: function PageThumbs_determineCropSize(aWindow) {
     let sw = aWindow.innerWidth;
     let sh = aWindow.innerHeight;
 
--- a/browser/components/thumbnails/test/head.js
+++ b/browser/components/thumbnails/test/head.js
@@ -89,25 +89,22 @@ function whenLoaded(aElement, aCallback)
  * Captures a screenshot for the currently selected tab, stores it in the cache,
  * retrieves it from the cache and compares pixel color values.
  * @param aRed The red component's intensity.
  * @param aGreen The green component's intensity.
  * @param aBlue The blue component's intensity.
  * @param aMessage The info message to print when comparing the pixel color.
  */
 function captureAndCheckColor(aRed, aGreen, aBlue, aMessage) {
-  let window = gBrowser.selectedTab.linkedBrowser.contentWindow;
-
-  let key = Date.now();
-  let data = PageThumbs.capture(window);
+  let browser = gBrowser.selectedBrowser;
 
-  // Store the thumbnail in the cache.
-  PageThumbs.store(key, data, function () {
+  // Capture the screenshot.
+  PageThumbs.captureAndStore(browser, function () {
     let width = 100, height = 100;
-    let thumb = PageThumbs.getThumbnailURL(key, width, height);
+    let thumb = PageThumbs.getThumbnailURL(browser.currentURI.spec, width, height);
 
     getXULDocument(function (aDocument) {
       let htmlns = "http://www.w3.org/1999/xhtml";
       let img = aDocument.createElementNS(htmlns, "img");
       img.setAttribute("src", thumb);
 
       whenLoaded(img, function () {
         let canvas = aDocument.createElementNS(htmlns, "canvas");
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -88,26 +88,26 @@ DebuggerView.Stackframes = {
    *
    * @param string aState
    *        Either "paused" or "attached".
    */
   updateState: function DVF_updateState(aState) {
     let resume = document.getElementById("resume");
     let status = document.getElementById("status");
 
-    // if we're paused, show a pause label and disable the resume button
+    // If we're paused, show a pause label and a resume label on the button.
     if (aState === "paused") {
       status.textContent = DebuggerView.getStr("pausedState");
-      resume.disabled = false;
+      resume.label = DebuggerView.getStr("resumeLabel");
     } else if (aState === "attached") {
-      // if we're attached, do the opposite
+      // If we're attached, do the opposite.
       status.textContent = DebuggerView.getStr("runningState");
-      resume.disabled = true;
+      resume.label = DebuggerView.getStr("pauseLabel");
     } else {
-      // no valid state parameter
+      // No valid state parameter.
       status.textContent = "";
     }
   },
 
   /**
    * Sets the onClick listener for the stackframes container.
    *
    * @param function aHandler
@@ -267,20 +267,24 @@ DebuggerView.Stackframes = {
     let root = document.documentElement;
     let debuggerClose = document.createEvent("Events");
 
     debuggerClose.initEvent("DebuggerClose", true, false);
     root.dispatchEvent(debuggerClose);
   },
 
   /**
-   * Listener handling the resume button click event.
+   * Listener handling the pause/resume button click event.
    */
   _onResumeButtonClick: function DVF__onResumeButtonClick() {
-    ThreadState.activeThread.resume();
+    if (ThreadState.activeThread.paused) {
+      ThreadState.activeThread.resume();
+    } else {
+      ThreadState.activeThread.interrupt();
+    }
   },
 
   /**
    * Specifies if the active thread has more frames which need to be loaded.
    */
   _dirty: false,
 
   /**
@@ -1088,16 +1092,27 @@ DebuggerView.Scripts = {
 
     let script = this._scripts.appendItem(aScriptNameText || aUrl, aUrl);
     script.setUserData("sourceScript", aSource, null);
     this._scripts.selectedItem = script;
     return script;
   },
 
   /**
+   * Returns the list of URIs for scripts in the page.
+   */
+  scriptLocations: function DVS_scriptLocations() {
+    let locations = [];
+    for (let i = 0; i < this._scripts.itemCount; i++) {
+      locations.push(this._scripts.getItemAtIndex(i).value);
+    }
+    return locations;
+  },
+
+  /**
    * The cached click listener for the scripts container.
    */
   _onScriptsChange: null,
 
   /**
    * The cached scripts container.
    */
   _scripts: null,
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -48,17 +48,17 @@
 <xul:window xmlns="http://www.w3.org/1999/xhtml"
             xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
     <xul:script type="text/javascript" src="debugger.js"/>
     <xul:script type="text/javascript" src="debugger-view.js"/>
 
     <div id="body" class="vbox flex">
         <xul:toolbar id="dbg-toolbar">
             <xul:button id="close">&debuggerUI.closeButton;</xul:button>
-            <xul:button id="resume">&debuggerUI.resumeButton;</xul:button>
+            <xul:button id="resume"/>
             <xul:menulist id="scripts"/>
         </xul:toolbar>
         <div id="dbg-content" class="hbox flex">
             <div id="stack" class="vbox">
                 <div class="title unselectable">&debuggerUI.stackTitle;</div>
                 <div id="stackframes" class="vbox flex"></div>
             </div>
             <div id="script" class="vbox flex">
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -64,16 +64,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_dbg_propertyview-08.js \
 	browser_dbg_panesize.js \
 	browser_dbg_stack-01.js \
 	browser_dbg_stack-02.js \
 	browser_dbg_stack-03.js \
 	browser_dbg_stack-04.js \
 	browser_dbg_location-changes.js \
 	browser_dbg_script-switching.js \
+	browser_dbg_pause-resume.js \
 	head.js \
 	$(NULL)
 
 _BROWSER_TEST_PAGES = \
 	browser_dbg_tab1.html \
 	browser_dbg_tab2.html \
 	browser_dbg_debuggerstatement.html \
 	browser_dbg_stack.html \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_pause-resume.js
@@ -0,0 +1,74 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var gPane = null;
+var gTab = null;
+var gDebuggee = null;
+var gDebugger = null;
+
+function test() {
+  debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.debuggerWindow;
+
+    testPause();
+  });
+}
+
+function testPause() {
+  is(gDebugger.StackFrames.activeThread.paused, false,
+    "Should be running after debug_tab_pane.");
+
+  let button = gDebugger.document.getElementById("resume");
+  is(button.label, gDebugger.DebuggerView.getStr("pauseLabel"),
+    "Button label should be pause when running.");
+
+  gPane.activeThread.addOneTimeListener("paused", function() {
+    Services.tm.currentThread.dispatch({ run: function() {
+
+      let frames = gDebugger.DebuggerView.Stackframes._frames;
+      let childNodes = frames.childNodes;
+
+      is(gDebugger.StackFrames.activeThread.paused, true,
+        "Should be paused after an interrupt request.");
+
+      is(button.label, gDebugger.DebuggerView.getStr("resumeLabel"),
+        "Button label should be resume when paused.");
+
+      is(frames.querySelectorAll(".dbg-stackframe").length, 0,
+        "Should have no frames when paused in the main loop.");
+
+      testResume();
+    }}, 0);
+  });
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    gDebugger.document.getElementById("resume"),
+    gDebugger);
+}
+
+function testResume() {
+  gPane.activeThread.addOneTimeListener("resumed", function() {
+    Services.tm.currentThread.dispatch({ run: function() {
+
+      is(gDebugger.StackFrames.activeThread.paused, false,
+        "Should be paused after an interrupt request.");
+
+      let button = gDebugger.document.getElementById("resume");
+      is(button.label, gDebugger.DebuggerView.getStr("pauseLabel"),
+        "Button label should be pause when running.");
+
+      removeTab(gTab);
+      finish();
+    }}, 0);
+  });
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    gDebugger.document.getElementById("resume"),
+    gDebugger);
+}
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -50,26 +50,28 @@ function testFrameParameters()
         "Should have the right property value for 'this'.");
 
       // Expand the __proto__ and arguments tree nodes. This causes their
       // properties to be retrieved and displayed.
       localNodes[0].expand();
       localNodes[1].expand();
 
       // Poll every few milliseconds until the properties are retrieved.
+      // It's important to set the timer in the chrome window, because the
+      // content window timers are disabled while the debuggee is paused.
       let count = 0;
-      let intervalID = content.setInterval(function(){
+      let intervalID = window.setInterval(function(){
         if (++count > 50) {
           ok(false, "Timed out while polling for the properties.");
           resumeAndFinish();
         }
         if (!localNodes[0].fetched || !localNodes[1].fetched) {
           return;
         }
-        content.clearInterval(intervalID);
+        window.clearInterval(intervalID);
         is(localNodes[0].querySelector(".property > .title > .key")
                         .textContent, "__proto__ ",
           "Should have the right property name for __proto__.");
 
         ok(localNodes[0].querySelector(".property > .title > .value")
                         .textContent.search(/object/) != -1,
           "__proto__ should be an object.");
 
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm
+++ b/browser/devtools/sourceeditor/source-editor-orion.jsm
@@ -17,16 +17,17 @@
  * The Initial Developer of the Original Code is
  * The Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Mihai Sucan <mihai.sucan@gmail.com> (original author)
  *   Kenny Heaton <kennyheaton@gmail.com>
+ *   Spyros Livathinos <livathinos.spyros@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -70,16 +71,18 @@ const ORION_THEMES = {
 /**
  * Known editor events you can listen for. This object maps SourceEditor.EVENTS
  * to Orion events.
  */
 const ORION_EVENTS = {
   ContextMenu: "ContextMenu",
   TextChanged: "ModelChanged",
   Selection: "Selection",
+  Focus: "Focus",
+  Blur: "Blur",
 };
 
 /**
  * Known Orion annotation types.
  */
 const ORION_ANNOTATION_TYPES = {
   currentBracket: "orion.annotation.currentBracket",
   matchingBracket: "orion.annotation.matchingBracket",
--- a/browser/devtools/sourceeditor/source-editor.jsm
+++ b/browser/devtools/sourceeditor/source-editor.jsm
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is
  * The Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Mihai Sucan <mihai.sucan@gmail.com> (original author)
+ *   Spyros Livathinos <livathinos.spyros@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -145,16 +146,26 @@ SourceEditor.EVENTS = {
   /**
    * The Selection event is fired when the editor selection changes. The event
    * object properties:
    *   - oldValue - the old selection range.
    *   - newValue - the new selection range.
    * Both ranges are objects which hold two properties: start and end.
    */
   SELECTION: "Selection",
+
+  /**
+   * The focus event is fired when the editor is focused.
+   */
+  FOCUS: "Focus",
+
+  /**
+   * The blur event is fired when the editor goes out of focus.
+   */
+  BLUR: "Blur",
 };
 
 /**
  * Extend a destination object with properties from a source object.
  *
  * @param object aDestination
  * @param object aSource
  */
--- a/browser/devtools/sourceeditor/test/Makefile.in
+++ b/browser/devtools/sourceeditor/test/Makefile.in
@@ -49,12 +49,13 @@ include $(topsrcdir)/config/rules.mk
 		browser_bug684862_paste_html.js \
 		browser_bug687573_vscroll.js \
 		browser_bug687568_pagescroll.js \
 		browser_bug687580_drag_and_drop.js \
 		browser_bug684546_reset_undo.js \
 		browser_bug695035_middle_click_paste.js \
 		browser_bug687160_line_api.js \
 		browser_bug650345_find.js \
+		browser_bug703692_focus_blur.js \
 		head.js \
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug703692_focus_blur.js
@@ -0,0 +1,71 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let tempScope = {};
+Cu.import("resource:///modules/source-editor.jsm", tempScope);
+let SourceEditor = tempScope.SourceEditor;
+
+let testWin;
+let editor;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  const windowUrl = "data:text/xml,<?xml version='1.0'?>" +
+    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+    " title='Test for bug 703692' width='600' height='500'><hbox flex='1'/></window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+  testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
+  testWin.addEventListener("load", function onWindowLoad() {
+    testWin.removeEventListener("load", onWindowLoad, false);
+    waitForFocus(initEditor, testWin);
+  }, false);
+}
+
+function initEditor()
+{
+  let hbox = testWin.document.querySelector("hbox");
+
+  editor = new SourceEditor();
+  editor.init(hbox, {}, editorLoaded);
+}
+
+function editorLoaded()
+{
+  let focusHandler = function(aEvent) {
+    editor.removeEventListener(SourceEditor.EVENTS.FOCUS, focusHandler);
+    editor.addEventListener(SourceEditor.EVENTS.BLUR, blurHandler);
+
+    ok(aEvent, "Focus event fired");
+    window.focus();
+  };
+
+  let blurHandler = function(aEvent) {
+    editor.removeEventListener(SourceEditor.EVENTS.BLUR, blurHandler);
+
+    ok(aEvent, "Blur event fired");
+    executeSoon(testEnd);
+  }
+
+  editor.addEventListener(SourceEditor.EVENTS.FOCUS, focusHandler);
+
+  editor.focus();
+}
+
+function testEnd()
+{
+  if (editor) {
+    editor.destroy();
+  }
+  if (testWin) {
+    testWin.close();
+  }
+  testWin = editor = null;
+
+  waitForFocus(finish, window);
+}
--- a/browser/devtools/webconsole/GcliCommands.jsm
+++ b/browser/devtools/webconsole/GcliCommands.jsm
@@ -120,8 +120,144 @@ gcli.addCommand({
       manual: gcli.lookup("inspectNodeManual")
     }
   ],
   exec: function Command_inspect(args, context) {
     let document = context.environment.chromeDocument;
     document.defaultView.InspectorUI.openInspectorUI(args.node);
   }
 });
+
+let breakpoints = [];
+
+/**
+ * 'break' command
+ */
+gcli.addCommand({
+  name: "break",
+  description: gcli.lookup("breakDesc"),
+  manual: gcli.lookup("breakManual")
+});
+
+
+/**
+ * 'break list' command
+ */
+gcli.addCommand({
+  name: "break list",
+  description: gcli.lookup("breaklistDesc"),
+  returnType: "html",
+  exec: function(args, context) {
+    if (breakpoints.length === 0) {
+      return gcli.lookup("breaklistNone");
+    }
+
+    let reply = gcli.lookup("breaklistIntro");
+    reply += "<ol>";
+    breakpoints.forEach(function(breakpoint) {
+      let text = gcli.lookupFormat("breaklistLineEntry",
+                                   [breakpoint.file, breakpoint.line]);
+      reply += "<li>" + text + "</li>";
+    });
+    reply += "</ol>";
+    return reply;
+  }
+});
+
+
+/**
+ * 'break add' command
+ */
+gcli.addCommand({
+  name: "break add",
+  description: gcli.lookup("breakaddDesc"),
+  manual: gcli.lookup("breakaddManual")
+});
+
+/**
+ * 'break add line' command
+ */
+gcli.addCommand({
+  name: "break add line",
+  description: gcli.lookup("breakaddlineDesc"),
+  params: [
+    {
+      name: "file",
+      type: {
+        name: "selection",
+        data: function() {
+          let win = HUDService.currentContext();
+          let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab);
+          let files = [];
+          if (dbg) {
+            let scriptsView = dbg.frame.contentWindow.DebuggerView.Scripts;
+            for each (let script in scriptsView.scriptLocations()) {
+              files.push(script);
+            }
+          }
+          return files;
+        }
+      },
+      description: gcli.lookup("breakaddlineFileDesc")
+    },
+    {
+      name: "line",
+      type: { name: "number", min: 1, step: 10 },
+      description: gcli.lookup("breakaddlineLineDesc")
+    }
+  ],
+  returnType: "html",
+  exec: function(args, context) {
+    args.type = "line";
+    let win = HUDService.currentContext();
+    let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab);
+    if (!dbg) {
+      return gcli.lookup("breakaddDebuggerStopped");
+    }
+    var promise = context.createPromise();
+    let position = { url: args.file, line: args.line };
+    dbg.activeThread.setBreakpoint(position, function(aResponse, aBpClient) {
+      if (aResponse.error) {
+        promise.resolve(gcli.lookupFormat("breakaddFailed",
+                        [ aResponse.error ]));
+        return;
+      }
+      args.client = aBpClient;
+      breakpoints.push(args);
+      promise.resolve(gcli.lookup("breakaddAdded"));
+    });
+    return promise;
+  }
+});
+
+
+/**
+ * 'break del' command
+ */
+gcli.addCommand({
+  name: "break del",
+  description: gcli.lookup("breakdelDesc"),
+  params: [
+    {
+      name: "breakid",
+      type: {
+        name: "number",
+        min: 0,
+        max: function() { return breakpoints.length - 1; }
+      },
+      description: gcli.lookup("breakdelBreakidDesc")
+    }
+  ],
+  returnType: "html",
+  exec: function(args, context) {
+    let breakpoint = breakpoints.splice(args.breakid, 1)[0];
+    var promise = context.createPromise();
+    try {
+      breakpoint.client.remove(function(aResponse) {
+                                 promise.resolve(gcli.lookup("breakdelRemoved"));
+                               });
+    } catch (ex) {
+      // If the debugger has been closed already, don't scare the user.
+      promise.resolve(gcli.lookup("breakdelRemoved"));
+    }
+    return promise;
+  }
+});
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -149,16 +149,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_gcli_inspect.js \
 	browser_gcli_integrate.js \
 	browser_gcli_require.js \
 	browser_gcli_web.js \
 	browser_webconsole_bug_658368_time_methods.js \
 	browser_webconsole_bug_622303_persistent_filters.js \
 	browser_webconsole_window_zombie.js \
 	browser_cached_messages.js \
+	browser_gcli_break.js \
 	head.js \
 	$(NULL)
 
 _BROWSER_TEST_PAGES = \
 	test-console.html \
 	test-network.html \
 	test-network-request.html \
 	test-mutation.html \
@@ -223,15 +224,16 @@ include $(topsrcdir)/config/rules.mk
 	test-bug-632275-getters.html \
 	test-bug-646025-console-file-location.html \
 	test-bug-678816-content.js \
 	test-file-location.js \
 	browser_gcli_inspect.html \
 	test-bug-658368-time-methods.html \
 	test-webconsole-error-observer.html \
 	test-for-of.html \
+	browser_gcli_break.html \
 	$(NULL)
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
 
 libs:: $(_BROWSER_TEST_PAGES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_gcli_break.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+	<head>
+		<title>Browser GCLI break command test</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+         http://creativecommons.org/publicdomain/zero/1.0/ -->
+    <script type="text/javascript">
+      function firstCall() {
+        eval("window.line0 = Error().lineNumber; secondCall();");
+      }
+      function secondCall() {
+        eval("debugger;");
+      }
+    </script>
+	</head>
+	<body>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_gcli_break.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// For more information on GCLI see:
+// - https://github.com/mozilla/gcli/blob/master/docs/index.md
+// - https://wiki.mozilla.org/DevTools/Features/GCLI
+
+// Tests that the break command works as it should
+
+let tempScope = {};
+Components.utils.import("resource:///modules/gcli.jsm", tempScope);
+let gcli = tempScope.gcli;
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/browser_gcli_break.html";
+registerCleanupFunction(function() {
+  gcliterm = undefined;
+  requisition = undefined;
+
+  Services.prefs.clearUserPref("devtools.gcli.enable");
+});
+
+function test() {
+  Services.prefs.setBoolPref("devtools.gcli.enable", true);
+  addTab(TEST_URI);
+  browser.addEventListener("DOMContentLoaded", onLoad, false);
+}
+
+let gcliterm;
+let requisition;
+
+function onLoad() {
+  browser.removeEventListener("DOMContentLoaded", onLoad, false);
+
+  try {
+    openConsole();
+
+    let hud = HUDService.getHudByWindow(content);
+    gcliterm = hud.gcliterm;
+    requisition = gcliterm.opts.requisition;
+
+    testSetup();
+    testCreateCommands();
+  }
+  catch (ex) {
+    ok(false, "Caught exception: " + ex)
+    gcli._internal.console.error("Test Failure", ex);
+    closeConsole();
+    finishTest();
+  }
+}
+
+function testSetup() {
+  ok(gcliterm, "We have a GCLI term");
+  ok(requisition, "We have a Requisition");
+}
+
+function testCreateCommands() {
+  type("brea");
+  is(gcliterm.completeNode.textContent, " break", "Completion for 'brea'");
+  is(requisition.getStatus().toString(), "ERROR", "brea is ERROR");
+
+  type("break");
+  is(requisition.getStatus().toString(), "ERROR", "break is ERROR");
+
+  type("break add");
+  is(requisition.getStatus().toString(), "ERROR", "break add is ERROR");
+
+  type("break add line");
+  is(requisition.getStatus().toString(), "ERROR", "break add line is ERROR");
+
+  let pane = DebuggerUI.toggleDebugger();
+  pane.onConnected = function test_onConnected(aPane) {
+    // Wait for the initial resume.
+    aPane.debuggerWindow.gClient.addOneTimeListener("resumed", function() {
+      delete aPane.onConnected;
+      aPane.debuggerWindow.gClient.activeThread.addOneTimeListener("scriptsadded", function() {
+        type("break add line " + TEST_URI + " " + content.wrappedJSObject.line0);
+        is(requisition.getStatus().toString(), "VALID", "break add line is VALID");
+        requisition.exec();
+
+        type("break list");
+        is(requisition.getStatus().toString(), "VALID", "break list is VALID");
+        requisition.exec();
+
+        aPane.debuggerWindow.gClient.activeThread.resume(function() {
+          type("break del 0");
+          is(requisition.getStatus().toString(), "VALID", "break del 0 is VALID");
+          requisition.exec();
+
+          closeConsole();
+          finishTest();
+        });
+      });
+      // Trigger newScript notifications using eval.
+      content.wrappedJSObject.firstCall();
+    });
+  }
+}
+
+function type(command) {
+  gcliterm.inputNode.value = command.slice(0, -1);
+  gcliterm.inputNode.focus();
+  EventUtils.synthesizeKey(command.slice(-1), {});
+}
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
@@ -14,22 +14,16 @@
 <!-- LOCALIZATION NOTE (debuggerMenu.commandkey): This is the command key that
   -  launches the debugger UI. Do not translate this one! -->
 <!ENTITY debuggerMenu.commandkey     "S">
 
 <!-- LOCALIZATION NOTE (debuggerUI.closeButton): This is the label for the
   -  button that closes the debugger UI. -->
 <!ENTITY debuggerUI.closeButton      "Close">
 
-<!-- LOCALIZATION NOTE (debuggerUI.resumeButton): This is the label for the
-  -  button that resumes the debugger, after it has reached a paused state. In
-  -  a paused state the debugger can be used to inspect stack frames, local,
-  -  variables etc. -->
-<!ENTITY debuggerUI.resumeButton     "Resume">
-
 <!-- LOCALIZATION NOTE (debuggerUI.stackTitle): This is the label for the
   -  widget that displays the call stack frames in the debugger. -->
 <!ENTITY debuggerUI.stackTitle       "Call stack">
 
 <!-- LOCALIZATION NOTE (debuggerUI.scriptTitle): This is the label for the
   -  widget that displays the source code for the script that is currently
   -  being inspected in the debugger. -->
 <!ENTITY debuggerUI.scriptTitle      "Script">
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -1,16 +1,24 @@
 # LOCALIZATION NOTE These strings are used inside the Script Debugger
 # which is available from the Web Developer sub-menu -> 'Script Debugger'.
 # The correct localization of this file might be to keep it in
 # English, or another language commonly spoken among web developers.
 # You want to make that choice consistent across the developer tools.
 # A good criteria is the language in which you'd find the best
 # documentation on web development on the web.
 
+# LOCALIZATION NOTE (pauseLabel): The label that is displayed on the pause
+# button when the debugger is in a running state.
+pauseLabel=Pause
+
+# LOCALIZATION NOTE (resumeLabel): The label that is displayed on the pause
+# button when the debugger is in a paused state.
+resumeLabel=Resume
+
 # LOCALIZATION NOTE (pausedState): The label that is displayed when the
 # debugger is in a paused state.
 pausedState=Paused
 
 # LOCALIZATION NOTE (runningState): The label that is displayed when the
 # debugger is in a running state.
 runningState=Running
 
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -51,12 +51,81 @@ inspectManual=Investigate the dimensions
 # when the user is using this command.
 inspectNodeDesc=CSS selector
 
 # LOCALIZATION NOTE (inspectNodeManual) A fuller description of the 'node'
 # parameter to the 'inspect' command, displayed when the user asks for help
 # on what it does.
 inspectNodeManual=A CSS selector for use with Document.querySelector which identifies a single element
 
+# LOCALIZATION NOTE (breakDesc) A very short string used to describe the
+# function of the break command.
+breakDesc=Manage breakpoints
+
+# LOCALIZATION NOTE (breakManual) A longer description describing the
+# set of commands that control breakpoints.
+breakManual=Commands to list, add and remove breakpoints
+
+# LOCALIZATION NOTE (breaklistDesc) A very short string used to describe the
+# function of the 'break list' command.
+breaklistDesc=Display known breakpoints
+
+# LOCALIZATION NOTE (breaklistLineEntry) Used in the output of the 'break list'
+# command to display a single line breakpoint.
+# %1$S=script URL, %2$S=line number
+breaklistLineEntry=Line breakpoint at %1$S:%2$S
+
+# LOCALIZATION NOTE (breaklistNone) Used in the output of the 'break list'
+# command to explain that the list is empty.
+breaklistNone=No breakpoints set
+
+# LOCALIZATION NOTE (breaklistIntro) Used in the output of the 'break list'
+# command to preface the list contents.
+breaklistIntro=The following breakpoints are set:
+
+# LOCALIZATION NOTE (breakaddAdded) Used in the output of the 'break add'
+# command to explain that a breakpoint was added.
+breakaddAdded=Added breakpoint
+
+# LOCALIZATION NOTE (breakaddFailed) Used in the output of the 'break add'
+# command to explain that a breakpoint could not be added.
+breakaddFailed=Could not set breakpoint: %S
+
+# LOCALIZATION NOTE (breakaddDesc) A very short string used to describe the
+# function of the 'break add' command.
+breakaddDesc=Add a breakpoint
+
+# LOCALIZATION NOTE (breakaddManual) A longer description describing the
+# set of commands that are responsible for adding breakpoints.
+breakaddManual=Breakpoint types supported: line
+
+# LOCALIZATION NOTE (breakaddDebuggerStopped) Used in the output of the
+# 'break add' command to explain that the debugger must be opened first.
+breakaddDebuggerStopped=The debugger must be opened before setting breakpoints
+
+# LOCALIZATION NOTE (breakaddlineDesc) A very short string used to describe the
+# function of the 'break add line' command.
+breakaddlineDesc=Add a line breakpoint
+
+# LOCALIZATION NOTE (breakaddlineFileDesc) A very short string used to describe
+# the function of the file parameter in the 'break add line' command.
+breakaddlineFileDesc=JS file URI
+
+# LOCALIZATION NOTE (breakaddlineLineDesc) A very short string used to describe
+# the function of the line parameter in the 'break add line' command.
+breakaddlineLineDesc=Line number
+
+# LOCALIZATION NOTE (breakdelDesc) A very short string used to describe the
+# function of the 'break del' command.
+breakdelDesc=Remove a breakpoint
+
+# LOCALIZATION NOTE (breakdelBreakidDesc) A very short string used to describe
+# the function of the index parameter in the 'break del' command.
+breakdelBreakidDesc=Index of breakpoint
+
+# LOCALIZATION NOTE (breakdelRemoved) Used in the output of the 'break del'
+# command to explain that a breakpoint was removed.
+breakdelRemoved=Breakpoint removed
+
 # LOCALIZATION NOTE (consolecloseDesc) A very short description of the
 # 'console close' command. This string is designed to be shown in a menu
 # alongside the command name, which is why it should be as short as possible.
 consolecloseDesc=Close the console
--- a/browser/modules/NewTabUtils.jsm
+++ b/browser/modules/NewTabUtils.jsm
@@ -167,17 +167,17 @@ PrivateBrowsingStorage.prototype = {
   setItem: function PrivateBrowsingStorage_setItem(aKey, aValue) {
     this._data.set(aKey, aValue);
   },
 
   /**
    * Clears the storage and removes all values.
    */
   clear: function PrivateBrowsingStorage_clear() {
-    this._data.listkeys().forEach(function (akey) {
+    this._data.listkeys().forEach(function (aKey) {
       this._data.del(aKey);
     }, this);
   }
 };
 
 /**
  * Singleton that serves as a registry for all open 'New Tab Page's.
  */
--- a/toolkit/devtools/debugger/dbg-client.jsm
+++ b/toolkit/devtools/debugger/dbg-client.jsm
@@ -190,25 +190,36 @@ function eventSource(aProto) {
  */
 const ThreadStateTypes = {
   "paused": "paused",
   "resumed": "attached",
   "detached": "detached"
 };
 
 /**
+ * Set of protocol messages that are sent by the server without a prior request
+ * by the client.
+ */
+const UnsolicitedNotifications = {
+  "newScript": "newScript",
+  "tabDetached": "tabDetached",
+  "tabNavigated": "tabNavigated"
+};
+
+/**
  * Set of debug protocol request types that specify the protocol request being
  * sent to the server.
  */
 const DebugProtocolTypes = {
   "attach": "attach",
   "clientEvaluate": "clientEvaluate",
   "delete": "delete",
   "detach": "detach",
   "frames": "frames",
+  "interrupt": "interrupt",
   "listTabs": "listTabs",
   "nameAndParameters": "nameAndParameters",
   "ownPropertyNames": "ownPropertyNames",
   "property": "property",
   "prototype": "prototype",
   "prototypeAndProperties": "prototypeAndProperties",
   "resume": "resume",
   "scripts": "scripts",
@@ -403,22 +414,25 @@ DebuggerClient.prototype = {
       this.notify("connected",
                   aPacket.applicationType,
                   aPacket.traits);
       return;
     }
 
     try {
       if (!aPacket.from) {
-        dumpn("Server did not specify an actor, dropping packet: " + JSON.stringify(aPacket));
+        Cu.reportError("Server did not specify an actor, dropping packet: " +
+                       JSON.stringify(aPacket));
         return;
       }
 
       let onResponse;
-      if (aPacket.from in this._activeRequests) {
+      // Don't count unsolicited notifications as responses.
+      if (aPacket.from in this._activeRequests &&
+          !(aPacket.type in UnsolicitedNotifications)) {
         onResponse = this._activeRequests[aPacket.from].onResponse;
         delete this._activeRequests[aPacket.from];
       }
 
       // paused/resumed/detached get special treatment...
       if (aPacket.type in ThreadStateTypes &&
           aPacket.from in this._threadClients) {
         this._threadClients[aPacket.from]._onThreadState(aPacket);
@@ -504,22 +518,23 @@ function ThreadClient(aClient, aActor) {
   this._actor = aActor;
   this._frameCache = [];
   this._scriptCache = {};
 }
 
 ThreadClient.prototype = {
   _state: "paused",
   get state() { return this._state; },
+  get paused() { return this._state === "paused"; },
 
   _actor: null,
   get actor() { return this._actor; },
 
   _assertPaused: function TC_assertPaused(aCommand) {
-    if (this._state !== "paused") {
+    if (!this.paused) {
       throw aCommand + " command sent while not paused.";
     }
   },
 
   /**
    * Resume a paused thread.
    *
    * @param function aOnResponse
@@ -541,16 +556,31 @@ ThreadClient.prototype = {
       }
       if (aOnResponse) {
         aOnResponse(aResponse);
       }
     });
   },
 
   /**
+   * Interrupt a running thread.
+   *
+   * @param function aOnResponse
+   *        Called with the response packet.
+   */
+  interrupt: function TC_interrupt(aOnResponse) {
+    let packet = { to: this._actor, type: DebugProtocolTypes.interrupt };
+    this._client.request(packet, function(aResponse) {
+      if (aOnResponse) {
+        aOnResponse(aResponse);
+      }
+    });
+  },
+
+  /**
    * Send a clientEvaluate packet to the debuggee. Response
    * will be a resume packet.
    */
   eval: function TC_eval(aFrame, aExpression, aOnResponse) {
     this._assertPaused("eval");
 
     // Put the client in a tentative "resuming" state so we can prevent
     // further requests that should only be sent in the paused state.
@@ -590,34 +620,59 @@ ThreadClient.prototype = {
       }
     });
   },
 
   /**
    * Request to set a breakpoint in the specified location.
    *
    * @param aLocation object
-   *        The source location object where the breakpoint
-   *        will be set.
+   *        The source location object where the breakpoint will be set.
    * @param aOnResponse integer
    *        Called with the thread's response.
    */
   setBreakpoint: function TC_setBreakpoint(aLocation, aOnResponse) {
-    this._assertPaused("setBreakpoint");
+    // A helper function that sets the breakpoint.
+    let doSetBreakpoint = function _doSetBreakpoint(aCallback) {
+      let packet = { to: this._actor, type: DebugProtocolTypes.setBreakpoint,
+                     location: aLocation };
+      this._client.request(packet, function (aResponse) {
+          if (aOnResponse) {
+            if (aResponse.error) {
+              if (aCallback) {
+                aCallback(aOnResponse.bind(undefined, aResponse));
+              } else {
+                aOnResponse(aResponse);
+              }
+              return;
+            }
+            let bpClient = new BreakpointClient(this._client, aResponse.actor);
+            if (aCallback) {
+              aCallback(aOnResponse(aResponse, bpClient));
+            } else {
+              aOnResponse(aResponse, bpClient);
+            }
+          }
+        }.bind(this));
+    }.bind(this);
 
-    let self = this;
-    let packet = { to: this._actor, type: DebugProtocolTypes.setBreakpoint,
-                   location: aLocation };
-    this._client.request(packet, function (aResponse) {
-                         if (aOnResponse) {
-                           let bpClient = new BreakpointClient(self._client,
-                                                               aResponse.actor);
-                           aOnResponse(aResponse, bpClient);
-                         }
-                       });
+    // If the debuggee is paused, just set the breakpoint.
+    if (this.paused) {
+      doSetBreakpoint();
+      return;
+    }
+    // Otherwise, force a pause in order to set the breakpoint.
+    this.interrupt(function(aResponse) {
+      if (aResponse.error) {
+        // Can't set the breakpoint if pausing failed.
+        aOnResponse(aResponse);
+        return;
+      }
+      doSetBreakpoint(this.resume.bind(this));
+    }.bind(this));
   },
 
   /**
    * Request the loaded scripts for the current thread.
    *
    * @param aOnResponse integer
    *        Called with the thread's response.
    */
@@ -691,18 +746,17 @@ ThreadClient.prototype = {
    * and can fill it using the fillFrames method.
    */
   get cachedFrames() { return this._frameCache; },
 
   /**
    * true if there are more stack frames available on the server.
    */
   get moreFrames() {
-    return this.state === "paused"
-      && (!this._frameCache || this._frameCache.length == 0
+    return this.paused && (!this._frameCache || this._frameCache.length == 0
           || !this._frameCache[this._frameCache.length - 1].oldest);
   },
 
   /**
    * Ensure that at least aTotal stack frames have been loaded in the
    * ThreadClient's stack frame cache. A framesadded event will be
    * sent when the stack frame cache is updated.
    *
--- a/toolkit/devtools/debugger/server/dbg-browser-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-browser-actors.js
@@ -360,35 +360,35 @@ BrowserTabActor.prototype = {
     }
 
     this._detach();
 
     return { type: "detached" };
   },
 
   /**
-   * Suppresses content-initiated events. Called right before entering the
-   * nested event loop.
+   * Prepare to enter a nested event loop by disabling debuggee events.
    */
   preNest: function BTA_preNest() {
-    this.browser.contentWindow
-        .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-        .getInterface(Ci.nsIDOMWindowUtils)
-        .suppressEventHandling(true);
+    let windowUtils = this.browser.contentWindow
+                          .QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindowUtils);
+    windowUtils.suppressEventHandling(true);
+    windowUtils.suspendTimeouts();
   },
 
   /**
-   * Re-enables content-initiated events. Called right after exiting the
-   * nested event loop.
+   * Prepare to exit a nested event loop by enabling debuggee events.
    */
   postNest: function BTA_postNest(aNestData) {
-    this.browser.contentWindow
-        .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-        .getInterface(Ci.nsIDOMWindowUtils)
-        .suppressEventHandling(false);
+    let windowUtils = this.browser.contentWindow
+                          .QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindowUtils);
+    windowUtils.resumeTimeouts();
+    windowUtils.suppressEventHandling(false);
   },
 
   /**
    * Handle location changes, by sending a tabNavigated notification to the
    * client.
    */
   onWindowCreated: function BTA_onWindowCreated(evt) {
     if (evt.target === this.browser.contentDocument) {
--- a/toolkit/devtools/debugger/server/dbg-script-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-script-actors.js
@@ -44,18 +44,18 @@
  */
 /**
  * Creates a ThreadActor.
  *
  * ThreadActors manage a JSInspector object and manage execution/inspection
  * of debuggees.
  *
  * @param aHooks object
- *        An object with preNest and postNest methods that can be called when
- *        entering and exiting a nested event loop.
+ *        An object with preNest and postNest methods for calling when entering
+ *        and exiting a nested event loop.
  */
 function ThreadActor(aHooks)
 {
   this._state = "detached";
   this._frameActors = [];
   this._environmentActors = [];
   this._hooks = aHooks ? aHooks : {};
 }
@@ -171,17 +171,17 @@ ThreadActor.prototype = {
 
       // Start a nested event loop.
       this._nest();
 
       // We already sent a response to this request, don't send one
       // now.
       return null;
     } catch(e) {
-      dumpn(e);
+      Cu.reportError(e);
       return { error: "notAttached", message: e.toString() };
     }
   },
 
   onDetach: function TA_onDetach(aRequest) {
     this.disconnect();
     return { type: "detached" };
   },
@@ -281,20 +281,18 @@ ThreadActor.prototype = {
    */
   onSetBreakpoint: function TA_onSetBreakpoint(aRequest) {
     if (this.state !== "paused") {
       return { error: "wrongState",
                message: "Breakpoints can only be set while the debuggee is paused."};
     }
 
     let location = aRequest.location;
-    // TODO: deal with actualLocation being different from the provided location
     if (!this._scripts[location.url] || location.line < 0) {
-      return { from: this.actorID,
-               error: "noScript" };
+      return { error: "noScript" };
     }
     // Fetch the list of scripts in that url.
     let scripts = this._scripts[location.url];
     // Fetch the specified script in that list.
     let script = null;
     for (let i = location.line; i >= 0; i--) {
       // Stop when the first script that contains this location is found.
       if (scripts[i]) {
@@ -302,29 +300,79 @@ ThreadActor.prototype = {
         // good.
         if (i + scripts[i].lineCount < location.line) {
           break;
         }
         script = scripts[i];
         break;
       }
     }
+
     if (!script) {
-      return { from: this.actorID,
-               error: "noScript" };
+      return { error: "noScript" };
     }
+
+    script = this._getInnermostContainer(script, location.line);
     let bpActor = new BreakpointActor(script, this);
     this.breakpointActorPool.addActor(bpActor);
-    var offsets = script.getLineOffsets(location.line);
-    for (var i = 0; i < offsets.length; i++) {
+
+    let offsets = script.getLineOffsets(location.line);
+    let codeFound = false;
+    for (let i = 0; i < offsets.length; i++) {
       script.setBreakpoint(offsets[i], bpActor);
+      codeFound = true;
+    }
+
+    let actualLocation;
+    if (offsets.length == 0) {
+      // No code at that line in any script, skipping forward.
+      let lines = script.getAllOffsets();
+      for (let line = location.line; line < lines.length; ++line) {
+        if (lines[line]) {
+          for (let i = 0; i < lines[line].length; i++) {
+            script.setBreakpoint(lines[line][i], bpActor);
+            codeFound = true;
+          }
+          actualLocation = location;
+          actualLocation.line = line;
+          break;
+        }
+      }
+    }
+    if (!codeFound) {
+      bpActor.onDelete();
+      return  { error: "noCodeAtLineColumn" };
     }
-    let packet = { from: this.actorID,
-                   actor: bpActor.actorID };
-    return packet;
+
+    return { actor: bpActor.actorID, actualLocation: actualLocation };
+  },
+
+  /**
+   * Get the innermost script that contains this line, by looking through child
+   * scripts of the supplied script.
+   *
+   * @param aScript Debugger.Script
+   *        The source script.
+   * @param aLine number
+   *        The line number.
+   */
+  _getInnermostContainer: function TA__getInnermostContainer(aScript, aLine) {
+    let children = aScript.getChildScripts();
+    if (children.length > 0) {
+      for (let i = 0; i < children.length; i++) {
+        let child = children[i];
+        // Stop when the first script that contains this location is found.
+        if (child.startLine <= aLine &&
+            child.startLine + child.lineCount > aLine) {
+          return this._getInnermostContainer(child, aLine);
+        }
+      }
+    }
+    // Location not found in children, this is the innermost containing script.
+    return aScript;
   },
 
   /**
    * Handle a protocol request to return the list of loaded scripts.
    */
   onScripts: function TA_onScripts(aRequest) {
     let scripts = [];
     for (let url in this._scripts) {
@@ -342,16 +390,56 @@ ThreadActor.prototype = {
     }
 
     let packet = { from: this.actorID,
                    scripts: scripts };
     return packet;
   },
 
   /**
+   * Handle a protocol request to pause the debuggee.
+   */
+  onInterrupt: function TA_onScripts(aRequest) {
+    if (this.state == "exited") {
+      return { type: "exited" };
+    } else if (this.state == "paused") {
+      // TODO: return the actual reason for the existing pause.
+      return { type: "paused", why: { type: "alreadyPaused" } };
+    } else if (this.state != "running") {
+      return { error: "wrongState",
+               message: "Received interrupt request in " + this.state +
+                        " state." };
+    }
+
+    try {
+      // Put ourselves in the paused state.
+      let packet = this._paused();
+      if (!packet) {
+        return { error: "notInterrupted" };
+      }
+      packet.why = { type: "interrupted" };
+
+      // Send the response to the interrupt request now (rather than
+      // returning it), because we're going to start a nested event loop
+      // here.
+      this.conn.send(packet);
+
+      // Start a nested event loop.
+      this._nest();
+
+      // We already sent a response to this request, don't send one
+      // now.
+      return null;
+    } catch(e) {
+      Cu.reportError(e);
+      return { error: "notInterrupted", message: e.toString() };
+    }
+  },
+
+  /**
    * Return the Debug.Frame for a frame mentioned by the protocol.
    */
   _requestFrame: function TA_requestFrame(aFrameID) {
     // XXXspec: doesn't actually specify how frames are named.  By
     // depth?  By actor?  Both?
     if (!aFrameID) {
       return this._youngestFrame;
     }
@@ -621,17 +709,18 @@ ThreadActor.prototype = {
       let packet = this._paused(aFrame);
       if (!packet) {
         return undefined;
       }
       packet.why = { type: "debuggerStatement" };
       this.conn.send(packet);
       return this._nest();
     } catch(e) {
-      dumpn("Got an exception during onDebuggerStatement: " + e + ': ' + e.stack);
+      Cu.reportError("Got an exception during onDebuggerStatement: " + e +
+                     ": " + e.stack);
       return undefined;
     }
   },
 
   /**
    * A function that the engine calls when a new script has been loaded into a
    * debuggee compartment. If the new code is part of a function, aFunction is
    * a Debugger.Object reference to the function object. (Not all code is part
@@ -640,20 +729,16 @@ ThreadActor.prototype = {
    * onNewScript without an accompanying function argument.)
    *
    * @param aScript Debugger.Script
    *        The source script that has been loaded into a debuggee compartment.
    * @param aFunction Debugger.Object
    *        The function object that the ew code is part of.
    */
   onNewScript: function TA_onNewScript(aScript, aFunction) {
-    dumpn("Got a new script:" + aScript + ", url: " + aScript.url +
-          ", startLine: " + aScript.startLine + ", lineCount: " +
-          aScript.lineCount + ", strictMode: " + aScript.strictMode +
-          ", function: " + aFunction);
     // Use a sparse array for storing the scripts for each URL in order to
     // optimize retrieval. XXX: in case this is not fast enough for very large
     // files with too many scripts, we could sort the hash of script locations
     // or use a trie.
     if (!this._scripts[aScript.url]) {
       this._scripts[aScript.url] = [];
     }
     this._scripts[aScript.url][aScript.startLine] = aScript;
@@ -665,16 +750,17 @@ ThreadActor.prototype = {
 };
 
 ThreadActor.prototype.requestTypes = {
   "attach": ThreadActor.prototype.onAttach,
   "detach": ThreadActor.prototype.onDetach,
   "resume": ThreadActor.prototype.onResume,
   "clientEvaluate": ThreadActor.prototype.onClientEvaluate,
   "frames": ThreadActor.prototype.onFrames,
+  "interrupt": ThreadActor.prototype.onInterrupt,
   "releaseMany": ThreadActor.prototype.onReleaseMany,
   "setBreakpoint": ThreadActor.prototype.onSetBreakpoint,
   "scripts": ThreadActor.prototype.onScripts
 };
 
 
 /**
  * Creates a PauseActor.
@@ -1074,17 +1160,17 @@ BreakpointActor.prototype = {
       if (!packet) {
         return undefined;
       }
       // TODO: add the rest of the breakpoints on that line.
       packet.why = { type: "breakpoint", actors: [ this.actorID ] };
       this.conn.send(packet);
       return this.threadActor._nest();
     } catch(e) {
-      dumpn("Got an exception during hit: " + e + ': ' + e.stack);
+      Cu.reportError("Got an exception during hit: " + e + ': ' + e.stack);
       return undefined;
     }
   },
 
   /**
    * Handle a protocol request to remove this breakpoint.
    *
    * @param aRequest object
@@ -1227,9 +1313,8 @@ EnvironmentActor.prototype = {
              bindings: this._bindings() };
   }
 };
 
 EnvironmentActor.prototype.requestTypes = {
   "assign": EnvironmentActor.prototype.onAssign,
   "bindings": EnvironmentActor.prototype.onBindings
 };
-
--- a/toolkit/devtools/debugger/server/dbg-server.js
+++ b/toolkit/devtools/debugger/server/dbg-server.js
@@ -427,17 +427,17 @@ DebuggerServerConnection.prototype = {
 
     var ret = null;
 
     // Dispatch the request to the actor.
     if (actor.requestTypes && actor.requestTypes[aPacket.type]) {
       try {
         ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket);
       } catch(e) {
-        dumpn(e);
+        Cu.reportError(e);
         ret = { error: "unknownError",
                 message: "An unknown error has occurred while processing request." };
       }
     } else {
       ret = { error: "unrecognizedPacketType",
               message: 'Actor "' + actor.actorID + '" does not recognize the packet type "' + aPacket.type + '"' };
     }
 
--- a/toolkit/devtools/debugger/tests/unit/test_breakpoint-01.js
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-01.js
@@ -41,18 +41,17 @@ function test_simple_breakpoint()
         bpClient.remove(function (aResponse) {
           gThreadClient.resume(function () {
             finishClient(gClient);
           });
         });
 
       });
       // Continue until the breakpoint is hit.
-      gThreadClient.resume(function () {
-      });
+      gThreadClient.resume();
 
     });
 
   });
 
   gDebuggee.eval("var line0 = Error().lineNumber;\n" +
                  "debugger;\n" +   // line0 + 1
                  "var a = 1;\n" +  // line0 + 2
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-02.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting breakpoints when the debuggee is running works.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient, "test-stack", function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_breakpoint_running();
+    });
+  });
+  do_test_pending();
+}
+
+function test_breakpoint_running()
+{
+  let path = getFilePath('test_breakpoint-01.js');
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "var a = 1;\n" +  // line0 + 1
+                 "var b = 2;\n");  // line0 + 2
+
+  // Setting the breakpoint later should interrupt the debuggee.
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    do_check_eq(aPacket.type, "paused");
+    do_check_eq(aPacket.why.type, "interrupted");
+  });
+
+  gThreadClient.setBreakpoint({ url: path, line: gDebuggee.line0 + 3}, function(aResponse) {
+    // Eval scripts don't stick around long enough for the breakpoint to be set,
+    // so just make sure we got the expected response from the actor.
+    do_check_eq(aResponse.error, "noScript");
+
+    do_execute_soon(function() {
+      finishClient(gClient);
+    });
+  });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-03.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line without code will skip forward.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient,
+                                    "test-stack",
+                                    function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_skip_breakpoint();
+    });
+  });
+  do_test_pending();
+}
+
+function test_skip_breakpoint()
+{
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    let path = getFilePath('test_breakpoint-03.js');
+    let location = { url: path, line: gDebuggee.line0 + 3};
+    gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
+      // Check that the breakpoint has properly skipped forward one line.
+      do_check_eq(aResponse.actualLocation.url, location.url);
+      do_check_eq(aResponse.actualLocation.line, location.line + 1);
+      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+        // Check the return value.
+        do_check_eq(aPacket.type, "paused");
+        do_check_eq(aPacket.why.type, "breakpoint");
+        do_check_eq(aPacket.why.actors[0], bpClient.actor);
+        // Check that the breakpoint worked.
+        do_check_eq(gDebuggee.a, 1);
+        do_check_eq(gDebuggee.b, undefined);
+
+        // Remove the breakpoint.
+        bpClient.remove(function (aResponse) {
+          gThreadClient.resume(function () {
+            finishClient(gClient);
+          });
+        });
+
+      });
+      // Continue until the breakpoint is hit.
+      gThreadClient.resume();
+
+    });
+
+  });
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "debugger;\n" +      // line0 + 1
+                 "var a = 1;\n" +     // line0 + 2
+                 "// A comment.\n" +  // line0 + 3
+                 "var b = 2;\n");     // line0 + 4
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-04.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line in a child script works.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient,
+                                    "test-stack",
+                                    function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_child_breakpoint();
+    });
+  });
+  do_test_pending();
+}
+
+function test_child_breakpoint()
+{
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    let path = getFilePath('test_breakpoint-04.js');
+    let location = { url: path, line: gDebuggee.line0 + 3};
+    gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
+      // actualLocation is not returned when breakpoints don't skip forward.
+      do_check_eq(aResponse.actualLocation, undefined);
+      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+        // Check the return value.
+        do_check_eq(aPacket.type, "paused");
+        do_check_eq(aPacket.why.type, "breakpoint");
+        do_check_eq(aPacket.why.actors[0], bpClient.actor);
+        // Check that the breakpoint worked.
+        do_check_eq(gDebuggee.a, 1);
+        do_check_eq(gDebuggee.b, undefined);
+
+        // Remove the breakpoint.
+        bpClient.remove(function (aResponse) {
+          gThreadClient.resume(function () {
+            finishClient(gClient);
+          });
+        });
+
+      });
+      // Continue until the breakpoint is hit.
+      gThreadClient.resume();
+
+    });
+
+  });
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "function foo() {\n" + // line0 + 1
+                 "  this.a = 1;\n" +     // line0 + 2
+                 "  this.b = 2;\n" +     // line0 + 3
+                 "}\n" +                // line0 + 4
+                 "debugger;\n" +        // line0 + 5
+                 "foo();\n");           // line0 + 6
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-05.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line without code in a child script
+ * will skip forward.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient,
+                                    "test-stack",
+                                    function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_child_skip_breakpoint();
+    });
+  });
+  do_test_pending();
+}
+
+function test_child_skip_breakpoint()
+{
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    let path = getFilePath('test_breakpoint-05.js');
+    let location = { url: path, line: gDebuggee.line0 + 3};
+    gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
+      // Check that the breakpoint has properly skipped forward one line.
+      do_check_eq(aResponse.actualLocation.url, location.url);
+      do_check_eq(aResponse.actualLocation.line, location.line + 1);
+      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+        // Check the return value.
+        do_check_eq(aPacket.type, "paused");
+        do_check_eq(aPacket.why.type, "breakpoint");
+        do_check_eq(aPacket.why.actors[0], bpClient.actor);
+        // Check that the breakpoint worked.
+        do_check_eq(gDebuggee.a, 1);
+        do_check_eq(gDebuggee.b, undefined);
+
+        // Remove the breakpoint.
+        bpClient.remove(function (aResponse) {
+          gThreadClient.resume(function () {
+            finishClient(gClient);
+          });
+        });
+
+      });
+      // Continue until the breakpoint is hit.
+      gThreadClient.resume();
+
+    });
+
+  });
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "function foo() {\n" + // line0 + 1
+                 "  this.a = 1;\n" +    // line0 + 2
+                 "  // A comment.\n" +  // line0 + 3
+                 "  this.b = 2;\n" +    // line0 + 4
+                 "}\n" +                // line0 + 5
+                 "debugger;\n" +        // line0 + 6
+                 "foo();\n");           // line0 + 7
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-06.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line without code in a deeply-nested
+ * child script will skip forward.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient,
+                                    "test-stack",
+                                    function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_nested_breakpoint();
+    });
+  });
+  do_test_pending();
+}
+
+function test_nested_breakpoint()
+{
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    let path = getFilePath('test_breakpoint-06.js');
+    let location = { url: path, line: gDebuggee.line0 + 5};
+    gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
+      // Check that the breakpoint has properly skipped forward one line.
+      do_check_eq(aResponse.actualLocation.url, location.url);
+      do_check_eq(aResponse.actualLocation.line, location.line + 1);
+      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+        // Check the return value.
+        do_check_eq(aPacket.type, "paused");
+        do_check_eq(aPacket.why.type, "breakpoint");
+        do_check_eq(aPacket.why.actors[0], bpClient.actor);
+        // Check that the breakpoint worked.
+        do_check_eq(gDebuggee.a, 1);
+        do_check_eq(gDebuggee.b, undefined);
+
+        // Remove the breakpoint.
+        bpClient.remove(function (aResponse) {
+          gThreadClient.resume(function () {
+            finishClient(gClient);
+          });
+        });
+
+      });
+      // Continue until the breakpoint is hit.
+      gThreadClient.resume();
+
+    });
+
+  });
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "function foo() {\n" +     // line0 + 1
+                 "  function bar() {\n" +   // line0 + 2
+                 "    function baz() {\n" + // line0 + 3
+                 "      this.a = 1;\n" +    // line0 + 4
+                 "      // A comment.\n" +  // line0 + 5
+                 "      this.b = 2;\n" +    // line0 + 6
+                 "    }\n" +                // line0 + 7
+                 "    baz();\n" +           // line0 + 8
+                 "  }\n" +                  // line0 + 9
+                 "  bar();\n" +             // line0 + 10
+                 "}\n" +                    // line0 + 11
+                 "debugger;\n" +            // line0 + 12
+                 "foo();\n");               // line0 + 13
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-07.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line without code in the second child
+ * script will skip forward.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient,
+                                    "test-stack",
+                                    function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_second_child_skip_breakpoint();
+    });
+  });
+  do_test_pending();
+}
+
+function test_second_child_skip_breakpoint()
+{
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    let path = getFilePath('test_breakpoint-07.js');
+    let location = { url: path, line: gDebuggee.line0 + 6};
+    gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
+      // Check that the breakpoint has properly skipped forward one line.
+      do_check_eq(aResponse.actualLocation.url, location.url);
+      do_check_eq(aResponse.actualLocation.line, location.line + 1);
+      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+        // Check the return value.
+        do_check_eq(aPacket.type, "paused");
+        do_check_eq(aPacket.why.type, "breakpoint");
+        do_check_eq(aPacket.why.actors[0], bpClient.actor);
+        // Check that the breakpoint worked.
+        do_check_eq(gDebuggee.a, 1);
+        do_check_eq(gDebuggee.b, undefined);
+
+        // Remove the breakpoint.
+        bpClient.remove(function (aResponse) {
+          gThreadClient.resume(function () {
+            finishClient(gClient);
+          });
+        });
+
+      });
+      // Continue until the breakpoint is hit.
+      gThreadClient.resume();
+
+    });
+
+  });
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "function foo() {\n" + // line0 + 1
+                 "  bar();\n" +         // line0 + 2
+                 "}\n" +                // line0 + 3
+                 "function bar() {\n" + // line0 + 4
+                 "  this.a = 1;\n" +    // line0 + 5
+                 "  // A comment.\n" +  // line0 + 6
+                 "  this.b = 2;\n" +    // line0 + 7
+                 "}\n" +                // line0 + 8
+                 "debugger;\n" +        // line0 + 9
+                 "foo();\n");           // line0 + 10
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-08.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line without code in a child script
+ * will skip forward, in a file with two scripts.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient,
+                                    "test-stack",
+                                    function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_child_skip_breakpoint();
+    });
+  });
+  do_test_pending();
+}
+
+function test_child_skip_breakpoint()
+{
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    let path = getFilePath('test_breakpoint-08.js');
+    let location = { url: path, line: gDebuggee.line0 + 3};
+    gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
+      // Check that the breakpoint has properly skipped forward one line.
+      do_check_eq(aResponse.actualLocation.url, location.url);
+      do_check_eq(aResponse.actualLocation.line, location.line + 1);
+      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+        // Check the return value.
+        do_check_eq(aPacket.type, "paused");
+        do_check_eq(aPacket.why.type, "breakpoint");
+        do_check_eq(aPacket.why.actors[0], bpClient.actor);
+        // Check that the breakpoint worked.
+        do_check_eq(gDebuggee.a, 1);
+        do_check_eq(gDebuggee.b, undefined);
+
+        // Remove the breakpoint.
+        bpClient.remove(function (aResponse) {
+          gThreadClient.resume(function () {
+            finishClient(gClient);
+          });
+        });
+
+      });
+      // Continue until the breakpoint is hit.
+      gThreadClient.resume();
+
+    });
+
+  });
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "function foo() {\n" + // line0 + 1
+                 "  this.a = 1;\n" +    // line0 + 2
+                 "  // A comment.\n" +  // line0 + 3
+                 "  this.b = 2;\n" +    // line0 + 4
+                 "}\n");                // line0 + 5
+  gDebuggee.eval("var line1 = Error().lineNumber;\n" +
+                 "debugger;\n" +        // line1 + 1
+                 "foo();\n");           // line1 + 2
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-09.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that removing a breakpoint works.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient,
+                                    "test-stack",
+                                    function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_remove_breakpoint();
+    });
+  });
+  do_test_pending();
+}
+
+function test_remove_breakpoint()
+{
+  let done = false;
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    let path = getFilePath('test_breakpoint-09.js');
+    let location = { url: path, line: gDebuggee.line0 + 1};
+    gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
+      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+        // Check the return value.
+        do_check_eq(aPacket.type, "paused");
+        do_check_eq(aPacket.why.type, "breakpoint");
+        do_check_eq(aPacket.why.actors[0], bpClient.actor);
+        // Check that the breakpoint worked.
+        do_check_eq(gDebuggee.a, undefined);
+
+        // Remove the breakpoint.
+        bpClient.remove(function (aResponse) {
+          done = true;
+          gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+            // The breakpoint should not be hit again.
+            gThreadClient.resume(function () {
+              do_check_true(false);
+            });
+          });
+          gThreadClient.resume();
+        });
+
+      });
+      // Continue until the breakpoint is hit.
+      gThreadClient.resume();
+
+    });
+
+  });
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "function foo(stop) {\n" + // line0 + 1
+                 "  this.a = 1;\n" +        // line0 + 2
+                 "  if (stop) return;\n" +  // line0 + 3
+                 "  delete this.a;\n" +     // line0 + 4
+                 "  foo(true);\n" +         // line0 + 5
+                 "}\n" +                    // line0 + 6
+                 "debugger;\n" +            // line1 + 7
+                 "foo();\n");               // line1 + 8
+  if (!done) {
+    do_check_true(false);
+  }
+  finishClient(gClient);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-10.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line with multiple entry points
+ * triggers no matter which entry point we reach.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient,
+                                    "test-stack",
+                                    function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_child_breakpoint();
+    });
+  });
+  do_test_pending();
+}
+
+function test_child_breakpoint()
+{
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    let path = getFilePath('test_breakpoint-10.js');
+    let location = { url: path, line: gDebuggee.line0 + 3};
+    gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
+      // actualLocation is not returned when breakpoints don't skip forward.
+      do_check_eq(aResponse.actualLocation, undefined);
+      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+        // Check the return value.
+        do_check_eq(aPacket.type, "paused");
+        do_check_eq(aPacket.why.type, "breakpoint");
+        do_check_eq(aPacket.why.actors[0], bpClient.actor);
+        // Check that the breakpoint worked.
+        do_check_eq(gDebuggee.i, 0);
+
+        gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+          // Check the return value.
+          do_check_eq(aPacket.type, "paused");
+          do_check_eq(aPacket.why.type, "breakpoint");
+          do_check_eq(aPacket.why.actors[0], bpClient.actor);
+          // Check that the breakpoint worked.
+          do_check_eq(gDebuggee.i, 1);
+
+          // Remove the breakpoint.
+          bpClient.remove(function (aResponse) {
+            gThreadClient.resume(function () {
+              finishClient(gClient);
+            });
+          });
+        });
+
+        // Continue until the breakpoint is hit again.
+        gThreadClient.resume();
+
+      });
+      // Continue until the breakpoint is hit.
+      gThreadClient.resume();
+
+    });
+
+  });
+
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "debugger;\n" +                      // line0 + 1
+                 "var a, i = 0;\n" +                  // line0 + 2
+                 "for (i = 1; i <= 2; i++) {\n" +     // line0 + 3
+                 "  a = i;\n" +                       // line0 + 4
+                 "}\n");                              // line0 + 5
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_interrupt.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gClient;
+var gDebuggee;
+
+function run_test()
+{
+  DebuggerServer.addActors("resource://test/testactors.js");
+
+  DebuggerServer.init();
+  gDebuggee = testGlobal("test-1");
+  DebuggerServer.addTestGlobal(gDebuggee);
+
+  let transport = DebuggerServer.connectPipe();
+  gClient = new DebuggerClient(transport);
+  gClient.connect(function(aType, aTraits) {
+    getTestGlobalContext(gClient, "test-1", function(aContext) {
+      test_attach(aContext);
+    });
+  });
+  do_test_pending();
+}
+
+function test_attach(aContext)
+{
+  gClient.attachThread(aContext.actor, function(aResponse, aThreadClient) {
+    do_check_eq(aThreadClient.paused, true);
+    aThreadClient.resume(function() {
+      test_interrupt();
+    });
+  });
+}
+
+function test_interrupt()
+{
+  do_check_eq(gClient.activeThread.paused, false);
+  gClient.activeThread.interrupt(function(aResponse) {
+    do_check_eq(gClient.activeThread.paused, true);
+    gClient.activeThread.resume(function() {
+      do_check_eq(gClient.activeThread.paused, false);
+      cleanup();
+    });
+  });
+}
+
+function cleanup()
+{
+  gClient.addListener("closed", function(aEvent) {
+    do_test_finished();
+  });
+  gClient.close();
+}
+
--- a/toolkit/devtools/debugger/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/debugger/tests/unit/xpcshell.ini
@@ -29,13 +29,23 @@ tail =
 [test_frameclient-02.js]
 [test_nativewrappers.js]
 [test_eval-01.js]
 [test_eval-02.js]
 [test_eval-03.js]
 [test_eval-04.js]
 [test_eval-05.js]
 [test_breakpoint-01.js]
+[test_breakpoint-02.js]
+[test_breakpoint-03.js]
+[test_breakpoint-04.js]
+[test_breakpoint-05.js]
+[test_breakpoint-06.js]
+[test_breakpoint-07.js]
+[test_breakpoint-08.js]
+[test_breakpoint-09.js]
+[test_breakpoint-10.js]
 [test_listscripts-01.js]
 [test_objectgrips-01.js]
 [test_objectgrips-02.js]
 [test_objectgrips-03.js]
 [test_objectgrips-04.js]
+[test_interrupt.js]